1
2 """GNUmed clinical patient record."""
3
4 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL v2 or later"
6
7
8 import sys
9 import logging
10 import threading
11 import datetime as pydt
12
13 if __name__ == '__main__':
14 sys.path.insert(0, '../../')
15
16 from Gnumed.pycommon import gmI18N
17 from Gnumed.pycommon import gmDateTime
18
19 if __name__ == '__main__':
20 from Gnumed.pycommon import gmLog2
21 gmI18N.activate_locale()
22 gmI18N.install_domain()
23 gmDateTime.init()
24
25 from Gnumed.pycommon import gmExceptions
26 from Gnumed.pycommon import gmPG2
27 from Gnumed.pycommon import gmDispatcher
28 from Gnumed.pycommon import gmCfg
29 from Gnumed.pycommon import gmTools
30
31 from Gnumed.business import gmAllergy
32 from Gnumed.business import gmPathLab
33 from Gnumed.business import gmLOINC
34 from Gnumed.business import gmClinNarrative
35 from Gnumed.business import gmSoapDefs
36 from Gnumed.business import gmEMRStructItems
37 from Gnumed.business import gmMedication
38 from Gnumed.business import gmVaccination
39 from Gnumed.business import gmFamilyHistory
40 from Gnumed.business import gmExternalCare
41 from Gnumed.business import gmOrganization
42 from Gnumed.business import gmAutoHints
43 from Gnumed.business.gmDemographicRecord import get_occupations
44
45
46 _log = logging.getLogger('gm.emr')
47
48 _here = None
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 _map_clin_root_item2type_str = {
66 'clin.encounter': _('Encounter'),
67 'clin.episode': _('Episode'),
68 'clin.health_issue': _('Health issue'),
69 'clin.external_care': _('External care'),
70 'clin.vaccination': _('Vaccination'),
71 'clin.clin_narrative': _('Clinical narrative'),
72 'clin.test_result': _('Test result'),
73 'clin.substance_intake': _('Substance intake'),
74 'clin.hospital_stay': _('Hospital stay'),
75 'clin.procedure': _('Performed procedure'),
76 'clin.allergy': _('Allergy'),
77 'clin.allergy_state': _('Allergy state'),
78 'clin.family_history': _('Family history'),
79 'blobs.doc_med': _('Document'),
80 'dem.message_inbox': _('Inbox message'),
81 'ref.auto_hint': _('Dynamic hint')
82 }
83
89
90
91 from Gnumed.business.gmDocuments import cDocument
92 from Gnumed.business.gmProviderInbox import cInboxMessage
93
94 _map_table2class = {
95 'clin.encounter': gmEMRStructItems.cEncounter,
96 'clin.episode': gmEMRStructItems.cEpisode,
97 'clin.health_issue': gmEMRStructItems.cHealthIssue,
98 'clin.external_care': gmExternalCare.cExternalCareItem,
99 'clin.vaccination': gmVaccination.cVaccination,
100 'clin.clin_narrative': gmClinNarrative.cNarrative,
101 'clin.test_result': gmPathLab.cTestResult,
102 'clin.substance_intake': gmMedication.cSubstanceIntakeEntry,
103 'clin.hospital_stay': gmEMRStructItems.cHospitalStay,
104 'clin.procedure': gmEMRStructItems.cPerformedProcedure,
105 'clin.allergy': gmAllergy.cAllergy,
106 'clin.allergy_state': gmAllergy.cAllergyState,
107 'clin.family_history': gmFamilyHistory.cFamilyHistory,
108 'clin.suppressed_hint': gmAutoHints.cSuppressedHint,
109 'blobs.doc_med': cDocument,
110 'dem.message_inbox': cInboxMessage,
111 'ref.auto_hint': gmAutoHints.cDynamicHint
112 }
113
115 try:
116 item_class = _map_table2class[table]
117 except KeyError:
118 _log.error('unmapped clin_root_item entry [%s], cannot instantiate', table)
119 return None
120
121 return item_class(aPK_obj = pk)
122
123
153
154
157
158
159 _delayed_execute = __noop_delayed_execute
160
161
163 if not callable(executor):
164 raise TypeError('executor <%s> is not callable' % executor)
165 global _delayed_execute
166 _delayed_execute = executor
167 _log.debug('setting delayed executor to <%s>', executor)
168
169
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
232 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
233 if self.__encounter is not None:
234 self.__encounter.unlock(exclusive = False)
235 return True
236
237
239 if action is None:
240 action = 'EMR access for pk_identity [%s]' % self.pk_patient
241 args = {'action': action}
242 cmd = 'SELECT gm.log_access2emr(%(action)s)'
243 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
244
245
258
259 calculator = property(_get_calculator, lambda x:x)
260
261
262
263
269
270
272
273 if kwds['table'] != 'clin.encounter':
274 return True
275 if self.current_encounter is None:
276 _log.debug('no local current-encounter, ignoring encounter modification signal')
277 return True
278 if int(kwds['pk_of_row']) != self.current_encounter['pk_encounter']:
279 _log.debug('modified encounter [%s] != local encounter [%s], ignoring signal', kwds['pk_of_row'], self.current_encounter['pk_encounter'])
280 return True
281
282 _log.debug('modification of our encounter (%s) signalled (%s)', self.current_encounter['pk_encounter'], kwds['pk_of_row'])
283
284
285
286 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
287
288
289
290
291
292
293 if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
294 _log.debug('same XMIN, no difference between DB and in-client instance of current encounter expected')
295 if self.current_encounter.is_modified():
296 _log.error('encounter modification signal from DB with same XMIN as in local in-client instance of encounter BUT local instance ALSO has .is_modified()=True')
297 _log.error('this hints at an error in .is_modified handling')
298 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
299 return True
300
301
302
303
304
305 if self.current_encounter.is_modified():
306 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'signalled enc loaded from DB')
307 raise ValueError('unsaved changes in locally active encounter [%s], cannot switch to DB state of encounter [%s]' % (
308 self.current_encounter['pk_encounter'],
309 curr_enc_in_db['pk_encounter']
310 ))
311
312
313
314
315
316
317
318
319
320
321
322
323
324 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
325 _log.debug('active encounter modified remotely, no locally pending changes, reloading from DB and locally announcing the remote modification')
326 self.current_encounter.refetch_payload()
327 gmDispatcher.send('current_encounter_modified')
328
329 return True
330
331
333
334
335
336 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351 if self.current_encounter.is_modified():
352 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
353 _log.error('current in client: %s', self.current_encounter)
354 raise ValueError('unsaved changes in active encounter [%s], cannot switch [%s]' % (
355 self.current_encounter['pk_encounter'],
356 curr_enc_in_db['pk_encounter']
357 ))
358
359 if self.current_encounter.same_payload(another_object = curr_enc_in_db):
360 _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
361 return True
362
363
364
365
366
367 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
368 _log.debug('active encounter modified remotely, reloading from DB and locally announcing the modification')
369 self.current_encounter.refetch_payload()
370 gmDispatcher.send('current_encounter_modified')
371
372 return True
373
374
375
376
377 - def get_family_history(self, episodes=None, issues=None, encounters=None):
378 fhx = gmFamilyHistory.get_family_history (
379 order_by = 'l10n_relation, condition',
380 patient = self.pk_patient
381 )
382
383 if episodes is not None:
384 fhx = [ f for f in fhx if f['pk_episode'] in episodes ]
385
386 if issues is not None:
387 fhx = [ f for f in fhx if f['pk_health_issue'] in issues ]
388
389 if encounters is not None:
390 fhx = [ f for f in fhx if f['pk_encounter'] in encounters ]
391
392 return fhx
393
394
395 - def add_family_history(self, episode=None, condition=None, relation=None):
396 return gmFamilyHistory.create_family_history (
397 encounter = self.current_encounter['pk_encounter'],
398 episode = episode,
399 condition = condition,
400 relation = relation
401 )
402
403
404
405
407 if self.__gender is not None:
408 return self.__gender
409 cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
410 args = {'pat': self.pk_patient}
411 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
412 self.__gender = rows[0]['gender']
413 self.__dob = rows[0]['dob']
414
417
418 gender = property(_get_gender, _set_gender)
419
420
422 if self.__dob is not None:
423 return self.__dob
424 cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
425 args = {'pat': self.pk_patient}
426 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
427 self.__gender = rows[0]['gender']
428 self.__dob = rows[0]['dob']
429
432
433 dob = property(_get_dob, _set_dob)
434
435
437 cmd = 'SELECT edc FROM clin.patient WHERE fk_identity = %(pat)s'
438 args = {'pat': self.pk_patient}
439 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
440 if len(rows) == 0:
441 return None
442 return rows[0]['edc']
443
445 cmd = """
446 INSERT INTO clin.patient (fk_identity, edc) SELECT
447 %(pat)s,
448 %(edc)s
449 WHERE NOT EXISTS (
450 SELECT 1 FROM clin.patient WHERE fk_identity = %(pat)s
451 )
452 RETURNING pk"""
453 args = {'pat': self.pk_patient, 'edc': edc}
454 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
455 if len(rows) == 0:
456 cmd = 'UPDATE clin.patient SET edc = %(edc)s WHERE fk_identity = %(pat)s'
457 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
458
459 EDC = property(_get_EDC, _set_EDC)
460
461
463 edc = self.EDC
464 if edc is None:
465 return False
466 if self.gender != 'f':
467 return True
468 now = gmDateTime.pydt_now_here()
469
470 if (self.dob + pydt.timedelta(weeks = 5 * 52)) > now:
471 return True
472
473 if (self.dob + pydt.timedelta(weeks = 55 * 52)) < now:
474 return True
475
476
477 if (edc - pydt.timedelta(days = 380)) > now:
478 return True
479
480
481
482
483 if edc < (now - pydt.timedelta(days = 380)):
484 return True
485
486 EDC_is_fishy = property(_EDC_is_fishy, lambda x:x)
487
488
490 try:
491 details['quit_when']
492 except KeyError:
493 details['quit_when'] = None
494
495 try:
496 details['last_confirmed']
497 if details['last_confirmed'] is None:
498 details['last_confirmed'] = gmDateTime.pydt_now_here()
499 except KeyError:
500 details['last_confirmed'] = gmDateTime.pydt_now_here()
501
502 try:
503 details['comment']
504 if details['comment'].strip() == '':
505 details['comment'] = None
506 except KeyError:
507 details['comment'] = None
508
509 return details
510
511
517
519
520 status_flag, details = status
521 self.__harmful_substance_use = None
522 args = {
523 'pat': self.pk_patient,
524 'status': status_flag
525 }
526 if status_flag is None:
527 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = NULL WHERE fk_identity = %(pat)s'
528 elif status_flag == 0:
529 details['quit_when'] = None
530 args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
531 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
532 else:
533 args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
534 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
535 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
536
537 smoking_status = property(_get_smoking_status, _set_smoking_status)
538
539
545
547
548 harmful, details = status
549 self.__harmful_substance_use = None
550 args = {'pat': self.pk_patient}
551 if harmful is None:
552 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = NULL, c2_details = NULL WHERE fk_identity = %(pat)s'
553 elif harmful is False:
554 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = FALSE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
555 else:
556 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = TRUE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
557 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
558
559 alcohol_status = property(_get_alcohol_status, _set_alcohol_status)
560
561
567
569
570 harmful, details = status
571 self.__harmful_substance_use = None
572 args = {'pat': self.pk_patient}
573 if harmful is None:
574 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = NULL, drugs_details = NULL WHERE fk_identity = %(pat)s'
575 elif harmful is False:
576 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = FALSE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
577 else:
578 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = TRUE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
579 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
580
581 drugs_status = property(_get_drugs_status, _set_drugs_status)
582
583
585
586 try:
587 self.__harmful_substance_use
588 except AttributeError:
589 self.__harmful_substance_use = None
590
591 if self.__harmful_substance_use is not None:
592 return self.__harmful_substance_use
593
594 args = {'pat': self.pk_patient}
595 cmd = """
596 SELECT
597 -- tobacco use
598 smoking_status,
599 smoking_details,
600 (smoking_details->>'last_confirmed')::timestamp with time zone
601 AS ts_last,
602 (smoking_details->>'quit_when')::timestamp with time zone
603 AS ts_quit,
604 -- c2 use
605 c2_currently_harmful_use,
606 c2_details,
607 -- other drugs use
608 drugs_currently_harmful_use,
609 drugs_details
610 FROM clin.patient
611 WHERE fk_identity = %(pat)s
612 """
613 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
614 if len(rows) == 0:
615 return None
616
617 status = rows[0]['smoking_status']
618 details = rows[0]['smoking_details']
619 if status is not None:
620 details['last_confirmed'] = rows[0]['ts_last']
621 details['quit_when'] = rows[0]['ts_quit']
622
623 self.__harmful_substance_use = {
624 'tobacco': (status, details),
625 'alcohol': (rows[0]['c2_currently_harmful_use'], rows[0]['c2_details']),
626 'drugs': (rows[0]['drugs_currently_harmful_use'], rows[0]['drugs_details'])
627 }
628
629 return self.__harmful_substance_use
630
631
633 cmd = 'SELECT * FROM clin.v_substance_intakes WHERE harmful_use_type = %s'
634
635 harmful_substance_use = property(_get_harmful_substance_use, lambda x:x)
636
637
716
717
719
720
721 use = self.harmful_substance_use
722
723 if use['alcohol'][0] is True:
724 return True
725 if use['drugs'][0] is True:
726 return True
727 if use['tobacco'][0] > 0:
728
729 if use['tobacco'][1]['quit_when'] is None:
730 return True
731
732
733 if use['alcohol'][0] is None:
734 return None
735 if use['drugs'][0] is None:
736 return None
737 if use['tobacco'][0] is None:
738 return None
739
740
741
742 return False
743
744 currently_abuses_substances = property(_get_currently_abuses_substances, lambda x:x)
745
746
747
748
760
761 performed_procedures = property(get_performed_procedures, lambda x:x)
762
765
774
776 where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_procedures_not_at_hospital WHERE pk_patient = %(pat)s)'
777 args = {'pat': self.pk_patient}
778 cmd = gmOrganization._SQL_get_org_unit % where
779 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
780 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
781
782
783
784
792
793 hospital_stays = property(get_hospital_stays, lambda x:x)
794
797
804
806 args = {'pat': self.pk_patient, 'range': cover_period}
807 where_parts = ['pk_patient = %(pat)s']
808 if cover_period is not None:
809 where_parts.append('discharge > (now() - %(range)s)')
810
811 cmd = """
812 SELECT hospital, count(1) AS frequency
813 FROM clin.v_hospital_stays
814 WHERE
815 %s
816 GROUP BY hospital
817 ORDER BY frequency DESC
818 """ % ' AND '.join(where_parts)
819
820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
821 return rows
822
824 where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s)'
825 args = {'pat': self.pk_patient}
826 cmd = gmOrganization._SQL_get_org_unit % where
827 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
828 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
829
830
831
832
833 - def add_notes(self, notes=None, episode=None, encounter=None):
846
847
862
863
864 - def get_clin_narrative(self, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
865 """Get SOAP notes pertinent to this encounter.
866
867 encounters
868 - list of encounters the narrative of which are to be retrieved
869 episodes
870 - list of episodes the narrative of which are to be retrieved
871 issues
872 - list of health issues the narrative of which are to be retrieved
873 soap_cats
874 - list of SOAP categories of the narrative to be retrieved
875 """
876 where_parts = ['pk_patient = %(pat)s']
877 args = {'pat': self.pk_patient}
878
879 if issues is not None:
880 where_parts.append('pk_health_issue IN %(issues)s')
881 if len(issues) == 0:
882 args['issues'] = tuple()
883 else:
884 if isinstance(issues[0], gmEMRStructItems.cHealthIssue):
885 args['issues'] = tuple([ i['pk_health_issue'] for i in issues ])
886 elif isinstance(issues[0], int):
887 args['issues'] = tuple(issues)
888 else:
889 raise ValueError('<issues> must be list of type int (=pk) or cHealthIssue, but 1st issue is: %s' % issues[0])
890
891 if episodes is not None:
892 where_parts.append('pk_episode IN %(epis)s')
893 if len(episodes) == 0:
894 args['epis'] = tuple()
895 else:
896 if isinstance(episodes[0], gmEMRStructItems.cEpisode):
897 args['epis'] = tuple([ e['pk_episode'] for e in episodes ])
898 elif isinstance(episodes[0], int):
899 args['epis'] = tuple(episodes)
900 else:
901 raise ValueError('<episodes> must be list of type int (=pk) or cEpisode, but 1st episode is: %s' % episodes[0])
902
903 if encounters is not None:
904 where_parts.append('pk_encounter IN %(encs)s')
905 if len(encounters) == 0:
906 args['encs'] = tuple()
907 else:
908 if isinstance(encounters[0], gmEMRStructItems.cEncounter):
909 args['encs'] = tuple([ e['pk_encounter'] for e in encounters ])
910 elif isinstance(encounters[0], int):
911 args['encs'] = tuple(encounters)
912 else:
913 raise ValueError('<encounters> must be list of type int (=pk) or cEncounter, but 1st encounter is: %s' % encounters[0])
914
915 if soap_cats is not None:
916 where_parts.append('c_vn.soap_cat IN %(cats)s')
917 args['cats'] = tuple(gmSoapDefs.soap_cats2list(soap_cats))
918
919 if providers is not None:
920 where_parts.append('c_vn.modified_by IN %(docs)s')
921 args['docs'] = tuple(providers)
922
923 cmd = """
924 SELECT
925 c_vn.*,
926 c_scr.rank AS soap_rank
927 FROM
928 clin.v_narrative c_vn
929 LEFT JOIN clin.soap_cat_ranks c_scr on c_vn.soap_cat = c_scr.soap_cat
930 WHERE %s
931 ORDER BY date, soap_rank
932 """ % ' AND '.join(where_parts)
933
934 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
935 return [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
936
937
938 - 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):
939 return gmClinNarrative.get_as_journal (
940 patient = self.pk_patient,
941 since = since,
942 until = until,
943 encounters = encounters,
944 episodes = episodes,
945 issues = issues,
946 soap_cats = soap_cats,
947 providers = providers,
948 order_by = order_by,
949 time_range = time_range,
950 active_encounter = self.active_encounter
951 )
952
953
955
956 search_term = search_term.strip()
957 if search_term == '':
958 return []
959
960 cmd = """
961 SELECT
962 *,
963 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
964 as episode,
965 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
966 as health_issue,
967 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
968 as encounter_started,
969 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
970 as encounter_ended,
971 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
972 as encounter_type
973 from clin.v_narrative4search vn4s
974 WHERE
975 pk_patient = %(pat)s and
976 vn4s.narrative ~ %(term)s
977 order by
978 encounter_started
979 """
980 rows, idx = gmPG2.run_ro_queries(queries = [
981 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
982 ])
983 return rows
984
985 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
986 fields = [
987 'age',
988 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
989 'modified_by',
990 'clin_when',
991 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
992 'pk_item',
993 'pk_encounter',
994 'pk_episode',
995 'pk_health_issue',
996 'src_table'
997 ]
998 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
999
1000 where_snippets = []
1001 params = {}
1002 where_snippets.append('pk_patient=%(pat_id)s')
1003 params['pat_id'] = self.pk_patient
1004 if not since is None:
1005 where_snippets.append('clin_when >= %(since)s')
1006 params['since'] = since
1007 if not until is None:
1008 where_snippets.append('clin_when <= %(until)s')
1009 params['until'] = until
1010
1011
1012
1013 if not encounters is None and len(encounters) > 0:
1014 params['enc'] = encounters
1015 if len(encounters) > 1:
1016 where_snippets.append('fk_encounter in %(enc)s')
1017 else:
1018 where_snippets.append('fk_encounter=%(enc)s')
1019
1020 if not episodes is None and len(episodes) > 0:
1021 params['epi'] = episodes
1022 if len(episodes) > 1:
1023 where_snippets.append('fk_episode in %(epi)s')
1024 else:
1025 where_snippets.append('fk_episode=%(epi)s')
1026
1027 if not issues is None and len(issues) > 0:
1028 params['issue'] = issues
1029 if len(issues) > 1:
1030 where_snippets.append('fk_health_issue in %(issue)s')
1031 else:
1032 where_snippets.append('fk_health_issue=%(issue)s')
1033
1034 where_clause = ' and '.join(where_snippets)
1035 order_by = 'order by src_table, age'
1036 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
1037
1038 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
1039 if rows is None:
1040 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
1041 return None
1042
1043
1044
1045
1046 items_by_table = {}
1047 for item in rows:
1048 src_table = item[view_col_idx['src_table']]
1049 pk_item = item[view_col_idx['pk_item']]
1050 if src_table not in items_by_table:
1051 items_by_table[src_table] = {}
1052 items_by_table[src_table][pk_item] = item
1053
1054
1055 issues = self.get_health_issues()
1056 issue_map = {}
1057 for issue in issues:
1058 issue_map[issue['pk_health_issue']] = issue['description']
1059 episodes = self.get_episodes()
1060 episode_map = {}
1061 for episode in episodes:
1062 episode_map[episode['pk_episode']] = episode['description']
1063 emr_data = {}
1064
1065 ro_conn = self._conn_pool.GetConnection('historica')
1066 curs = ro_conn.cursor()
1067 for src_table in items_by_table.keys():
1068 item_ids = items_by_table[src_table].keys()
1069
1070
1071 if len(item_ids) == 0:
1072 _log.info('no items in table [%s] ?!?' % src_table)
1073 continue
1074 elif len(item_ids) == 1:
1075 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
1076 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
1077 _log.error('cannot load items from table [%s]' % src_table)
1078
1079 continue
1080 elif len(item_ids) > 1:
1081 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
1082 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
1083 _log.error('cannot load items from table [%s]' % src_table)
1084
1085 continue
1086 rows = curs.fetchall()
1087 table_col_idx = gmPG.get_col_indices(curs)
1088
1089 for row in rows:
1090
1091 pk_item = row[table_col_idx['pk_item']]
1092 view_row = items_by_table[src_table][pk_item]
1093 age = view_row[view_col_idx['age']]
1094
1095 try:
1096 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
1097 except:
1098 episode_name = view_row[view_col_idx['pk_episode']]
1099 try:
1100 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
1101 except:
1102 issue_name = view_row[view_col_idx['pk_health_issue']]
1103
1104 if age not in emr_data:
1105 emr_data[age] = []
1106
1107 emr_data[age].append(
1108 _('%s: encounter (%s)') % (
1109 view_row[view_col_idx['clin_when']],
1110 view_row[view_col_idx['pk_encounter']]
1111 )
1112 )
1113 emr_data[age].append(_('health issue: %s') % issue_name)
1114 emr_data[age].append(_('episode : %s') % episode_name)
1115
1116
1117
1118 cols2ignore = [
1119 'pk_audit', 'row_version', 'modified_when', 'modified_by',
1120 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
1121 ]
1122 col_data = []
1123 for col_name in table_col_idx.keys():
1124 if col_name in cols2ignore:
1125 continue
1126 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
1127 emr_data[age].append("----------------------------------------------------")
1128 emr_data[age].append("-- %s from table %s" % (
1129 view_row[view_col_idx['modified_string']],
1130 src_table
1131 ))
1132 emr_data[age].append("-- written %s by %s" % (
1133 view_row[view_col_idx['modified_when']],
1134 view_row[view_col_idx['modified_by']]
1135 ))
1136 emr_data[age].append("----------------------------------------------------")
1137 curs.close()
1138 return emr_data
1139
1141 return self.pk_patient
1142
1144 union_query = '\n union all\n'.join ([
1145 """
1146 SELECT ((
1147 -- all relevant health issues + active episodes WITH health issue
1148 SELECT COUNT(1)
1149 FROM clin.v_problem_list
1150 WHERE
1151 pk_patient = %(pat)s
1152 AND
1153 pk_health_issue is not null
1154 ) + (
1155 -- active episodes WITHOUT health issue
1156 SELECT COUNT(1)
1157 FROM clin.v_problem_list
1158 WHERE
1159 pk_patient = %(pat)s
1160 AND
1161 pk_health_issue is null
1162 ))""",
1163 'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
1164 'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
1165 'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
1166 'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
1167 'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
1168 'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
1169
1170 """
1171 SELECT count(1)
1172 FROM clin.v_substance_intakes
1173 WHERE
1174 pk_patient = %(pat)s
1175 AND
1176 is_currently_active IN (null, true)
1177 AND
1178 intake_is_approved_of IN (null, true)""",
1179 'SELECT count(1) FROM clin.v_vaccinations WHERE pk_patient = %(pat)s'
1180 ])
1181
1182 rows, idx = gmPG2.run_ro_queries (
1183 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
1184 get_col_idx = False
1185 )
1186
1187 stats = dict (
1188 problems = rows[0][0],
1189 encounters = rows[1][0],
1190 items = rows[2][0],
1191 documents = rows[3][0],
1192 results = rows[4][0],
1193 stays = rows[5][0],
1194 procedures = rows[6][0],
1195 active_drugs = rows[7][0],
1196 vaccinations = rows[8][0]
1197 )
1198
1199 return stats
1200
1213
1346
1347
1368
1369
1370
1371 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
1372 """Retrieves patient allergy items.
1373
1374 remove_sensitivities
1375 - retrieve real allergies only, without sensitivities
1376 since
1377 - initial date for allergy items
1378 until
1379 - final date for allergy items
1380 encounters
1381 - list of encounters whose allergies are to be retrieved
1382 episodes
1383 - list of episodes whose allergies are to be retrieved
1384 issues
1385 - list of health issues whose allergies are to be retrieved
1386 """
1387 cmd = "SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
1388 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
1389 filtered_allergies = []
1390 for r in rows:
1391 filtered_allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
1392
1393
1394 if ID_list is not None:
1395 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_allergy'] in ID_list ]
1396 if len(filtered_allergies) == 0:
1397 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
1398
1399 return None
1400 else:
1401 return filtered_allergies
1402
1403 if remove_sensitivities:
1404 filtered_allergies = [ allg for allg in filtered_allergies if allg['type'] == 'allergy' ]
1405 if since is not None:
1406 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] >= since ]
1407 if until is not None:
1408 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] < until ]
1409 if issues is not None:
1410 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_health_issue'] in issues ]
1411 if episodes is not None:
1412 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_episode'] in episodes ]
1413 if encounters is not None:
1414 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_encounter'] in encounters ]
1415
1416 return filtered_allergies
1417
1418 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
1419 if encounter_id is None:
1420 encounter_id = self.current_encounter['pk_encounter']
1421
1422 if episode_id is None:
1423 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
1424 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
1425 episode_id = epi['pk_episode']
1426
1427 new_allergy = gmAllergy.create_allergy (
1428 allergene = allergene,
1429 allg_type = allg_type,
1430 encounter_id = encounter_id,
1431 episode_id = episode_id
1432 )
1433
1434 return new_allergy
1435
1437 cmd = 'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1438 args = {'pk_allg': pk_allergy}
1439 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1440
1441
1443 """Cave: only use with one potential allergic agent
1444 otherwise you won't know which of the agents the allergy is to."""
1445
1446
1447 if self.allergy_state is None:
1448 return None
1449
1450
1451 if self.allergy_state == 0:
1452 return False
1453
1454 args = {
1455 'atcs': atcs,
1456 'inns': inns,
1457 'prod_name': product_name,
1458 'pat': self.pk_patient
1459 }
1460 allergenes = []
1461 where_parts = []
1462
1463 if len(atcs) == 0:
1464 atcs = None
1465 if atcs is not None:
1466 where_parts.append('atc_code in %(atcs)s')
1467 if len(inns) == 0:
1468 inns = None
1469 if inns is not None:
1470 where_parts.append('generics in %(inns)s')
1471 allergenes.extend(inns)
1472 if product_name is not None:
1473 where_parts.append('substance = %(prod_name)s')
1474 allergenes.append(product_name)
1475
1476 if len(allergenes) != 0:
1477 where_parts.append('allergene in %(allgs)s')
1478 args['allgs'] = tuple(allergenes)
1479
1480 cmd = """
1481 SELECT * FROM clin.v_pat_allergies
1482 WHERE
1483 pk_patient = %%(pat)s
1484 AND ( %s )""" % ' OR '.join(where_parts)
1485
1486 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1487
1488 if len(rows) == 0:
1489 return False
1490
1491 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1492
1502
1505
1506 allergy_state = property(_get_allergy_state, _set_allergy_state)
1507
1508
1509
1516
1517 external_care_items = property(get_external_care_items, lambda x:x)
1518
1519
1520
1521
1522 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1523 """Fetches from backend patient episodes.
1524
1525 id_list - Episodes' PKs list
1526 issues - Health issues' PKs list to filter episodes by
1527 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1528 """
1529 if (unlinked_only is True) and (issues is not None):
1530 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1531
1532 if order_by is None:
1533 order_by = ''
1534 else:
1535 order_by = 'ORDER BY %s' % order_by
1536
1537 args = {
1538 'pat': self.pk_patient,
1539 'open': open_status
1540 }
1541 where_parts = ['pk_patient = %(pat)s']
1542
1543 if open_status is not None:
1544 where_parts.append('episode_open IS %(open)s')
1545
1546 if unlinked_only:
1547 where_parts.append('pk_health_issue is NULL')
1548
1549 if issues is not None:
1550 where_parts.append('pk_health_issue IN %(issues)s')
1551 args['issues'] = tuple(issues)
1552
1553 if id_list is not None:
1554 where_parts.append('pk_episode IN %(epis)s')
1555 args['epis'] = tuple(id_list)
1556
1557 cmd = "SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1558 ' AND '.join(where_parts),
1559 order_by
1560 )
1561 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1562
1563 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1564
1565 episodes = property(get_episodes, lambda x:x)
1566
1568 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1569
1570 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1571
1573 cmd = """SELECT distinct pk_episode
1574 from clin.v_pat_items
1575 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1576 args = {
1577 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1578 'pat': self.pk_patient
1579 }
1580 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1581 if len(rows) == 0:
1582 return []
1583 epis = []
1584 for row in rows:
1585 epis.append(row[0])
1586 return self.get_episodes(id_list=epis)
1587
1588 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False, allow_dupes=False, link_obj=None):
1589 """Add episode 'episode_name' for a patient's health issue.
1590
1591 - silently returns if episode already exists
1592 """
1593 episode = gmEMRStructItems.create_episode (
1594 link_obj = link_obj,
1595 pk_health_issue = pk_health_issue,
1596 episode_name = episode_name,
1597 is_open = is_open,
1598 encounter = self.current_encounter['pk_encounter'],
1599 allow_dupes = allow_dupes
1600 )
1601 return episode
1602
1604
1605
1606 issue_where = gmTools.coalesce(issue, '', 'and pk_health_issue = %(issue)s')
1607
1608 cmd = """
1609 SELECT pk
1610 from clin.episode
1611 WHERE pk = (
1612 SELECT distinct on(pk_episode) pk_episode
1613 from clin.v_pat_items
1614 WHERE
1615 pk_patient = %%(pat)s
1616 and
1617 modified_when = (
1618 SELECT max(vpi.modified_when)
1619 from clin.v_pat_items vpi
1620 WHERE vpi.pk_patient = %%(pat)s
1621 )
1622 %s
1623 -- guard against several episodes created at the same moment of time
1624 limit 1
1625 )""" % issue_where
1626 rows, idx = gmPG2.run_ro_queries(queries = [
1627 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1628 ])
1629 if len(rows) != 0:
1630 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1631
1632
1633
1634 cmd = """
1635 SELECT vpe0.pk_episode
1636 from
1637 clin.v_pat_episodes vpe0
1638 WHERE
1639 vpe0.pk_patient = %%(pat)s
1640 and
1641 vpe0.episode_modified_when = (
1642 SELECT max(vpe1.episode_modified_when)
1643 from clin.v_pat_episodes vpe1
1644 WHERE vpe1.pk_episode = vpe0.pk_episode
1645 )
1646 %s""" % issue_where
1647 rows, idx = gmPG2.run_ro_queries(queries = [
1648 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1649 ])
1650 if len(rows) != 0:
1651 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1652
1653 return None
1654
1657
1658
1659
1660 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1661 """Retrieve a patient's problems.
1662
1663 "Problems" are the UNION of:
1664
1665 - issues which are .clinically_relevant
1666 - episodes which are .is_open
1667
1668 Therefore, both an issue and the open episode
1669 thereof can each be listed as a problem.
1670
1671 include_closed_episodes/include_irrelevant_issues will
1672 include those -- which departs from the definition of
1673 the problem list being "active" items only ...
1674
1675 episodes - episodes' PKs to filter problems by
1676 issues - health issues' PKs to filter problems by
1677 """
1678
1679
1680 args = {'pat': self.pk_patient}
1681
1682 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1683 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1684
1685
1686 problems = []
1687 for row in rows:
1688 pk_args = {
1689 'pk_patient': self.pk_patient,
1690 'pk_health_issue': row['pk_health_issue'],
1691 'pk_episode': row['pk_episode']
1692 }
1693 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1694
1695
1696 other_rows = []
1697 if include_closed_episodes:
1698 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1699 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1700 other_rows.extend(rows)
1701
1702 if include_irrelevant_issues:
1703 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1704 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1705 other_rows.extend(rows)
1706
1707 if len(other_rows) > 0:
1708 for row in other_rows:
1709 pk_args = {
1710 'pk_patient': self.pk_patient,
1711 'pk_health_issue': row['pk_health_issue'],
1712 'pk_episode': row['pk_episode']
1713 }
1714 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1715
1716
1717 if issues is not None:
1718 problems = [ p for p in problems if p['pk_health_issue'] in issues ]
1719 if episodes is not None:
1720 problems = [ p for p in problems if p['pk_episode'] in episodes ]
1721
1722 return problems
1723
1724
1727
1728
1731
1732
1735
1736
1738 cmd = "SELECT * FROM clin.v_candidate_diagnoses WHERE pk_patient = %(pat)s"
1739 rows, idx = gmPG2.run_ro_queries (
1740 queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}],
1741 get_col_idx = False
1742 )
1743 return rows
1744
1745 candidate_diagnoses = property(get_candidate_diagnoses)
1746
1747
1748
1749
1751
1752 cmd = "SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1753 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1754 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1755
1756 if id_list is None:
1757 return issues
1758
1759 if len(id_list) == 0:
1760 raise ValueError('id_list to filter by is empty, most likely a programming error')
1761
1762 filtered_issues = []
1763 for issue in issues:
1764 if issue['pk_health_issue'] in id_list:
1765 filtered_issues.append(issue)
1766
1767 return filtered_issues
1768
1769 health_issues = property(get_health_issues, lambda x:x)
1770
1771
1779
1782
1783
1784
1785 - def get_current_medications(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1786 return self._get_current_substance_intakes (
1787 include_inactive = include_inactive,
1788 include_unapproved = include_unapproved,
1789 order_by = order_by,
1790 episodes = episodes,
1791 issues = issues,
1792 exclude_medications = False,
1793 exclude_potential_abuses = True
1794 )
1795
1796
1798 return self._get_current_substance_intakes (
1799 include_inactive = True,
1800 include_unapproved = True,
1801 order_by = order_by,
1802 episodes = None,
1803 issues = None,
1804 exclude_medications = True,
1805 exclude_potential_abuses = False
1806 )
1807
1808 abused_substances = property(_get_abused_substances, lambda x:x)
1809
1810
1811 - def _get_current_substance_intakes(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None, exclude_potential_abuses=False, exclude_medications=False):
1812
1813 where_parts = ['pk_patient = %(pat)s']
1814 args = {'pat': self.pk_patient}
1815
1816 if not include_inactive:
1817 where_parts.append('is_currently_active IN (TRUE, NULL)')
1818
1819 if not include_unapproved:
1820 where_parts.append('intake_is_approved_of IN (TRUE, NULL)')
1821
1822 if exclude_potential_abuses:
1823 where_parts.append('harmful_use_type IS NULL')
1824
1825 if exclude_medications:
1826 where_parts.append('harmful_use_type IS NOT NULL')
1827
1828 if order_by is None:
1829 order_by = ''
1830 else:
1831 order_by = 'ORDER BY %s' % order_by
1832
1833 cmd = "SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1834 '\nAND '.join(where_parts),
1835 order_by
1836 )
1837 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1838 intakes = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1839
1840 if episodes is not None:
1841 intakes = [ i for i in intakes if i['pk_episode'] in episodes ]
1842
1843 if issues is not None:
1844 intakes = [ i for i in intakes if i ['pk_health_issue'] in issues ]
1845
1846 return intakes
1847
1848
1849 - def add_substance_intake(self, pk_component=None, pk_episode=None, pk_drug_product=None, pk_health_issue=None):
1862
1863
1865 return gmMedication.substance_intake_exists (
1866 pk_component = pk_component,
1867 pk_substance = pk_substance,
1868 pk_identity = self.pk_patient,
1869 pk_drug_product = pk_drug_product
1870 )
1871
1872
1873
1874
1882
1883
1885 """Returns latest given vaccination for each vaccinated indication.
1886
1887 as a dict {'l10n_indication': cVaccination instance}
1888
1889 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1890 """
1891 args = {'pat': self.pk_patient}
1892 where_parts = ['c_v_shots.pk_patient = %(pat)s']
1893
1894 if (episodes is not None) and (len(episodes) > 0):
1895 where_parts.append('c_v_shots.pk_episode IN %(epis)s')
1896 args['epis'] = tuple(episodes)
1897
1898 if (issues is not None) and (len(issues) > 0):
1899 where_parts.append('c_v_shots.pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1900 args['issues'] = tuple(issues)
1901
1902 if (atc_indications is not None) and (len(atc_indications) > 0):
1903 where_parts.append('c_v_plv4i.atc_indication IN %(atc_inds)s')
1904 args['atc_inds'] = tuple(atc_indications)
1905
1906
1907 cmd = """
1908 SELECT
1909 c_v_shots.*,
1910 c_v_plv4i.l10n_indication,
1911 c_v_plv4i.no_of_shots
1912 FROM
1913 clin.v_vaccinations c_v_shots
1914 JOIN clin.v_pat_last_vacc4indication c_v_plv4i ON (c_v_shots.pk_vaccination = c_v_plv4i.pk_vaccination)
1915 WHERE %s
1916 """ % '\nAND '.join(where_parts)
1917 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1918
1919
1920 if len(rows) == 0:
1921 return {}
1922
1923
1924
1925 vaccs = {}
1926 for shot_row in rows:
1927 vaccs[shot_row['l10n_indication']] = (
1928 shot_row['no_of_shots'],
1929 gmVaccination.cVaccination(row = {'idx': idx, 'data': shot_row, 'pk_field': 'pk_vaccination'})
1930 )
1931
1932 return vaccs
1933
1934
1935 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1936
1937 args = {'pat': self.pk_patient}
1938 where_parts = ['pk_patient = %(pat)s']
1939
1940 if order_by is None:
1941 order_by = ''
1942 else:
1943 order_by = 'ORDER BY %s' % order_by
1944
1945 if (episodes is not None) and (len(episodes) > 0):
1946 where_parts.append('pk_episode IN %(epis)s')
1947 args['epis'] = tuple(episodes)
1948
1949 if (issues is not None) and (len(issues) > 0):
1950 where_parts.append('pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1951 args['issues'] = tuple(issues)
1952
1953 if (encounters is not None) and (len(encounters) > 0):
1954 where_parts.append('pk_encounter IN %(encs)s')
1955 args['encs'] = tuple(encounters)
1956
1957 cmd = '%s %s' % (
1958 gmVaccination._SQL_get_vaccination_fields % '\nAND '.join(where_parts),
1959 order_by
1960 )
1961 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1962 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1963
1964 return vaccs
1965
1966 vaccinations = property(get_vaccinations, lambda x:x)
1967
1968
1969
1970
1972 """Retrieves vaccination regimes the patient is on.
1973
1974 optional:
1975 * ID - PK of the vaccination regime
1976 * indications - indications we want to retrieve vaccination
1977 regimes for, must be primary language, not l10n_indication
1978 """
1979
1980
1981 cmd = """SELECT distinct on(pk_course) pk_course
1982 FROM clin.v_vaccs_scheduled4pat
1983 WHERE pk_patient=%s"""
1984 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1985 if rows is None:
1986 _log.error('cannot retrieve scheduled vaccination courses')
1987 return None
1988
1989 for row in rows:
1990 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1991
1992
1993 filtered_regimes = []
1994 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1995 if ID is not None:
1996 filtered_regimes = [ r for r in filtered_regimes if r['pk_course'] == ID ]
1997 if len(filtered_regimes) == 0:
1998 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1999 return []
2000 else:
2001 return filtered_regimes[0]
2002 if indications is not None:
2003 filtered_regimes = [ r for r in filtered_regimes if r['indication'] in indications ]
2004
2005 return filtered_regimes
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2034 """Retrieves list of vaccinations the patient has received.
2035
2036 optional:
2037 * ID - PK of a vaccination
2038 * indications - indications we want to retrieve vaccination
2039 items for, must be primary language, not l10n_indication
2040 * since - initial date for allergy items
2041 * until - final date for allergy items
2042 * encounters - list of encounters whose allergies are to be retrieved
2043 * episodes - list of episodes whose allergies are to be retrieved
2044 * issues - list of health issues whose allergies are to be retrieved
2045 """
2046 try:
2047 self.__db_cache['vaccinations']['vaccinated']
2048 except KeyError:
2049 self.__db_cache['vaccinations']['vaccinated'] = []
2050
2051 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
2052 WHERE pk_patient=%s
2053 order by indication, date"""
2054 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2055 if rows is None:
2056 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
2057 del self.__db_cache['vaccinations']['vaccinated']
2058 return None
2059
2060 vaccs_by_ind = {}
2061 for row in rows:
2062 vacc_row = {
2063 'pk_field': 'pk_vaccination',
2064 'idx': idx,
2065 'data': row
2066 }
2067 vacc = gmVaccination.cVaccination(row=vacc_row)
2068 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
2069
2070 try:
2071 vaccs_by_ind[vacc['indication']].append(vacc)
2072 except KeyError:
2073 vaccs_by_ind[vacc['indication']] = [vacc]
2074
2075
2076 for ind in vaccs_by_ind.keys():
2077 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
2078 for vacc in vaccs_by_ind[ind]:
2079
2080
2081 seq_no = vaccs_by_ind[ind].index(vacc) + 1
2082 vacc['seq_no'] = seq_no
2083
2084
2085 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
2086 continue
2087 if seq_no > vacc_regimes[0]['shots']:
2088 vacc['is_booster'] = True
2089 del vaccs_by_ind
2090
2091
2092 filtered_shots = []
2093 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
2094 if ID is not None:
2095 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
2096 if len(filtered_shots) == 0:
2097 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
2098 return None
2099 else:
2100 return filtered_shots[0]
2101 if since is not None:
2102 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
2103 if until is not None:
2104 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
2105 if issues is not None:
2106 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
2107 if episodes is not None:
2108 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
2109 if encounters is not None:
2110 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
2111 if indications is not None:
2112 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2113 return filtered_shots
2114
2116 """Retrieves vaccinations scheduled for a regime a patient is on.
2117
2118 The regime is referenced by its indication (not l10n)
2119
2120 * indications - List of indications (not l10n) of regimes we want scheduled
2121 vaccinations to be fetched for
2122 """
2123 try:
2124 self.__db_cache['vaccinations']['scheduled']
2125 except KeyError:
2126 self.__db_cache['vaccinations']['scheduled'] = []
2127 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
2128 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2129 if rows is None:
2130 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
2131 del self.__db_cache['vaccinations']['scheduled']
2132 return None
2133
2134 for row in rows:
2135 vacc_row = {
2136 'pk_field': 'pk_vacc_def',
2137 'idx': idx,
2138 'data': row
2139 }
2140 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
2141
2142
2143 if indications is None:
2144 return self.__db_cache['vaccinations']['scheduled']
2145 filtered_shots = []
2146 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
2147 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2148 return filtered_shots
2149
2151 try:
2152 self.__db_cache['vaccinations']['missing']
2153 except KeyError:
2154 self.__db_cache['vaccinations']['missing'] = {}
2155
2156 self.__db_cache['vaccinations']['missing']['due'] = []
2157
2158 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
2159 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2160 if rows is None:
2161 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
2162 return None
2163 pk_args = {'pat_id': self.pk_patient}
2164 if rows is not None:
2165 for row in rows:
2166 pk_args['indication'] = row[0]
2167 pk_args['seq_no'] = row[1]
2168 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
2169
2170
2171 self.__db_cache['vaccinations']['missing']['boosters'] = []
2172
2173 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
2174 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2175 if rows is None:
2176 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
2177 return None
2178 pk_args = {'pat_id': self.pk_patient}
2179 if rows is not None:
2180 for row in rows:
2181 pk_args['indication'] = row[0]
2182 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
2183
2184
2185 if indications is None:
2186 return self.__db_cache['vaccinations']['missing']
2187 if len(indications) == 0:
2188 return self.__db_cache['vaccinations']['missing']
2189
2190 filtered_shots = {
2191 'due': [],
2192 'boosters': []
2193 }
2194 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
2195 if due_shot['indication'] in indications:
2196 filtered_shots['due'].append(due_shot)
2197 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
2198 if due_shot['indication'] in indications:
2199 filtered_shots['boosters'].append(due_shot)
2200 return filtered_shots
2201
2202
2203
2204
2206 return self.__encounter
2207
2209
2210 if self.__encounter is None:
2211 _log.debug('first setting of active encounter in this clinical record instance')
2212 encounter.lock(exclusive = False)
2213 self.__encounter = encounter
2214 gmDispatcher.send('current_encounter_switched')
2215 return True
2216
2217
2218 _log.debug('switching of active encounter')
2219
2220 if self.__encounter.is_modified():
2221 gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
2222 _log.error('current in client: %s', self.__encounter)
2223 raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
2224 self.__encounter['pk_encounter'],
2225 encounter['pk_encounter']
2226 ))
2227
2228 prev_enc = self.__encounter
2229 encounter.lock(exclusive = False)
2230 self.__encounter = encounter
2231 prev_enc.unlock(exclusive = False)
2232 gmDispatcher.send('current_encounter_switched')
2233
2234 return True
2235
2236 current_encounter = property(_get_current_encounter, _set_current_encounter)
2237 active_encounter = property(_get_current_encounter, _set_current_encounter)
2238
2239
2241 _log.debug('setting up active encounter for identity [%s]', self.pk_patient)
2242
2243
2244 _delayed_execute(self.log_access, action = 'pulling chart for identity [%s]' % self.pk_patient)
2245
2246
2247
2248 self.remove_empty_encounters()
2249
2250
2251 if self.__activate_very_recent_encounter():
2252 return
2253
2254 fairly_recent_enc = self.__get_fairly_recent_encounter()
2255
2256
2257 self.start_new_encounter()
2258
2259 if fairly_recent_enc is None:
2260 return
2261
2262
2263 gmDispatcher.send (
2264 signal = 'ask_for_encounter_continuation',
2265 new_encounter = self.__encounter,
2266 fairly_recent_encounter = fairly_recent_enc
2267 )
2268
2269
2271 """Try to attach to a "very recent" encounter if there is one.
2272
2273 returns:
2274 False: no "very recent" encounter
2275 True: success
2276 """
2277 cfg_db = gmCfg.cCfgSQL()
2278 min_ttl = cfg_db.get2 (
2279 option = 'encounter.minimum_ttl',
2280 workplace = _here.active_workplace,
2281 bias = 'user',
2282 default = '1 hour 30 minutes'
2283 )
2284 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2285 SELECT pk_encounter
2286 FROM clin.v_most_recent_encounters
2287 WHERE
2288 pk_patient = %s
2289 and
2290 last_affirmed > (now() - %s::interval)
2291 ORDER BY
2292 last_affirmed DESC
2293 LIMIT 1
2294 )"""
2295 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}], get_col_idx = True)
2296
2297
2298 if len(enc_rows) == 0:
2299 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
2300 return False
2301
2302 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0]['pk_encounter'])
2303
2304
2305 self.current_encounter = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2306 return True
2307
2308
2310 cfg_db = gmCfg.cCfgSQL()
2311 min_ttl = cfg_db.get2 (
2312 option = 'encounter.minimum_ttl',
2313 workplace = _here.active_workplace,
2314 bias = 'user',
2315 default = '1 hour 30 minutes'
2316 )
2317 max_ttl = cfg_db.get2 (
2318 option = 'encounter.maximum_ttl',
2319 workplace = _here.active_workplace,
2320 bias = 'user',
2321 default = '6 hours'
2322 )
2323
2324
2325 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2326 SELECT pk_encounter
2327 FROM clin.v_most_recent_encounters
2328 WHERE
2329 pk_patient=%s
2330 AND
2331 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2332 ORDER BY
2333 last_affirmed DESC
2334 LIMIT 1
2335 )"""
2336 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2337
2338
2339 if len(enc_rows) == 0:
2340 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2341 return None
2342
2343 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2344 return gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2500
2501
2502 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False, order_by=None, max_encounters=None):
2503 """Retrieves patient's encounters.
2504
2505 id_list - PKs of encounters to fetch
2506 since - initial date for encounter items, DateTime instance
2507 until - final date for encounter items, DateTime instance
2508 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
2509 issues - PKs of the health issues the encounters belong to (many-to-many relation)
2510 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
2511
2512 NOTE: if you specify *both* issues and episodes
2513 you will get the *aggregate* of all encounters even
2514 if the episodes all belong to the health issues listed.
2515 IOW, the issues broaden the episode list rather than
2516 the episode list narrowing the episodes-from-issues
2517 list.
2518 Rationale: If it was the other way round it would be
2519 redundant to specify the list of issues at all.
2520 """
2521
2522 if (issues is not None) and (len(issues) > 0):
2523
2524 cmd = "SELECT distinct pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue in %(issue_pks)s AND pk_patient = %(pat)s"
2525 args = {'issue_pks': tuple(issues), 'pat': self.pk_patient}
2526 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2527 epis4issues_pks = [ r['pk_episode'] for r in rows ]
2528 if episodes is None:
2529 episodes = []
2530 episodes.extend(epis4issues_pks)
2531
2532 if (episodes is not None) and (len(episodes) > 0):
2533
2534
2535
2536 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2537 cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
2538 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2539 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2540 if id_list is None:
2541 id_list = []
2542 id_list.extend(encs4epis_pks)
2543
2544 where_parts = ['c_vpe.pk_patient = %(pat)s']
2545 args = {'pat': self.pk_patient}
2546
2547 if skip_empty:
2548 where_parts.append("""NOT (
2549 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
2550 AND
2551 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
2552 AND
2553 NOT EXISTS (
2554 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
2555 UNION ALL
2556 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
2557 ))""")
2558
2559 if since is not None:
2560 where_parts.append('c_vpe.started >= %(start)s')
2561 args['start'] = since
2562
2563 if until is not None:
2564 where_parts.append('c_vpe.last_affirmed <= %(end)s')
2565 args['end'] = since
2566
2567 if (id_list is not None) and (len(id_list) > 0):
2568 where_parts.append('c_vpe.pk_encounter IN %(enc_pks)s')
2569 args['enc_pks'] = tuple(id_list)
2570
2571 if order_by is None:
2572 order_by = 'c_vpe.started'
2573
2574 if max_encounters is None:
2575 limit = ''
2576 else:
2577 limit = 'LIMIT %s' % max_encounters
2578
2579 cmd = """
2580 SELECT * FROM clin.v_pat_encounters c_vpe
2581 WHERE
2582 %s
2583 ORDER BY %s %s
2584 """ % (
2585 ' AND '.join(where_parts),
2586 order_by,
2587 limit
2588 )
2589 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2590 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
2591
2592
2593 filtered_encounters = []
2594 filtered_encounters.extend(encounters)
2595
2596 if (episodes is not None) and (len(episodes) > 0):
2597
2598
2599
2600 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2601 cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
2602 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2603 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2604 filtered_encounters = [ enc for enc in filtered_encounters if enc['pk_encounter'] in encs4epis_pks ]
2605
2606 return filtered_encounters
2607
2608
2610 """Retrieves first encounter for a particular issue and/or episode.
2611
2612 issue_id - First encounter associated health issue
2613 episode - First encounter associated episode
2614 """
2615 if issue_id is None:
2616 issues = None
2617 else:
2618 issues = [issue_id]
2619
2620 if episode_id is None:
2621 episodes = None
2622 else:
2623 episodes = [episode_id]
2624
2625 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started', max_encounters = 1)
2626 if len(encounters) == 0:
2627 return None
2628
2629 return encounters[0]
2630
2631 first_encounter = property(get_first_encounter, lambda x:x)
2632
2633
2635 args = {'pat': self.pk_patient}
2636 cmd = """
2637 SELECT MIN(earliest) FROM (
2638 (
2639 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
2640
2641 ) UNION ALL (
2642
2643 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
2644
2645 ) UNION ALL (
2646
2647 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2648
2649 ) UNION ALL (
2650
2651 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2652
2653 ) UNION ALL (
2654
2655 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2656
2657 ) UNION ALL (
2658
2659 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2660
2661 ) UNION ALL (
2662
2663 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2664
2665 )
2666 ) AS candidates"""
2667 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2668 return rows[0][0]
2669
2670 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2671
2672
2674 encounters = self.get_encounters(order_by = 'started DESC', max_encounters = 1)
2675 if len(encounters) == 0:
2676 return None
2677 return encounters[0]['last_affirmed']
2678
2679 most_recent_care_date = property(get_most_recent_care_date)
2680
2681
2683 """Retrieves last encounter for a concrete issue and/or episode
2684
2685 issue_id - Last encounter associated health issue
2686 episode_id - Last encounter associated episode
2687 """
2688 if issue_id is None:
2689 issues = None
2690 else:
2691 issues = [issue_id]
2692
2693 if episode_id is None:
2694 episodes = None
2695 else:
2696 episodes = [episode_id]
2697
2698 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started DESC', max_encounters = 1)
2699 if len(encounters) == 0:
2700 return None
2701
2702 return encounters[0]
2703
2704 last_encounter = property(get_last_encounter, lambda x:x)
2705
2706
2708 args = {'pat': self.pk_patient, 'range': cover_period}
2709 where_parts = ['pk_patient = %(pat)s']
2710 if cover_period is not None:
2711 where_parts.append('last_affirmed > now() - %(range)s')
2712
2713 cmd = """
2714 SELECT l10n_type, count(1) AS frequency
2715 FROM clin.v_pat_encounters
2716 WHERE
2717 %s
2718 GROUP BY l10n_type
2719 ORDER BY frequency DESC
2720 """ % ' AND '.join(where_parts)
2721 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2722 return rows
2723
2724
2726
2727 args = {'pat': self.pk_patient}
2728
2729 if (issue_id is None) and (episode_id is None):
2730 cmd = """
2731 SELECT * FROM clin.v_pat_encounters
2732 WHERE pk_patient = %(pat)s
2733 ORDER BY started DESC
2734 LIMIT 2
2735 """
2736 else:
2737 where_parts = []
2738
2739 if issue_id is not None:
2740 where_parts.append('pk_health_issue = %(issue)s')
2741 args['issue'] = issue_id
2742
2743 if episode_id is not None:
2744 where_parts.append('pk_episode = %(epi)s')
2745 args['epi'] = episode_id
2746
2747 cmd = """
2748 SELECT *
2749 FROM clin.v_pat_encounters
2750 WHERE
2751 pk_patient = %%(pat)s
2752 AND
2753 pk_encounter IN (
2754 SELECT distinct pk_encounter
2755 FROM clin.v_narrative
2756 WHERE
2757 %s
2758 )
2759 ORDER BY started DESC
2760 LIMIT 2
2761 """ % ' AND '.join(where_parts)
2762
2763 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2764
2765 if len(rows) == 0:
2766 return None
2767
2768
2769 if len(rows) == 1:
2770
2771 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2772
2773 return None
2774
2775 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2776
2777
2778 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2779 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2780
2781 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2782
2783 last_but_one_encounter = property(get_last_but_one_encounter, lambda x:x)
2784
2785
2787 _log.debug('removing empty encounters for pk_identity [%s]', self.pk_patient)
2788 cfg_db = gmCfg.cCfgSQL()
2789 ttl = cfg_db.get2 (
2790 option = 'encounter.ttl_if_empty',
2791 workplace = _here.active_workplace,
2792 bias = 'user',
2793 default = '1 week'
2794 )
2795
2796 cmd = "SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
2797 args = {'pat': self.pk_patient, 'ttl': ttl}
2798 try:
2799 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
2800 except:
2801 _log.exception('error deleting empty encounters')
2802 return False
2803
2804 if not rows[0][0]:
2805 _log.debug('no encounters deleted (less than 2 exist)')
2806
2807 return True
2808
2809
2810
2811
2817
2818
2826
2827
2834
2835
2841
2842
2851
2852
2859
2860
2866
2867
2870
2871
2873 if order_by is None:
2874 order_by = ''
2875 else:
2876 order_by = 'ORDER BY %s' % order_by
2877 cmd = """
2878 SELECT * FROM clin.v_test_results
2879 WHERE
2880 pk_patient = %%(pat)s
2881 AND
2882 reviewed IS FALSE
2883 %s""" % order_by
2884 args = {'pat': self.pk_patient}
2885 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2886 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2887
2888
2889
2891 """Retrieve data about test types for which this patient has results."""
2892 if order_by is None:
2893 order_by = ''
2894 else:
2895 order_by = 'ORDER BY %s' % order_by
2896
2897 if unique_meta_types:
2898 cmd = """
2899 SELECT * FROM clin.v_test_types c_vtt
2900 WHERE c_vtt.pk_test_type IN (
2901 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
2902 FROM clin.v_test_results c_vtr1
2903 WHERE
2904 c_vtr1.pk_patient = %%(pat)s
2905 AND
2906 c_vtr1.pk_meta_test_type IS NOT NULL
2907 UNION ALL
2908 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
2909 FROM clin.v_test_results c_vtr2
2910 WHERE
2911 c_vtr2.pk_patient = %%(pat)s
2912 AND
2913 c_vtr2.pk_meta_test_type IS NULL
2914 )
2915 %s""" % order_by
2916 else:
2917 cmd = """
2918 SELECT * FROM clin.v_test_types c_vtt
2919 WHERE c_vtt.pk_test_type IN (
2920 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
2921 FROM clin.v_test_results c_vtr
2922 WHERE c_vtr.pk_patient = %%(pat)s
2923 )
2924 %s""" % order_by
2925
2926 args = {'pat': self.pk_patient}
2927 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2928 return [ gmPathLab.cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
2929
2930
2932 """Get the dates for which we have results."""
2933 where_parts = ['pk_patient = %(pat)s']
2934 args = {'pat': self.pk_patient}
2935
2936 if tests is not None:
2937 where_parts.append('pk_test_type IN %(tests)s')
2938 args['tests'] = tuple(tests)
2939
2940 cmd = """
2941 SELECT DISTINCT ON (clin_when_day)
2942 clin_when_day,
2943 is_reviewed
2944 FROM (
2945 SELECT
2946 date_trunc('day', clin_when)
2947 AS clin_when_day,
2948 bool_and(reviewed)
2949 AS is_reviewed
2950 FROM (
2951 SELECT
2952 clin_when,
2953 reviewed,
2954 pk_patient,
2955 pk_test_result
2956 FROM clin.v_test_results
2957 WHERE %s
2958 )
2959 AS patient_tests
2960 GROUP BY clin_when_day
2961 )
2962 AS grouped_days
2963 ORDER BY clin_when_day %s
2964 """ % (
2965 ' AND '.join(where_parts),
2966 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
2967 )
2968 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2969 return rows
2970
2971
2973 """Get the issues/episodes for which we have results."""
2974 where_parts = ['pk_patient = %(pat)s']
2975 args = {'pat': self.pk_patient}
2976
2977 if tests is not None:
2978 where_parts.append('pk_test_type IN %(tests)s')
2979 args['tests'] = tuple(tests)
2980 where = ' AND '.join(where_parts)
2981 cmd = """
2982 SELECT * FROM ((
2983 -- issues, each including all it"s episodes
2984 SELECT
2985 health_issue AS problem,
2986 pk_health_issue,
2987 NULL::integer AS pk_episode,
2988 1 AS rank
2989 FROM clin.v_test_results
2990 WHERE pk_health_issue IS NOT NULL AND %s
2991 GROUP BY pk_health_issue, problem
2992 ) UNION ALL (
2993 -- episodes w/o issue
2994 SELECT
2995 episode AS problem,
2996 NULL::integer AS pk_health_issue,
2997 pk_episode,
2998 2 AS rank
2999 FROM clin.v_test_results
3000 WHERE pk_health_issue IS NULL AND %s
3001 GROUP BY pk_episode, problem
3002 )) AS grouped_union
3003 ORDER BY rank, problem
3004 """ % (where, where)
3005 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
3006 return rows
3007
3008
3009 - def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
3016
3018
3019 where_parts = ['pk_patient = %(pat)s']
3020 args = {'pat': self.pk_patient}
3021
3022 if tests is not None:
3023 where_parts.append('pk_test_type IN %(tests)s')
3024 args['tests'] = tuple(tests)
3025
3026 if encounter is not None:
3027 where_parts.append('pk_encounter = %(enc)s')
3028 args['enc'] = encounter
3029
3030 if episodes is not None:
3031 where_parts.append('pk_episode IN %(epis)s')
3032 args['epis'] = tuple(episodes)
3033
3034 cmd = """
3035 SELECT * FROM clin.v_test_results
3036 WHERE %s
3037 ORDER BY clin_when %s, pk_episode, unified_name
3038 """ % (
3039 ' AND '.join(where_parts),
3040 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
3041 )
3042 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3043
3044 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
3045
3046 return tests
3047
3048 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
3049
3050 try:
3051 epi = int(episode)
3052 except:
3053 epi = episode['pk_episode']
3054
3055 try:
3056 type = int(type)
3057 except:
3058 type = type['pk_test_type']
3059
3060 tr = gmPathLab.create_test_result (
3061 link_obj = link_obj,
3062 encounter = self.current_encounter['pk_encounter'],
3063 episode = epi,
3064 type = type,
3065 intended_reviewer = intended_reviewer,
3066 val_num = val_num,
3067 val_alpha = val_alpha,
3068 unit = unit
3069 )
3070
3071 return tr
3072
3073
3075 where = 'pk_org_unit IN (%s)' % """
3076 SELECT DISTINCT fk_org_unit FROM clin.test_org WHERE pk IN (
3077 SELECT DISTINCT pk_test_org FROM clin.v_test_results where pk_patient = %(pat)s
3078 )"""
3079 args = {'pat': self.pk_patient}
3080 cmd = gmOrganization._SQL_get_org_unit % where
3081 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3082 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
3083
3084
3118
3119 best_gfr_or_crea = property(_get_best_gfr_or_crea, lambda x:x)
3120
3121
3124
3125 bmi = property(_get_bmi, lambda x:x)
3126
3127
3130
3131 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
3132
3133
3134
3135
3140
3141 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
3155
3156
3157
3158
3159 if __name__ == "__main__":
3160
3161 if len(sys.argv) == 1:
3162 sys.exit()
3163
3164 if sys.argv[1] != 'test':
3165 sys.exit()
3166
3167 from Gnumed.pycommon import gmLog2
3168
3169 from Gnumed.business import gmPraxis
3170 branches = gmPraxis.get_praxis_branches()
3171 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
3172
3174 print(args)
3175 print(kwargs)
3176 args[0](*args[1:], **kwargs)
3177
3178 set_delayed_executor(_do_delayed)
3179
3180
3194
3195
3200
3201
3202
3203
3210
3211
3213 emr = cClinicalRecord(aPKey=12)
3214 rows, idx = emr.get_measurements_by_date()
3215 print("test results:")
3216 for row in rows:
3217 print(row)
3218
3219
3226
3227
3232
3233
3235 emr = cClinicalRecord(aPKey=12)
3236
3237 probs = emr.get_problems()
3238 print("normal probs (%s):" % len(probs))
3239 for p in probs:
3240 print('%s (%s)' % (p['problem'], p['type']))
3241
3242 probs = emr.get_problems(include_closed_episodes=True)
3243 print("probs + closed episodes (%s):" % len(probs))
3244 for p in probs:
3245 print('%s (%s)' % (p['problem'], p['type']))
3246
3247 probs = emr.get_problems(include_irrelevant_issues=True)
3248 print("probs + issues (%s):" % len(probs))
3249 for p in probs:
3250 print('%s (%s)' % (p['problem'], p['type']))
3251
3252 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
3253 print("probs + issues + epis (%s):" % len(probs))
3254 for p in probs:
3255 print('%s (%s)' % (p['problem'], p['type']))
3256
3257
3259 emr = cClinicalRecord(aPKey=12)
3260 tr = emr.add_test_result (
3261 episode = 1,
3262 intended_reviewer = 1,
3263 type = 1,
3264 val_num = 75,
3265 val_alpha = 'somewhat obese',
3266 unit = 'kg'
3267 )
3268 print(tr)
3269
3270
3274
3275
3280
3281
3286
3287
3292
3293
3298
3299
3304
3305
3310
3311
3315
3316
3318 emr = cClinicalRecord(aPKey = 12)
3319 for journal_line in emr.get_as_journal():
3320
3321 print('%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line)
3322 print("")
3323
3324
3328
3329
3334
3335
3341
3342
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378 test_get_dx()
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426