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 from clin.v_narrative4search vn4s
933 WHERE
934 pk_patient = %(pat)s and
935 vn4s.narrative ~ %(term)s
936 order by
937 encounter_started
938 """
939 rows, idx = gmPG2.run_ro_queries(queries = [
940 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
941 ])
942 return rows
943
944 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
945 fields = [
946 'age',
947 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
948 'modified_by',
949 'clin_when',
950 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
951 'pk_item',
952 'pk_encounter',
953 'pk_episode',
954 'pk_health_issue',
955 'src_table'
956 ]
957 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
958
959 where_snippets = []
960 params = {}
961 where_snippets.append('pk_patient=%(pat_id)s')
962 params['pat_id'] = self.pk_patient
963 if not since is None:
964 where_snippets.append('clin_when >= %(since)s')
965 params['since'] = since
966 if not until is None:
967 where_snippets.append('clin_when <= %(until)s')
968 params['until'] = until
969
970
971
972 if not encounters is None and len(encounters) > 0:
973 params['enc'] = encounters
974 if len(encounters) > 1:
975 where_snippets.append('fk_encounter in %(enc)s')
976 else:
977 where_snippets.append('fk_encounter=%(enc)s')
978
979 if not episodes is None and len(episodes) > 0:
980 params['epi'] = episodes
981 if len(episodes) > 1:
982 where_snippets.append('fk_episode in %(epi)s')
983 else:
984 where_snippets.append('fk_episode=%(epi)s')
985
986 if not issues is None and len(issues) > 0:
987 params['issue'] = issues
988 if len(issues) > 1:
989 where_snippets.append('fk_health_issue in %(issue)s')
990 else:
991 where_snippets.append('fk_health_issue=%(issue)s')
992
993 where_clause = ' and '.join(where_snippets)
994 order_by = 'order by src_table, age'
995 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
996
997 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
998 if rows is None:
999 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
1000 return None
1001
1002
1003
1004
1005 items_by_table = {}
1006 for item in rows:
1007 src_table = item[view_col_idx['src_table']]
1008 pk_item = item[view_col_idx['pk_item']]
1009 if src_table not in items_by_table:
1010 items_by_table[src_table] = {}
1011 items_by_table[src_table][pk_item] = item
1012
1013
1014 issues = self.get_health_issues()
1015 issue_map = {}
1016 for issue in issues:
1017 issue_map[issue['pk_health_issue']] = issue['description']
1018 episodes = self.get_episodes()
1019 episode_map = {}
1020 for episode in episodes:
1021 episode_map[episode['pk_episode']] = episode['description']
1022 emr_data = {}
1023
1024 ro_conn = self._conn_pool.GetConnection('historica')
1025 curs = ro_conn.cursor()
1026 for src_table in items_by_table.keys():
1027 item_ids = items_by_table[src_table].keys()
1028
1029
1030 if len(item_ids) == 0:
1031 _log.info('no items in table [%s] ?!?' % src_table)
1032 continue
1033 elif len(item_ids) == 1:
1034 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
1035 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
1036 _log.error('cannot load items from table [%s]' % src_table)
1037
1038 continue
1039 elif len(item_ids) > 1:
1040 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
1041 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
1042 _log.error('cannot load items from table [%s]' % src_table)
1043
1044 continue
1045 rows = curs.fetchall()
1046 table_col_idx = gmPG.get_col_indices(curs)
1047
1048 for row in rows:
1049
1050 pk_item = row[table_col_idx['pk_item']]
1051 view_row = items_by_table[src_table][pk_item]
1052 age = view_row[view_col_idx['age']]
1053
1054 try:
1055 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
1056 except:
1057 episode_name = view_row[view_col_idx['pk_episode']]
1058 try:
1059 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
1060 except:
1061 issue_name = view_row[view_col_idx['pk_health_issue']]
1062
1063 if age not in emr_data:
1064 emr_data[age] = []
1065
1066 emr_data[age].append(
1067 _('%s: encounter (%s)') % (
1068 view_row[view_col_idx['clin_when']],
1069 view_row[view_col_idx['pk_encounter']]
1070 )
1071 )
1072 emr_data[age].append(_('health issue: %s') % issue_name)
1073 emr_data[age].append(_('episode : %s') % episode_name)
1074
1075
1076
1077 cols2ignore = [
1078 'pk_audit', 'row_version', 'modified_when', 'modified_by',
1079 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
1080 ]
1081 col_data = []
1082 for col_name in table_col_idx.keys():
1083 if col_name in cols2ignore:
1084 continue
1085 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
1086 emr_data[age].append("----------------------------------------------------")
1087 emr_data[age].append("-- %s from table %s" % (
1088 view_row[view_col_idx['modified_string']],
1089 src_table
1090 ))
1091 emr_data[age].append("-- written %s by %s" % (
1092 view_row[view_col_idx['modified_when']],
1093 view_row[view_col_idx['modified_by']]
1094 ))
1095 emr_data[age].append("----------------------------------------------------")
1096 curs.close()
1097 return emr_data
1098
1100 return self.pk_patient
1101
1103 union_query = '\n union all\n'.join ([
1104 """
1105 SELECT ((
1106 -- all relevant health issues + active episodes WITH health issue
1107 SELECT COUNT(1)
1108 FROM clin.v_problem_list
1109 WHERE
1110 pk_patient = %(pat)s
1111 AND
1112 pk_health_issue is not null
1113 ) + (
1114 -- active episodes WITHOUT health issue
1115 SELECT COUNT(1)
1116 FROM clin.v_problem_list
1117 WHERE
1118 pk_patient = %(pat)s
1119 AND
1120 pk_health_issue is null
1121 ))""",
1122 'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
1123 'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
1124 'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
1125 'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
1126 'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
1127 'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
1128
1129 """
1130 SELECT count(1)
1131 FROM clin.v_substance_intakes
1132 WHERE
1133 pk_patient = %(pat)s
1134 AND
1135 is_currently_active IN (null, true)
1136 AND
1137 intake_is_approved_of IN (null, true)""",
1138 'SELECT count(1) FROM clin.v_vaccinations WHERE pk_patient = %(pat)s'
1139 ])
1140
1141 rows, idx = gmPG2.run_ro_queries (
1142 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
1143 get_col_idx = False
1144 )
1145
1146 stats = dict (
1147 problems = rows[0][0],
1148 encounters = rows[1][0],
1149 items = rows[2][0],
1150 documents = rows[3][0],
1151 results = rows[4][0],
1152 stays = rows[5][0],
1153 procedures = rows[6][0],
1154 active_drugs = rows[7][0],
1155 vaccinations = rows[8][0]
1156 )
1157
1158 return stats
1159
1172
1305
1306
1327
1328
1329 - 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):
1330 return gmClinNarrative.get_as_journal (
1331 patient = self.pk_patient,
1332 since = since,
1333 until = until,
1334 encounters = encounters,
1335 episodes = episodes,
1336 issues = issues,
1337 soap_cats = soap_cats,
1338 providers = providers,
1339 order_by = order_by,
1340 time_range = time_range,
1341 active_encounter = self.active_encounter
1342 )
1343
1344
1345 - def get_generic_emr_items(self, pk_encounters=None, pk_episodes=None, pk_health_issues=None, use_active_encounter=False, order_by=None):
1358
1359
1360
1361
1362 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
1363 """Retrieves patient allergy items.
1364
1365 remove_sensitivities
1366 - retrieve real allergies only, without sensitivities
1367 since
1368 - initial date for allergy items
1369 until
1370 - final date for allergy items
1371 encounters
1372 - list of encounters whose allergies are to be retrieved
1373 episodes
1374 - list of episodes whose allergies are to be retrieved
1375 issues
1376 - list of health issues whose allergies are to be retrieved
1377 """
1378 cmd = "SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
1379 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
1380 filtered_allergies = []
1381 for r in rows:
1382 filtered_allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
1383
1384
1385 if ID_list is not None:
1386 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_allergy'] in ID_list ]
1387 if len(filtered_allergies) == 0:
1388 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
1389
1390 return None
1391 else:
1392 return filtered_allergies
1393
1394 if remove_sensitivities:
1395 filtered_allergies = [ allg for allg in filtered_allergies if allg['type'] == 'allergy' ]
1396 if since is not None:
1397 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] >= since ]
1398 if until is not None:
1399 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] < until ]
1400 if issues is not None:
1401 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_health_issue'] in issues ]
1402 if episodes is not None:
1403 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_episode'] in episodes ]
1404 if encounters is not None:
1405 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_encounter'] in encounters ]
1406
1407 return filtered_allergies
1408
1409 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
1410 if encounter_id is None:
1411 encounter_id = self.current_encounter['pk_encounter']
1412
1413 if episode_id is None:
1414 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
1415 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
1416 episode_id = epi['pk_episode']
1417
1418 new_allergy = gmAllergy.create_allergy (
1419 allergene = allergene,
1420 allg_type = allg_type,
1421 encounter_id = encounter_id,
1422 episode_id = episode_id
1423 )
1424
1425 return new_allergy
1426
1428 cmd = 'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1429 args = {'pk_allg': pk_allergy}
1430 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1431
1432
1434 """Cave: only use with one potential allergic agent
1435 otherwise you won't know which of the agents the allergy is to."""
1436
1437
1438 if self.allergy_state is None:
1439 return None
1440
1441
1442 if self.allergy_state == 0:
1443 return False
1444
1445 args = {
1446 'atcs': atcs,
1447 'inns': inns,
1448 'prod_name': product_name,
1449 'pat': self.pk_patient
1450 }
1451 allergenes = []
1452 where_parts = []
1453
1454 if len(atcs) == 0:
1455 atcs = None
1456 if atcs is not None:
1457 where_parts.append('atc_code in %(atcs)s')
1458 if len(inns) == 0:
1459 inns = None
1460 if inns is not None:
1461 where_parts.append('generics in %(inns)s')
1462 allergenes.extend(inns)
1463 if product_name is not None:
1464 where_parts.append('substance = %(prod_name)s')
1465 allergenes.append(product_name)
1466
1467 if len(allergenes) != 0:
1468 where_parts.append('allergene in %(allgs)s')
1469 args['allgs'] = tuple(allergenes)
1470
1471 cmd = """
1472 SELECT * FROM clin.v_pat_allergies
1473 WHERE
1474 pk_patient = %%(pat)s
1475 AND ( %s )""" % ' OR '.join(where_parts)
1476
1477 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1478
1479 if len(rows) == 0:
1480 return False
1481
1482 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1483
1493
1496
1497 allergy_state = property(_get_allergy_state, _set_allergy_state)
1498
1499
1500
1507
1508 external_care_items = property(get_external_care_items, lambda x:x)
1509
1510
1511
1512
1513 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1514 """Fetches from backend patient episodes.
1515
1516 id_list - Episodes' PKs list
1517 issues - Health issues' PKs list to filter episodes by
1518 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1519 """
1520 if (unlinked_only is True) and (issues is not None):
1521 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1522
1523 if order_by is None:
1524 order_by = ''
1525 else:
1526 order_by = 'ORDER BY %s' % order_by
1527
1528 args = {
1529 'pat': self.pk_patient,
1530 'open': open_status
1531 }
1532 where_parts = ['pk_patient = %(pat)s']
1533
1534 if open_status is not None:
1535 where_parts.append('episode_open IS %(open)s')
1536
1537 if unlinked_only:
1538 where_parts.append('pk_health_issue is NULL')
1539
1540 if issues is not None:
1541 where_parts.append('pk_health_issue IN %(issues)s')
1542 args['issues'] = tuple(issues)
1543
1544 if id_list is not None:
1545 where_parts.append('pk_episode IN %(epis)s')
1546 args['epis'] = tuple(id_list)
1547
1548 cmd = "SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1549 ' AND '.join(where_parts),
1550 order_by
1551 )
1552 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1553
1554 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1555
1556 episodes = property(get_episodes, lambda x:x)
1557
1559 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1560
1561 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1562
1564 cmd = """SELECT distinct pk_episode
1565 from clin.v_pat_items
1566 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1567 args = {
1568 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1569 'pat': self.pk_patient
1570 }
1571 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1572 if len(rows) == 0:
1573 return []
1574 epis = []
1575 for row in rows:
1576 epis.append(row[0])
1577 return self.get_episodes(id_list=epis)
1578
1579 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False, allow_dupes=False, link_obj=None):
1580 """Add episode 'episode_name' for a patient's health issue.
1581
1582 - silently returns if episode already exists
1583 """
1584 episode = gmEMRStructItems.create_episode (
1585 link_obj = link_obj,
1586 pk_health_issue = pk_health_issue,
1587 episode_name = episode_name,
1588 is_open = is_open,
1589 encounter = self.current_encounter['pk_encounter'],
1590 allow_dupes = allow_dupes
1591 )
1592 return episode
1593
1595
1596 issue_where = gmTools.coalesce (
1597 value2test = issue,
1598 return_instead = '',
1599 value2return = 'and pk_health_issue = %(issue)s'
1600 )
1601 cmd = """
1602 SELECT pk
1603 from clin.episode
1604 WHERE pk = (
1605 SELECT distinct on(pk_episode) pk_episode
1606 from clin.v_pat_items
1607 WHERE
1608 pk_patient = %%(pat)s
1609 and
1610 modified_when = (
1611 SELECT max(vpi.modified_when)
1612 from clin.v_pat_items vpi
1613 WHERE vpi.pk_patient = %%(pat)s
1614 )
1615 %s
1616 -- guard against several episodes created at the same moment of time
1617 limit 1
1618 )""" % issue_where
1619 rows, idx = gmPG2.run_ro_queries(queries = [
1620 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1621 ])
1622 if len(rows) != 0:
1623 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1624
1625
1626
1627 cmd = """
1628 SELECT vpe0.pk_episode
1629 from
1630 clin.v_pat_episodes vpe0
1631 WHERE
1632 vpe0.pk_patient = %%(pat)s
1633 and
1634 vpe0.episode_modified_when = (
1635 SELECT max(vpe1.episode_modified_when)
1636 from clin.v_pat_episodes vpe1
1637 WHERE vpe1.pk_episode = vpe0.pk_episode
1638 )
1639 %s""" % issue_where
1640 rows, idx = gmPG2.run_ro_queries(queries = [
1641 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1642 ])
1643 if len(rows) != 0:
1644 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1645
1646 return None
1647
1650
1651
1652
1653 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1654 """Retrieve a patient's problems.
1655
1656 "Problems" are the UNION of:
1657
1658 - issues which are .clinically_relevant
1659 - episodes which are .is_open
1660
1661 Therefore, both an issue and the open episode
1662 thereof can each be listed as a problem.
1663
1664 include_closed_episodes/include_irrelevant_issues will
1665 include those -- which departs from the definition of
1666 the problem list being "active" items only ...
1667
1668 episodes - episodes' PKs to filter problems by
1669 issues - health issues' PKs to filter problems by
1670 """
1671
1672
1673 args = {'pat': self.pk_patient}
1674
1675 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1676 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1677
1678
1679 problems = []
1680 for row in rows:
1681 pk_args = {
1682 'pk_patient': self.pk_patient,
1683 'pk_health_issue': row['pk_health_issue'],
1684 'pk_episode': row['pk_episode']
1685 }
1686 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1687
1688
1689 other_rows = []
1690 if include_closed_episodes:
1691 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1692 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1693 other_rows.extend(rows)
1694
1695 if include_irrelevant_issues:
1696 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1697 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1698 other_rows.extend(rows)
1699
1700 if len(other_rows) > 0:
1701 for row in other_rows:
1702 pk_args = {
1703 'pk_patient': self.pk_patient,
1704 'pk_health_issue': row['pk_health_issue'],
1705 'pk_episode': row['pk_episode']
1706 }
1707 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1708
1709
1710 if issues is not None:
1711 problems = [ p for p in problems if p['pk_health_issue'] in issues ]
1712 if episodes is not None:
1713 problems = [ p for p in problems if p['pk_episode'] in episodes ]
1714
1715 return problems
1716
1717
1720
1721
1724
1725
1728
1729
1731 cmd = "SELECT * FROM clin.v_candidate_diagnoses WHERE pk_patient = %(pat)s"
1732 rows, idx = gmPG2.run_ro_queries (
1733 queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}],
1734 get_col_idx = False
1735 )
1736 return rows
1737
1738 candidate_diagnoses = property(get_candidate_diagnoses)
1739
1740
1741
1742
1744
1745 cmd = "SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1746 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1747 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1748
1749 if id_list is None:
1750 return issues
1751
1752 if len(id_list) == 0:
1753 raise ValueError('id_list to filter by is empty, most likely a programming error')
1754
1755 filtered_issues = []
1756 for issue in issues:
1757 if issue['pk_health_issue'] in id_list:
1758 filtered_issues.append(issue)
1759
1760 return filtered_issues
1761
1762 health_issues = property(get_health_issues, lambda x:x)
1763
1764
1772
1775
1776
1777
1778 - def get_current_medications(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1779 return self._get_current_substance_intakes (
1780 include_inactive = include_inactive,
1781 include_unapproved = include_unapproved,
1782 order_by = order_by,
1783 episodes = episodes,
1784 issues = issues,
1785 exclude_medications = False,
1786 exclude_potential_abuses = True
1787 )
1788
1789
1791 return self._get_current_substance_intakes (
1792 include_inactive = True,
1793 include_unapproved = True,
1794 order_by = order_by,
1795 episodes = None,
1796 issues = None,
1797 exclude_medications = True,
1798 exclude_potential_abuses = False
1799 )
1800
1801 abused_substances = property(_get_abused_substances, lambda x:x)
1802
1803
1804 - 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):
1805
1806 where_parts = ['pk_patient = %(pat)s']
1807 args = {'pat': self.pk_patient}
1808
1809 if not include_inactive:
1810 where_parts.append('is_currently_active IN (TRUE, NULL)')
1811
1812 if not include_unapproved:
1813 where_parts.append('intake_is_approved_of IN (TRUE, NULL)')
1814
1815 if exclude_potential_abuses:
1816 where_parts.append('harmful_use_type IS NULL')
1817
1818 if exclude_medications:
1819 where_parts.append('harmful_use_type IS NOT NULL')
1820
1821 if order_by is None:
1822 order_by = ''
1823 else:
1824 order_by = 'ORDER BY %s' % order_by
1825
1826 cmd = "SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1827 '\nAND '.join(where_parts),
1828 order_by
1829 )
1830 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1831 intakes = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1832
1833 if episodes is not None:
1834 intakes = [ i for i in intakes if i['pk_episode'] in episodes ]
1835
1836 if issues is not None:
1837 intakes = [ i for i in intakes if i ['pk_health_issue'] in issues ]
1838
1839 return intakes
1840
1841
1842 - def add_substance_intake(self, pk_component=None, pk_episode=None, pk_drug_product=None, pk_health_issue=None):
1855
1856
1858 return gmMedication.substance_intake_exists (
1859 pk_component = pk_component,
1860 pk_substance = pk_substance,
1861 pk_identity = self.pk_patient,
1862 pk_drug_product = pk_drug_product
1863 )
1864
1865
1866
1867
1875
1876
1878 """Returns latest given vaccination for each vaccinated indication.
1879
1880 as a dict {'l10n_indication': cVaccination instance}
1881
1882 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1883 """
1884 args = {'pat': self.pk_patient}
1885 where_parts = ['c_v_shots.pk_patient = %(pat)s']
1886
1887 if (episodes is not None) and (len(episodes) > 0):
1888 where_parts.append('c_v_shots.pk_episode IN %(epis)s')
1889 args['epis'] = tuple(episodes)
1890
1891 if (issues is not None) and (len(issues) > 0):
1892 where_parts.append('c_v_shots.pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1893 args['issues'] = tuple(issues)
1894
1895 if (atc_indications is not None) and (len(atc_indications) > 0):
1896 where_parts.append('c_v_plv4i.atc_indication IN %(atc_inds)s')
1897 args['atc_inds'] = tuple(atc_indications)
1898
1899
1900 cmd = """
1901 SELECT
1902 c_v_shots.*,
1903 c_v_plv4i.l10n_indication,
1904 c_v_plv4i.no_of_shots
1905 FROM
1906 clin.v_vaccinations c_v_shots
1907 JOIN clin.v_pat_last_vacc4indication c_v_plv4i ON (c_v_shots.pk_vaccination = c_v_plv4i.pk_vaccination)
1908 WHERE %s
1909 """ % '\nAND '.join(where_parts)
1910 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1911
1912
1913 if len(rows) == 0:
1914 return {}
1915
1916
1917
1918 vaccs = {}
1919 for shot_row in rows:
1920 vaccs[shot_row['l10n_indication']] = (
1921 shot_row['no_of_shots'],
1922 gmVaccination.cVaccination(row = {'idx': idx, 'data': shot_row, 'pk_field': 'pk_vaccination'})
1923 )
1924
1925 return vaccs
1926
1927
1928 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1929 return gmVaccination.get_vaccinations (
1930 pk_identity = self.pk_patient,
1931 pk_episodes = episodes,
1932 pk_health_issues = issues,
1933 pk_encounters = encounters,
1934 order_by = order_by,
1935 return_pks = False
1936 )
1937
1938 vaccinations = property(get_vaccinations, lambda x:x)
1939
1940
1941
1942
1944 """Retrieves vaccination regimes the patient is on.
1945
1946 optional:
1947 * ID - PK of the vaccination regime
1948 * indications - indications we want to retrieve vaccination
1949 regimes for, must be primary language, not l10n_indication
1950 """
1951
1952
1953 cmd = """SELECT distinct on(pk_course) pk_course
1954 FROM clin.v_vaccs_scheduled4pat
1955 WHERE pk_patient=%s"""
1956 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1957 if rows is None:
1958 _log.error('cannot retrieve scheduled vaccination courses')
1959 return None
1960
1961 for row in rows:
1962 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1963
1964
1965 filtered_regimes = []
1966 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1967 if ID is not None:
1968 filtered_regimes = [ r for r in filtered_regimes if r['pk_course'] == ID ]
1969 if len(filtered_regimes) == 0:
1970 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1971 return []
1972 else:
1973 return filtered_regimes[0]
1974 if indications is not None:
1975 filtered_regimes = [ r for r in filtered_regimes if r['indication'] in indications ]
1976
1977 return filtered_regimes
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2006 """Retrieves list of vaccinations the patient has received.
2007
2008 optional:
2009 * ID - PK of a vaccination
2010 * indications - indications we want to retrieve vaccination
2011 items for, must be primary language, not l10n_indication
2012 * since - initial date for allergy items
2013 * until - final date for allergy items
2014 * encounters - list of encounters whose allergies are to be retrieved
2015 * episodes - list of episodes whose allergies are to be retrieved
2016 * issues - list of health issues whose allergies are to be retrieved
2017 """
2018 try:
2019 self.__db_cache['vaccinations']['vaccinated']
2020 except KeyError:
2021 self.__db_cache['vaccinations']['vaccinated'] = []
2022
2023 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
2024 WHERE pk_patient=%s
2025 order by indication, date"""
2026 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2027 if rows is None:
2028 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
2029 del self.__db_cache['vaccinations']['vaccinated']
2030 return None
2031
2032 vaccs_by_ind = {}
2033 for row in rows:
2034 vacc_row = {
2035 'pk_field': 'pk_vaccination',
2036 'idx': idx,
2037 'data': row
2038 }
2039 vacc = gmVaccination.cVaccination(row=vacc_row)
2040 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
2041
2042 try:
2043 vaccs_by_ind[vacc['indication']].append(vacc)
2044 except KeyError:
2045 vaccs_by_ind[vacc['indication']] = [vacc]
2046
2047
2048 for ind in vaccs_by_ind.keys():
2049 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
2050 for vacc in vaccs_by_ind[ind]:
2051
2052
2053 seq_no = vaccs_by_ind[ind].index(vacc) + 1
2054 vacc['seq_no'] = seq_no
2055
2056
2057 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
2058 continue
2059 if seq_no > vacc_regimes[0]['shots']:
2060 vacc['is_booster'] = True
2061 del vaccs_by_ind
2062
2063
2064 filtered_shots = []
2065 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
2066 if ID is not None:
2067 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
2068 if len(filtered_shots) == 0:
2069 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
2070 return None
2071 else:
2072 return filtered_shots[0]
2073 if since is not None:
2074 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
2075 if until is not None:
2076 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
2077 if issues is not None:
2078 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
2079 if episodes is not None:
2080 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
2081 if encounters is not None:
2082 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
2083 if indications is not None:
2084 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2085 return filtered_shots
2086
2088 """Retrieves vaccinations scheduled for a regime a patient is on.
2089
2090 The regime is referenced by its indication (not l10n)
2091
2092 * indications - List of indications (not l10n) of regimes we want scheduled
2093 vaccinations to be fetched for
2094 """
2095 try:
2096 self.__db_cache['vaccinations']['scheduled']
2097 except KeyError:
2098 self.__db_cache['vaccinations']['scheduled'] = []
2099 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
2100 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2101 if rows is None:
2102 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
2103 del self.__db_cache['vaccinations']['scheduled']
2104 return None
2105
2106 for row in rows:
2107 vacc_row = {
2108 'pk_field': 'pk_vacc_def',
2109 'idx': idx,
2110 'data': row
2111 }
2112 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
2113
2114
2115 if indications is None:
2116 return self.__db_cache['vaccinations']['scheduled']
2117 filtered_shots = []
2118 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
2119 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2120 return filtered_shots
2121
2123 try:
2124 self.__db_cache['vaccinations']['missing']
2125 except KeyError:
2126 self.__db_cache['vaccinations']['missing'] = {}
2127
2128 self.__db_cache['vaccinations']['missing']['due'] = []
2129
2130 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
2131 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2132 if rows is None:
2133 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
2134 return None
2135 pk_args = {'pat_id': self.pk_patient}
2136 if rows is not None:
2137 for row in rows:
2138 pk_args['indication'] = row[0]
2139 pk_args['seq_no'] = row[1]
2140 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
2141
2142
2143 self.__db_cache['vaccinations']['missing']['boosters'] = []
2144
2145 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
2146 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2147 if rows is None:
2148 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
2149 return None
2150 pk_args = {'pat_id': self.pk_patient}
2151 if rows is not None:
2152 for row in rows:
2153 pk_args['indication'] = row[0]
2154 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
2155
2156
2157 if indications is None:
2158 return self.__db_cache['vaccinations']['missing']
2159 if len(indications) == 0:
2160 return self.__db_cache['vaccinations']['missing']
2161
2162 filtered_shots = {
2163 'due': [],
2164 'boosters': []
2165 }
2166 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
2167 if due_shot['indication'] in indications:
2168 filtered_shots['due'].append(due_shot)
2169 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
2170 if due_shot['indication'] in indications:
2171 filtered_shots['boosters'].append(due_shot)
2172 return filtered_shots
2173
2174
2175
2176
2178 return self.__encounter
2179
2181
2182 if self.__encounter is None:
2183 _log.debug('first setting of active encounter in this clinical record instance')
2184 encounter.lock(exclusive = False)
2185 self.__encounter = encounter
2186 gmDispatcher.send('current_encounter_switched')
2187 return True
2188
2189
2190 _log.debug('switching of active encounter')
2191
2192 if self.__encounter.is_modified():
2193 gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
2194 _log.error('current in client: %s', self.__encounter)
2195 raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
2196 self.__encounter['pk_encounter'],
2197 encounter['pk_encounter']
2198 ))
2199
2200 prev_enc = self.__encounter
2201 encounter.lock(exclusive = False)
2202 self.__encounter = encounter
2203 prev_enc.unlock(exclusive = False)
2204 gmDispatcher.send('current_encounter_switched')
2205
2206 return True
2207
2208 current_encounter = property(_get_current_encounter, _set_current_encounter)
2209 active_encounter = property(_get_current_encounter, _set_current_encounter)
2210
2211
2213 _log.debug('setting up active encounter for identity [%s]', self.pk_patient)
2214
2215
2216 _delayed_execute(self.log_access, action = 'pulling chart for identity [%s]' % self.pk_patient)
2217
2218
2219
2220 self.remove_empty_encounters()
2221
2222
2223 if self.__activate_very_recent_encounter():
2224 return
2225
2226 fairly_recent_enc = self.__get_fairly_recent_encounter()
2227
2228
2229 self.start_new_encounter()
2230
2231 if fairly_recent_enc is None:
2232 return
2233
2234
2235 gmDispatcher.send (
2236 signal = 'ask_for_encounter_continuation',
2237 new_encounter = self.__encounter,
2238 fairly_recent_encounter = fairly_recent_enc
2239 )
2240
2241
2243 """Try to attach to a "very recent" encounter if there is one.
2244
2245 returns:
2246 False: no "very recent" encounter
2247 True: success
2248 """
2249 cfg_db = gmCfg.cCfgSQL()
2250 min_ttl = cfg_db.get2 (
2251 option = 'encounter.minimum_ttl',
2252 workplace = _here.active_workplace,
2253 bias = 'user',
2254 default = '1 hour 30 minutes'
2255 )
2256 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2257 SELECT pk_encounter
2258 FROM clin.v_most_recent_encounters
2259 WHERE
2260 pk_patient = %s
2261 and
2262 last_affirmed > (now() - %s::interval)
2263 ORDER BY
2264 last_affirmed DESC
2265 LIMIT 1
2266 )"""
2267 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}], get_col_idx = True)
2268
2269
2270 if len(enc_rows) == 0:
2271 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
2272 return False
2273
2274 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0]['pk_encounter'])
2275
2276
2277 self.current_encounter = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2278 return True
2279
2280
2282 cfg_db = gmCfg.cCfgSQL()
2283 min_ttl = cfg_db.get2 (
2284 option = 'encounter.minimum_ttl',
2285 workplace = _here.active_workplace,
2286 bias = 'user',
2287 default = '1 hour 30 minutes'
2288 )
2289 max_ttl = cfg_db.get2 (
2290 option = 'encounter.maximum_ttl',
2291 workplace = _here.active_workplace,
2292 bias = 'user',
2293 default = '6 hours'
2294 )
2295
2296
2297 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2298 SELECT pk_encounter
2299 FROM clin.v_most_recent_encounters
2300 WHERE
2301 pk_patient=%s
2302 AND
2303 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2304 ORDER BY
2305 last_affirmed DESC
2306 LIMIT 1
2307 )"""
2308 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2309
2310
2311 if len(enc_rows) == 0:
2312 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2313 return None
2314
2315 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2316 return gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2317
2318
2319
2320
2321
2322
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
2472
2473
2474 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False, order_by=None, max_encounters=None):
2475 """Retrieves patient's encounters.
2476
2477 id_list - PKs of encounters to fetch
2478 since - initial date for encounter items, DateTime instance
2479 until - final date for encounter items, DateTime instance
2480 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
2481 issues - PKs of the health issues the encounters belong to (many-to-many relation)
2482 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
2483
2484 NOTE: if you specify *both* issues and episodes
2485 you will get the *aggregate* of all encounters even
2486 if the episodes all belong to the health issues listed.
2487 IOW, the issues broaden the episode list rather than
2488 the episode list narrowing the episodes-from-issues
2489 list.
2490 Rationale: If it was the other way round it would be
2491 redundant to specify the list of issues at all.
2492 """
2493
2494 if (issues is not None) and (len(issues) > 0):
2495
2496 cmd = "SELECT distinct pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue in %(issue_pks)s AND pk_patient = %(pat)s"
2497 args = {'issue_pks': tuple(issues), 'pat': self.pk_patient}
2498 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2499 epis4issues_pks = [ r['pk_episode'] for r in rows ]
2500 if episodes is None:
2501 episodes = []
2502 episodes.extend(epis4issues_pks)
2503
2504 if (episodes is not None) and (len(episodes) > 0):
2505
2506
2507
2508 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2509 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)"
2510 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2511 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2512 if id_list is None:
2513 id_list = []
2514 id_list.extend(encs4epis_pks)
2515
2516 where_parts = ['c_vpe.pk_patient = %(pat)s']
2517 args = {'pat': self.pk_patient}
2518
2519 if skip_empty:
2520 where_parts.append("""NOT (
2521 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
2522 AND
2523 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
2524 AND
2525 NOT EXISTS (
2526 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
2527 UNION ALL
2528 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
2529 ))""")
2530
2531 if since is not None:
2532 where_parts.append('c_vpe.started >= %(start)s')
2533 args['start'] = since
2534
2535 if until is not None:
2536 where_parts.append('c_vpe.last_affirmed <= %(end)s')
2537 args['end'] = since
2538
2539 if (id_list is not None) and (len(id_list) > 0):
2540 where_parts.append('c_vpe.pk_encounter IN %(enc_pks)s')
2541 args['enc_pks'] = tuple(id_list)
2542
2543 if order_by is None:
2544 order_by = 'c_vpe.started'
2545
2546 if max_encounters is None:
2547 limit = ''
2548 else:
2549 limit = 'LIMIT %s' % max_encounters
2550
2551 cmd = """
2552 SELECT * FROM clin.v_pat_encounters c_vpe
2553 WHERE
2554 %s
2555 ORDER BY %s %s
2556 """ % (
2557 ' AND '.join(where_parts),
2558 order_by,
2559 limit
2560 )
2561 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2562 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
2563
2564
2565 filtered_encounters = []
2566 filtered_encounters.extend(encounters)
2567
2568 if (episodes is not None) and (len(episodes) > 0):
2569
2570
2571
2572 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2573 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)"
2574 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2575 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2576 filtered_encounters = [ enc for enc in filtered_encounters if enc['pk_encounter'] in encs4epis_pks ]
2577
2578 return filtered_encounters
2579
2580
2582 """Retrieves first encounter for a particular issue and/or episode.
2583
2584 issue_id - First encounter associated health issue
2585 episode - First encounter associated episode
2586 """
2587 if issue_id is None:
2588 issues = None
2589 else:
2590 issues = [issue_id]
2591
2592 if episode_id is None:
2593 episodes = None
2594 else:
2595 episodes = [episode_id]
2596
2597 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started', max_encounters = 1)
2598 if len(encounters) == 0:
2599 return None
2600
2601 return encounters[0]
2602
2603 first_encounter = property(get_first_encounter, lambda x:x)
2604
2605
2607 args = {'pat': self.pk_patient}
2608 cmd = """
2609 SELECT MIN(earliest) FROM (
2610 (
2611 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
2612
2613 ) UNION ALL (
2614
2615 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
2616
2617 ) UNION ALL (
2618
2619 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2620
2621 ) UNION ALL (
2622
2623 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2624
2625 ) UNION ALL (
2626
2627 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2628
2629 ) UNION ALL (
2630
2631 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2632
2633 ) UNION ALL (
2634
2635 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2636
2637 )
2638 ) AS candidates"""
2639 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2640 return rows[0][0]
2641
2642 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2643
2644
2646 encounters = self.get_encounters(order_by = 'started DESC', max_encounters = 1)
2647 if len(encounters) == 0:
2648 return None
2649 return encounters[0]['last_affirmed']
2650
2651 most_recent_care_date = property(get_most_recent_care_date)
2652
2653
2655 """Retrieves last encounter for a concrete issue and/or episode
2656
2657 issue_id - Last encounter associated health issue
2658 episode_id - Last encounter associated episode
2659 """
2660 if issue_id is None:
2661 issues = None
2662 else:
2663 issues = [issue_id]
2664
2665 if episode_id is None:
2666 episodes = None
2667 else:
2668 episodes = [episode_id]
2669
2670 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started DESC', max_encounters = 1)
2671 if len(encounters) == 0:
2672 return None
2673
2674 return encounters[0]
2675
2676 last_encounter = property(get_last_encounter, lambda x:x)
2677
2678
2680 args = {'pat': self.pk_patient, 'range': cover_period}
2681 where_parts = ['pk_patient = %(pat)s']
2682 if cover_period is not None:
2683 where_parts.append('last_affirmed > now() - %(range)s')
2684
2685 cmd = """
2686 SELECT l10n_type, count(1) AS frequency
2687 FROM clin.v_pat_encounters
2688 WHERE
2689 %s
2690 GROUP BY l10n_type
2691 ORDER BY frequency DESC
2692 """ % ' AND '.join(where_parts)
2693 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2694 return rows
2695
2696
2698
2699 args = {'pat': self.pk_patient}
2700
2701 if (issue_id is None) and (episode_id is None):
2702 cmd = """
2703 SELECT * FROM clin.v_pat_encounters
2704 WHERE pk_patient = %(pat)s
2705 ORDER BY started DESC
2706 LIMIT 2
2707 """
2708 else:
2709 where_parts = []
2710
2711 if issue_id is not None:
2712 where_parts.append('pk_health_issue = %(issue)s')
2713 args['issue'] = issue_id
2714
2715 if episode_id is not None:
2716 where_parts.append('pk_episode = %(epi)s')
2717 args['epi'] = episode_id
2718
2719 cmd = """
2720 SELECT *
2721 FROM clin.v_pat_encounters
2722 WHERE
2723 pk_patient = %%(pat)s
2724 AND
2725 pk_encounter IN (
2726 SELECT distinct pk_encounter
2727 FROM clin.v_narrative
2728 WHERE
2729 %s
2730 )
2731 ORDER BY started DESC
2732 LIMIT 2
2733 """ % ' AND '.join(where_parts)
2734
2735 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2736
2737 if len(rows) == 0:
2738 return None
2739
2740
2741 if len(rows) == 1:
2742
2743 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2744
2745 return None
2746
2747 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2748
2749
2750 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2751 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2752
2753 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2754
2755 last_but_one_encounter = property(get_last_but_one_encounter, lambda x:x)
2756
2757
2759 _log.debug('removing empty encounters for pk_identity [%s]', self.pk_patient)
2760 cfg_db = gmCfg.cCfgSQL()
2761 ttl = cfg_db.get2 (
2762 option = 'encounter.ttl_if_empty',
2763 workplace = _here.active_workplace,
2764 bias = 'user',
2765 default = '1 week'
2766 )
2767
2768 cmd = "SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
2769 args = {'pat': self.pk_patient, 'ttl': ttl}
2770 try:
2771 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
2772 except:
2773 _log.exception('error deleting empty encounters')
2774 return False
2775
2776 if not rows[0][0]:
2777 _log.debug('no encounters deleted (less than 2 exist)')
2778
2779 return True
2780
2781
2782
2783
2789
2790
2798
2799
2806
2807
2813
2814
2823
2824
2831
2832
2838
2839
2842
2843
2845 if order_by is None:
2846 order_by = ''
2847 else:
2848 order_by = 'ORDER BY %s' % order_by
2849 cmd = """
2850 SELECT * FROM clin.v_test_results
2851 WHERE
2852 pk_patient = %%(pat)s
2853 AND
2854 reviewed IS FALSE
2855 %s""" % order_by
2856 args = {'pat': self.pk_patient}
2857 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2858 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2859
2860
2861
2863 """Retrieve data about test types for which this patient has results."""
2864 if order_by is None:
2865 order_by = ''
2866 else:
2867 order_by = 'ORDER BY %s' % order_by
2868
2869 if unique_meta_types:
2870 cmd = """
2871 SELECT * FROM clin.v_test_types c_vtt
2872 WHERE c_vtt.pk_test_type IN (
2873 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
2874 FROM clin.v_test_results c_vtr1
2875 WHERE
2876 c_vtr1.pk_patient = %%(pat)s
2877 AND
2878 c_vtr1.pk_meta_test_type IS NOT NULL
2879 UNION ALL
2880 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
2881 FROM clin.v_test_results c_vtr2
2882 WHERE
2883 c_vtr2.pk_patient = %%(pat)s
2884 AND
2885 c_vtr2.pk_meta_test_type IS NULL
2886 )
2887 %s""" % order_by
2888 else:
2889 cmd = """
2890 SELECT * FROM clin.v_test_types c_vtt
2891 WHERE c_vtt.pk_test_type IN (
2892 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
2893 FROM clin.v_test_results c_vtr
2894 WHERE c_vtr.pk_patient = %%(pat)s
2895 )
2896 %s""" % order_by
2897
2898 args = {'pat': self.pk_patient}
2899 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2900 return [ gmPathLab.cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
2901
2902
2904 """Get the dates for which we have results."""
2905 where_parts = ['pk_patient = %(pat)s']
2906 args = {'pat': self.pk_patient}
2907
2908 if tests is not None:
2909 where_parts.append('pk_test_type IN %(tests)s')
2910 args['tests'] = tuple(tests)
2911
2912 cmd = """
2913 SELECT DISTINCT ON (clin_when_day)
2914 clin_when_day,
2915 is_reviewed
2916 FROM (
2917 SELECT
2918 date_trunc('day', clin_when)
2919 AS clin_when_day,
2920 bool_and(reviewed)
2921 AS is_reviewed
2922 FROM (
2923 SELECT
2924 clin_when,
2925 reviewed,
2926 pk_patient,
2927 pk_test_result
2928 FROM clin.v_test_results
2929 WHERE %s
2930 )
2931 AS patient_tests
2932 GROUP BY clin_when_day
2933 )
2934 AS grouped_days
2935 ORDER BY clin_when_day %s
2936 """ % (
2937 ' AND '.join(where_parts),
2938 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
2939 )
2940 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2941 return rows
2942
2943
2945 """Get the issues/episodes for which we have results."""
2946 where_parts = ['pk_patient = %(pat)s']
2947 args = {'pat': self.pk_patient}
2948
2949 if tests is not None:
2950 where_parts.append('pk_test_type IN %(tests)s')
2951 args['tests'] = tuple(tests)
2952 where = ' AND '.join(where_parts)
2953 cmd = """
2954 SELECT * FROM ((
2955 -- issues, each including all it"s episodes
2956 SELECT
2957 health_issue AS problem,
2958 pk_health_issue,
2959 NULL::integer AS pk_episode,
2960 1 AS rank
2961 FROM clin.v_test_results
2962 WHERE pk_health_issue IS NOT NULL AND %s
2963 GROUP BY pk_health_issue, problem
2964 ) UNION ALL (
2965 -- episodes w/o issue
2966 SELECT
2967 episode AS problem,
2968 NULL::integer AS pk_health_issue,
2969 pk_episode,
2970 2 AS rank
2971 FROM clin.v_test_results
2972 WHERE pk_health_issue IS NULL AND %s
2973 GROUP BY pk_episode, problem
2974 )) AS grouped_union
2975 ORDER BY rank, problem
2976 """ % (where, where)
2977 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2978 return rows
2979
2980
2981 - def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
2988
2990
2991 where_parts = ['pk_patient = %(pat)s']
2992 args = {'pat': self.pk_patient}
2993
2994 if tests is not None:
2995 where_parts.append('pk_test_type IN %(tests)s')
2996 args['tests'] = tuple(tests)
2997
2998 if encounter is not None:
2999 where_parts.append('pk_encounter = %(enc)s')
3000 args['enc'] = encounter
3001
3002 if episodes is not None:
3003 where_parts.append('pk_episode IN %(epis)s')
3004 args['epis'] = tuple(episodes)
3005
3006 cmd = """
3007 SELECT * FROM clin.v_test_results
3008 WHERE %s
3009 ORDER BY clin_when %s, pk_episode, unified_name
3010 """ % (
3011 ' AND '.join(where_parts),
3012 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
3013 )
3014 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3015
3016 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
3017
3018 return tests
3019
3020 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
3021
3022 try:
3023 epi = int(episode)
3024 except:
3025 epi = episode['pk_episode']
3026
3027 try:
3028 type = int(type)
3029 except:
3030 type = type['pk_test_type']
3031
3032 tr = gmPathLab.create_test_result (
3033 link_obj = link_obj,
3034 encounter = self.current_encounter['pk_encounter'],
3035 episode = epi,
3036 type = type,
3037 intended_reviewer = intended_reviewer,
3038 val_num = val_num,
3039 val_alpha = val_alpha,
3040 unit = unit
3041 )
3042
3043 return tr
3044
3045
3047 where = 'pk_org_unit IN (%s)' % """
3048 SELECT DISTINCT fk_org_unit FROM clin.test_org WHERE pk IN (
3049 SELECT DISTINCT pk_test_org FROM clin.v_test_results where pk_patient = %(pat)s
3050 )"""
3051 args = {'pat': self.pk_patient}
3052 cmd = gmOrganization._SQL_get_org_unit % where
3053 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3054 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
3055
3056
3058 measured_gfrs = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
3059 measured_gfr = measured_gfrs[0] if len(measured_gfrs) > 0 else None
3060 creas = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
3061 crea = creas[0] if len(creas) > 0 else None
3062
3063 if (measured_gfr is None) and (crea is None):
3064 return None
3065
3066 if (measured_gfr is not None) and (crea is None):
3067 return measured_gfr
3068
3069
3070 if measured_gfr is None:
3071 eGFR = self.calculator.eGFR
3072 if eGFR.numeric_value is None:
3073 return crea
3074 return eGFR
3075
3076
3077 two_weeks = pydt.timedelta(weeks = 2)
3078 gfr_too_old = (crea['clin_when'] - measured_gfr['clin_when']) > two_weeks
3079 if not gfr_too_old:
3080 return measured_gfr
3081
3082
3083
3084 eGFR = self.calculator.eGFR
3085 if eGFR.numeric_value is None:
3086
3087
3088 return crea
3089
3090 return eGFR
3091
3092 best_gfr_or_crea = property(_get_best_gfr_or_crea, lambda x:x)
3093
3094
3097
3098 bmi = property(_get_bmi, lambda x:x)
3099
3100
3103
3104 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
3105
3106
3107
3108
3113
3114 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
3128
3129
3130
3131
3132 if __name__ == "__main__":
3133
3134 if len(sys.argv) == 1:
3135 sys.exit()
3136
3137 if sys.argv[1] != 'test':
3138 sys.exit()
3139
3140 from Gnumed.pycommon import gmLog2
3141
3142 from Gnumed.business import gmPraxis
3143 branches = gmPraxis.get_praxis_branches()
3144 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
3145
3147 print(args)
3148 print(kwargs)
3149 args[0](*args[1:], **kwargs)
3150
3151 set_delayed_executor(_do_delayed)
3152
3153
3167
3168
3173
3174
3175
3176
3183
3184
3186 emr = cClinicalRecord(aPKey=12)
3187 rows, idx = emr.get_measurements_by_date()
3188 print("test results:")
3189 for row in rows:
3190 print(row)
3191
3192
3199
3200
3205
3206
3208 emr = cClinicalRecord(aPKey=12)
3209
3210 probs = emr.get_problems()
3211 print("normal probs (%s):" % len(probs))
3212 for p in probs:
3213 print('%s (%s)' % (p['problem'], p['type']))
3214
3215 probs = emr.get_problems(include_closed_episodes=True)
3216 print("probs + closed episodes (%s):" % len(probs))
3217 for p in probs:
3218 print('%s (%s)' % (p['problem'], p['type']))
3219
3220 probs = emr.get_problems(include_irrelevant_issues=True)
3221 print("probs + issues (%s):" % len(probs))
3222 for p in probs:
3223 print('%s (%s)' % (p['problem'], p['type']))
3224
3225 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
3226 print("probs + issues + epis (%s):" % len(probs))
3227 for p in probs:
3228 print('%s (%s)' % (p['problem'], p['type']))
3229
3230
3232 emr = cClinicalRecord(aPKey=12)
3233 tr = emr.add_test_result (
3234 episode = 1,
3235 intended_reviewer = 1,
3236 type = 1,
3237 val_num = 75,
3238 val_alpha = 'somewhat obese',
3239 unit = 'kg'
3240 )
3241 print(tr)
3242
3243
3247
3248
3253
3254
3259
3260
3265
3266
3271
3272
3277
3278
3283
3284
3288
3289
3291 emr = cClinicalRecord(aPKey = 12)
3292 for journal_line in emr.get_as_journal():
3293
3294 print('%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line)
3295 print("")
3296
3297
3301
3302
3307
3308
3314
3315
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353 emr = cClinicalRecord(aPKey = 12)
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372 v1 = emr.vaccinations
3373 print(v1)
3374 v2 = gmVaccination.get_vaccinations(pk_identity = 12, return_pks = True)
3375 print(v2)
3376 for v in v1:
3377 if v['pk_vaccination'] not in v2:
3378 print('ERROR')
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