1
2 """GNUmed clinical patient record.
3
4 Make sure to call set_func_ask_user() and set_encounter_ttl() early on in
5 your code (before cClinicalRecord.__init__() is called for the first time).
6 """
7
8 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL v2 or later"
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import sys
26 import logging
27
28
29 if __name__ == '__main__':
30 sys.path.insert(0, '../../')
31 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
32 gmI18N.activate_locale()
33 gmI18N.install_domain()
34 gmDateTime.init()
35
36 from Gnumed.pycommon import gmExceptions
37 from Gnumed.pycommon import gmPG2
38 from Gnumed.pycommon import gmDispatcher
39 from Gnumed.pycommon import gmI18N
40 from Gnumed.pycommon import gmCfg
41 from Gnumed.pycommon import gmTools
42 from Gnumed.pycommon import gmDateTime
43
44 from Gnumed.business import gmAllergy
45 from Gnumed.business import gmPathLab
46 from Gnumed.business import gmLOINC
47 from Gnumed.business import gmClinNarrative
48 from Gnumed.business import gmEMRStructItems
49 from Gnumed.business import gmMedication
50 from Gnumed.business import gmVaccination
51 from Gnumed.business import gmFamilyHistory
52 from Gnumed.business.gmDemographicRecord import get_occupations
53
54
55 _log = logging.getLogger('gm.emr')
56
57 _me = None
58 _here = None
59
60
61
62 _func_ask_user = None
63
65 if not callable(a_func):
66 _log.error('[%] not callable, not setting _func_ask_user', a_func)
67 return False
68
69 _log.debug('setting _func_ask_user to [%s]', a_func)
70
71 global _func_ask_user
72 _func_ask_user = a_func
73
74
76
77 _clin_root_item_children_union_query = None
78
79 - def __init__(self, aPKey=None, allow_user_interaction=True):
136
139
141 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
142 return True
143
144
145
150
152
153
154
155 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
156
157
158
159
160
161
162
163
164
165
166
167
168
169 if self.current_encounter.is_modified():
170 _log.debug('unsaved changes in active encounter, cannot switch to another one')
171 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
172
173 if self.current_encounter.same_payload(another_object = curr_enc_in_db):
174 _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
175 return True
176
177
178
179
180
181 _log.debug('active encounter modified remotely, reloading and announcing the modification')
182 self.current_encounter.refetch_payload()
183 gmDispatcher.send(u'current_encounter_modified')
184
185 return True
186
189
191 try:
192 del self.__db_cache['health issues']
193 except KeyError:
194 pass
195 return 1
196
198
199
200
201
202 return 1
203
204
205
206 - def get_family_history(self, episodes=None, issues=None, encounters=None):
207 fhx = gmFamilyHistory.get_family_history (
208 order_by = u'l10n_relation, condition',
209 patient = self.pk_patient
210 )
211
212 if episodes is not None:
213 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx)
214
215 if issues is not None:
216 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx)
217
218 if encounters is not None:
219 fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx)
220
221 return fhx
222
223 - def add_family_history(self, episode=None, condition=None, relation=None):
224 return gmFamilyHistory.create_family_history (
225 encounter = self.current_encounter['pk_encounter'],
226 episode = episode,
227 condition = condition,
228 relation = relation
229 )
230
231
232
244
245 performed_procedures = property(get_performed_procedures, lambda x:x)
246
249
258
259
260
268
269 hospital_stays = property(get_hospital_stays, lambda x:x)
270
273
280
282 args = {'pat': self.pk_patient, 'range': cover_period}
283 where_parts = [u'pk_patient = %(pat)s']
284 if cover_period is not None:
285 where_parts.append(u'discharge > (now() - %(range)s)')
286
287 cmd = u"""
288 SELECT hospital, count(1) AS frequency
289 FROM clin.v_hospital_stays
290 WHERE
291 %s
292 GROUP BY hospital
293 ORDER BY frequency DESC
294 """ % u' AND '.join(where_parts)
295
296 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
297 return rows
298
299
300
301 - def add_notes(self, notes=None, episode=None, encounter=None):
317
334
335 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
336 """Get SOAP notes pertinent to this encounter.
337
338 since
339 - initial date for narrative items
340 until
341 - final date for narrative items
342 encounters
343 - list of encounters whose narrative are to be retrieved
344 episodes
345 - list of episodes whose narrative are to be retrieved
346 issues
347 - list of health issues whose narrative are to be retrieved
348 soap_cats
349 - list of SOAP categories of the narrative to be retrieved
350 """
351 where_parts = [u'pk_patient = %(pat)s']
352 args = {u'pat': self.pk_patient}
353
354 if issues is not None:
355 where_parts.append(u'pk_health_issue IN %(issues)s')
356 args['issues'] = tuple(issues)
357
358 if episodes is not None:
359 where_parts.append(u'pk_episode IN %(epis)s')
360 args['epis'] = tuple(episodes)
361
362 if encounters is not None:
363 where_parts.append(u'pk_encounter IN %(encs)s')
364 args['encs'] = tuple(encounters)
365
366 if soap_cats is not None:
367 where_parts.append(u'soap_cat IN %(cats)s')
368 soap_cats = list(soap_cats)
369 args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ]
370 if None in soap_cats:
371 args['cats'].append(None)
372 args['cats'] = tuple(args['cats'])
373
374 cmd = u"""
375 SELECT
376 c_vpn.*,
377 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = c_vpn.soap_cat) AS soap_rank
378 FROM clin.v_pat_narrative c_vpn
379 WHERE %s
380 ORDER BY date, soap_rank
381 """ % u' AND '.join(where_parts)
382
383 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
384
385 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
386
387 if since is not None:
388 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
389
390 if until is not None:
391 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
392
393 if providers is not None:
394 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
395
396 return filtered_narrative
397
398 - 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):
399 return gmClinNarrative.get_as_journal (
400 patient = self.pk_patient,
401 since = since,
402 until = until,
403 encounters = encounters,
404 episodes = episodes,
405 issues = issues,
406 soap_cats = soap_cats,
407 providers = providers,
408 order_by = order_by,
409 time_range = time_range
410 )
411
413
414 search_term = search_term.strip()
415 if search_term == '':
416 return []
417
418 cmd = u"""
419 SELECT
420 *,
421 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
422 as episode,
423 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
424 as health_issue,
425 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
426 as encounter_started,
427 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
428 as encounter_ended,
429 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
430 as encounter_type
431 from clin.v_narrative4search vn4s
432 WHERE
433 pk_patient = %(pat)s and
434 vn4s.narrative ~ %(term)s
435 order by
436 encounter_started
437 """
438 rows, idx = gmPG2.run_ro_queries(queries = [
439 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
440 ])
441 return rows
442
444
445
446
447
448
449
450 try:
451 return self.__db_cache['text dump old']
452 except KeyError:
453 pass
454
455 fields = [
456 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
457 'modified_by',
458 'clin_when',
459 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
460 'pk_item',
461 'pk_encounter',
462 'pk_episode',
463 'pk_health_issue',
464 'src_table'
465 ]
466 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % ', '.join(fields)
467 ro_conn = self._conn_pool.GetConnection('historica')
468 curs = ro_conn.cursor()
469 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
470 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
471 curs.close()
472 return None
473 rows = curs.fetchall()
474 view_col_idx = gmPG2.get_col_indices(curs)
475
476
477 items_by_table = {}
478 for item in rows:
479 src_table = item[view_col_idx['src_table']]
480 pk_item = item[view_col_idx['pk_item']]
481 if not items_by_table.has_key(src_table):
482 items_by_table[src_table] = {}
483 items_by_table[src_table][pk_item] = item
484
485
486 issues = self.get_health_issues()
487 issue_map = {}
488 for issue in issues:
489 issue_map[issue['pk']] = issue['description']
490 episodes = self.get_episodes()
491 episode_map = {}
492 for episode in episodes:
493 episode_map[episode['pk_episode']] = episode['description']
494 emr_data = {}
495
496 for src_table in items_by_table.keys():
497 item_ids = items_by_table[src_table].keys()
498
499
500 if len(item_ids) == 0:
501 _log.info('no items in table [%s] ?!?' % src_table)
502 continue
503 elif len(item_ids) == 1:
504 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
505 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
506 _log.error('cannot load items from table [%s]' % src_table)
507
508 continue
509 elif len(item_ids) > 1:
510 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
511 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
512 _log.error('cannot load items from table [%s]' % src_table)
513
514 continue
515 rows = curs.fetchall()
516 table_col_idx = gmPG.get_col_indices(curs)
517
518 for row in rows:
519
520 pk_item = row[table_col_idx['pk_item']]
521 view_row = items_by_table[src_table][pk_item]
522 age = view_row[view_col_idx['age']]
523
524 try:
525 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
526 except:
527 episode_name = view_row[view_col_idx['pk_episode']]
528 try:
529 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
530 except:
531 issue_name = view_row[view_col_idx['pk_health_issue']]
532
533 if not emr_data.has_key(age):
534 emr_data[age] = []
535
536 emr_data[age].append(
537 _('%s: encounter (%s)') % (
538 view_row[view_col_idx['clin_when']],
539 view_row[view_col_idx['pk_encounter']]
540 )
541 )
542 emr_data[age].append(_('health issue: %s') % issue_name)
543 emr_data[age].append(_('episode : %s') % episode_name)
544
545
546
547 cols2ignore = [
548 'pk_audit', 'row_version', 'modified_when', 'modified_by',
549 'pk_item', 'id', 'fk_encounter', 'fk_episode'
550 ]
551 col_data = []
552 for col_name in table_col_idx.keys():
553 if col_name in cols2ignore:
554 continue
555 emr_data[age].append("=> %s:" % col_name)
556 emr_data[age].append(row[table_col_idx[col_name]])
557 emr_data[age].append("----------------------------------------------------")
558 emr_data[age].append("-- %s from table %s" % (
559 view_row[view_col_idx['modified_string']],
560 src_table
561 ))
562 emr_data[age].append("-- written %s by %s" % (
563 view_row[view_col_idx['modified_when']],
564 view_row[view_col_idx['modified_by']]
565 ))
566 emr_data[age].append("----------------------------------------------------")
567 curs.close()
568 self._conn_pool.ReleaseConnection('historica')
569 return emr_data
570
571 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
572
573
574
575
576
577
578 try:
579 return self.__db_cache['text dump']
580 except KeyError:
581 pass
582
583
584 fields = [
585 'age',
586 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
587 'modified_by',
588 'clin_when',
589 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
590 'pk_item',
591 'pk_encounter',
592 'pk_episode',
593 'pk_health_issue',
594 'src_table'
595 ]
596 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
597
598 where_snippets = []
599 params = {}
600 where_snippets.append('pk_patient=%(pat_id)s')
601 params['pat_id'] = self.pk_patient
602 if not since is None:
603 where_snippets.append('clin_when >= %(since)s')
604 params['since'] = since
605 if not until is None:
606 where_snippets.append('clin_when <= %(until)s')
607 params['until'] = until
608
609
610
611 if not encounters is None and len(encounters) > 0:
612 params['enc'] = encounters
613 if len(encounters) > 1:
614 where_snippets.append('fk_encounter in %(enc)s')
615 else:
616 where_snippets.append('fk_encounter=%(enc)s')
617
618 if not episodes is None and len(episodes) > 0:
619 params['epi'] = episodes
620 if len(episodes) > 1:
621 where_snippets.append('fk_episode in %(epi)s')
622 else:
623 where_snippets.append('fk_episode=%(epi)s')
624
625 if not issues is None and len(issues) > 0:
626 params['issue'] = issues
627 if len(issues) > 1:
628 where_snippets.append('fk_health_issue in %(issue)s')
629 else:
630 where_snippets.append('fk_health_issue=%(issue)s')
631
632 where_clause = ' and '.join(where_snippets)
633 order_by = 'order by src_table, age'
634 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
635
636 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
637 if rows is None:
638 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
639 return None
640
641
642
643
644 items_by_table = {}
645 for item in rows:
646 src_table = item[view_col_idx['src_table']]
647 pk_item = item[view_col_idx['pk_item']]
648 if not items_by_table.has_key(src_table):
649 items_by_table[src_table] = {}
650 items_by_table[src_table][pk_item] = item
651
652
653 issues = self.get_health_issues()
654 issue_map = {}
655 for issue in issues:
656 issue_map[issue['pk_health_issue']] = issue['description']
657 episodes = self.get_episodes()
658 episode_map = {}
659 for episode in episodes:
660 episode_map[episode['pk_episode']] = episode['description']
661 emr_data = {}
662
663 ro_conn = self._conn_pool.GetConnection('historica')
664 curs = ro_conn.cursor()
665 for src_table in items_by_table.keys():
666 item_ids = items_by_table[src_table].keys()
667
668
669 if len(item_ids) == 0:
670 _log.info('no items in table [%s] ?!?' % src_table)
671 continue
672 elif len(item_ids) == 1:
673 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
674 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
675 _log.error('cannot load items from table [%s]' % src_table)
676
677 continue
678 elif len(item_ids) > 1:
679 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
680 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
681 _log.error('cannot load items from table [%s]' % src_table)
682
683 continue
684 rows = curs.fetchall()
685 table_col_idx = gmPG.get_col_indices(curs)
686
687 for row in rows:
688
689 pk_item = row[table_col_idx['pk_item']]
690 view_row = items_by_table[src_table][pk_item]
691 age = view_row[view_col_idx['age']]
692
693 try:
694 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
695 except:
696 episode_name = view_row[view_col_idx['pk_episode']]
697 try:
698 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
699 except:
700 issue_name = view_row[view_col_idx['pk_health_issue']]
701
702 if not emr_data.has_key(age):
703 emr_data[age] = []
704
705 emr_data[age].append(
706 _('%s: encounter (%s)') % (
707 view_row[view_col_idx['clin_when']],
708 view_row[view_col_idx['pk_encounter']]
709 )
710 )
711 emr_data[age].append(_('health issue: %s') % issue_name)
712 emr_data[age].append(_('episode : %s') % episode_name)
713
714
715
716 cols2ignore = [
717 'pk_audit', 'row_version', 'modified_when', 'modified_by',
718 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
719 ]
720 col_data = []
721 for col_name in table_col_idx.keys():
722 if col_name in cols2ignore:
723 continue
724 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
725 emr_data[age].append("----------------------------------------------------")
726 emr_data[age].append("-- %s from table %s" % (
727 view_row[view_col_idx['modified_string']],
728 src_table
729 ))
730 emr_data[age].append("-- written %s by %s" % (
731 view_row[view_col_idx['modified_when']],
732 view_row[view_col_idx['modified_by']]
733 ))
734 emr_data[age].append("----------------------------------------------------")
735 curs.close()
736 return emr_data
737
739 return self.pk_patient
740
742 union_query = u'\n union all\n'.join ([
743 u"""
744 SELECT ((
745 -- all relevant health issues + active episodes WITH health issue
746 SELECT COUNT(1)
747 FROM clin.v_problem_list
748 WHERE
749 pk_patient = %(pat)s
750 AND
751 pk_health_issue is not null
752 ) + (
753 -- active episodes WITHOUT health issue
754 SELECT COUNT(1)
755 FROM clin.v_problem_list
756 WHERE
757 pk_patient = %(pat)s
758 AND
759 pk_health_issue is null
760 ))""",
761 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
762 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
763 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
764 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
765 u'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
766 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
767
768 u"""
769 SELECT count(1)
770 from clin.v_substance_intakes
771 WHERE
772 pk_patient = %(pat)s
773 and is_currently_active in (null, true)
774 and intake_is_approved_of in (null, true)""",
775 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
776 ])
777
778 rows, idx = gmPG2.run_ro_queries (
779 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
780 get_col_idx = False
781 )
782
783 stats = dict (
784 problems = rows[0][0],
785 encounters = rows[1][0],
786 items = rows[2][0],
787 documents = rows[3][0],
788 results = rows[4][0],
789 stays = rows[5][0],
790 procedures = rows[6][0],
791 active_drugs = rows[7][0],
792 vaccinations = rows[8][0]
793 )
794
795 return stats
796
809
911
932
933
934
935 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
936 """Retrieves patient allergy items.
937
938 remove_sensitivities
939 - retrieve real allergies only, without sensitivities
940 since
941 - initial date for allergy items
942 until
943 - final date for allergy items
944 encounters
945 - list of encounters whose allergies are to be retrieved
946 episodes
947 - list of episodes whose allergies are to be retrieved
948 issues
949 - list of health issues whose allergies are to be retrieved
950 """
951 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
952 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
953 allergies = []
954 for r in rows:
955 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
956
957
958 filtered_allergies = []
959 filtered_allergies.extend(allergies)
960
961 if ID_list is not None:
962 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
963 if len(filtered_allergies) == 0:
964 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
965
966 return None
967 else:
968 return filtered_allergies
969
970 if remove_sensitivities:
971 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
972 if since is not None:
973 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
974 if until is not None:
975 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
976 if issues is not None:
977 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
978 if episodes is not None:
979 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
980 if encounters is not None:
981 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
982
983 return filtered_allergies
984
985 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
986 if encounter_id is None:
987 encounter_id = self.current_encounter['pk_encounter']
988
989 if episode_id is None:
990 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
991 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
992 episode_id = epi['pk_episode']
993
994 new_allergy = gmAllergy.create_allergy (
995 allergene = allergene,
996 allg_type = allg_type,
997 encounter_id = encounter_id,
998 episode_id = episode_id
999 )
1000
1001 return new_allergy
1002
1004 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1005 args = {'pk_allg': pk_allergy}
1006 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1007
1009 """Cave: only use with one potential allergic agent
1010 otherwise you won't know which of the agents the allergy is to."""
1011
1012
1013 if self.allergy_state is None:
1014 return None
1015
1016
1017 if self.allergy_state == 0:
1018 return False
1019
1020 args = {
1021 'atcs': atcs,
1022 'inns': inns,
1023 'brand': brand,
1024 'pat': self.pk_patient
1025 }
1026 allergenes = []
1027 where_parts = []
1028
1029 if len(atcs) == 0:
1030 atcs = None
1031 if atcs is not None:
1032 where_parts.append(u'atc_code in %(atcs)s')
1033 if len(inns) == 0:
1034 inns = None
1035 if inns is not None:
1036 where_parts.append(u'generics in %(inns)s')
1037 allergenes.extend(inns)
1038 if brand is not None:
1039 where_parts.append(u'substance = %(brand)s')
1040 allergenes.append(brand)
1041
1042 if len(allergenes) != 0:
1043 where_parts.append(u'allergene in %(allgs)s')
1044 args['allgs'] = tuple(allergenes)
1045
1046 cmd = u"""
1047 SELECT * FROM clin.v_pat_allergies
1048 WHERE
1049 pk_patient = %%(pat)s
1050 AND ( %s )""" % u' OR '.join(where_parts)
1051
1052 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1053
1054 if len(rows) == 0:
1055 return False
1056
1057 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1058
1068
1071
1072 allergy_state = property(_get_allergy_state, _set_allergy_state)
1073
1074
1075
1076 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1077 """Fetches from backend patient episodes.
1078
1079 id_list - Episodes' PKs list
1080 issues - Health issues' PKs list to filter episodes by
1081 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1082 """
1083 if (unlinked_only is True) and (issues is not None):
1084 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1085
1086 if order_by is None:
1087 order_by = u''
1088 else:
1089 order_by = u'ORDER BY %s' % order_by
1090
1091 args = {
1092 'pat': self.pk_patient,
1093 'open': open_status
1094 }
1095 where_parts = [u'pk_patient = %(pat)s']
1096
1097 if open_status is not None:
1098 where_parts.append(u'episode_open IS %(open)s')
1099
1100 if unlinked_only:
1101 where_parts.append(u'pk_health_issue is NULL')
1102
1103 if issues is not None:
1104 where_parts.append(u'pk_health_issue IN %(issues)s')
1105 args['issues'] = tuple(issues)
1106
1107 if id_list is not None:
1108 where_parts.append(u'pk_episode IN %(epis)s')
1109 args['epis'] = tuple(id_list)
1110
1111 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1112 u' AND '.join(where_parts),
1113 order_by
1114 )
1115 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1116
1117 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1118
1119 episodes = property(get_episodes, lambda x:x)
1120
1122 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1123
1124 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1125
1127 cmd = u"""SELECT distinct pk_episode
1128 from clin.v_pat_items
1129 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1130 args = {
1131 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1132 'pat': self.pk_patient
1133 }
1134 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1135 if len(rows) == 0:
1136 return []
1137 epis = []
1138 for row in rows:
1139 epis.append(row[0])
1140 return self.get_episodes(id_list=epis)
1141
1142 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1143 """Add episode 'episode_name' for a patient's health issue.
1144
1145 - silently returns if episode already exists
1146 """
1147 episode = gmEMRStructItems.create_episode (
1148 pk_health_issue = pk_health_issue,
1149 episode_name = episode_name,
1150 is_open = is_open,
1151 encounter = self.current_encounter['pk_encounter']
1152 )
1153 return episode
1154
1156
1157
1158 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1159
1160 cmd = u"""
1161 SELECT pk
1162 from clin.episode
1163 WHERE pk = (
1164 SELECT distinct on(pk_episode) pk_episode
1165 from clin.v_pat_items
1166 WHERE
1167 pk_patient = %%(pat)s
1168 and
1169 modified_when = (
1170 SELECT max(vpi.modified_when)
1171 from clin.v_pat_items vpi
1172 WHERE vpi.pk_patient = %%(pat)s
1173 )
1174 %s
1175 -- guard against several episodes created at the same moment of time
1176 limit 1
1177 )""" % issue_where
1178 rows, idx = gmPG2.run_ro_queries(queries = [
1179 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1180 ])
1181 if len(rows) != 0:
1182 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1183
1184
1185
1186 cmd = u"""
1187 SELECT vpe0.pk_episode
1188 from
1189 clin.v_pat_episodes vpe0
1190 WHERE
1191 vpe0.pk_patient = %%(pat)s
1192 and
1193 vpe0.episode_modified_when = (
1194 SELECT max(vpe1.episode_modified_when)
1195 from clin.v_pat_episodes vpe1
1196 WHERE vpe1.pk_episode = vpe0.pk_episode
1197 )
1198 %s""" % issue_where
1199 rows, idx = gmPG2.run_ro_queries(queries = [
1200 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1201 ])
1202 if len(rows) != 0:
1203 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1204
1205 return None
1206
1209
1210
1211
1212 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1213 """Retrieve a patient's problems.
1214
1215 "Problems" are the UNION of:
1216
1217 - issues which are .clinically_relevant
1218 - episodes which are .is_open
1219
1220 Therefore, both an issue and the open episode
1221 thereof can each be listed as a problem.
1222
1223 include_closed_episodes/include_irrelevant_issues will
1224 include those -- which departs from the definition of
1225 the problem list being "active" items only ...
1226
1227 episodes - episodes' PKs to filter problems by
1228 issues - health issues' PKs to filter problems by
1229 """
1230
1231
1232 args = {'pat': self.pk_patient}
1233
1234 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1235 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1236
1237
1238 problems = []
1239 for row in rows:
1240 pk_args = {
1241 u'pk_patient': self.pk_patient,
1242 u'pk_health_issue': row['pk_health_issue'],
1243 u'pk_episode': row['pk_episode']
1244 }
1245 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1246
1247
1248 other_rows = []
1249 if include_closed_episodes:
1250 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1251 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1252 other_rows.extend(rows)
1253
1254 if include_irrelevant_issues:
1255 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1256 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1257 other_rows.extend(rows)
1258
1259 if len(other_rows) > 0:
1260 for row in other_rows:
1261 pk_args = {
1262 u'pk_patient': self.pk_patient,
1263 u'pk_health_issue': row['pk_health_issue'],
1264 u'pk_episode': row['pk_episode']
1265 }
1266 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1267
1268
1269 if (episodes is None) and (issues is None):
1270 return problems
1271
1272
1273 if issues is not None:
1274 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1275 if episodes is not None:
1276 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1277
1278 return problems
1279
1282
1285
1288
1289
1290
1292
1293 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1294 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1295 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1296
1297 if id_list is None:
1298 return issues
1299
1300 if len(id_list) == 0:
1301 raise ValueError('id_list to filter by is empty, most likely a programming error')
1302
1303 filtered_issues = []
1304 for issue in issues:
1305 if issue['pk_health_issue'] in id_list:
1306 filtered_issues.append(issue)
1307
1308 return filtered_issues
1309
1310 health_issues = property(get_health_issues, lambda x:x)
1311
1319
1322
1323
1324
1326
1327 where_parts = [u'pk_patient = %(pat)s']
1328 args = {'pat': self.pk_patient}
1329
1330 if not include_inactive:
1331 where_parts.append(u'is_currently_active IN (true, null)')
1332
1333 if not include_unapproved:
1334 where_parts.append(u'intake_is_approved_of IN (true, null)')
1335
1336 if order_by is None:
1337 order_by = u''
1338 else:
1339 order_by = u'ORDER BY %s' % order_by
1340
1341 cmd = u"SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1342 u'\nAND '.join(where_parts),
1343 order_by
1344 )
1345 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1346 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1347
1348 if episodes is not None:
1349 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1350
1351 if issues is not None:
1352 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1353
1354 return meds
1355
1356 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1364
1371
1372
1373
1381
1383 """Returns latest given vaccination for each vaccinated indication.
1384
1385 as a dict {'l10n_indication': cVaccination instance}
1386
1387 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1388 """
1389
1390 args = {'pat': self.pk_patient}
1391 where_parts = [u'pk_patient = %(pat)s']
1392
1393 if (episodes is not None) and (len(episodes) > 0):
1394 where_parts.append(u'pk_episode IN %(epis)s')
1395 args['epis'] = tuple(episodes)
1396
1397 if (issues is not None) and (len(issues) > 0):
1398 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1399 args['issues'] = tuple(issues)
1400
1401 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1402 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1403
1404
1405 if len(rows) == 0:
1406 return {}
1407
1408 vpks = [ ind['pk_vaccination'] for ind in rows ]
1409 vinds = [ ind['l10n_indication'] for ind in rows ]
1410 ind_counts = [ ind['indication_count'] for ind in rows ]
1411
1412
1413 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1414 args = {'pks': tuple(vpks)}
1415 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1416
1417 vaccs = {}
1418 for idx in range(len(vpks)):
1419 pk = vpks[idx]
1420 ind_count = ind_counts[idx]
1421 for r in rows:
1422 if r['pk_vaccination'] == pk:
1423 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1424
1425 return vaccs
1426
1427 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1428
1429 args = {'pat': self.pk_patient}
1430 where_parts = [u'pk_patient = %(pat)s']
1431
1432 if order_by is None:
1433 order_by = u''
1434 else:
1435 order_by = u'ORDER BY %s' % order_by
1436
1437 if (episodes is not None) and (len(episodes) > 0):
1438 where_parts.append(u'pk_episode IN %(epis)s')
1439 args['epis'] = tuple(episodes)
1440
1441 if (issues is not None) and (len(issues) > 0):
1442 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1443 args['issues'] = tuple(issues)
1444
1445 if (encounters is not None) and (len(encounters) > 0):
1446 where_parts.append(u'pk_encounter IN %(encs)s')
1447 args['encs'] = tuple(encounters)
1448
1449 cmd = u'%s %s' % (
1450 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1451 order_by
1452 )
1453 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1454 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1455
1456 return vaccs
1457
1458 vaccinations = property(get_vaccinations, lambda x:x)
1459
1460
1461
1463 """Retrieves vaccination regimes the patient is on.
1464
1465 optional:
1466 * ID - PK of the vaccination regime
1467 * indications - indications we want to retrieve vaccination
1468 regimes for, must be primary language, not l10n_indication
1469 """
1470
1471 try:
1472 self.__db_cache['vaccinations']['scheduled regimes']
1473 except KeyError:
1474
1475 self.__db_cache['vaccinations']['scheduled regimes'] = []
1476 cmd = """SELECT distinct on(pk_course) pk_course
1477 FROM clin.v_vaccs_scheduled4pat
1478 WHERE pk_patient=%s"""
1479 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1480 if rows is None:
1481 _log.error('cannot retrieve scheduled vaccination courses')
1482 del self.__db_cache['vaccinations']['scheduled regimes']
1483 return None
1484
1485 for row in rows:
1486 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1487
1488
1489 filtered_regimes = []
1490 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1491 if ID is not None:
1492 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1493 if len(filtered_regimes) == 0:
1494 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1495 return []
1496 else:
1497 return filtered_regimes[0]
1498 if indications is not None:
1499 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1500
1501 return filtered_regimes
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1530 """Retrieves list of vaccinations the patient has received.
1531
1532 optional:
1533 * ID - PK of a vaccination
1534 * indications - indications we want to retrieve vaccination
1535 items for, must be primary language, not l10n_indication
1536 * since - initial date for allergy items
1537 * until - final date for allergy items
1538 * encounters - list of encounters whose allergies are to be retrieved
1539 * episodes - list of episodes whose allergies are to be retrieved
1540 * issues - list of health issues whose allergies are to be retrieved
1541 """
1542 try:
1543 self.__db_cache['vaccinations']['vaccinated']
1544 except KeyError:
1545 self.__db_cache['vaccinations']['vaccinated'] = []
1546
1547 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1548 WHERE pk_patient=%s
1549 order by indication, date"""
1550 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1551 if rows is None:
1552 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1553 del self.__db_cache['vaccinations']['vaccinated']
1554 return None
1555
1556 vaccs_by_ind = {}
1557 for row in rows:
1558 vacc_row = {
1559 'pk_field': 'pk_vaccination',
1560 'idx': idx,
1561 'data': row
1562 }
1563 vacc = gmVaccination.cVaccination(row=vacc_row)
1564 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1565
1566 try:
1567 vaccs_by_ind[vacc['indication']].append(vacc)
1568 except KeyError:
1569 vaccs_by_ind[vacc['indication']] = [vacc]
1570
1571
1572 for ind in vaccs_by_ind.keys():
1573 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1574 for vacc in vaccs_by_ind[ind]:
1575
1576
1577 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1578 vacc['seq_no'] = seq_no
1579
1580
1581 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1582 continue
1583 if seq_no > vacc_regimes[0]['shots']:
1584 vacc['is_booster'] = True
1585 del vaccs_by_ind
1586
1587
1588 filtered_shots = []
1589 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1590 if ID is not None:
1591 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1592 if len(filtered_shots) == 0:
1593 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1594 return None
1595 else:
1596 return filtered_shots[0]
1597 if since is not None:
1598 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1599 if until is not None:
1600 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1601 if issues is not None:
1602 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1603 if episodes is not None:
1604 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1605 if encounters is not None:
1606 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1607 if indications is not None:
1608 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1609 return filtered_shots
1610
1612 """Retrieves vaccinations scheduled for a regime a patient is on.
1613
1614 The regime is referenced by its indication (not l10n)
1615
1616 * indications - List of indications (not l10n) of regimes we want scheduled
1617 vaccinations to be fetched for
1618 """
1619 try:
1620 self.__db_cache['vaccinations']['scheduled']
1621 except KeyError:
1622 self.__db_cache['vaccinations']['scheduled'] = []
1623 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1624 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1625 if rows is None:
1626 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1627 del self.__db_cache['vaccinations']['scheduled']
1628 return None
1629
1630 for row in rows:
1631 vacc_row = {
1632 'pk_field': 'pk_vacc_def',
1633 'idx': idx,
1634 'data': row
1635 }
1636 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1637
1638
1639 if indications is None:
1640 return self.__db_cache['vaccinations']['scheduled']
1641 filtered_shots = []
1642 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1643 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1644 return filtered_shots
1645
1647 try:
1648 self.__db_cache['vaccinations']['missing']
1649 except KeyError:
1650 self.__db_cache['vaccinations']['missing'] = {}
1651
1652 self.__db_cache['vaccinations']['missing']['due'] = []
1653
1654 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1655 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1656 if rows is None:
1657 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1658 return None
1659 pk_args = {'pat_id': self.pk_patient}
1660 if rows is not None:
1661 for row in rows:
1662 pk_args['indication'] = row[0]
1663 pk_args['seq_no'] = row[1]
1664 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1665
1666
1667 self.__db_cache['vaccinations']['missing']['boosters'] = []
1668
1669 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1670 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1671 if rows is None:
1672 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1673 return None
1674 pk_args = {'pat_id': self.pk_patient}
1675 if rows is not None:
1676 for row in rows:
1677 pk_args['indication'] = row[0]
1678 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1679
1680
1681 if indications is None:
1682 return self.__db_cache['vaccinations']['missing']
1683 if len(indications) == 0:
1684 return self.__db_cache['vaccinations']['missing']
1685
1686 filtered_shots = {
1687 'due': [],
1688 'boosters': []
1689 }
1690 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1691 if due_shot['indication'] in indications:
1692 filtered_shots['due'].append(due_shot)
1693 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1694 if due_shot['indication'] in indications:
1695 filtered_shots['boosters'].append(due_shot)
1696 return filtered_shots
1697
1698
1699
1701 return self.__encounter
1702
1704
1705
1706 if self.__encounter is None:
1707 _log.debug('first setting of active encounter in this clinical record instance')
1708 else:
1709 _log.debug('switching of active encounter')
1710
1711 if self.__encounter.is_modified():
1712 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1713 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723 self.__encounter = encounter
1724 gmDispatcher.send(u'current_encounter_switched')
1725
1726 return True
1727
1728 current_encounter = property(_get_current_encounter, _set_current_encounter)
1729 active_encounter = property(_get_current_encounter, _set_current_encounter)
1730
1732
1733
1734 if self.__activate_very_recent_encounter():
1735 return True
1736
1737
1738 if self.__activate_fairly_recent_encounter(allow_user_interaction = allow_user_interaction):
1739 return True
1740
1741
1742 self.start_new_encounter()
1743 return True
1744
1746 """Try to attach to a "very recent" encounter if there is one.
1747
1748 returns:
1749 False: no "very recent" encounter, create new one
1750 True: success
1751 """
1752 cfg_db = gmCfg.cCfgSQL()
1753 min_ttl = cfg_db.get2 (
1754 option = u'encounter.minimum_ttl',
1755 workplace = _here.active_workplace,
1756 bias = u'user',
1757 default = u'1 hour 30 minutes'
1758 )
1759 cmd = u"""
1760 SELECT pk_encounter
1761 FROM clin.v_most_recent_encounters
1762 WHERE
1763 pk_patient = %s
1764 and
1765 last_affirmed > (now() - %s::interval)
1766 ORDER BY
1767 last_affirmed DESC"""
1768 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1769
1770 if len(enc_rows) == 0:
1771 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1772 return False
1773
1774 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1775 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1776 return True
1777
1779 """Try to attach to a "fairly recent" encounter if there is one.
1780
1781 returns:
1782 False: no "fairly recent" encounter, create new one
1783 True: success
1784 """
1785 if _func_ask_user is None:
1786 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1787 return False
1788
1789 if not allow_user_interaction:
1790 _log.exception('user interaction not desired, not looking for fairly recent encounter')
1791 return False
1792
1793 cfg_db = gmCfg.cCfgSQL()
1794 min_ttl = cfg_db.get2 (
1795 option = u'encounter.minimum_ttl',
1796 workplace = _here.active_workplace,
1797 bias = u'user',
1798 default = u'1 hour 30 minutes'
1799 )
1800 max_ttl = cfg_db.get2 (
1801 option = u'encounter.maximum_ttl',
1802 workplace = _here.active_workplace,
1803 bias = u'user',
1804 default = u'6 hours'
1805 )
1806 cmd = u"""
1807 SELECT pk_encounter
1808 FROM clin.v_most_recent_encounters
1809 WHERE
1810 pk_patient=%s
1811 AND
1812 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
1813 ORDER BY
1814 last_affirmed DESC"""
1815 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1816
1817 if len(enc_rows) == 0:
1818 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1819 return False
1820
1821 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
1822
1823 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1824
1825 cmd = u"""
1826 SELECT title, firstnames, lastnames, gender, dob
1827 FROM dem.v_basic_person WHERE pk_identity=%s"""
1828 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1829 pat = pats[0]
1830 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1831 gmTools.coalesce(pat[0], u'')[:5],
1832 pat[1][:15],
1833 pat[2][:15],
1834 pat[3],
1835 gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
1836 self.pk_patient
1837 )
1838 msg = _(
1839 '%s\n'
1840 '\n'
1841 "This patient's chart was worked on only recently:\n"
1842 '\n'
1843 ' %s %s - %s (%s)\n'
1844 '\n'
1845 ' Request: %s\n'
1846 ' Outcome: %s\n'
1847 '\n'
1848 'Do you want to continue that consultation\n'
1849 'or do you want to start a new one ?\n'
1850 ) % (
1851 pat_str,
1852 gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
1853 gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
1854 encounter['l10n_type'],
1855 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1856 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1857 )
1858 attach = False
1859 try:
1860 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1861 except:
1862 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1863 return False
1864 if not attach:
1865 return False
1866
1867
1868 self.current_encounter = encounter
1869 _log.debug('"fairly recent" encounter re-activated')
1870 return True
1871
1888
1889 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False):
1890 """Retrieves patient's encounters.
1891
1892 id_list - PKs of encounters to fetch
1893 since - initial date for encounter items, DateTime instance
1894 until - final date for encounter items, DateTime instance
1895 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1896 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1897 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
1898
1899 NOTE: if you specify *both* issues and episodes
1900 you will get the *aggregate* of all encounters even
1901 if the episodes all belong to the health issues listed.
1902 IOW, the issues broaden the episode list rather than
1903 the episode list narrowing the episodes-from-issues
1904 list.
1905 Rationale: If it was the other way round it would be
1906 redundant to specify the list of issues at all.
1907 """
1908 where_parts = [u'c_vpe.pk_patient = %(pat)s']
1909 args = {'pat': self.pk_patient}
1910
1911 if skip_empty:
1912 where_parts.append(u"""NOT (
1913 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
1914 AND
1915 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
1916 AND
1917 NOT EXISTS (
1918 SELECT 1 FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_patient = %(pat)s AND c_vpi.pk_encounter = c_vpe.pk_encounter
1919 UNION ALL
1920 SELECT 1 FROM blobs.v_doc_med b_vdm WHERE b_vdm.pk_patient = %(pat)s AND b_vdm.pk_encounter = c_vpe.pk_encounter
1921 ))""")
1922
1923 if since is not None:
1924 where_parts.append(u'c_vpe.started >= %(start)s')
1925 args['start'] = since
1926
1927 if until is not None:
1928 where_parts.append(u'c_vpe.last_affirmed <= %(end)s')
1929 args['end'] = since
1930
1931 cmd = u"""
1932 SELECT *
1933 FROM clin.v_pat_encounters c_vpe
1934 WHERE
1935 %s
1936 ORDER BY started
1937 """ % u' AND '.join(where_parts)
1938 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1939 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
1940
1941
1942 filtered_encounters = []
1943 filtered_encounters.extend(encounters)
1944
1945 if id_list is not None:
1946 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1947
1948 if (issues is not None) and (len(issues) > 0):
1949 issues = tuple(issues)
1950
1951
1952 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1953 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1954 epi_ids = map(lambda x:x[0], rows)
1955 if episodes is None:
1956 episodes = []
1957 episodes.extend(epi_ids)
1958
1959 if (episodes is not None) and (len(episodes) > 0):
1960 episodes = tuple(episodes)
1961
1962
1963 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1964 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1965 enc_ids = map(lambda x:x[0], rows)
1966 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1967
1968 return filtered_encounters
1969
1971 """Retrieves first encounter for a particular issue and/or episode.
1972
1973 issue_id - First encounter associated health issue
1974 episode - First encounter associated episode
1975 """
1976
1977 if issue_id is None:
1978 issues = None
1979 else:
1980 issues = [issue_id]
1981
1982 if episode_id is None:
1983 episodes = None
1984 else:
1985 episodes = [episode_id]
1986
1987 encounters = self.get_encounters(issues=issues, episodes=episodes)
1988 if len(encounters) == 0:
1989 return None
1990
1991
1992 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1993 return encounters[0]
1994
1996 args = {'pat': self.pk_patient}
1997 cmd = u"""
1998 SELECT MIN(earliest) FROM (
1999 (
2000 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
2001
2002 ) UNION ALL (
2003
2004 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
2005
2006 ) UNION ALL (
2007
2008 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2009
2010 ) UNION ALL (
2011
2012 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2013
2014 ) UNION ALL (
2015
2016 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2017
2018 ) UNION ALL (
2019
2020 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2021
2022 ) UNION ALL (
2023
2024 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2025
2026 )
2027 ) AS candidates"""
2028 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2029 return rows[0][0]
2030
2031 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2032
2034 """Retrieves last encounter for a concrete issue and/or episode
2035
2036 issue_id - Last encounter associated health issue
2037 episode_id - Last encounter associated episode
2038 """
2039
2040
2041 if issue_id is None:
2042 issues = None
2043 else:
2044 issues = [issue_id]
2045
2046 if episode_id is None:
2047 episodes = None
2048 else:
2049 episodes = [episode_id]
2050
2051 encounters = self.get_encounters(issues=issues, episodes=episodes)
2052 if len(encounters) == 0:
2053 return None
2054
2055
2056 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
2057 return encounters[-1]
2058
2059 last_encounter = property(get_last_encounter, lambda x:x)
2060
2062 args = {'pat': self.pk_patient, 'range': cover_period}
2063 where_parts = [u'pk_patient = %(pat)s']
2064 if cover_period is not None:
2065 where_parts.append(u'last_affirmed > now() - %(range)s')
2066
2067 cmd = u"""
2068 SELECT l10n_type, count(1) AS frequency
2069 FROM clin.v_pat_encounters
2070 WHERE
2071 %s
2072 GROUP BY l10n_type
2073 ORDER BY frequency DESC
2074 """ % u' AND '.join(where_parts)
2075 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2076 return rows
2077
2079
2080 args = {'pat': self.pk_patient}
2081
2082 if (issue_id is None) and (episode_id is None):
2083
2084 cmd = u"""
2085 SELECT * FROM clin.v_pat_encounters
2086 WHERE pk_patient = %(pat)s
2087 ORDER BY started DESC
2088 LIMIT 2
2089 """
2090 else:
2091 where_parts = []
2092
2093 if issue_id is not None:
2094 where_parts.append(u'pk_health_issue = %(issue)s')
2095 args['issue'] = issue_id
2096
2097 if episode_id is not None:
2098 where_parts.append(u'pk_episode = %(epi)s')
2099 args['epi'] = episode_id
2100
2101 cmd = u"""
2102 SELECT *
2103 FROM clin.v_pat_encounters
2104 WHERE
2105 pk_patient = %%(pat)s
2106 AND
2107 pk_encounter IN (
2108 SELECT distinct pk_encounter
2109 FROM clin.v_pat_narrative
2110 WHERE
2111 %s
2112 )
2113 ORDER BY started DESC
2114 LIMIT 2
2115 """ % u' AND '.join(where_parts)
2116
2117 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2118
2119 if len(rows) == 0:
2120 return None
2121
2122
2123 if len(rows) == 1:
2124
2125 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2126
2127 return None
2128
2129 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2130
2131
2132 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2133 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2134
2135 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2136
2138 cfg_db = gmCfg.cCfgSQL()
2139 ttl = cfg_db.get2 (
2140 option = u'encounter.ttl_if_empty',
2141 workplace = _here.active_workplace,
2142 bias = u'user',
2143 default = u'1 week'
2144 )
2145
2146
2147 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
2148 args = {'pat': self.pk_patient, 'ttl': ttl}
2149 try:
2150 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2151 except:
2152 _log.exception('error deleting empty encounters')
2153
2154 return True
2155
2156
2157
2165
2174
2176 if order_by is None:
2177 order_by = u''
2178 else:
2179 order_by = u'ORDER BY %s' % order_by
2180 cmd = u"""
2181 SELECT * FROM clin.v_test_results
2182 WHERE
2183 pk_patient = %%(pat)s
2184 AND
2185 reviewed IS FALSE
2186 %s""" % order_by
2187 args = {'pat': self.pk_patient}
2188 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2189 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2190
2191
2193 """Retrieve data about test types for which this patient has results."""
2194
2195 cmd = u"""
2196 SELECT * FROM (
2197 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2198 FROM clin.v_test_results
2199 WHERE pk_patient = %(pat)s
2200 ) AS foo
2201 ORDER BY clin_when desc, unified_name
2202 """
2203 args = {'pat': self.pk_patient}
2204 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2205 return [ gmPathLab.cMeasurementType(aPK_obj = row['pk_test_type']) for row in rows ]
2206
2208 """Get the dates for which we have results."""
2209 where_parts = [u'pk_patient = %(pat)s']
2210 args = {'pat': self.pk_patient}
2211
2212 if tests is not None:
2213 where_parts.append(u'pk_test_type IN %(tests)s')
2214 args['tests'] = tuple(tests)
2215
2216 cmd = u"""
2217 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2218 FROM clin.v_test_results
2219 WHERE %s
2220 ORDER BY cwhen %s
2221 """ % (
2222 u' AND '.join(where_parts),
2223 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2224 )
2225 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2226 return rows
2227
2228 - def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
2235
2237
2238 where_parts = [u'pk_patient = %(pat)s']
2239 args = {'pat': self.pk_patient}
2240
2241 if tests is not None:
2242 where_parts.append(u'pk_test_type IN %(tests)s')
2243 args['tests'] = tuple(tests)
2244
2245 if encounter is not None:
2246 where_parts.append(u'pk_encounter = %(enc)s')
2247 args['enc'] = encounter
2248
2249 if episodes is not None:
2250 where_parts.append(u'pk_episode IN %(epis)s')
2251 args['epis'] = tuple(episodes)
2252
2253 cmd = u"""
2254 SELECT * FROM clin.v_test_results
2255 WHERE %s
2256 ORDER BY clin_when %s, pk_episode, unified_name
2257 """ % (
2258 u' AND '.join(where_parts),
2259 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2260 )
2261 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2262
2263 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2264
2265 return tests
2266
2267 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2268
2269 try:
2270 epi = int(episode)
2271 except:
2272 epi = episode['pk_episode']
2273
2274 try:
2275 type = int(type)
2276 except:
2277 type = type['pk_test_type']
2278
2279 if intended_reviewer is None:
2280 intended_reviewer = _me['pk_staff']
2281
2282 tr = gmPathLab.create_test_result (
2283 encounter = self.current_encounter['pk_encounter'],
2284 episode = epi,
2285 type = type,
2286 intended_reviewer = intended_reviewer,
2287 val_num = val_num,
2288 val_alpha = val_alpha,
2289 unit = unit
2290 )
2291
2292 return tr
2293
2294
2295
2296
2301
2302 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2316
2317
2318
2319
2320 if __name__ == "__main__":
2321
2322 if len(sys.argv) == 1:
2323 sys.exit()
2324
2325 if sys.argv[1] != 'test':
2326 sys.exit()
2327
2328 from Gnumed.pycommon import gmLog2
2329
2343
2350
2357
2359 emr = cClinicalRecord(aPKey=12)
2360 rows, idx = emr.get_measurements_by_date()
2361 print "test results:"
2362 for row in rows:
2363 print row
2364
2371
2376
2378 emr = cClinicalRecord(aPKey=12)
2379
2380 probs = emr.get_problems()
2381 print "normal probs (%s):" % len(probs)
2382 for p in probs:
2383 print u'%s (%s)' % (p['problem'], p['type'])
2384
2385 probs = emr.get_problems(include_closed_episodes=True)
2386 print "probs + closed episodes (%s):" % len(probs)
2387 for p in probs:
2388 print u'%s (%s)' % (p['problem'], p['type'])
2389
2390 probs = emr.get_problems(include_irrelevant_issues=True)
2391 print "probs + issues (%s):" % len(probs)
2392 for p in probs:
2393 print u'%s (%s)' % (p['problem'], p['type'])
2394
2395 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2396 print "probs + issues + epis (%s):" % len(probs)
2397 for p in probs:
2398 print u'%s (%s)' % (p['problem'], p['type'])
2399
2401 emr = cClinicalRecord(aPKey=12)
2402 tr = emr.add_test_result (
2403 episode = 1,
2404 intended_reviewer = 1,
2405 type = 1,
2406 val_num = 75,
2407 val_alpha = u'somewhat obese',
2408 unit = u'kg'
2409 )
2410 print tr
2411
2415
2420
2425
2429
2431 emr = cClinicalRecord(aPKey = 12)
2432 for journal_line in emr.get_as_journal():
2433
2434 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2435 print ""
2436
2440
2445
2446
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470 test_format_as_journal()
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518