| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed clinical patient record.
2
3 This is a clinical record object intended to let a useful
4 client-side API crystallize from actual use in true XP fashion.
5
6 Make sure to call set_func_ask_user() and set_encounter_ttl()
7 early on in your code (before cClinicalRecord.__init__() is
8 called for the first time).
9 """
10 #============================================================
11 __version__ = "$Revision: 1.308 $"
12 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
13 __license__ = "GPL"
14
15 #===================================================
16 # TODO
17 # Basically we'll probably have to:
18 #
19 # a) serialize access to re-getting data from the cache so
20 # that later-but-concurrent cache accesses spin until
21 # the first one completes the refetch from the database
22 #
23 # b) serialize access to the cache per-se such that cache
24 # flushes vs. cache regets happen atomically (where
25 # flushes would abort/restart current regets)
26 #===================================================
27
28 # standard libs
29 import sys, string, time, copy, locale
30
31
32 # 3rd party
33 import logging
34
35
36 if __name__ == '__main__':
37 sys.path.insert(0, '../../')
38 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
39 gmI18N.activate_locale()
40 gmI18N.install_domain()
41 gmDateTime.init()
42
43 from Gnumed.pycommon import gmExceptions, gmPG2, gmDispatcher, gmI18N, gmCfg, gmTools, gmDateTime
44 from Gnumed.business import gmAllergy
45 from Gnumed.business import gmPathLab
46 from Gnumed.business import gmClinNarrative
47 from Gnumed.business import gmEMRStructItems
48 from Gnumed.business import gmMedication
49 from Gnumed.business import gmVaccination
50 from Gnumed.business import gmFamilyHistory
51
52
53 _log = logging.getLogger('gm.emr')
54 _log.debug(__version__)
55
56 _me = None
57 _here = None
58 #============================================================
59 # helper functions
60 #------------------------------------------------------------
61 _func_ask_user = None
62
64 if not callable(a_func):
65 _log.error('[%] not callable, not setting _func_ask_user', a_func)
66 return False
67
68 _log.debug('setting _func_ask_user to [%s]', a_func)
69
70 global _func_ask_user
71 _func_ask_user = a_func
72
73 #============================================================
75
76 _clin_root_item_children_union_query = None
77
79 """Fails if
80
81 - no connection to database possible
82 - patient referenced by aPKey does not exist
83 """
84 self.pk_patient = aPKey # == identity.pk == primary key
85
86 # log access to patient record (HIPAA, for example)
87 cmd = u'SELECT gm.log_access2emr(%(todo)s)'
88 args = {'todo': u'patient [%s]' % aPKey}
89 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
90
91 from Gnumed.business import gmSurgery, gmPerson
92 global _me
93 if _me is None:
94 _me = gmPerson.gmCurrentProvider()
95 global _here
96 if _here is None:
97 _here = gmSurgery.gmCurrentPractice()
98
99 # ...........................................
100 # this is a hack to speed up get_encounters()
101 clin_root_item_children = gmPG2.get_child_tables('clin', 'clin_root_item')
102 if cClinicalRecord._clin_root_item_children_union_query is None:
103 union_phrase = u"""
104 SELECT fk_encounter from
105 %s.%s cn
106 inner join
107 (SELECT pk FROM clin.episode ep WHERE ep.fk_health_issue in %%s) as epi
108 on (cn.fk_episode = epi.pk)
109 """
110 cClinicalRecord._clin_root_item_children_union_query = u'union\n'.join (
111 [ union_phrase % (child[0], child[1]) for child in clin_root_item_children ]
112 )
113 # ...........................................
114
115 self.__db_cache = {}
116
117 # load current or create new encounter
118 if _func_ask_user is None:
119 _log.error('[_func_ask_user] is None')
120 print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__
121 self.remove_empty_encounters()
122 self.__encounter = None
123 if not self.__initiate_active_encounter():
124 raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey
125
126 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
127
128 # register backend notification interests
129 # (keep this last so we won't hang on threads when
130 # failing this constructor for other reasons ...)
131 if not self._register_interests():
132 raise gmExceptions.ConstructorError, "cannot register signal interests"
133
134 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
135 #--------------------------------------------------------
138 #--------------------------------------------------------
140 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
141
142 return True
143 #--------------------------------------------------------
144 # messaging
145 #--------------------------------------------------------
147 gmDispatcher.connect(signal = u'encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
148
149 return True
150 #--------------------------------------------------------
152 # get the current encounter as an extra instance
153 # from the database to check for changes
154 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
155
156 # the encounter just retrieved and the active encounter
157 # have got the same transaction ID so there's no change
158 # in the database, there could be a local change in
159 # the active encounter but that doesn't matter
160 if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
161 return True
162
163 # there must have been a change to the active encounter
164 # committed to the database from elsewhere,
165 # we must fail propagating the change, however, if
166 # there are local changes
167 if self.current_encounter.is_modified():
168 _log.debug('unsaved changes in active encounter, cannot switch to another one')
169 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
170
171 # there was a change in the database from elsewhere,
172 # locally, however, we don't have any changes, therefore
173 # we can propagate the remote change locally without
174 # losing anything
175 _log.debug('active encounter modified remotely, reloading and announcing the modification')
176 self.current_encounter.refetch_payload()
177 gmDispatcher.send(u'current_encounter_modified')
178
179 return True
180 #--------------------------------------------------------
183 #--------------------------------------------------------
190 #--------------------------------------------------------
197 #--------------------------------------------------------
199 _log.debug('DB: clin_root_item modification')
200 #--------------------------------------------------------
201 # API: family history
202 #--------------------------------------------------------
204 fhx = gmFamilyHistory.get_family_history (
205 order_by = u'l10n_relation, condition',
206 patient = self.pk_patient
207 )
208
209 if episodes is not None:
210 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx)
211
212 if issues is not None:
213 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx)
214
215 return fhx
216 #--------------------------------------------------------
218 return gmFamilyHistory.create_family_history (
219 encounter = self.current_encounter['pk_encounter'],
220 episode = episode,
221 condition = condition,
222 relation = relation
223 )
224 #--------------------------------------------------------
225 # API: performed procedures
226 #--------------------------------------------------------
228
229 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
230
231 if episodes is not None:
232 procs = filter(lambda p: p['pk_episode'] in episodes, procs)
233
234 if issues is not None:
235 procs = filter(lambda p: p['pk_health_issue'] in issues, procs)
236
237 return procs
238 #--------------------------------------------------------
239 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
240 return gmEMRStructItems.create_performed_procedure (
241 encounter = self.current_encounter['pk_encounter'],
242 episode = episode,
243 location = location,
244 hospital_stay = hospital_stay,
245 procedure = procedure
246 )
247 #--------------------------------------------------------
248 # API: hospital stays
249 #--------------------------------------------------------
251
252 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient)
253
254 if episodes is not None:
255 stays = filter(lambda s: s['pk_episode'] in episodes, stays)
256
257 if issues is not None:
258 stays = filter(lambda s: s['pk_health_issue'] in issues, stays)
259
260 return stays
261 #--------------------------------------------------------
263 return gmEMRStructItems.create_hospital_stay (
264 encounter = self.current_encounter['pk_encounter'],
265 episode = episode
266 )
267 #--------------------------------------------------------
268 # API: narrative
269 #--------------------------------------------------------
271
272 enc = gmTools.coalesce (
273 encounter,
274 self.current_encounter['pk_encounter']
275 )
276
277 for note in notes:
278 success, data = gmClinNarrative.create_clin_narrative (
279 narrative = note[1],
280 soap_cat = note[0],
281 episode_id = episode,
282 encounter_id = enc
283 )
284
285 return True
286 #--------------------------------------------------------
288 if note.strip() == '':
289 _log.info('will not create empty clinical note')
290 return None
291 status, data = gmClinNarrative.create_clin_narrative (
292 narrative = note,
293 soap_cat = soap_cat,
294 episode_id = episode['pk_episode'],
295 encounter_id = self.current_encounter['pk_encounter']
296 )
297 if not status:
298 _log.error(str(data))
299 return None
300 return data
301 #--------------------------------------------------------
302 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
303 """Get SOAP notes pertinent to this encounter.
304
305 since
306 - initial date for narrative items
307 until
308 - final date for narrative items
309 encounters
310 - list of encounters whose narrative are to be retrieved
311 episodes
312 - list of episodes whose narrative are to be retrieved
313 issues
314 - list of health issues whose narrative are to be retrieved
315 soap_cats
316 - list of SOAP categories of the narrative to be retrieved
317 """
318 cmd = u"""
319 SELECT cvpn.*, (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat) as soap_rank
320 from clin.v_pat_narrative cvpn
321 WHERE pk_patient = %s
322 order by date, soap_rank
323 """
324
325 ##########################
326 # support row_version in narrative for display in tree
327
328 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
329
330 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
331
332 if since is not None:
333 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
334
335 if until is not None:
336 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
337
338 if issues is not None:
339 filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative)
340
341 if episodes is not None:
342 filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative)
343
344 if encounters is not None:
345 filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative)
346
347 if soap_cats is not None:
348 soap_cats = map(lambda c: c.lower(), soap_cats)
349 filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative)
350
351 if providers is not None:
352 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
353
354 return filtered_narrative
355 #--------------------------------------------------------
356 - def get_as_journal(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None):
357 return gmClinNarrative.get_as_journal (
358 patient = self.pk_patient,
359 since = since,
360 until = until,
361 encounters = encounters,
362 episodes = episodes,
363 issues = issues,
364 soap_cats = soap_cats,
365 providers = providers,
366 order_by = order_by,
367 time_range = time_range
368 )
369 #--------------------------------------------------------
371
372 search_term = search_term.strip()
373 if search_term == '':
374 return []
375
376 cmd = u"""
377 SELECT
378 *,
379 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
380 as episode,
381 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
382 as health_issue,
383 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
384 as encounter_started,
385 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
386 as encounter_ended,
387 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
388 as encounter_type
389 from clin.v_narrative4search vn4s
390 WHERE
391 pk_patient = %(pat)s and
392 vn4s.narrative ~ %(term)s
393 order by
394 encounter_started
395 """ # case sensitive
396 rows, idx = gmPG2.run_ro_queries(queries = [
397 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
398 ])
399 return rows
400 #--------------------------------------------------------
402 # don't know how to invalidate this by means of
403 # a notify without catching notifies from *all*
404 # child tables, the best solution would be if
405 # inserts in child tables would also fire triggers
406 # of ancestor tables, but oh well,
407 # until then the text dump will not be cached ...
408 try:
409 return self.__db_cache['text dump old']
410 except KeyError:
411 pass
412 # not cached so go get it
413 fields = [
414 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
415 'modified_by',
416 'clin_when',
417 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
418 'pk_item',
419 'pk_encounter',
420 'pk_episode',
421 'pk_health_issue',
422 'src_table'
423 ]
424 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % string.join(fields, ', ')
425 ro_conn = self._conn_pool.GetConnection('historica')
426 curs = ro_conn.cursor()
427 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
428 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
429 curs.close()
430 return None
431 rows = curs.fetchall()
432 view_col_idx = gmPG2.get_col_indices(curs)
433
434 # aggregate by src_table for item retrieval
435 items_by_table = {}
436 for item in rows:
437 src_table = item[view_col_idx['src_table']]
438 pk_item = item[view_col_idx['pk_item']]
439 if not items_by_table.has_key(src_table):
440 items_by_table[src_table] = {}
441 items_by_table[src_table][pk_item] = item
442
443 # get mapping for issue/episode IDs
444 issues = self.get_health_issues()
445 issue_map = {}
446 for issue in issues:
447 issue_map[issue['pk']] = issue['description']
448 episodes = self.get_episodes()
449 episode_map = {}
450 for episode in episodes:
451 episode_map[episode['pk_episode']] = episode['description']
452 emr_data = {}
453 # get item data from all source tables
454 for src_table in items_by_table.keys():
455 item_ids = items_by_table[src_table].keys()
456 # we don't know anything about the columns of
457 # the source tables but, hey, this is a dump
458 if len(item_ids) == 0:
459 _log.info('no items in table [%s] ?!?' % src_table)
460 continue
461 elif len(item_ids) == 1:
462 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
463 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
464 _log.error('cannot load items from table [%s]' % src_table)
465 # skip this table
466 continue
467 elif len(item_ids) > 1:
468 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
469 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
470 _log.error('cannot load items from table [%s]' % src_table)
471 # skip this table
472 continue
473 rows = curs.fetchall()
474 table_col_idx = gmPG.get_col_indices(curs)
475 # format per-table items
476 for row in rows:
477 # FIXME: make this get_pkey_name()
478 pk_item = row[table_col_idx['pk_item']]
479 view_row = items_by_table[src_table][pk_item]
480 age = view_row[view_col_idx['age']]
481 # format metadata
482 try:
483 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
484 except:
485 episode_name = view_row[view_col_idx['pk_episode']]
486 try:
487 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
488 except:
489 issue_name = view_row[view_col_idx['pk_health_issue']]
490
491 if not emr_data.has_key(age):
492 emr_data[age] = []
493
494 emr_data[age].append(
495 _('%s: encounter (%s)') % (
496 view_row[view_col_idx['clin_when']],
497 view_row[view_col_idx['pk_encounter']]
498 )
499 )
500 emr_data[age].append(_('health issue: %s') % issue_name)
501 emr_data[age].append(_('episode : %s') % episode_name)
502 # format table specific data columns
503 # - ignore those, they are metadata, some
504 # are in clin.v_pat_items data already
505 cols2ignore = [
506 'pk_audit', 'row_version', 'modified_when', 'modified_by',
507 'pk_item', 'id', 'fk_encounter', 'fk_episode'
508 ]
509 col_data = []
510 for col_name in table_col_idx.keys():
511 if col_name in cols2ignore:
512 continue
513 emr_data[age].append("=> %s:" % col_name)
514 emr_data[age].append(row[table_col_idx[col_name]])
515 emr_data[age].append("----------------------------------------------------")
516 emr_data[age].append("-- %s from table %s" % (
517 view_row[view_col_idx['modified_string']],
518 src_table
519 ))
520 emr_data[age].append("-- written %s by %s" % (
521 view_row[view_col_idx['modified_when']],
522 view_row[view_col_idx['modified_by']]
523 ))
524 emr_data[age].append("----------------------------------------------------")
525 curs.close()
526 self._conn_pool.ReleaseConnection('historica')
527 return emr_data
528 #--------------------------------------------------------
530 # don't know how to invalidate this by means of
531 # a notify without catching notifies from *all*
532 # child tables, the best solution would be if
533 # inserts in child tables would also fire triggers
534 # of ancestor tables, but oh well,
535 # until then the text dump will not be cached ...
536 try:
537 return self.__db_cache['text dump']
538 except KeyError:
539 pass
540 # not cached so go get it
541 # -- get the data --
542 fields = [
543 'age',
544 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
545 'modified_by',
546 'clin_when',
547 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
548 'pk_item',
549 'pk_encounter',
550 'pk_episode',
551 'pk_health_issue',
552 'src_table'
553 ]
554 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
555 # handle constraint conditions
556 where_snippets = []
557 params = {}
558 where_snippets.append('pk_patient=%(pat_id)s')
559 params['pat_id'] = self.pk_patient
560 if not since is None:
561 where_snippets.append('clin_when >= %(since)s')
562 params['since'] = since
563 if not until is None:
564 where_snippets.append('clin_when <= %(until)s')
565 params['until'] = until
566 # FIXME: these are interrelated, eg if we constrain encounter
567 # we automatically constrain issue/episode, so handle that,
568 # encounters
569 if not encounters is None and len(encounters) > 0:
570 params['enc'] = encounters
571 if len(encounters) > 1:
572 where_snippets.append('fk_encounter in %(enc)s')
573 else:
574 where_snippets.append('fk_encounter=%(enc)s')
575 # episodes
576 if not episodes is None and len(episodes) > 0:
577 params['epi'] = episodes
578 if len(episodes) > 1:
579 where_snippets.append('fk_episode in %(epi)s')
580 else:
581 where_snippets.append('fk_episode=%(epi)s')
582 # health issues
583 if not issues is None and len(issues) > 0:
584 params['issue'] = issues
585 if len(issues) > 1:
586 where_snippets.append('fk_health_issue in %(issue)s')
587 else:
588 where_snippets.append('fk_health_issue=%(issue)s')
589
590 where_clause = ' and '.join(where_snippets)
591 order_by = 'order by src_table, age'
592 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
593
594 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
595 if rows is None:
596 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
597 return None
598
599 # -- sort the data --
600 # FIXME: by issue/encounter/episode, eg formatting
601 # aggregate by src_table for item retrieval
602 items_by_table = {}
603 for item in rows:
604 src_table = item[view_col_idx['src_table']]
605 pk_item = item[view_col_idx['pk_item']]
606 if not items_by_table.has_key(src_table):
607 items_by_table[src_table] = {}
608 items_by_table[src_table][pk_item] = item
609
610 # get mapping for issue/episode IDs
611 issues = self.get_health_issues()
612 issue_map = {}
613 for issue in issues:
614 issue_map[issue['pk_health_issue']] = issue['description']
615 episodes = self.get_episodes()
616 episode_map = {}
617 for episode in episodes:
618 episode_map[episode['pk_episode']] = episode['description']
619 emr_data = {}
620 # get item data from all source tables
621 ro_conn = self._conn_pool.GetConnection('historica')
622 curs = ro_conn.cursor()
623 for src_table in items_by_table.keys():
624 item_ids = items_by_table[src_table].keys()
625 # we don't know anything about the columns of
626 # the source tables but, hey, this is a dump
627 if len(item_ids) == 0:
628 _log.info('no items in table [%s] ?!?' % src_table)
629 continue
630 elif len(item_ids) == 1:
631 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
632 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
633 _log.error('cannot load items from table [%s]' % src_table)
634 # skip this table
635 continue
636 elif len(item_ids) > 1:
637 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
638 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
639 _log.error('cannot load items from table [%s]' % src_table)
640 # skip this table
641 continue
642 rows = curs.fetchall()
643 table_col_idx = gmPG.get_col_indices(curs)
644 # format per-table items
645 for row in rows:
646 # FIXME: make this get_pkey_name()
647 pk_item = row[table_col_idx['pk_item']]
648 view_row = items_by_table[src_table][pk_item]
649 age = view_row[view_col_idx['age']]
650 # format metadata
651 try:
652 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
653 except:
654 episode_name = view_row[view_col_idx['pk_episode']]
655 try:
656 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
657 except:
658 issue_name = view_row[view_col_idx['pk_health_issue']]
659
660 if not emr_data.has_key(age):
661 emr_data[age] = []
662
663 emr_data[age].append(
664 _('%s: encounter (%s)') % (
665 view_row[view_col_idx['clin_when']],
666 view_row[view_col_idx['pk_encounter']]
667 )
668 )
669 emr_data[age].append(_('health issue: %s') % issue_name)
670 emr_data[age].append(_('episode : %s') % episode_name)
671 # format table specific data columns
672 # - ignore those, they are metadata, some
673 # are in clin.v_pat_items data already
674 cols2ignore = [
675 'pk_audit', 'row_version', 'modified_when', 'modified_by',
676 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
677 ]
678 col_data = []
679 for col_name in table_col_idx.keys():
680 if col_name in cols2ignore:
681 continue
682 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
683 emr_data[age].append("----------------------------------------------------")
684 emr_data[age].append("-- %s from table %s" % (
685 view_row[view_col_idx['modified_string']],
686 src_table
687 ))
688 emr_data[age].append("-- written %s by %s" % (
689 view_row[view_col_idx['modified_when']],
690 view_row[view_col_idx['modified_by']]
691 ))
692 emr_data[age].append("----------------------------------------------------")
693 curs.close()
694 return emr_data
695 #--------------------------------------------------------
698 #--------------------------------------------------------
700 union_query = u'\n union all\n'.join ([
701 u"""
702 SELECT ((
703 -- all relevant health issues + active episodes WITH health issue
704 SELECT COUNT(1)
705 FROM clin.v_problem_list
706 WHERE
707 pk_patient = %(pat)s
708 AND
709 pk_health_issue is not null
710 ) + (
711 -- active episodes WITHOUT health issue
712 SELECT COUNT(1)
713 FROM clin.v_problem_list
714 WHERE
715 pk_patient = %(pat)s
716 AND
717 pk_health_issue is null
718 ))""",
719 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
720 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
721 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
722 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
723 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
724 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
725 # active and approved substances == medication
726 u"""
727 SELECT count(1)
728 from clin.v_pat_substance_intake
729 WHERE
730 pk_patient = %(pat)s
731 and is_currently_active in (null, true)
732 and intake_is_approved_of in (null, true)""",
733 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
734 ])
735
736 rows, idx = gmPG2.run_ro_queries (
737 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
738 get_col_idx = False
739 )
740
741 stats = dict (
742 problems = rows[0][0],
743 encounters = rows[1][0],
744 items = rows[2][0],
745 documents = rows[3][0],
746 results = rows[4][0],
747 stays = rows[5][0],
748 procedures = rows[6][0],
749 active_drugs = rows[7][0],
750 vaccinations = rows[8][0]
751 )
752
753 return stats
754 #--------------------------------------------------------
756 return _(
757 'Medical problems: %(problems)s\n'
758 'Total encounters: %(encounters)s\n'
759 'Total EMR entries: %(items)s\n'
760 'Active medications: %(active_drugs)s\n'
761 'Documents: %(documents)s\n'
762 'Test results: %(results)s\n'
763 'Hospital stays: %(stays)s\n'
764 'Procedures: %(procedures)s\n'
765 'Vaccinations: %(vaccinations)s'
766 ) % self.get_statistics()
767 #--------------------------------------------------------
769
770 stats = self.get_statistics()
771 first = self.get_first_encounter()
772 last = self.get_last_encounter()
773 probs = self.get_problems()
774
775 txt = _('EMR Statistics\n\n')
776 if len(probs) > 0:
777 txt += _(' %s known problems. Clinically relevant thereof:\n') % stats['problems']
778 else:
779 txt += _(' %s known problems\n') % stats['problems']
780 for prob in probs:
781 if not prob['clinically_relevant']:
782 continue
783 txt += u' \u00BB%s\u00AB (%s)\n' % (
784 prob['problem'],
785 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
786 )
787 txt += u'\n'
788 txt += _(' %s encounters from %s to %s\n') % (
789 stats['encounters'],
790 first['started'].strftime('%x').decode(gmI18N.get_encoding()),
791 last['started'].strftime('%x').decode(gmI18N.get_encoding())
792 )
793 txt += _(' %s active medications\n') % stats['active_drugs']
794 txt += _(' %s documents\n') % stats['documents']
795 txt += _(' %s test results\n') % stats['results']
796 txt += _(' %s hospital stays\n') % stats['stays']
797 # FIXME: perhaps only count "ongoing ones"
798 txt += _(' %s performed procedures\n\n') % stats['procedures']
799
800 txt += _('Allergies and Intolerances\n')
801
802 allg_state = self.allergy_state
803 txt += (u' ' + allg_state.state_string)
804 if allg_state['last_confirmed'] is not None:
805 txt += (_(' (last confirmed %s)') % allg_state['last_confirmed'].strftime('%x').decode(gmI18N.get_encoding()))
806 txt += u'\n'
807 txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n')
808 for allg in self.get_allergies():
809 txt += u' %s: %s\n' % (
810 allg['descriptor'],
811 gmTools.coalesce(allg['reaction'], _('unknown reaction'))
812 )
813
814 txt += u'\n'
815 txt += _('Family History')
816 txt += u'\n'
817
818 fhx = self.get_family_history()
819 for f in fhx:
820 txt += u'%s\n' % f.format(left_margin = 1)
821
822 txt += u'\n'
823 txt += _('Vaccinations')
824 txt += u'\n'
825
826 vaccs = self.get_latest_vaccinations()
827 inds = sorted(vaccs.keys())
828 for ind in inds:
829 ind_count, vacc = vaccs[ind]
830 txt += u' %s (%s%s): %s @ %s (%s %s%s%s)\n' % (
831 ind,
832 gmTools.u_sum,
833 ind_count,
834 vacc['date_given'].strftime('%b %Y').decode(gmI18N.get_encoding()),
835 gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
836 start = dob,
837 end = vacc['date_given']
838 )),
839 vacc['vaccine'],
840 gmTools.u_left_double_angle_quote,
841 vacc['batch_no'],
842 gmTools.u_right_double_angle_quote
843 )
844
845 return txt
846 #--------------------------------------------------------
847 # allergy API
848 #--------------------------------------------------------
849 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
850 """Retrieves patient allergy items.
851
852 remove_sensitivities
853 - retrieve real allergies only, without sensitivities
854 since
855 - initial date for allergy items
856 until
857 - final date for allergy items
858 encounters
859 - list of encounters whose allergies are to be retrieved
860 episodes
861 - list of episodes whose allergies are to be retrieved
862 issues
863 - list of health issues whose allergies are to be retrieved
864 """
865 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
866 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
867 allergies = []
868 for r in rows:
869 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
870
871 # ok, let's constrain our list
872 filtered_allergies = []
873 filtered_allergies.extend(allergies)
874
875 if ID_list is not None:
876 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
877 if len(filtered_allergies) == 0:
878 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
879 # better fail here contrary to what we do elsewhere
880 return None
881 else:
882 return filtered_allergies
883
884 if remove_sensitivities:
885 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
886 if since is not None:
887 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
888 if until is not None:
889 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
890 if issues is not None:
891 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
892 if episodes is not None:
893 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
894 if encounters is not None:
895 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
896
897 return filtered_allergies
898 #--------------------------------------------------------
900 if encounter_id is None:
901 encounter_id = self.current_encounter['pk_encounter']
902
903 if episode_id is None:
904 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
905 epi = self.add_episode(episode_name = allergene, pk_health_issue = issue['pk_health_issue'])
906 episode_id = epi['pk_episode']
907
908 new_allergy = gmAllergy.create_allergy (
909 allergene = allergene,
910 allg_type = allg_type,
911 encounter_id = encounter_id,
912 episode_id = episode_id
913 )
914
915 return new_allergy
916 #--------------------------------------------------------
918 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
919 args = {'pk_allg': pk_allergy}
920 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
921 #--------------------------------------------------------
923 """Cave: only use with one potential allergic agent
924 otherwise you won't know which of the agents the allergy is to."""
925
926 # we don't know the state
927 if self.allergy_state is None:
928 return None
929
930 # we know there's no allergies
931 if self.allergy_state == 0:
932 return False
933
934 args = {
935 'atcs': atcs,
936 'inns': inns,
937 'brand': brand,
938 'pat': self.pk_patient
939 }
940 allergenes = []
941 where_parts = []
942
943 if len(atcs) == 0:
944 atcs = None
945 if atcs is not None:
946 where_parts.append(u'atc_code in %(atcs)s')
947 if len(inns) == 0:
948 inns = None
949 if inns is not None:
950 where_parts.append(u'generics in %(inns)s')
951 allergenes.extend(inns)
952 if brand is not None:
953 where_parts.append(u'substance = %(brand)s')
954 allergenes.append(brand)
955
956 if len(allergenes) != 0:
957 where_parts.append(u'allergene in %(allgs)s')
958 args['allgs'] = tuple(allergenes)
959
960 cmd = u"""
961 SELECT * FROM clin.v_pat_allergies
962 WHERE
963 pk_patient = %%(pat)s
964 AND ( %s )""" % u' OR '.join(where_parts)
965
966 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
967
968 if len(rows) == 0:
969 return False
970
971 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
972 #--------------------------------------------------------
974
975 if state not in gmAllergy.allergy_states:
976 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
977
978 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
979 allg_state['has_allergy'] = state
980 allg_state.save_payload()
981 return True
982
985
986 allergy_state = property(_get_allergy_state, _set_allergy_state)
987 #--------------------------------------------------------
988 # episodes API
989 #--------------------------------------------------------
991 """Fetches from backend patient episodes.
992
993 id_list - Episodes' PKs list
994 issues - Health issues' PKs list to filter episodes by
995 open_status - return all episodes, only open or closed one(s)
996 """
997 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
998 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
999 tmp = []
1000 for r in rows:
1001 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
1002
1003 # now filter
1004 if (id_list is None) and (issues is None) and (open_status is None):
1005 return tmp
1006
1007 # ok, let's filter episode list
1008 filtered_episodes = []
1009 filtered_episodes.extend(tmp)
1010 if open_status is not None:
1011 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
1012
1013 if issues is not None:
1014 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
1015
1016 if id_list is not None:
1017 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
1018
1019 return filtered_episodes
1020 #------------------------------------------------------------------
1022 cmd = u"""SELECT distinct pk_episode
1023 from clin.v_pat_items
1024 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1025 args = {
1026 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1027 'pat': self.pk_patient
1028 }
1029 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1030 if len(rows) == 0:
1031 return []
1032 epis = []
1033 for row in rows:
1034 epis.append(row[0])
1035 return self.get_episodes(id_list=epis)
1036 #------------------------------------------------------------------
1038 """Add episode 'episode_name' for a patient's health issue.
1039
1040 - silently returns if episode already exists
1041 """
1042 episode = gmEMRStructItems.create_episode (
1043 pk_health_issue = pk_health_issue,
1044 episode_name = episode_name,
1045 is_open = is_open,
1046 encounter = self.current_encounter['pk_encounter']
1047 )
1048 return episode
1049 #--------------------------------------------------------
1051 # try to find the episode with the most recently modified clinical item
1052
1053 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1054
1055 cmd = u"""
1056 SELECT pk
1057 from clin.episode
1058 WHERE pk = (
1059 SELECT distinct on(pk_episode) pk_episode
1060 from clin.v_pat_items
1061 WHERE
1062 pk_patient = %%(pat)s
1063 and
1064 modified_when = (
1065 SELECT max(vpi.modified_when)
1066 from clin.v_pat_items vpi
1067 WHERE vpi.pk_patient = %%(pat)s
1068 )
1069 %s
1070 -- guard against several episodes created at the same moment of time
1071 limit 1
1072 )""" % issue_where
1073 rows, idx = gmPG2.run_ro_queries(queries = [
1074 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1075 ])
1076 if len(rows) != 0:
1077 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1078
1079 # no clinical items recorded, so try to find
1080 # the youngest episode for this patient
1081 cmd = u"""
1082 SELECT vpe0.pk_episode
1083 from
1084 clin.v_pat_episodes vpe0
1085 WHERE
1086 vpe0.pk_patient = %%(pat)s
1087 and
1088 vpe0.episode_modified_when = (
1089 SELECT max(vpe1.episode_modified_when)
1090 from clin.v_pat_episodes vpe1
1091 WHERE vpe1.pk_episode = vpe0.pk_episode
1092 )
1093 %s""" % issue_where
1094 rows, idx = gmPG2.run_ro_queries(queries = [
1095 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1096 ])
1097 if len(rows) != 0:
1098 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1099
1100 return None
1101 #--------------------------------------------------------
1104 #--------------------------------------------------------
1105 # problems API
1106 #--------------------------------------------------------
1107 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1108 """Retrieve a patient's problems.
1109
1110 "Problems" are the UNION of:
1111
1112 - issues which are .clinically_relevant
1113 - episodes which are .is_open
1114
1115 Therefore, both an issue and the open episode
1116 thereof can each be listed as a problem.
1117
1118 include_closed_episodes/include_irrelevant_issues will
1119 include those -- which departs from the definition of
1120 the problem list being "active" items only ...
1121
1122 episodes - episodes' PKs to filter problems by
1123 issues - health issues' PKs to filter problems by
1124 """
1125 # FIXME: this could use a good measure of streamlining, probably
1126
1127 args = {'pat': self.pk_patient}
1128
1129 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s"""
1130 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1131
1132 # Instantiate problem items
1133 problems = []
1134 for row in rows:
1135 pk_args = {
1136 u'pk_patient': self.pk_patient,
1137 u'pk_health_issue': row['pk_health_issue'],
1138 u'pk_episode': row['pk_episode']
1139 }
1140 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1141
1142 # include non-problems ?
1143 other_rows = []
1144 if include_closed_episodes:
1145 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1146 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1147 other_rows.extend(rows)
1148
1149 if include_irrelevant_issues:
1150 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1151 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1152 other_rows.extend(rows)
1153
1154 if len(other_rows) > 0:
1155 for row in other_rows:
1156 pk_args = {
1157 u'pk_patient': self.pk_patient,
1158 u'pk_health_issue': row['pk_health_issue'],
1159 u'pk_episode': row['pk_episode']
1160 }
1161 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1162
1163 # filter ?
1164 if (episodes is None) and (issues is None):
1165 return problems
1166
1167 # filter
1168 if issues is not None:
1169 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1170 if episodes is not None:
1171 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1172
1173 return problems
1174 #--------------------------------------------------------
1177 #--------------------------------------------------------
1180 #--------------------------------------------------------
1183 #--------------------------------------------------------
1184 # health issues API
1185 #--------------------------------------------------------
1187
1188 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1189 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1190 issues = []
1191 for row in rows:
1192 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1193 issues.append(gmEMRStructItems.cHealthIssue(row=r))
1194
1195 if id_list is None:
1196 return issues
1197
1198 if len(id_list) == 0:
1199 raise ValueError('id_list to filter by is empty, most likely a programming error')
1200
1201 filtered_issues = []
1202 for issue in issues:
1203 if issue['pk_health_issue'] in id_list:
1204 filtered_issues.append(issue)
1205
1206 return filtered_issues
1207 #------------------------------------------------------------------
1209 """Adds patient health issue."""
1210 return gmEMRStructItems.create_health_issue (
1211 description = issue_name,
1212 encounter = self.current_encounter['pk_encounter'],
1213 patient = self.pk_patient
1214 )
1215 #--------------------------------------------------------
1218 #--------------------------------------------------------
1219 # API: substance intake
1220 #--------------------------------------------------------
1221 - def get_current_substance_intake(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1222
1223 where_parts = [u'pk_patient = %(pat)s']
1224
1225 if not include_inactive:
1226 where_parts.append(u'is_currently_active in (true, null)')
1227
1228 if not include_unapproved:
1229 where_parts.append(u'intake_is_approved_of in (true, null)')
1230
1231 if order_by is None:
1232 order_by = u''
1233 else:
1234 order_by = u'order by %s' % order_by
1235
1236 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1237 u'\nand '.join(where_parts),
1238 order_by
1239 )
1240
1241 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1242
1243 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1244
1245 if episodes is not None:
1246 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1247
1248 if issues is not None:
1249 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1250
1251 return meds
1252 #--------------------------------------------------------
1253 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1254 return gmMedication.create_substance_intake (
1255 pk_substance = pk_substance,
1256 pk_component = pk_component,
1257 encounter = self.current_encounter['pk_encounter'],
1258 episode = episode,
1259 preparation = preparation
1260 )
1261 #--------------------------------------------------------
1262 # vaccinations API
1263 #--------------------------------------------------------
1265 return gmVaccination.create_vaccination (
1266 encounter = self.current_encounter['pk_encounter'],
1267 episode = episode,
1268 vaccine = vaccine,
1269 batch_no = batch_no
1270 )
1271 #--------------------------------------------------------
1273 """Returns latest given vaccination for each vaccinated indication.
1274
1275 as a dict {'l10n_indication': cVaccination instance}
1276
1277 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1278 """
1279 # find the PKs
1280 args = {'pat': self.pk_patient}
1281 where_parts = [u'pk_patient = %(pat)s']
1282
1283 if (episodes is not None) and (len(episodes) > 0):
1284 where_parts.append(u'pk_episode IN %(epis)s')
1285 args['epis'] = tuple(episodes)
1286
1287 if (issues is not None) and (len(issues) > 0):
1288 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1289 args['issues'] = tuple(issues)
1290
1291 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1292 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1293
1294 # none found
1295 if len(rows) == 0:
1296 return {}
1297
1298 vpks = [ ind['pk_vaccination'] for ind in rows ]
1299 vinds = [ ind['l10n_indication'] for ind in rows ]
1300 ind_counts = [ ind['indication_count'] for ind in rows ]
1301
1302 # turn them into vaccinations
1303 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1304 args = {'pks': tuple(vpks)}
1305 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1306
1307 vaccs = {}
1308 for idx in range(len(vpks)):
1309 pk = vpks[idx]
1310 ind_count = ind_counts[idx]
1311 for r in rows:
1312 if r['pk_vaccination'] == pk:
1313 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1314
1315 return vaccs
1316 #--------------------------------------------------------
1318
1319 args = {'pat': self.pk_patient}
1320 where_parts = [u'pk_patient = %(pat)s']
1321
1322 if order_by is None:
1323 order_by = u''
1324 else:
1325 order_by = u'order by %s' % order_by
1326
1327 if (episodes is not None) and (len(episodes) > 0):
1328 where_parts.append(u'pk_episode IN %(epis)s')
1329 args['epis'] = tuple(episodes)
1330
1331 if (issues is not None) and (len(issues) > 0):
1332 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1333 args['issues'] = tuple(issues)
1334
1335 if (encounters is not None) and (len(encounters) > 0):
1336 where_parts.append(u'pk_encounter IN %(encs)s')
1337 args['encs'] = tuple(encounters)
1338
1339 cmd = u'%s %s' % (
1340 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1341 order_by
1342 )
1343 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1344 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1345
1346 return vaccs
1347 #--------------------------------------------------------
1348 # old/obsolete:
1349 #--------------------------------------------------------
1351 """Retrieves vaccination regimes the patient is on.
1352
1353 optional:
1354 * ID - PK of the vaccination regime
1355 * indications - indications we want to retrieve vaccination
1356 regimes for, must be primary language, not l10n_indication
1357 """
1358 # FIXME: use course, not regime
1359 try:
1360 self.__db_cache['vaccinations']['scheduled regimes']
1361 except KeyError:
1362 # retrieve vaccination regimes definitions
1363 self.__db_cache['vaccinations']['scheduled regimes'] = []
1364 cmd = """SELECT distinct on(pk_course) pk_course
1365 FROM clin.v_vaccs_scheduled4pat
1366 WHERE pk_patient=%s"""
1367 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1368 if rows is None:
1369 _log.error('cannot retrieve scheduled vaccination courses')
1370 del self.__db_cache['vaccinations']['scheduled regimes']
1371 return None
1372 # Instantiate vaccination items and keep cache
1373 for row in rows:
1374 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1375
1376 # ok, let's constrain our list
1377 filtered_regimes = []
1378 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1379 if ID is not None:
1380 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1381 if len(filtered_regimes) == 0:
1382 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1383 return []
1384 else:
1385 return filtered_regimes[0]
1386 if indications is not None:
1387 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1388
1389 return filtered_regimes
1390 #--------------------------------------------------------
1391 # def get_vaccinated_indications(self):
1392 # """Retrieves patient vaccinated indications list.
1393 #
1394 # Note that this does NOT rely on the patient being on
1395 # some schedule or other but rather works with what the
1396 # patient has ACTUALLY been vaccinated against. This is
1397 # deliberate !
1398 # """
1399 # # most likely, vaccinations will be fetched close
1400 # # by so it makes sense to count on the cache being
1401 # # filled (or fill it for nearby use)
1402 # vaccinations = self.get_vaccinations()
1403 # if vaccinations is None:
1404 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1405 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
1406 # if len(vaccinations) == 0:
1407 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
1408 # v_indications = []
1409 # for vacc in vaccinations:
1410 # tmp = [vacc['indication'], vacc['l10n_indication']]
1411 # # remove duplicates
1412 # if tmp in v_indications:
1413 # continue
1414 # v_indications.append(tmp)
1415 # return (True, v_indications)
1416 #--------------------------------------------------------
1417 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1418 """Retrieves list of vaccinations the patient has received.
1419
1420 optional:
1421 * ID - PK of a vaccination
1422 * indications - indications we want to retrieve vaccination
1423 items for, must be primary language, not l10n_indication
1424 * since - initial date for allergy items
1425 * until - final date for allergy items
1426 * encounters - list of encounters whose allergies are to be retrieved
1427 * episodes - list of episodes whose allergies are to be retrieved
1428 * issues - list of health issues whose allergies are to be retrieved
1429 """
1430 try:
1431 self.__db_cache['vaccinations']['vaccinated']
1432 except KeyError:
1433 self.__db_cache['vaccinations']['vaccinated'] = []
1434 # Important fetch ordering by indication, date to know if a vaccination is booster
1435 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1436 WHERE pk_patient=%s
1437 order by indication, date"""
1438 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1439 if rows is None:
1440 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1441 del self.__db_cache['vaccinations']['vaccinated']
1442 return None
1443 # Instantiate vaccination items
1444 vaccs_by_ind = {}
1445 for row in rows:
1446 vacc_row = {
1447 'pk_field': 'pk_vaccination',
1448 'idx': idx,
1449 'data': row
1450 }
1451 vacc = gmVaccination.cVaccination(row=vacc_row)
1452 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1453 # keep them, ordered by indication
1454 try:
1455 vaccs_by_ind[vacc['indication']].append(vacc)
1456 except KeyError:
1457 vaccs_by_ind[vacc['indication']] = [vacc]
1458
1459 # calculate sequence number and is_booster
1460 for ind in vaccs_by_ind.keys():
1461 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1462 for vacc in vaccs_by_ind[ind]:
1463 # due to the "order by indication, date" the vaccinations are in the
1464 # right temporal order inside the indication-keyed dicts
1465 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1466 vacc['seq_no'] = seq_no
1467 # if no active schedule for indication we cannot
1468 # check for booster status (eg. seq_no > max_shot)
1469 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1470 continue
1471 if seq_no > vacc_regimes[0]['shots']:
1472 vacc['is_booster'] = True
1473 del vaccs_by_ind
1474
1475 # ok, let's constrain our list
1476 filtered_shots = []
1477 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1478 if ID is not None:
1479 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1480 if len(filtered_shots) == 0:
1481 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1482 return None
1483 else:
1484 return filtered_shots[0]
1485 if since is not None:
1486 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1487 if until is not None:
1488 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1489 if issues is not None:
1490 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1491 if episodes is not None:
1492 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1493 if encounters is not None:
1494 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1495 if indications is not None:
1496 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1497 return filtered_shots
1498 #--------------------------------------------------------
1500 """Retrieves vaccinations scheduled for a regime a patient is on.
1501
1502 The regime is referenced by its indication (not l10n)
1503
1504 * indications - List of indications (not l10n) of regimes we want scheduled
1505 vaccinations to be fetched for
1506 """
1507 try:
1508 self.__db_cache['vaccinations']['scheduled']
1509 except KeyError:
1510 self.__db_cache['vaccinations']['scheduled'] = []
1511 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1512 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1513 if rows is None:
1514 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1515 del self.__db_cache['vaccinations']['scheduled']
1516 return None
1517 # Instantiate vaccination items
1518 for row in rows:
1519 vacc_row = {
1520 'pk_field': 'pk_vacc_def',
1521 'idx': idx,
1522 'data': row
1523 }
1524 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1525
1526 # ok, let's constrain our list
1527 if indications is None:
1528 return self.__db_cache['vaccinations']['scheduled']
1529 filtered_shots = []
1530 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1531 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1532 return filtered_shots
1533 #--------------------------------------------------------
1535 try:
1536 self.__db_cache['vaccinations']['missing']
1537 except KeyError:
1538 self.__db_cache['vaccinations']['missing'] = {}
1539 # 1) non-booster
1540 self.__db_cache['vaccinations']['missing']['due'] = []
1541 # get list of (indication, seq_no) tuples
1542 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1543 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1544 if rows is None:
1545 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1546 return None
1547 pk_args = {'pat_id': self.pk_patient}
1548 if rows is not None:
1549 for row in rows:
1550 pk_args['indication'] = row[0]
1551 pk_args['seq_no'] = row[1]
1552 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1553
1554 # 2) boosters
1555 self.__db_cache['vaccinations']['missing']['boosters'] = []
1556 # get list of indications
1557 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1558 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1559 if rows is None:
1560 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1561 return None
1562 pk_args = {'pat_id': self.pk_patient}
1563 if rows is not None:
1564 for row in rows:
1565 pk_args['indication'] = row[0]
1566 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1567
1568 # if any filters ...
1569 if indications is None:
1570 return self.__db_cache['vaccinations']['missing']
1571 if len(indications) == 0:
1572 return self.__db_cache['vaccinations']['missing']
1573 # ... apply them
1574 filtered_shots = {
1575 'due': [],
1576 'boosters': []
1577 }
1578 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1579 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
1580 filtered_shots['due'].append(due_shot)
1581 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1582 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
1583 filtered_shots['boosters'].append(due_shot)
1584 return filtered_shots
1585 #------------------------------------------------------------------
1586 # API: encounters
1587 #------------------------------------------------------------------
1590
1592
1593 # first ever setting ?
1594 if self.__encounter is None:
1595 _log.debug('first setting of active encounter in this clinical record instance')
1596 else:
1597 _log.debug('switching of active encounter')
1598 # fail if the currently active encounter has unsaved changes
1599 if self.__encounter.is_modified():
1600 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1601 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1602
1603 # set the currently active encounter and announce that change
1604 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1605 encounter['last_affirmed'] = gmDateTime.pydt_now_here() # this will trigger an "encounter_mod_db"
1606 encounter.save()
1607 self.__encounter = encounter
1608 gmDispatcher.send(u'current_encounter_switched')
1609
1610 return True
1611
1612 current_encounter = property(_get_current_encounter, _set_current_encounter)
1613 active_encounter = property(_get_current_encounter, _set_current_encounter)
1614 #------------------------------------------------------------------
1616
1617 # 1) "very recent" encounter recorded ?
1618 if self.__activate_very_recent_encounter():
1619 return True
1620
1621 # 2) "fairly recent" encounter recorded ?
1622 if self.__activate_fairly_recent_encounter():
1623 return True
1624
1625 # 3) start a completely new encounter
1626 self.start_new_encounter()
1627 return True
1628 #------------------------------------------------------------------
1630 """Try to attach to a "very recent" encounter if there is one.
1631
1632 returns:
1633 False: no "very recent" encounter, create new one
1634 True: success
1635 """
1636 cfg_db = gmCfg.cCfgSQL()
1637 min_ttl = cfg_db.get2 (
1638 option = u'encounter.minimum_ttl',
1639 workplace = _here.active_workplace,
1640 bias = u'user',
1641 default = u'1 hour 30 minutes'
1642 )
1643 cmd = u"""
1644 SELECT pk_encounter
1645 FROM clin.v_most_recent_encounters
1646 WHERE
1647 pk_patient = %s
1648 and
1649 last_affirmed > (now() - %s::interval)"""
1650 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1651 # none found
1652 if len(enc_rows) == 0:
1653 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1654 return False
1655 # attach to existing
1656 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1657 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1658 return True
1659 #------------------------------------------------------------------
1661 """Try to attach to a "fairly recent" encounter if there is one.
1662
1663 returns:
1664 False: no "fairly recent" encounter, create new one
1665 True: success
1666 """
1667 if _func_ask_user is None:
1668 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1669 return False
1670
1671 cfg_db = gmCfg.cCfgSQL()
1672 min_ttl = cfg_db.get2 (
1673 option = u'encounter.minimum_ttl',
1674 workplace = _here.active_workplace,
1675 bias = u'user',
1676 default = u'1 hour 30 minutes'
1677 )
1678 max_ttl = cfg_db.get2 (
1679 option = u'encounter.maximum_ttl',
1680 workplace = _here.active_workplace,
1681 bias = u'user',
1682 default = u'6 hours'
1683 )
1684 cmd = u"""
1685 SELECT pk_encounter
1686 FROM clin.v_most_recent_encounters
1687 WHERE
1688 pk_patient=%s
1689 AND
1690 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)"""
1691 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1692 # none found
1693 if len(enc_rows) == 0:
1694 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1695 return False
1696 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1697 # ask user whether to attach or not
1698 cmd = u"""
1699 SELECT title, firstnames, lastnames, gender, dob
1700 FROM dem.v_basic_person WHERE pk_identity=%s"""
1701 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1702 pat = pats[0]
1703 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1704 gmTools.coalesce(pat[0], u'')[:5],
1705 pat[1][:15],
1706 pat[2][:15],
1707 pat[3],
1708 pat[4].strftime('%x'),
1709 self.pk_patient
1710 )
1711 enc = gmI18N.get_encoding()
1712 msg = _(
1713 '%s\n'
1714 '\n'
1715 "This patient's chart was worked on only recently:\n"
1716 '\n'
1717 ' %s %s - %s (%s)\n'
1718 '\n'
1719 ' Request: %s\n'
1720 ' Outcome: %s\n'
1721 '\n'
1722 'Do you want to continue that consultation\n'
1723 'or do you want to start a new one ?\n'
1724 ) % (
1725 pat_str,
1726 encounter['started'].strftime('%x').decode(enc),
1727 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1728 encounter['l10n_type'],
1729 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1730 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1731 )
1732 attach = False
1733 try:
1734 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1735 except:
1736 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1737 return False
1738 if not attach:
1739 return False
1740
1741 # attach to existing
1742 self.current_encounter = encounter
1743
1744 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1745 return True
1746 #------------------------------------------------------------------
1748 cfg_db = gmCfg.cCfgSQL()
1749 # FIXME: look for MRU/MCU encounter type config here
1750 enc_type = cfg_db.get2 (
1751 option = u'encounter.default_type',
1752 workplace = _here.active_workplace,
1753 bias = u'user',
1754 default = u'in surgery'
1755 )
1756 self.current_encounter = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
1757 _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
1758 #------------------------------------------------------------------
1760 """Retrieves patient's encounters.
1761
1762 id_list - PKs of encounters to fetch
1763 since - initial date for encounter items, DateTime instance
1764 until - final date for encounter items, DateTime instance
1765 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1766 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1767
1768 NOTE: if you specify *both* issues and episodes
1769 you will get the *aggregate* of all encounters even
1770 if the episodes all belong to the health issues listed.
1771 IOW, the issues broaden the episode list rather than
1772 the episode list narrowing the episodes-from-issues
1773 list.
1774 Rationale: If it was the other way round it would be
1775 redundant to specify the list of issues at all.
1776 """
1777 # fetch all encounters for patient
1778 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1779 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1780 encounters = []
1781 for r in rows:
1782 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1783
1784 # we've got the encounters, start filtering
1785 filtered_encounters = []
1786 filtered_encounters.extend(encounters)
1787 if id_list is not None:
1788 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1789 if since is not None:
1790 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1791 if until is not None:
1792 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1793
1794 if (issues is not None) and (len(issues) > 0):
1795
1796 issues = tuple(issues)
1797
1798 # Syan attests that an explicit union of child tables is way faster
1799 # as there seem to be problems with parent table expansion and use
1800 # of child table indexes, so if get_encounter() runs very slow on
1801 # your machine use the lines below
1802
1803 # rows = gmPG.run_ro_query('historica', cClinicalRecord._clin_root_item_children_union_query, None, (tuple(issues),))
1804 # if rows is None:
1805 # _log.error('cannot load encounters for issues [%s] (patient [%s])' % (str(issues), self.pk_patient))
1806 # else:
1807 # enc_ids = map(lambda x:x[0], rows)
1808 # filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1809
1810 # this problem seems fixed for us as of PostgreSQL 8.2 :-)
1811
1812 # however, this seems like the proper approach:
1813 # - find episodes corresponding to the health issues in question
1814 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1815 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1816 epi_ids = map(lambda x:x[0], rows)
1817 if episodes is None:
1818 episodes = []
1819 episodes.extend(epi_ids)
1820
1821 if (episodes is not None) and (len(episodes) > 0):
1822
1823 episodes = tuple(episodes)
1824
1825 # if the episodes to filter by belong to the patient in question so will
1826 # the encounters found with them - hence we don't need a WHERE on the patient ...
1827 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1828 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1829 enc_ids = map(lambda x:x[0], rows)
1830 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1831
1832 return filtered_encounters
1833 #--------------------------------------------------------
1835 """Retrieves first encounter for a particular issue and/or episode
1836
1837 issue_id - First encounter associated health issue
1838 episode - First encounter associated episode
1839 """
1840 # FIXME: use direct query
1841
1842 if issue_id is None:
1843 issues = None
1844 else:
1845 issues = [issue_id]
1846
1847 if episode_id is None:
1848 episodes = None
1849 else:
1850 episodes = [episode_id]
1851
1852 encounters = self.get_encounters(issues=issues, episodes=episodes)
1853 if len(encounters) == 0:
1854 return None
1855
1856 # FIXME: this does not scale particularly well, I assume
1857 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1858 return encounters[0]
1859 #--------------------------------------------------------
1861 """Retrieves last encounter for a concrete issue and/or episode
1862
1863 issue_id - Last encounter associated health issue
1864 episode_id - Last encounter associated episode
1865 """
1866 # FIXME: use direct query
1867
1868 if issue_id is None:
1869 issues = None
1870 else:
1871 issues = [issue_id]
1872
1873 if episode_id is None:
1874 episodes = None
1875 else:
1876 episodes = [episode_id]
1877
1878 encounters = self.get_encounters(issues=issues, episodes=episodes)
1879 if len(encounters) == 0:
1880 return None
1881
1882 # FIXME: this does not scale particularly well, I assume
1883 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1884 return encounters[-1]
1885 #------------------------------------------------------------------
1887
1888 args = {'pat': self.pk_patient}
1889
1890 if (issue_id is None) and (episode_id is None):
1891
1892 cmd = u"""
1893 SELECT * FROM clin.v_pat_encounters
1894 WHERE pk_patient = %(pat)s
1895 ORDER BY started DESC
1896 LIMIT 2
1897 """
1898 else:
1899 where_parts = []
1900
1901 if issue_id is not None:
1902 where_parts.append(u'pk_health_issue = %(issue)s')
1903 args['issue'] = issue_id
1904
1905 if episode_id is not None:
1906 where_parts.append(u'pk_episode = %(epi)s')
1907 args['epi'] = episode_id
1908
1909 cmd = u"""
1910 SELECT *
1911 FROM clin.v_pat_encounters
1912 WHERE
1913 pk_patient = %%(pat)s
1914 AND
1915 pk_encounter IN (
1916 SELECT distinct pk_encounter
1917 FROM clin.v_pat_narrative
1918 WHERE
1919 %s
1920 )
1921 ORDER BY started DESC
1922 LIMIT 2
1923 """ % u' AND '.join(where_parts)
1924
1925 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1926
1927 if len(rows) == 0:
1928 return None
1929
1930 # just one encounter within the above limits
1931 if len(rows) == 1:
1932 # is it the current encounter ?
1933 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1934 # yes
1935 return None
1936 # no
1937 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1938
1939 # more than one encounter
1940 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1941 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
1942
1943 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1944 #------------------------------------------------------------------
1946 cfg_db = gmCfg.cCfgSQL()
1947 ttl = cfg_db.get2 (
1948 option = u'encounter.ttl_if_empty',
1949 workplace = _here.active_workplace,
1950 bias = u'user',
1951 default = u'1 week'
1952 )
1953
1954 # # FIXME: this should be done async
1955 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
1956 args = {'pat': self.pk_patient, 'ttl': ttl}
1957 try:
1958 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1959 except:
1960 _log.exception('error deleting empty encounters')
1961
1962 return True
1963 #------------------------------------------------------------------
1964 # measurements API
1965 #------------------------------------------------------------------
1966 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
1968 """Retrieve data about test types for which this patient has results."""
1969
1970 cmd = u"""
1971 SELECT * FROM (
1972 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
1973 FROM clin.v_test_results
1974 WHERE pk_patient = %(pat)s
1975 ) AS foo
1976 ORDER BY clin_when desc, unified_name
1977 """
1978 args = {'pat': self.pk_patient}
1979 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1980 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
1981 #------------------------------------------------------------------
1983 """Retrieve details on tests grouped under unified names for this patient's results."""
1984 cmd = u"""
1985 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
1986 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
1987 from clin.v_test_results
1988 WHERE pk_patient = %(pat)s
1989 )
1990 order by unified_name"""
1991 args = {'pat': self.pk_patient}
1992 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1993 return rows, idx
1994 #------------------------------------------------------------------
1996 """Get the dates for which we have results."""
1997 cmd = u"""
1998 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
1999 from clin.v_test_results
2000 WHERE pk_patient = %(pat)s
2001 order by cwhen desc"""
2002 args = {'pat': self.pk_patient}
2003 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2004 return rows
2005 #------------------------------------------------------------------
2007
2008 cmd = u"""
2009 SELECT *, xmin_test_result FROM clin.v_test_results
2010 WHERE pk_patient = %(pat)s
2011 order by clin_when desc, pk_episode, unified_name"""
2012 args = {'pat': self.pk_patient}
2013 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2014
2015 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2016
2017 if episodes is not None:
2018 tests = [ t for t in tests if t['pk_episode'] in episodes ]
2019
2020 if encounter is not None:
2021 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
2022
2023 return tests
2024 #------------------------------------------------------------------
2025 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2026
2027 try:
2028 epi = int(episode)
2029 except:
2030 epi = episode['pk_episode']
2031
2032 try:
2033 type = int(type)
2034 except:
2035 type = type['pk_test_type']
2036
2037 if intended_reviewer is None:
2038 from Gnumed.business import gmPerson
2039 intended_reviewer = _me['pk_staff']
2040
2041 tr = gmPathLab.create_test_result (
2042 encounter = self.current_encounter['pk_encounter'],
2043 episode = epi,
2044 type = type,
2045 intended_reviewer = intended_reviewer,
2046 val_num = val_num,
2047 val_alpha = val_alpha,
2048 unit = unit
2049 )
2050
2051 return tr
2052 #------------------------------------------------------------------
2054
2055 cfg_db = gmCfg.cCfgSQL()
2056
2057 mass_loincs = cfg_db.get2 (
2058 option = u'lab.body_mass_loincs',
2059 workplace = _here.active_workplace,
2060 bias = u'user',
2061 default = []
2062 )
2063
2064 height_loincs = cfg_db.get2 (
2065 option = u'lab.body_height_loincs',
2066 workplace = _here.active_workplace,
2067 bias = u'user',
2068 default = []
2069 )
2070
2071 return gmPathLab.calculate_bmi(mass = mass, height = height) # age = age
2072 #------------------------------------------------------------------
2073 #------------------------------------------------------------------
2074 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2075 """Retrieves lab result clinical items.
2076
2077 limit - maximum number of results to retrieve
2078 since - initial date
2079 until - final date
2080 encounters - list of encounters
2081 episodes - list of episodes
2082 issues - list of health issues
2083 """
2084 try:
2085 return self.__db_cache['lab results']
2086 except KeyError:
2087 pass
2088 self.__db_cache['lab results'] = []
2089 if limit is None:
2090 lim = ''
2091 else:
2092 # only use limit if all other constraints are None
2093 if since is None and until is None and encounters is None and episodes is None and issues is None:
2094 lim = "limit %s" % limit
2095 else:
2096 lim = ''
2097
2098 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
2099 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2100 if rows is None:
2101 return False
2102 for row in rows:
2103 lab_row = {
2104 'pk_field': 'pk_result',
2105 'idx': idx,
2106 'data': row
2107 }
2108 lab_result = gmPathLab.cLabResult(row=lab_row)
2109 self.__db_cache['lab results'].append(lab_result)
2110
2111 # ok, let's constrain our list
2112 filtered_lab_results = []
2113 filtered_lab_results.extend(self.__db_cache['lab results'])
2114 if since is not None:
2115 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
2116 if until is not None:
2117 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
2118 if issues is not None:
2119 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
2120 if episodes is not None:
2121 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
2122 if encounters is not None:
2123 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
2124 return filtered_lab_results
2125 #------------------------------------------------------------------
2127 # FIXME: verify that it is our patient ? ...
2128 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
2129 return req
2130 #------------------------------------------------------------------
2132 if encounter_id is None:
2133 encounter_id = self.current_encounter['pk_encounter']
2134 status, data = gmPathLab.create_lab_request(
2135 lab=lab,
2136 req_id=req_id,
2137 pat_id=self.pk_patient,
2138 encounter_id=encounter_id,
2139 episode_id=episode_id
2140 )
2141 if not status:
2142 _log.error(str(data))
2143 return None
2144 return data
2145 #============================================================
2146 # main
2147 #------------------------------------------------------------
2148 if __name__ == "__main__":
2149
2150 if len(sys.argv) == 1:
2151 sys.exit()
2152
2153 if sys.argv[1] != 'test':
2154 sys.exit()
2155
2156 from Gnumed.pycommon import gmLog2
2157 #-----------------------------------------
2159 emr = cClinicalRecord(aPKey=1)
2160 state = emr.allergy_state
2161 print "allergy state is:", state
2162
2163 print "setting state to 0"
2164 emr.allergy_state = 0
2165
2166 print "setting state to None"
2167 emr.allergy_state = None
2168
2169 print "setting state to 'abc'"
2170 emr.allergy_state = 'abc'
2171 #-----------------------------------------
2173 emr = cClinicalRecord(aPKey=12)
2174 rows = emr.get_test_types_for_results()
2175 print "test result names:"
2176 for row in rows:
2177 print row
2178 #-----------------------------------------
2180 emr = cClinicalRecord(aPKey=12)
2181 rows = emr.get_dates_for_results()
2182 print "test result dates:"
2183 for row in rows:
2184 print row
2185 #-----------------------------------------
2187 emr = cClinicalRecord(aPKey=12)
2188 rows, idx = emr.get_measurements_by_date()
2189 print "test results:"
2190 for row in rows:
2191 print row
2192 #-----------------------------------------
2194 emr = cClinicalRecord(aPKey=12)
2195 tests = emr.get_test_results_by_date()
2196 print "test results:"
2197 for test in tests:
2198 print test
2199 #-----------------------------------------
2201 emr = cClinicalRecord(aPKey=12)
2202 rows, idx = emr.get_test_types_details()
2203 print "test type details:"
2204 for row in rows:
2205 print row
2206 #-----------------------------------------
2208 emr = cClinicalRecord(aPKey=12)
2209 for key, item in emr.get_statistics().iteritems():
2210 print key, ":", item
2211 #-----------------------------------------
2213 emr = cClinicalRecord(aPKey=12)
2214
2215 probs = emr.get_problems()
2216 print "normal probs (%s):" % len(probs)
2217 for p in probs:
2218 print u'%s (%s)' % (p['problem'], p['type'])
2219
2220 probs = emr.get_problems(include_closed_episodes=True)
2221 print "probs + closed episodes (%s):" % len(probs)
2222 for p in probs:
2223 print u'%s (%s)' % (p['problem'], p['type'])
2224
2225 probs = emr.get_problems(include_irrelevant_issues=True)
2226 print "probs + issues (%s):" % len(probs)
2227 for p in probs:
2228 print u'%s (%s)' % (p['problem'], p['type'])
2229
2230 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2231 print "probs + issues + epis (%s):" % len(probs)
2232 for p in probs:
2233 print u'%s (%s)' % (p['problem'], p['type'])
2234 #-----------------------------------------
2236 emr = cClinicalRecord(aPKey=12)
2237 tr = emr.add_test_result (
2238 episode = 1,
2239 intended_reviewer = 1,
2240 type = 1,
2241 val_num = 75,
2242 val_alpha = u'somewhat obese',
2243 unit = u'kg'
2244 )
2245 print tr
2246 #-----------------------------------------
2250 #-----------------------------------------
2252 emr = cClinicalRecord(aPKey=12)
2253 print emr.get_last_encounter(issue_id=2)
2254 print emr.get_last_but_one_encounter(issue_id=2)
2255 #-----------------------------------------
2257 emr = cClinicalRecord(aPKey=12)
2258 for med in emr.get_current_substance_intake():
2259 print med
2260 #-----------------------------------------
2262 emr = cClinicalRecord(aPKey = 12)
2263 print emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), brand = sys.argv[2])
2264 #-----------------------------------------
2266 emr = cClinicalRecord(aPKey = 12)
2267 for journal_line in emr.get_as_journal():
2268 #print journal_line.keys()
2269 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2270 print ""
2271 #-----------------------------------------
2272 #test_allergy_state()
2273 #test_is_allergic_to()
2274
2275 #test_get_test_names()
2276 #test_get_dates_for_results()
2277 #test_get_measurements()
2278 #test_get_test_results_by_date()
2279 #test_get_test_types_details()
2280 #test_get_statistics()
2281 #test_get_problems()
2282 #test_add_test_result()
2283 #test_get_most_recent_episode()
2284 #test_get_almost_recent_encounter()
2285 #test_get_meds()
2286 test_get_as_journal()
2287
2288 # emr = cClinicalRecord(aPKey = 12)
2289
2290 # # Vacc regimes
2291 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
2292 # print '\nVaccination regimes: '
2293 # for a_regime in vacc_regimes:
2294 # pass
2295 # #print a_regime
2296 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
2297 # #print vacc_regime
2298
2299 # # vaccination regimes and vaccinations for regimes
2300 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
2301 # print 'Vaccinations for the regime:'
2302 # for a_scheduled_vacc in scheduled_vaccs:
2303 # pass
2304 # #print ' %s' %(a_scheduled_vacc)
2305
2306 # # vaccination next shot and booster
2307 # vaccinations = emr.get_vaccinations()
2308 # for a_vacc in vaccinations:
2309 # print '\nVaccination %s , date: %s, booster: %s, seq no: %s' %(a_vacc['batch_no'], a_vacc['date'].strftime('%Y-%m-%d'), a_vacc['is_booster'], a_vacc['seq_no'])
2310
2311 # # first and last encounters
2312 # first_encounter = emr.get_first_encounter(issue_id = 1)
2313 # print '\nFirst encounter: ' + str(first_encounter)
2314 # last_encounter = emr.get_last_encounter(episode_id = 1)
2315 # print '\nLast encounter: ' + str(last_encounter)
2316 # print ''
2317
2318 # # lab results
2319 # lab = emr.get_lab_results()
2320 # lab_file = open('lab-data.txt', 'wb')
2321 # for lab_result in lab:
2322 # lab_file.write(str(lab_result))
2323 # lab_file.write('\n')
2324 # lab_file.close()
2325
2326 #dump = record.get_missing_vaccinations()
2327 #f = open('vaccs.lst', 'wb')
2328 #if dump is not None:
2329 # print "=== due ==="
2330 # f.write("=== due ===\n")
2331 # for row in dump['due']:
2332 # print row
2333 # f.write(repr(row))
2334 # f.write('\n')
2335 # print "=== overdue ==="
2336 # f.write("=== overdue ===\n")
2337 # for row in dump['overdue']:
2338 # print row
2339 # f.write(repr(row))
2340 # f.write('\n')
2341 #f.close()
2342
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Jul 28 03:57:04 2011 | http://epydoc.sourceforge.net |