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 gmGenericEMRItem
32 from Gnumed.business import gmAllergy
33 from Gnumed.business import gmPathLab
34 from Gnumed.business import gmLOINC
35 from Gnumed.business import gmClinNarrative
36 from Gnumed.business import gmSoapDefs
37 from Gnumed.business import gmEMRStructItems
38 from Gnumed.business import gmMedication
39 from Gnumed.business import gmVaccination
40 from Gnumed.business import gmFamilyHistory
41 from Gnumed.business import gmExternalCare
42 from Gnumed.business import gmOrganization
43 from Gnumed.business import gmAutoHints
44 from Gnumed.business.gmDemographicRecord import get_occupations
45
46
47 _log = logging.getLogger('gm.emr')
48
49 _here = None
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 from Gnumed.business.gmDocuments import cDocument
67 from Gnumed.business.gmProviderInbox import cInboxMessage
68
69 _map_table2class = {
70 'clin.encounter': gmEMRStructItems.cEncounter,
71 'clin.episode': gmEMRStructItems.cEpisode,
72 'clin.health_issue': gmEMRStructItems.cHealthIssue,
73 'clin.external_care': gmExternalCare.cExternalCareItem,
74 'clin.vaccination': gmVaccination.cVaccination,
75 'clin.clin_narrative': gmClinNarrative.cNarrative,
76 'clin.test_result': gmPathLab.cTestResult,
77 'clin.substance_intake': gmMedication.cSubstanceIntakeEntry,
78 'clin.hospital_stay': gmEMRStructItems.cHospitalStay,
79 'clin.procedure': gmEMRStructItems.cPerformedProcedure,
80 'clin.allergy': gmAllergy.cAllergy,
81 'clin.allergy_state': gmAllergy.cAllergyState,
82 'clin.family_history': gmFamilyHistory.cFamilyHistory,
83 'clin.suppressed_hint': gmAutoHints.cSuppressedHint,
84 'blobs.doc_med': cDocument,
85 'dem.message_inbox': cInboxMessage,
86 'ref.auto_hint': gmAutoHints.cDynamicHint
87 }
88
90 try:
91 item_class = _map_table2class[table]
92 except KeyError:
93 _log.error('unmapped clin_root_item entry [%s], cannot instantiate', table)
94 return None
95
96 return item_class(aPK_obj = pk)
97
98
128
129
132
133
134 _delayed_execute = __noop_delayed_execute
135
136
138 if not callable(executor):
139 raise TypeError('executor <%s> is not callable' % executor)
140 global _delayed_execute
141 _delayed_execute = executor
142 _log.debug('setting delayed executor to <%s>', executor)
143
144
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
207 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
208 if self.__encounter is not None:
209 self.__encounter.unlock(exclusive = False)
210 return True
211
212
214 if action is None:
215 action = 'EMR access for pk_identity [%s]' % self.pk_patient
216 args = {'action': action}
217 cmd = 'SELECT gm.log_access2emr(%(action)s)'
218 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
219
220
233
234 calculator = property(_get_calculator, lambda x:x)
235
236
237
238
244
245
247
248 if kwds['table'] != 'clin.encounter':
249 return True
250 if self.current_encounter is None:
251 _log.debug('no local current-encounter, ignoring encounter modification signal')
252 return True
253 if int(kwds['pk_of_row']) != self.current_encounter['pk_encounter']:
254 _log.debug('modified encounter [%s] != local encounter [%s], ignoring signal', kwds['pk_of_row'], self.current_encounter['pk_encounter'])
255 return True
256
257 _log.debug('modification of our encounter (%s) signalled (%s)', self.current_encounter['pk_encounter'], kwds['pk_of_row'])
258
259
260
261 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
262
263
264
265
266
267
268 if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
269 _log.debug('same XMIN, no difference between DB and in-client instance of current encounter expected')
270 if self.current_encounter.is_modified():
271 _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')
272 _log.error('this hints at an error in .is_modified handling')
273 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')
274 return True
275
276
277
278
279
280 if self.current_encounter.is_modified():
281 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')
282 raise ValueError('unsaved changes in locally active encounter [%s], cannot switch to DB state of encounter [%s]' % (
283 self.current_encounter['pk_encounter'],
284 curr_enc_in_db['pk_encounter']
285 ))
286
287
288
289
290
291
292
293
294
295
296
297
298
299 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')
300 _log.debug('active encounter modified remotely, no locally pending changes, reloading from DB and locally announcing the remote modification')
301 self.current_encounter.refetch_payload()
302 gmDispatcher.send('current_encounter_modified')
303
304 return True
305
306
308
309
310
311 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326 if self.current_encounter.is_modified():
327 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')
328 _log.error('current in client: %s', self.current_encounter)
329 raise ValueError('unsaved changes in active encounter [%s], cannot switch [%s]' % (
330 self.current_encounter['pk_encounter'],
331 curr_enc_in_db['pk_encounter']
332 ))
333
334 if self.current_encounter.same_payload(another_object = curr_enc_in_db):
335 _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
336 return True
337
338
339
340
341
342 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')
343 _log.debug('active encounter modified remotely, reloading from DB and locally announcing the modification')
344 self.current_encounter.refetch_payload()
345 gmDispatcher.send('current_encounter_modified')
346
347 return True
348
349
350
351
352 - def get_family_history(self, episodes=None, issues=None, encounters=None):
353 fhx = gmFamilyHistory.get_family_history (
354 order_by = 'l10n_relation, condition',
355 patient = self.pk_patient
356 )
357
358 if episodes is not None:
359 fhx = [ f for f in fhx if f['pk_episode'] in episodes ]
360
361 if issues is not None:
362 fhx = [ f for f in fhx if f['pk_health_issue'] in issues ]
363
364 if encounters is not None:
365 fhx = [ f for f in fhx if f['pk_encounter'] in encounters ]
366
367 return fhx
368
369
370 - def add_family_history(self, episode=None, condition=None, relation=None):
371 return gmFamilyHistory.create_family_history (
372 encounter = self.current_encounter['pk_encounter'],
373 episode = episode,
374 condition = condition,
375 relation = relation
376 )
377
378
379
380
382 if self.__gender is not None:
383 return self.__gender
384 cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
385 args = {'pat': self.pk_patient}
386 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
387 self.__gender = rows[0]['gender']
388 self.__dob = rows[0]['dob']
389
392
393 gender = property(_get_gender, _set_gender)
394
395
397 if self.__dob is not None:
398 return self.__dob
399 cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
400 args = {'pat': self.pk_patient}
401 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
402 self.__gender = rows[0]['gender']
403 self.__dob = rows[0]['dob']
404
407
408 dob = property(_get_dob, _set_dob)
409
410
412 cmd = 'SELECT edc FROM clin.patient WHERE fk_identity = %(pat)s'
413 args = {'pat': self.pk_patient}
414 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
415 if len(rows) == 0:
416 return None
417 return rows[0]['edc']
418
420 cmd = """
421 INSERT INTO clin.patient (fk_identity, edc) SELECT
422 %(pat)s,
423 %(edc)s
424 WHERE NOT EXISTS (
425 SELECT 1 FROM clin.patient WHERE fk_identity = %(pat)s
426 )
427 RETURNING pk"""
428 args = {'pat': self.pk_patient, 'edc': edc}
429 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
430 if len(rows) == 0:
431 cmd = 'UPDATE clin.patient SET edc = %(edc)s WHERE fk_identity = %(pat)s'
432 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
433
434 EDC = property(_get_EDC, _set_EDC)
435
436
438 edc = self.EDC
439 if edc is None:
440 return False
441 if self.gender != 'f':
442 return True
443 now = gmDateTime.pydt_now_here()
444
445 if (self.dob + pydt.timedelta(weeks = 5 * 52)) > now:
446 return True
447
448 if (self.dob + pydt.timedelta(weeks = 55 * 52)) < now:
449 return True
450
451
452 if (edc - pydt.timedelta(days = 380)) > now:
453 return True
454
455
456
457
458 if edc < (now - pydt.timedelta(days = 380)):
459 return True
460
461 EDC_is_fishy = property(_EDC_is_fishy, lambda x:x)
462
463
465 try:
466 details['quit_when']
467 except KeyError:
468 details['quit_when'] = None
469
470 try:
471 details['last_confirmed']
472 if details['last_confirmed'] is None:
473 details['last_confirmed'] = gmDateTime.pydt_now_here()
474 except KeyError:
475 details['last_confirmed'] = gmDateTime.pydt_now_here()
476
477 try:
478 details['comment']
479 if details['comment'].strip() == '':
480 details['comment'] = None
481 except KeyError:
482 details['comment'] = None
483
484 return details
485
486
492
494
495 status_flag, details = status
496 self.__harmful_substance_use = None
497 args = {
498 'pat': self.pk_patient,
499 'status': status_flag
500 }
501 if status_flag is None:
502 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = NULL WHERE fk_identity = %(pat)s'
503 elif status_flag == 0:
504 details['quit_when'] = None
505 args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
506 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
507 else:
508 args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
509 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
510 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
511
512 smoking_status = property(_get_smoking_status, _set_smoking_status)
513
514
520
522
523 harmful, details = status
524 self.__harmful_substance_use = None
525 args = {'pat': self.pk_patient}
526 if harmful is None:
527 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = NULL, c2_details = NULL WHERE fk_identity = %(pat)s'
528 elif harmful is False:
529 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = FALSE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
530 else:
531 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = TRUE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
532 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
533
534 alcohol_status = property(_get_alcohol_status, _set_alcohol_status)
535
536
542
544
545 harmful, details = status
546 self.__harmful_substance_use = None
547 args = {'pat': self.pk_patient}
548 if harmful is None:
549 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = NULL, drugs_details = NULL WHERE fk_identity = %(pat)s'
550 elif harmful is False:
551 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = FALSE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
552 else:
553 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = TRUE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
554 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
555
556 drugs_status = property(_get_drugs_status, _set_drugs_status)
557
558
560
561 try:
562 self.__harmful_substance_use
563 except AttributeError:
564 self.__harmful_substance_use = None
565
566 if self.__harmful_substance_use is not None:
567 return self.__harmful_substance_use
568
569 args = {'pat': self.pk_patient}
570 cmd = """
571 SELECT
572 -- tobacco use
573 smoking_status,
574 smoking_details,
575 (smoking_details->>'last_confirmed')::timestamp with time zone
576 AS ts_last,
577 (smoking_details->>'quit_when')::timestamp with time zone
578 AS ts_quit,
579 -- c2 use
580 c2_currently_harmful_use,
581 c2_details,
582 -- other drugs use
583 drugs_currently_harmful_use,
584 drugs_details
585 FROM clin.patient
586 WHERE fk_identity = %(pat)s
587 """
588 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
589 if len(rows) == 0:
590 return None
591
592 status = rows[0]['smoking_status']
593 details = rows[0]['smoking_details']
594 if status is not None:
595 details['last_confirmed'] = rows[0]['ts_last']
596 details['quit_when'] = rows[0]['ts_quit']
597
598 self.__harmful_substance_use = {
599 'tobacco': (status, details),
600 'alcohol': (rows[0]['c2_currently_harmful_use'], rows[0]['c2_details']),
601 'drugs': (rows[0]['drugs_currently_harmful_use'], rows[0]['drugs_details'])
602 }
603
604 return self.__harmful_substance_use
605
606
608 cmd = 'SELECT * FROM clin.v_substance_intakes WHERE harmful_use_type = %s'
609
610 harmful_substance_use = property(_get_harmful_substance_use, lambda x:x)
611
612
691
692
694
695
696 use = self.harmful_substance_use
697
698 if use['alcohol'][0] is True:
699 return True
700 if use['drugs'][0] is True:
701 return True
702 if use['tobacco'][0] > 0:
703
704 if use['tobacco'][1]['quit_when'] is None:
705 return True
706
707
708 if use['alcohol'][0] is None:
709 return None
710 if use['drugs'][0] is None:
711 return None
712 if use['tobacco'][0] is None:
713 return None
714
715
716
717 return False
718
719 currently_abuses_substances = property(_get_currently_abuses_substances, lambda x:x)
720
721
722
723
735
736 performed_procedures = property(get_performed_procedures, lambda x:x)
737
740
749
751 where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_procedures_not_at_hospital WHERE pk_patient = %(pat)s)'
752 args = {'pat': self.pk_patient}
753 cmd = gmOrganization._SQL_get_org_unit % where
754 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
755 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
756
757
758
759
767
768 hospital_stays = property(get_hospital_stays, lambda x:x)
769
772
779
781 args = {'pat': self.pk_patient, 'range': cover_period}
782 where_parts = ['pk_patient = %(pat)s']
783 if cover_period is not None:
784 where_parts.append('discharge > (now() - %(range)s)')
785
786 cmd = """
787 SELECT hospital, count(1) AS frequency
788 FROM clin.v_hospital_stays
789 WHERE
790 %s
791 GROUP BY hospital
792 ORDER BY frequency DESC
793 """ % ' AND '.join(where_parts)
794
795 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
796 return rows
797
799 where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s)'
800 args = {'pat': self.pk_patient}
801 cmd = gmOrganization._SQL_get_org_unit % where
802 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
803 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
804
805
806
807
808 - def add_notes(self, notes=None, episode=None, encounter=None):
821
822
837
838
839 - def get_clin_narrative(self, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
840 """Get SOAP notes pertinent to this encounter.
841
842 encounters
843 - list of encounters the narrative of which are to be retrieved
844 episodes
845 - list of episodes the narrative of which are to be retrieved
846 issues
847 - list of health issues the narrative of which are to be retrieved
848 soap_cats
849 - list of SOAP categories of the narrative to be retrieved
850 """
851 where_parts = ['pk_patient = %(pat)s']
852 args = {'pat': self.pk_patient}
853
854 if issues is not None:
855 where_parts.append('pk_health_issue IN %(issues)s')
856 if len(issues) == 0:
857 args['issues'] = tuple()
858 else:
859 if isinstance(issues[0], gmEMRStructItems.cHealthIssue):
860 args['issues'] = tuple([ i['pk_health_issue'] for i in issues ])
861 elif isinstance(issues[0], int):
862 args['issues'] = tuple(issues)
863 else:
864 raise ValueError('<issues> must be list of type int (=pk) or cHealthIssue, but 1st issue is: %s' % issues[0])
865
866 if episodes is not None:
867 where_parts.append('pk_episode IN %(epis)s')
868 if len(episodes) == 0:
869 args['epis'] = tuple()
870 else:
871 if isinstance(episodes[0], gmEMRStructItems.cEpisode):
872 args['epis'] = tuple([ e['pk_episode'] for e in episodes ])
873 elif isinstance(episodes[0], int):
874 args['epis'] = tuple(episodes)
875 else:
876 raise ValueError('<episodes> must be list of type int (=pk) or cEpisode, but 1st episode is: %s' % episodes[0])
877
878 if encounters is not None:
879 where_parts.append('pk_encounter IN %(encs)s')
880 if len(encounters) == 0:
881 args['encs'] = tuple()
882 else:
883 if isinstance(encounters[0], gmEMRStructItems.cEncounter):
884 args['encs'] = tuple([ e['pk_encounter'] for e in encounters ])
885 elif isinstance(encounters[0], int):
886 args['encs'] = tuple(encounters)
887 else:
888 raise ValueError('<encounters> must be list of type int (=pk) or cEncounter, but 1st encounter is: %s' % encounters[0])
889
890 if soap_cats is not None:
891 where_parts.append('c_vn.soap_cat IN %(cats)s')
892 args['cats'] = tuple(gmSoapDefs.soap_cats2list(soap_cats))
893
894 if providers is not None:
895 where_parts.append('c_vn.modified_by IN %(docs)s')
896 args['docs'] = tuple(providers)
897
898 cmd = """
899 SELECT
900 c_vn.*,
901 c_scr.rank AS soap_rank
902 FROM
903 clin.v_narrative c_vn
904 LEFT JOIN clin.soap_cat_ranks c_scr on c_vn.soap_cat = c_scr.soap_cat
905 WHERE %s
906 ORDER BY date, soap_rank
907 """ % ' AND '.join(where_parts)
908
909 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
910 return [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
911
912
914
915 search_term = search_term.strip()
916 if search_term == '':
917 return []
918
919 cmd = """
920 SELECT
921 *,
922 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
923 as episode,
924 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
925 as health_issue,
926 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
927 as encounter_started,
928 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
929 as encounter_ended,
930 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
931 as encounter_type
932 -- CHANGE BACK IN V23:
933 --FROM clin.v_narrative4search vn4s
934 FROM v_narrative4search vn4s
935 WHERE
936 pk_patient = %(pat)s and
937 vn4s.narrative ~ %(term)s
938 order by
939 encounter_started
940 """
941
942 queries = [
943 {'cmd': gmClinNarrative._VIEW_clin_v_narrative4search},
944 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
945 ]
946 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
947 return rows
948
949
950 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
951 fields = [
952 'age',
953 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
954 'modified_by',
955 'clin_when',
956 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
957 'pk_item',
958 'pk_encounter',
959 'pk_episode',
960 'pk_health_issue',
961 'src_table'
962 ]
963 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
964
965 where_snippets = []
966 params = {}
967 where_snippets.append('pk_patient=%(pat_id)s')
968 params['pat_id'] = self.pk_patient
969 if not since is None:
970 where_snippets.append('clin_when >= %(since)s')
971 params['since'] = since
972 if not until is None:
973 where_snippets.append('clin_when <= %(until)s')
974 params['until'] = until
975
976
977
978 if not encounters is None and len(encounters) > 0:
979 params['enc'] = encounters
980 if len(encounters) > 1:
981 where_snippets.append('fk_encounter in %(enc)s')
982 else:
983 where_snippets.append('fk_encounter=%(enc)s')
984
985 if not episodes is None and len(episodes) > 0:
986 params['epi'] = episodes
987 if len(episodes) > 1:
988 where_snippets.append('fk_episode in %(epi)s')
989 else:
990 where_snippets.append('fk_episode=%(epi)s')
991
992 if not issues is None and len(issues) > 0:
993 params['issue'] = issues
994 if len(issues) > 1:
995 where_snippets.append('fk_health_issue in %(issue)s')
996 else:
997 where_snippets.append('fk_health_issue=%(issue)s')
998
999 where_clause = ' and '.join(where_snippets)
1000 order_by = 'order by src_table, age'
1001 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
1002
1003 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
1004 if rows is None:
1005 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
1006 return None
1007
1008
1009
1010
1011 items_by_table = {}
1012 for item in rows:
1013 src_table = item[view_col_idx['src_table']]
1014 pk_item = item[view_col_idx['pk_item']]
1015 if src_table not in items_by_table:
1016 items_by_table[src_table] = {}
1017 items_by_table[src_table][pk_item] = item
1018
1019
1020 issues = self.get_health_issues()
1021 issue_map = {}
1022 for issue in issues:
1023 issue_map[issue['pk_health_issue']] = issue['description']
1024 episodes = self.get_episodes()
1025 episode_map = {}
1026 for episode in episodes:
1027 episode_map[episode['pk_episode']] = episode['description']
1028 emr_data = {}
1029
1030 ro_conn = self._conn_pool.GetConnection('historica')
1031 curs = ro_conn.cursor()
1032 for src_table in items_by_table.keys():
1033 item_ids = items_by_table[src_table].keys()
1034
1035
1036 if len(item_ids) == 0:
1037 _log.info('no items in table [%s] ?!?' % src_table)
1038 continue
1039 elif len(item_ids) == 1:
1040 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
1041 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
1042 _log.error('cannot load items from table [%s]' % src_table)
1043
1044 continue
1045 elif len(item_ids) > 1:
1046 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
1047 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
1048 _log.error('cannot load items from table [%s]' % src_table)
1049
1050 continue
1051 rows = curs.fetchall()
1052 table_col_idx = gmPG.get_col_indices(curs)
1053
1054 for row in rows:
1055
1056 pk_item = row[table_col_idx['pk_item']]
1057 view_row = items_by_table[src_table][pk_item]
1058 age = view_row[view_col_idx['age']]
1059
1060 try:
1061 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
1062 except Exception:
1063 episode_name = view_row[view_col_idx['pk_episode']]
1064 try:
1065 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
1066 except Exception:
1067 issue_name = view_row[view_col_idx['pk_health_issue']]
1068
1069 if age not in emr_data:
1070 emr_data[age] = []
1071
1072 emr_data[age].append(
1073 _('%s: encounter (%s)') % (
1074 view_row[view_col_idx['clin_when']],
1075 view_row[view_col_idx['pk_encounter']]
1076 )
1077 )
1078 emr_data[age].append(_('health issue: %s') % issue_name)
1079 emr_data[age].append(_('episode : %s') % episode_name)
1080
1081
1082
1083 cols2ignore = [
1084 'pk_audit', 'row_version', 'modified_when', 'modified_by',
1085 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
1086 ]
1087 col_data = []
1088 for col_name in table_col_idx.keys():
1089 if col_name in cols2ignore:
1090 continue
1091 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
1092 emr_data[age].append("----------------------------------------------------")
1093 emr_data[age].append("-- %s from table %s" % (
1094 view_row[view_col_idx['modified_string']],
1095 src_table
1096 ))
1097 emr_data[age].append("-- written %s by %s" % (
1098 view_row[view_col_idx['modified_when']],
1099 view_row[view_col_idx['modified_by']]
1100 ))
1101 emr_data[age].append("----------------------------------------------------")
1102 curs.close()
1103 return emr_data
1104
1106 return self.pk_patient
1107
1109 union_query = '\n union all\n'.join ([
1110 """
1111 SELECT ((
1112 -- all relevant health issues + active episodes WITH health issue
1113 SELECT COUNT(1)
1114 FROM clin.v_problem_list
1115 WHERE
1116 pk_patient = %(pat)s
1117 AND
1118 pk_health_issue is not null
1119 ) + (
1120 -- active episodes WITHOUT health issue
1121 SELECT COUNT(1)
1122 FROM clin.v_problem_list
1123 WHERE
1124 pk_patient = %(pat)s
1125 AND
1126 pk_health_issue is null
1127 ))""",
1128 'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
1129 'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
1130 'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
1131 'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
1132 'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
1133 'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
1134
1135 """
1136 SELECT count(1)
1137 FROM clin.v_substance_intakes
1138 WHERE
1139 pk_patient = %(pat)s
1140 AND
1141 is_currently_active IN (null, true)
1142 AND
1143 intake_is_approved_of IN (null, true)""",
1144 'SELECT count(1) FROM clin.v_vaccinations WHERE pk_patient = %(pat)s'
1145 ])
1146
1147 rows, idx = gmPG2.run_ro_queries (
1148 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
1149 get_col_idx = False
1150 )
1151
1152 stats = dict (
1153 problems = rows[0][0],
1154 encounters = rows[1][0],
1155 items = rows[2][0],
1156 documents = rows[3][0],
1157 results = rows[4][0],
1158 stays = rows[5][0],
1159 procedures = rows[6][0],
1160 active_drugs = rows[7][0],
1161 vaccinations = rows[8][0]
1162 )
1163
1164 return stats
1165
1178
1311
1312
1333
1334
1335 - 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):
1336 return gmClinNarrative.get_as_journal (
1337 patient = self.pk_patient,
1338 since = since,
1339 until = until,
1340 encounters = encounters,
1341 episodes = episodes,
1342 issues = issues,
1343 soap_cats = soap_cats,
1344 providers = providers,
1345 order_by = order_by,
1346 time_range = time_range,
1347 active_encounter = self.active_encounter
1348 )
1349
1350
1351 - def get_generic_emr_items(self, pk_encounters=None, pk_episodes=None, pk_health_issues=None, use_active_encounter=False, order_by=None):
1364
1365
1366
1367
1368 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
1369 """Retrieves patient allergy items.
1370
1371 remove_sensitivities
1372 - retrieve real allergies only, without sensitivities
1373 since
1374 - initial date for allergy items
1375 until
1376 - final date for allergy items
1377 encounters
1378 - list of encounters whose allergies are to be retrieved
1379 episodes
1380 - list of episodes whose allergies are to be retrieved
1381 issues
1382 - list of health issues whose allergies are to be retrieved
1383 """
1384 cmd = "SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
1385 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
1386 filtered_allergies = []
1387 for r in rows:
1388 filtered_allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
1389
1390
1391 if ID_list is not None:
1392 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_allergy'] in ID_list ]
1393 if len(filtered_allergies) == 0:
1394 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
1395
1396 return None
1397 else:
1398 return filtered_allergies
1399
1400 if remove_sensitivities:
1401 filtered_allergies = [ allg for allg in filtered_allergies if allg['type'] == 'allergy' ]
1402 if since is not None:
1403 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] >= since ]
1404 if until is not None:
1405 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] < until ]
1406 if issues is not None:
1407 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_health_issue'] in issues ]
1408 if episodes is not None:
1409 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_episode'] in episodes ]
1410 if encounters is not None:
1411 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_encounter'] in encounters ]
1412
1413 return filtered_allergies
1414
1415 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
1416 if encounter_id is None:
1417 encounter_id = self.current_encounter['pk_encounter']
1418
1419 if episode_id is None:
1420 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
1421 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
1422 episode_id = epi['pk_episode']
1423
1424 new_allergy = gmAllergy.create_allergy (
1425 allergene = allergene,
1426 allg_type = allg_type,
1427 encounter_id = encounter_id,
1428 episode_id = episode_id
1429 )
1430
1431 return new_allergy
1432
1434 cmd = 'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1435 args = {'pk_allg': pk_allergy}
1436 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1437
1438
1440 """Cave: only use with one potential allergic agent
1441 otherwise you won't know which of the agents the allergy is to."""
1442
1443
1444 if self.allergy_state is None:
1445 return None
1446
1447
1448 if self.allergy_state == 0:
1449 return False
1450
1451 args = {
1452 'atcs': atcs,
1453 'inns': inns,
1454 'prod_name': product_name,
1455 'pat': self.pk_patient
1456 }
1457 allergenes = []
1458 where_parts = []
1459
1460 if len(atcs) == 0:
1461 atcs = None
1462 if atcs is not None:
1463 where_parts.append('atc_code in %(atcs)s')
1464 if len(inns) == 0:
1465 inns = None
1466 if inns is not None:
1467 where_parts.append('generics in %(inns)s')
1468 allergenes.extend(inns)
1469 if product_name is not None:
1470 where_parts.append('substance = %(prod_name)s')
1471 allergenes.append(product_name)
1472
1473 if len(allergenes) != 0:
1474 where_parts.append('allergene in %(allgs)s')
1475 args['allgs'] = tuple(allergenes)
1476
1477 cmd = """
1478 SELECT * FROM clin.v_pat_allergies
1479 WHERE
1480 pk_patient = %%(pat)s
1481 AND ( %s )""" % ' OR '.join(where_parts)
1482
1483 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1484
1485 if len(rows) == 0:
1486 return False
1487
1488 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1489
1499
1502
1503 allergy_state = property(_get_allergy_state, _set_allergy_state)
1504
1505
1506
1513
1514 external_care_items = property(get_external_care_items, lambda x:x)
1515
1516
1517
1518
1519 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1520 """Fetches from backend patient episodes.
1521
1522 id_list - Episodes' PKs list
1523 issues - Health issues' PKs list to filter episodes by
1524 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1525 """
1526 if (unlinked_only is True) and (issues is not None):
1527 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1528
1529 if order_by is None:
1530 order_by = ''
1531 else:
1532 order_by = 'ORDER BY %s' % order_by
1533
1534 args = {
1535 'pat': self.pk_patient,
1536 'open': open_status
1537 }
1538 where_parts = ['pk_patient = %(pat)s']
1539
1540 if open_status is not None:
1541 where_parts.append('episode_open IS %(open)s')
1542
1543 if unlinked_only:
1544 where_parts.append('pk_health_issue is NULL')
1545
1546 if issues is not None:
1547 where_parts.append('pk_health_issue IN %(issues)s')
1548 args['issues'] = tuple(issues)
1549
1550 if id_list is not None:
1551 where_parts.append('pk_episode IN %(epis)s')
1552 args['epis'] = tuple(id_list)
1553
1554 cmd = "SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1555 ' AND '.join(where_parts),
1556 order_by
1557 )
1558 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1559
1560 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1561
1562 episodes = property(get_episodes, lambda x:x)
1563
1565 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1566
1567 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1568
1570 cmd = """SELECT distinct pk_episode
1571 from clin.v_pat_items
1572 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1573 args = {
1574 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1575 'pat': self.pk_patient
1576 }
1577 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1578 if len(rows) == 0:
1579 return []
1580 epis = []
1581 for row in rows:
1582 epis.append(row[0])
1583 return self.get_episodes(id_list=epis)
1584
1585 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False, allow_dupes=False, link_obj=None):
1586 """Add episode 'episode_name' for a patient's health issue.
1587
1588 - silently returns if episode already exists
1589 """
1590 episode = gmEMRStructItems.create_episode (
1591 link_obj = link_obj,
1592 pk_health_issue = pk_health_issue,
1593 episode_name = episode_name,
1594 is_open = is_open,
1595 encounter = self.current_encounter['pk_encounter'],
1596 allow_dupes = allow_dupes
1597 )
1598 return episode
1599
1601
1602 issue_where = gmTools.coalesce (
1603 value2test = issue,
1604 return_instead = '',
1605 value2return = 'and pk_health_issue = %(issue)s'
1606 )
1607 cmd = """
1608 SELECT pk
1609 from clin.episode
1610 WHERE pk = (
1611 SELECT distinct on(pk_episode) pk_episode
1612 from clin.v_pat_items
1613 WHERE
1614 pk_patient = %%(pat)s
1615 and
1616 modified_when = (
1617 SELECT max(vpi.modified_when)
1618 from clin.v_pat_items vpi
1619 WHERE vpi.pk_patient = %%(pat)s
1620 )
1621 %s
1622 -- guard against several episodes created at the same moment of time
1623 limit 1
1624 )""" % issue_where
1625 rows, idx = gmPG2.run_ro_queries(queries = [
1626 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1627 ])
1628 if len(rows) != 0:
1629 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1630
1631
1632
1633 cmd = """
1634 SELECT vpe0.pk_episode
1635 from
1636 clin.v_pat_episodes vpe0
1637 WHERE
1638 vpe0.pk_patient = %%(pat)s
1639 and
1640 vpe0.episode_modified_when = (
1641 SELECT max(vpe1.episode_modified_when)
1642 from clin.v_pat_episodes vpe1
1643 WHERE vpe1.pk_episode = vpe0.pk_episode
1644 )
1645 %s""" % issue_where
1646 rows, idx = gmPG2.run_ro_queries(queries = [
1647 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1648 ])
1649 if len(rows) != 0:
1650 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1651
1652 return None
1653
1656
1657
1658
1659 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1660 """Retrieve a patient's problems.
1661
1662 "Problems" are the UNION of:
1663
1664 - issues which are .clinically_relevant
1665 - episodes which are .is_open
1666
1667 Therefore, both an issue and the open episode
1668 thereof can each be listed as a problem.
1669
1670 include_closed_episodes/include_irrelevant_issues will
1671 include those -- which departs from the definition of
1672 the problem list being "active" items only ...
1673
1674 episodes - episodes' PKs to filter problems by
1675 issues - health issues' PKs to filter problems by
1676 """
1677
1678
1679 args = {'pat': self.pk_patient}
1680
1681 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1682 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1683
1684
1685 problems = []
1686 for row in rows:
1687 pk_args = {
1688 'pk_patient': self.pk_patient,
1689 'pk_health_issue': row['pk_health_issue'],
1690 'pk_episode': row['pk_episode']
1691 }
1692 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1693
1694
1695 other_rows = []
1696 if include_closed_episodes:
1697 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1698 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1699 other_rows.extend(rows)
1700
1701 if include_irrelevant_issues:
1702 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1703 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1704 other_rows.extend(rows)
1705
1706 if len(other_rows) > 0:
1707 for row in other_rows:
1708 pk_args = {
1709 'pk_patient': self.pk_patient,
1710 'pk_health_issue': row['pk_health_issue'],
1711 'pk_episode': row['pk_episode']
1712 }
1713 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1714
1715
1716 if issues is not None:
1717 problems = [ p for p in problems if p['pk_health_issue'] in issues ]
1718 if episodes is not None:
1719 problems = [ p for p in problems if p['pk_episode'] in episodes ]
1720
1721 return problems
1722
1723
1726
1727
1730
1731
1734
1735
1737 cmd = "SELECT * FROM clin.v_candidate_diagnoses WHERE pk_patient = %(pat)s"
1738 rows, idx = gmPG2.run_ro_queries (
1739 queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}],
1740 get_col_idx = False
1741 )
1742 return rows
1743
1744 candidate_diagnoses = property(get_candidate_diagnoses)
1745
1746
1747
1748
1750
1751 cmd = "SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1752 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1753 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1754
1755 if id_list is None:
1756 return issues
1757
1758 if len(id_list) == 0:
1759 raise ValueError('id_list to filter by is empty, most likely a programming error')
1760
1761 filtered_issues = []
1762 for issue in issues:
1763 if issue['pk_health_issue'] in id_list:
1764 filtered_issues.append(issue)
1765
1766 return filtered_issues
1767
1768 health_issues = property(get_health_issues, lambda x:x)
1769
1770
1778
1781
1782
1783
1784 - def get_current_medications(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1785 return self._get_current_substance_intakes (
1786 include_inactive = include_inactive,
1787 include_unapproved = include_unapproved,
1788 order_by = order_by,
1789 episodes = episodes,
1790 issues = issues,
1791 exclude_medications = False,
1792 exclude_potential_abuses = True
1793 )
1794
1795
1797 return self._get_current_substance_intakes (
1798 include_inactive = True,
1799 include_unapproved = True,
1800 order_by = order_by,
1801 episodes = None,
1802 issues = None,
1803 exclude_medications = True,
1804 exclude_potential_abuses = False
1805 )
1806
1807 abused_substances = property(_get_abused_substances, lambda x:x)
1808
1809
1810 - 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):
1811
1812 where_parts = ['pk_patient = %(pat)s']
1813 args = {'pat': self.pk_patient}
1814
1815 if not include_inactive:
1816 where_parts.append('is_currently_active IN (TRUE, NULL)')
1817
1818 if not include_unapproved:
1819 where_parts.append('intake_is_approved_of IN (TRUE, NULL)')
1820
1821 if exclude_potential_abuses:
1822 where_parts.append('harmful_use_type IS NULL')
1823
1824 if exclude_medications:
1825 where_parts.append('harmful_use_type IS NOT NULL')
1826
1827 if order_by is None:
1828 order_by = ''
1829 else:
1830 order_by = 'ORDER BY %s' % order_by
1831
1832 cmd = "SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1833 '\nAND '.join(where_parts),
1834 order_by
1835 )
1836 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1837 intakes = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1838
1839 if episodes is not None:
1840 intakes = [ i for i in intakes if i['pk_episode'] in episodes ]
1841
1842 if issues is not None:
1843 intakes = [ i for i in intakes if i ['pk_health_issue'] in issues ]
1844
1845 return intakes
1846
1847
1848 - def add_substance_intake(self, pk_component=None, pk_episode=None, pk_drug_product=None, pk_health_issue=None):
1861
1862
1864 return gmMedication.substance_intake_exists (
1865 pk_component = pk_component,
1866 pk_substance = pk_substance,
1867 pk_identity = self.pk_patient,
1868 pk_drug_product = pk_drug_product
1869 )
1870
1871
1872
1873
1881
1882
1884 """Returns latest given vaccination for each vaccinated indication.
1885
1886 as a dict {'l10n_indication': cVaccination instance}
1887
1888 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1889 """
1890 args = {'pat': self.pk_patient}
1891 where_parts = ['c_v_shots.pk_patient = %(pat)s']
1892
1893 if (episodes is not None) and (len(episodes) > 0):
1894 where_parts.append('c_v_shots.pk_episode IN %(epis)s')
1895 args['epis'] = tuple(episodes)
1896
1897 if (issues is not None) and (len(issues) > 0):
1898 where_parts.append('c_v_shots.pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1899 args['issues'] = tuple(issues)
1900
1901 if (atc_indications is not None) and (len(atc_indications) > 0):
1902 where_parts.append('c_v_plv4i.atc_indication IN %(atc_inds)s')
1903 args['atc_inds'] = tuple(atc_indications)
1904
1905
1906 cmd = """
1907 SELECT
1908 c_v_shots.*,
1909 c_v_plv4i.l10n_indication,
1910 c_v_plv4i.no_of_shots
1911 FROM
1912 clin.v_vaccinations c_v_shots
1913 JOIN clin.v_pat_last_vacc4indication c_v_plv4i ON (c_v_shots.pk_vaccination = c_v_plv4i.pk_vaccination)
1914 WHERE %s
1915 """ % '\nAND '.join(where_parts)
1916 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1917
1918
1919 if len(rows) == 0:
1920 return {}
1921
1922
1923
1924 vaccs = {}
1925 for shot_row in rows:
1926 vaccs[shot_row['l10n_indication']] = (
1927 shot_row['no_of_shots'],
1928 gmVaccination.cVaccination(row = {'idx': idx, 'data': shot_row, 'pk_field': 'pk_vaccination'})
1929 )
1930
1931 return vaccs
1932
1933
1934 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1935 return gmVaccination.get_vaccinations (
1936 pk_identity = self.pk_patient,
1937 pk_episodes = episodes,
1938 pk_health_issues = issues,
1939 pk_encounters = encounters,
1940 order_by = order_by,
1941 return_pks = False
1942 )
1943
1944 vaccinations = property(get_vaccinations, lambda x:x)
1945
1946
1947
1948
1950 """Retrieves vaccination regimes the patient is on.
1951
1952 optional:
1953 * ID - PK of the vaccination regime
1954 * indications - indications we want to retrieve vaccination
1955 regimes for, must be primary language, not l10n_indication
1956 """
1957
1958
1959 cmd = """SELECT distinct on(pk_course) pk_course
1960 FROM clin.v_vaccs_scheduled4pat
1961 WHERE pk_patient=%s"""
1962 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1963 if rows is None:
1964 _log.error('cannot retrieve scheduled vaccination courses')
1965 return None
1966
1967 for row in rows:
1968 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1969
1970
1971 filtered_regimes = []
1972 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1973 if ID is not None:
1974 filtered_regimes = [ r for r in filtered_regimes if r['pk_course'] == ID ]
1975 if len(filtered_regimes) == 0:
1976 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1977 return []
1978 else:
1979 return filtered_regimes[0]
1980 if indications is not None:
1981 filtered_regimes = [ r for r in filtered_regimes if r['indication'] in indications ]
1982
1983 return filtered_regimes
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2012 """Retrieves list of vaccinations the patient has received.
2013
2014 optional:
2015 * ID - PK of a vaccination
2016 * indications - indications we want to retrieve vaccination
2017 items for, must be primary language, not l10n_indication
2018 * since - initial date for allergy items
2019 * until - final date for allergy items
2020 * encounters - list of encounters whose allergies are to be retrieved
2021 * episodes - list of episodes whose allergies are to be retrieved
2022 * issues - list of health issues whose allergies are to be retrieved
2023 """
2024 try:
2025 self.__db_cache['vaccinations']['vaccinated']
2026 except KeyError:
2027 self.__db_cache['vaccinations']['vaccinated'] = []
2028
2029 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
2030 WHERE pk_patient=%s
2031 order by indication, date"""
2032 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2033 if rows is None:
2034 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
2035 del self.__db_cache['vaccinations']['vaccinated']
2036 return None
2037
2038 vaccs_by_ind = {}
2039 for row in rows:
2040 vacc_row = {
2041 'pk_field': 'pk_vaccination',
2042 'idx': idx,
2043 'data': row
2044 }
2045 vacc = gmVaccination.cVaccination(row=vacc_row)
2046 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
2047
2048 try:
2049 vaccs_by_ind[vacc['indication']].append(vacc)
2050 except KeyError:
2051 vaccs_by_ind[vacc['indication']] = [vacc]
2052
2053
2054 for ind in vaccs_by_ind.keys():
2055 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
2056 for vacc in vaccs_by_ind[ind]:
2057
2058
2059 seq_no = vaccs_by_ind[ind].index(vacc) + 1
2060 vacc['seq_no'] = seq_no
2061
2062
2063 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
2064 continue
2065 if seq_no > vacc_regimes[0]['shots']:
2066 vacc['is_booster'] = True
2067 del vaccs_by_ind
2068
2069
2070 filtered_shots = []
2071 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
2072 if ID is not None:
2073 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
2074 if len(filtered_shots) == 0:
2075 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
2076 return None
2077 else:
2078 return filtered_shots[0]
2079 if since is not None:
2080 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
2081 if until is not None:
2082 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
2083 if issues is not None:
2084 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
2085 if episodes is not None:
2086 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
2087 if encounters is not None:
2088 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
2089 if indications is not None:
2090 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2091 return filtered_shots
2092
2094 """Retrieves vaccinations scheduled for a regime a patient is on.
2095
2096 The regime is referenced by its indication (not l10n)
2097
2098 * indications - List of indications (not l10n) of regimes we want scheduled
2099 vaccinations to be fetched for
2100 """
2101 try:
2102 self.__db_cache['vaccinations']['scheduled']
2103 except KeyError:
2104 self.__db_cache['vaccinations']['scheduled'] = []
2105 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
2106 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2107 if rows is None:
2108 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
2109 del self.__db_cache['vaccinations']['scheduled']
2110 return None
2111
2112 for row in rows:
2113 vacc_row = {
2114 'pk_field': 'pk_vacc_def',
2115 'idx': idx,
2116 'data': row
2117 }
2118 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
2119
2120
2121 if indications is None:
2122 return self.__db_cache['vaccinations']['scheduled']
2123 filtered_shots = []
2124 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
2125 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2126 return filtered_shots
2127
2129 try:
2130 self.__db_cache['vaccinations']['missing']
2131 except KeyError:
2132 self.__db_cache['vaccinations']['missing'] = {}
2133
2134 self.__db_cache['vaccinations']['missing']['due'] = []
2135
2136 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
2137 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2138 if rows is None:
2139 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
2140 return None
2141 pk_args = {'pat_id': self.pk_patient}
2142 if rows is not None:
2143 for row in rows:
2144 pk_args['indication'] = row[0]
2145 pk_args['seq_no'] = row[1]
2146 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
2147
2148
2149 self.__db_cache['vaccinations']['missing']['boosters'] = []
2150
2151 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
2152 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2153 if rows is None:
2154 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
2155 return None
2156 pk_args = {'pat_id': self.pk_patient}
2157 if rows is not None:
2158 for row in rows:
2159 pk_args['indication'] = row[0]
2160 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
2161
2162
2163 if indications is None:
2164 return self.__db_cache['vaccinations']['missing']
2165 if len(indications) == 0:
2166 return self.__db_cache['vaccinations']['missing']
2167
2168 filtered_shots = {
2169 'due': [],
2170 'boosters': []
2171 }
2172 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
2173 if due_shot['indication'] in indications:
2174 filtered_shots['due'].append(due_shot)
2175 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
2176 if due_shot['indication'] in indications:
2177 filtered_shots['boosters'].append(due_shot)
2178 return filtered_shots
2179
2180
2181
2182
2184 return self.__encounter
2185
2187
2188 if self.__encounter is None:
2189 _log.debug('first setting of active encounter in this clinical record instance')
2190 encounter.lock(exclusive = False)
2191 self.__encounter = encounter
2192 gmDispatcher.send('current_encounter_switched')
2193 return True
2194
2195
2196 _log.debug('switching of active encounter')
2197
2198 if self.__encounter.is_modified():
2199 gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
2200 _log.error('current in client: %s', self.__encounter)
2201 raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
2202 self.__encounter['pk_encounter'],
2203 encounter['pk_encounter']
2204 ))
2205
2206 prev_enc = self.__encounter
2207 encounter.lock(exclusive = False)
2208 self.__encounter = encounter
2209 prev_enc.unlock(exclusive = False)
2210 gmDispatcher.send('current_encounter_switched')
2211
2212 return True
2213
2214 current_encounter = property(_get_current_encounter, _set_current_encounter)
2215 active_encounter = property(_get_current_encounter, _set_current_encounter)
2216
2217
2219 _log.debug('setting up active encounter for identity [%s]', self.pk_patient)
2220
2221
2222 _delayed_execute(self.log_access, action = 'pulling chart for identity [%s]' % self.pk_patient)
2223
2224
2225
2226 self.remove_empty_encounters()
2227
2228
2229 if self.__activate_very_recent_encounter():
2230 return
2231
2232 fairly_recent_enc = self.__get_fairly_recent_encounter()
2233
2234
2235 self.start_new_encounter()
2236
2237 if fairly_recent_enc is None:
2238 return
2239
2240
2241 gmDispatcher.send (
2242 signal = 'ask_for_encounter_continuation',
2243 new_encounter = self.__encounter,
2244 fairly_recent_encounter = fairly_recent_enc
2245 )
2246
2247
2249 """Try to attach to a "very recent" encounter if there is one.
2250
2251 returns:
2252 False: no "very recent" encounter
2253 True: success
2254 """
2255 cfg_db = gmCfg.cCfgSQL()
2256 min_ttl = cfg_db.get2 (
2257 option = 'encounter.minimum_ttl',
2258 workplace = _here.active_workplace,
2259 bias = 'user',
2260 default = '1 hour 30 minutes'
2261 )
2262 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2263 SELECT pk_encounter
2264 FROM clin.v_most_recent_encounters
2265 WHERE
2266 pk_patient = %s
2267 and
2268 last_affirmed > (now() - %s::interval)
2269 ORDER BY
2270 last_affirmed DESC
2271 LIMIT 1
2272 )"""
2273 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}], get_col_idx = True)
2274
2275
2276 if len(enc_rows) == 0:
2277 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
2278 return False
2279
2280 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0]['pk_encounter'])
2281
2282
2283 self.current_encounter = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2284 return True
2285
2286
2288 cfg_db = gmCfg.cCfgSQL()
2289 min_ttl = cfg_db.get2 (
2290 option = 'encounter.minimum_ttl',
2291 workplace = _here.active_workplace,
2292 bias = 'user',
2293 default = '1 hour 30 minutes'
2294 )
2295 max_ttl = cfg_db.get2 (
2296 option = 'encounter.maximum_ttl',
2297 workplace = _here.active_workplace,
2298 bias = 'user',
2299 default = '6 hours'
2300 )
2301
2302
2303 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2304 SELECT pk_encounter
2305 FROM clin.v_most_recent_encounters
2306 WHERE
2307 pk_patient=%s
2308 AND
2309 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2310 ORDER BY
2311 last_affirmed DESC
2312 LIMIT 1
2313 )"""
2314 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2315
2316
2317 if len(enc_rows) == 0:
2318 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2319 return None
2320
2321 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2322 return gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
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
2478
2479
2480 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False, order_by=None, max_encounters=None):
2481 """Retrieves patient's encounters.
2482
2483 id_list - PKs of encounters to fetch
2484 since - initial date for encounter items, DateTime instance
2485 until - final date for encounter items, DateTime instance
2486 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
2487 issues - PKs of the health issues the encounters belong to (many-to-many relation)
2488 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
2489
2490 NOTE: if you specify *both* issues and episodes
2491 you will get the *aggregate* of all encounters even
2492 if the episodes all belong to the health issues listed.
2493 IOW, the issues broaden the episode list rather than
2494 the episode list narrowing the episodes-from-issues
2495 list.
2496 Rationale: If it was the other way round it would be
2497 redundant to specify the list of issues at all.
2498 """
2499
2500 if (issues is not None) and (len(issues) > 0):
2501
2502 cmd = "SELECT distinct pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue in %(issue_pks)s AND pk_patient = %(pat)s"
2503 args = {'issue_pks': tuple(issues), 'pat': self.pk_patient}
2504 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2505 epis4issues_pks = [ r['pk_episode'] for r in rows ]
2506 if episodes is None:
2507 episodes = []
2508 episodes.extend(epis4issues_pks)
2509
2510 if (episodes is not None) and (len(episodes) > 0):
2511
2512
2513
2514 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2515 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)"
2516 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2517 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2518 if id_list is None:
2519 id_list = []
2520 id_list.extend(encs4epis_pks)
2521
2522 where_parts = ['c_vpe.pk_patient = %(pat)s']
2523 args = {'pat': self.pk_patient}
2524
2525 if skip_empty:
2526 where_parts.append("""NOT (
2527 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
2528 AND
2529 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
2530 AND
2531 NOT EXISTS (
2532 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
2533 UNION ALL
2534 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
2535 ))""")
2536
2537 if since is not None:
2538 where_parts.append('c_vpe.started >= %(start)s')
2539 args['start'] = since
2540
2541 if until is not None:
2542 where_parts.append('c_vpe.last_affirmed <= %(end)s')
2543 args['end'] = since
2544
2545 if (id_list is not None) and (len(id_list) > 0):
2546 where_parts.append('c_vpe.pk_encounter IN %(enc_pks)s')
2547 args['enc_pks'] = tuple(id_list)
2548
2549 if order_by is None:
2550 order_by = 'c_vpe.started'
2551
2552 if max_encounters is None:
2553 limit = ''
2554 else:
2555 limit = 'LIMIT %s' % max_encounters
2556
2557 cmd = """
2558 SELECT * FROM clin.v_pat_encounters c_vpe
2559 WHERE
2560 %s
2561 ORDER BY %s %s
2562 """ % (
2563 ' AND '.join(where_parts),
2564 order_by,
2565 limit
2566 )
2567 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2568 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
2569
2570
2571 filtered_encounters = []
2572 filtered_encounters.extend(encounters)
2573
2574 if (episodes is not None) and (len(episodes) > 0):
2575
2576
2577
2578 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2579 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)"
2580 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2581 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2582 filtered_encounters = [ enc for enc in filtered_encounters if enc['pk_encounter'] in encs4epis_pks ]
2583
2584 return filtered_encounters
2585
2586
2588 """Retrieves first encounter for a particular issue and/or episode.
2589
2590 issue_id - First encounter associated health issue
2591 episode - First encounter associated episode
2592 """
2593 if issue_id is None:
2594 issues = None
2595 else:
2596 issues = [issue_id]
2597
2598 if episode_id is None:
2599 episodes = None
2600 else:
2601 episodes = [episode_id]
2602
2603 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started', max_encounters = 1)
2604 if len(encounters) == 0:
2605 return None
2606
2607 return encounters[0]
2608
2609 first_encounter = property(get_first_encounter, lambda x:x)
2610
2611
2613 args = {'pat': self.pk_patient}
2614 cmd = """
2615 SELECT MIN(earliest) FROM (
2616 (
2617 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
2618
2619 ) UNION ALL (
2620
2621 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
2622
2623 ) UNION ALL (
2624
2625 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2626
2627 ) UNION ALL (
2628
2629 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2630
2631 ) UNION ALL (
2632
2633 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2634
2635 ) UNION ALL (
2636
2637 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2638
2639 ) UNION ALL (
2640
2641 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2642
2643 )
2644 ) AS candidates"""
2645 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2646 return rows[0][0]
2647
2648 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2649
2650
2652 encounters = self.get_encounters(order_by = 'started DESC', max_encounters = 1)
2653 if len(encounters) == 0:
2654 return None
2655 return encounters[0]['last_affirmed']
2656
2657 most_recent_care_date = property(get_most_recent_care_date)
2658
2659
2661 """Retrieves last encounter for a concrete issue and/or episode
2662
2663 issue_id - Last encounter associated health issue
2664 episode_id - Last encounter associated episode
2665 """
2666 if issue_id is None:
2667 issues = None
2668 else:
2669 issues = [issue_id]
2670
2671 if episode_id is None:
2672 episodes = None
2673 else:
2674 episodes = [episode_id]
2675
2676 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started DESC', max_encounters = 1)
2677 if len(encounters) == 0:
2678 return None
2679
2680 return encounters[0]
2681
2682 last_encounter = property(get_last_encounter, lambda x:x)
2683
2684
2686 args = {'pat': self.pk_patient, 'range': cover_period}
2687 where_parts = ['pk_patient = %(pat)s']
2688 if cover_period is not None:
2689 where_parts.append('last_affirmed > now() - %(range)s')
2690
2691 cmd = """
2692 SELECT l10n_type, count(1) AS frequency
2693 FROM clin.v_pat_encounters
2694 WHERE
2695 %s
2696 GROUP BY l10n_type
2697 ORDER BY frequency DESC
2698 """ % ' AND '.join(where_parts)
2699 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2700 return rows
2701
2702
2704
2705 args = {'pat': self.pk_patient}
2706
2707 if (issue_id is None) and (episode_id is None):
2708 cmd = """
2709 SELECT * FROM clin.v_pat_encounters
2710 WHERE pk_patient = %(pat)s
2711 ORDER BY started DESC
2712 LIMIT 2
2713 """
2714 else:
2715 where_parts = []
2716
2717 if issue_id is not None:
2718 where_parts.append('pk_health_issue = %(issue)s')
2719 args['issue'] = issue_id
2720
2721 if episode_id is not None:
2722 where_parts.append('pk_episode = %(epi)s')
2723 args['epi'] = episode_id
2724
2725 cmd = """
2726 SELECT *
2727 FROM clin.v_pat_encounters
2728 WHERE
2729 pk_patient = %%(pat)s
2730 AND
2731 pk_encounter IN (
2732 SELECT distinct pk_encounter
2733 FROM clin.v_narrative
2734 WHERE
2735 %s
2736 )
2737 ORDER BY started DESC
2738 LIMIT 2
2739 """ % ' AND '.join(where_parts)
2740
2741 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2742
2743 if len(rows) == 0:
2744 return None
2745
2746
2747 if len(rows) == 1:
2748
2749 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2750
2751 return None
2752
2753 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2754
2755
2756 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2757 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2758
2759 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2760
2761 last_but_one_encounter = property(get_last_but_one_encounter, lambda x:x)
2762
2763
2765 _log.debug('removing empty encounters for pk_identity [%s]', self.pk_patient)
2766 cfg_db = gmCfg.cCfgSQL()
2767 ttl = cfg_db.get2 (
2768 option = 'encounter.ttl_if_empty',
2769 workplace = _here.active_workplace,
2770 bias = 'user',
2771 default = '1 week'
2772 )
2773
2774 cmd = "SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
2775 args = {'pat': self.pk_patient, 'ttl': ttl}
2776 try:
2777 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
2778 except Exception:
2779 _log.exception('error deleting empty encounters')
2780 return False
2781
2782 if not rows[0][0]:
2783 _log.debug('no encounters deleted (less than 2 exist)')
2784
2785 return True
2786
2787
2788
2789
2795
2796
2804
2805
2812
2813
2819
2820
2829
2830
2837
2838
2844
2845
2848
2849
2851 if order_by is None:
2852 order_by = ''
2853 else:
2854 order_by = 'ORDER BY %s' % order_by
2855 cmd = """
2856 SELECT * FROM clin.v_test_results
2857 WHERE
2858 pk_patient = %%(pat)s
2859 AND
2860 reviewed IS FALSE
2861 %s""" % order_by
2862 args = {'pat': self.pk_patient}
2863 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2864 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2865
2866
2867
2869 """Retrieve data about test types for which this patient has results."""
2870 if order_by is None:
2871 order_by = ''
2872 else:
2873 order_by = 'ORDER BY %s' % order_by
2874
2875 if unique_meta_types:
2876 cmd = """
2877 SELECT * FROM clin.v_test_types c_vtt
2878 WHERE c_vtt.pk_test_type IN (
2879 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
2880 FROM clin.v_test_results c_vtr1
2881 WHERE
2882 c_vtr1.pk_patient = %%(pat)s
2883 AND
2884 c_vtr1.pk_meta_test_type IS NOT NULL
2885 UNION ALL
2886 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
2887 FROM clin.v_test_results c_vtr2
2888 WHERE
2889 c_vtr2.pk_patient = %%(pat)s
2890 AND
2891 c_vtr2.pk_meta_test_type IS NULL
2892 )
2893 %s""" % order_by
2894 else:
2895 cmd = """
2896 SELECT * FROM clin.v_test_types c_vtt
2897 WHERE c_vtt.pk_test_type IN (
2898 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
2899 FROM clin.v_test_results c_vtr
2900 WHERE c_vtr.pk_patient = %%(pat)s
2901 )
2902 %s""" % order_by
2903
2904 args = {'pat': self.pk_patient}
2905 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2906 return [ gmPathLab.cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
2907
2908
2910 """Get the dates for which we have results."""
2911 where_parts = ['pk_patient = %(pat)s']
2912 args = {'pat': self.pk_patient}
2913
2914 if tests is not None:
2915 where_parts.append('pk_test_type IN %(tests)s')
2916 args['tests'] = tuple(tests)
2917
2918 cmd = """
2919 SELECT DISTINCT ON (clin_when_day)
2920 clin_when_day,
2921 is_reviewed
2922 FROM (
2923 SELECT
2924 date_trunc('day', clin_when)
2925 AS clin_when_day,
2926 bool_and(reviewed)
2927 AS is_reviewed
2928 FROM (
2929 SELECT
2930 clin_when,
2931 reviewed,
2932 pk_patient,
2933 pk_test_result
2934 FROM clin.v_test_results
2935 WHERE %s
2936 )
2937 AS patient_tests
2938 GROUP BY clin_when_day
2939 )
2940 AS grouped_days
2941 ORDER BY clin_when_day %s
2942 """ % (
2943 ' AND '.join(where_parts),
2944 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
2945 )
2946 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2947 return rows
2948
2949
2951 """Get the issues/episodes for which we have results."""
2952 where_parts = ['pk_patient = %(pat)s']
2953 args = {'pat': self.pk_patient}
2954
2955 if tests is not None:
2956 where_parts.append('pk_test_type IN %(tests)s')
2957 args['tests'] = tuple(tests)
2958 where = ' AND '.join(where_parts)
2959 cmd = """
2960 SELECT * FROM ((
2961 -- issues, each including all it"s episodes
2962 SELECT
2963 health_issue AS problem,
2964 pk_health_issue,
2965 NULL::integer AS pk_episode,
2966 1 AS rank
2967 FROM clin.v_test_results
2968 WHERE pk_health_issue IS NOT NULL AND %s
2969 GROUP BY pk_health_issue, problem
2970 ) UNION ALL (
2971 -- episodes w/o issue
2972 SELECT
2973 episode AS problem,
2974 NULL::integer AS pk_health_issue,
2975 pk_episode,
2976 2 AS rank
2977 FROM clin.v_test_results
2978 WHERE pk_health_issue IS NULL AND %s
2979 GROUP BY pk_episode, problem
2980 )) AS grouped_union
2981 ORDER BY rank, problem
2982 """ % (where, where)
2983 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2984 return rows
2985
2986
2987 - def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
2994
2996
2997 where_parts = ['pk_patient = %(pat)s']
2998 args = {'pat': self.pk_patient}
2999
3000 if tests is not None:
3001 where_parts.append('pk_test_type IN %(tests)s')
3002 args['tests'] = tuple(tests)
3003
3004 if encounter is not None:
3005 where_parts.append('pk_encounter = %(enc)s')
3006 args['enc'] = encounter
3007
3008 if episodes is not None:
3009 where_parts.append('pk_episode IN %(epis)s')
3010 args['epis'] = tuple(episodes)
3011
3012 cmd = """
3013 SELECT * FROM clin.v_test_results
3014 WHERE %s
3015 ORDER BY clin_when %s, pk_episode, unified_name
3016 """ % (
3017 ' AND '.join(where_parts),
3018 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
3019 )
3020 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3021
3022 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
3023
3024 return tests
3025
3026 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
3027
3028 try:
3029 epi = int(episode)
3030 except Exception:
3031 epi = episode['pk_episode']
3032
3033 try:
3034 type = int(type)
3035 except Exception:
3036 type = type['pk_test_type']
3037
3038 tr = gmPathLab.create_test_result (
3039 link_obj = link_obj,
3040 encounter = self.current_encounter['pk_encounter'],
3041 episode = epi,
3042 type = type,
3043 intended_reviewer = intended_reviewer,
3044 val_num = val_num,
3045 val_alpha = val_alpha,
3046 unit = unit
3047 )
3048
3049 return tr
3050
3051
3053 where = 'pk_org_unit IN (%s)' % """
3054 SELECT DISTINCT fk_org_unit FROM clin.test_org WHERE pk IN (
3055 SELECT DISTINCT pk_test_org FROM clin.v_test_results where pk_patient = %(pat)s
3056 )"""
3057 args = {'pat': self.pk_patient}
3058 cmd = gmOrganization._SQL_get_org_unit % where
3059 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3060 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
3061
3062
3064 measured_gfrs = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
3065 measured_gfr = measured_gfrs[0] if len(measured_gfrs) > 0 else None
3066 creas = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
3067 crea = creas[0] if len(creas) > 0 else None
3068
3069 if (measured_gfr is None) and (crea is None):
3070 return None
3071
3072 if (measured_gfr is not None) and (crea is None):
3073 return measured_gfr
3074
3075
3076 if measured_gfr is None:
3077 eGFR = self.calculator.eGFR
3078 if eGFR.numeric_value is None:
3079 return crea
3080 return eGFR
3081
3082
3083 two_weeks = pydt.timedelta(weeks = 2)
3084 gfr_too_old = (crea['clin_when'] - measured_gfr['clin_when']) > two_weeks
3085 if not gfr_too_old:
3086 return measured_gfr
3087
3088
3089
3090 eGFR = self.calculator.eGFR
3091 if eGFR.numeric_value is None:
3092
3093
3094 return crea
3095
3096 return eGFR
3097
3098 best_gfr_or_crea = property(_get_best_gfr_or_crea, lambda x:x)
3099
3100
3103
3104 bmi = property(_get_bmi, lambda x:x)
3105
3106
3109
3110 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
3111
3112
3113
3114
3119
3120 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
3134
3135
3136
3137
3138 if __name__ == "__main__":
3139
3140 if len(sys.argv) == 1:
3141 sys.exit()
3142
3143 if sys.argv[1] != 'test':
3144 sys.exit()
3145
3146 from Gnumed.pycommon import gmLog2
3147
3148 from Gnumed.business import gmPraxis
3149 branches = gmPraxis.get_praxis_branches()
3150 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
3151
3153 print(args)
3154 print(kwargs)
3155 args[0](*args[1:], **kwargs)
3156
3157 set_delayed_executor(_do_delayed)
3158
3159
3173
3174
3179
3180
3181
3182
3189
3190
3192 emr = cClinicalRecord(aPKey=12)
3193 rows, idx = emr.get_measurements_by_date()
3194 print("test results:")
3195 for row in rows:
3196 print(row)
3197
3198
3205
3206
3211
3212
3214 emr = cClinicalRecord(aPKey=12)
3215
3216 probs = emr.get_problems()
3217 print("normal probs (%s):" % len(probs))
3218 for p in probs:
3219 print('%s (%s)' % (p['problem'], p['type']))
3220
3221 probs = emr.get_problems(include_closed_episodes=True)
3222 print("probs + closed episodes (%s):" % len(probs))
3223 for p in probs:
3224 print('%s (%s)' % (p['problem'], p['type']))
3225
3226 probs = emr.get_problems(include_irrelevant_issues=True)
3227 print("probs + issues (%s):" % len(probs))
3228 for p in probs:
3229 print('%s (%s)' % (p['problem'], p['type']))
3230
3231 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
3232 print("probs + issues + epis (%s):" % len(probs))
3233 for p in probs:
3234 print('%s (%s)' % (p['problem'], p['type']))
3235
3236
3238 emr = cClinicalRecord(aPKey=12)
3239 tr = emr.add_test_result (
3240 episode = 1,
3241 intended_reviewer = 1,
3242 type = 1,
3243 val_num = 75,
3244 val_alpha = 'somewhat obese',
3245 unit = 'kg'
3246 )
3247 print(tr)
3248
3249
3253
3254
3259
3260
3265
3266
3271
3272
3277
3278
3283
3284
3289
3290
3294
3295
3297 emr = cClinicalRecord(aPKey = 12)
3298 for journal_line in emr.get_as_journal():
3299
3300 print('%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line)
3301 print("")
3302
3303
3307
3308
3313
3314
3320
3321
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359 emr = cClinicalRecord(aPKey = 12)
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378 v1 = emr.vaccinations
3379 print(v1)
3380 v2 = gmVaccination.get_vaccinations(pk_identity = 12, return_pks = True)
3381 print(v2)
3382 for v in v1:
3383 if v['pk_vaccination'] not in v2:
3384 print('ERROR')
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