| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2 """GNUmed clinical patient record."""
3 #============================================================
4 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL v2 or later"
6
7 # standard libs
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 # helper functions
52 #------------------------------------------------------------
53 #_func_ask_user = None
54 #
55 #def set_func_ask_user(a_func = None):
56 # if not callable(a_func):
57 # _log.error('[%] not callable, not setting _func_ask_user', a_func)
58 # return False
59 #
60 # _log.debug('setting _func_ask_user to [%s]', a_func)
61 #
62 # global _func_ask_user
63 # _func_ask_user = a_func
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 #------------------------------------------------------------
100
101 instance = instantiate_clin_root_item(table, pk)
102 if instance is None:
103 return _('cannot instantiate clinical root item <%s(%s)>' % (table, pk))
104
105 # if patient is not None:
106 # if patient.ID != instance['pk_patient']:
107 # raise ValueError(u'patient passed in: [%s], but instance is: [%s:%s:%s]' % (patient.ID, table, pk, instance['pk_patient']))
108
109 if hasattr(instance, 'format_maximum_information'):
110 return '\n'.join(instance.format_maximum_information(patient = patient))
111
112 if hasattr(instance, 'format'):
113 try:
114 formatted = instance.format(patient = patient)
115 except TypeError:
116 formatted = instance.format()
117 if type(formatted) == type([]):
118 return '\n'.join(formatted)
119 return formatted
120
121 d = instance.fields_as_dict (
122 date_format = '%Y %b %d %H:%M',
123 none_string = gmTools.u_diameter,
124 escape_style = None,
125 bool_strings = [_('True'), _('False')]
126 )
127 return gmTools.format_dict_like(d, tabular = True, value_delimiters = None)
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 #------------------------------------------------------------
146
148 """Fails if
149
150 - no connection to database possible
151 - patient referenced by aPKey does not exist
152 """
153 self.pk_patient = aPKey # == identity.pk == primary key
154 self.gender = None
155 self.dob = None
156
157 from Gnumed.business import gmPraxis
158 global _here
159 if _here is None:
160 _here = gmPraxis.gmCurrentPraxisBranch()
161
162 self.__encounter = None
163 self.__setup_active_encounter()
164
165 # register backend notification interests
166 # (keep this last so we won't hang on threads when
167 # failing this constructor for other reasons ...)
168 if not self._register_interests():
169 raise gmExceptions.ConstructorError("cannot register signal interests")
170
171 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
172 #_delayed_execute(gmAllergy.ensure_has_allergy_state, encounter = self.current_encounter['pk_encounter'])
173
174 self.__calculator = None
175
176 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
177
178 # #--------------------------------------------------------
179 # def __old_style_init(self, allow_user_interaction=True):
180 #
181 # _log.error('%s.__old_style_init() used', self.__class__.__name__)
182 # print u'*** GNUmed [%s]: __old_style_init() used ***' % self.__class__.__name__
183 #
184 # # FIXME: delegate to worker thread
185 # # log access to patient record (HIPAA, for example)
186 # cmd = u'SELECT gm.log_access2emr(%(todo)s)'
187 # args = {'todo': u'patient [%s]' % self.pk_patient}
188 # gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
189 #
190 # # load current or create new encounter
191 # if _func_ask_user is None:
192 # _log.error('[_func_ask_user] is None')
193 # print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__
194 #
195 ## # FIXME: delegate to worker thread ?
196 # self.remove_empty_encounters()
197 #
198 # self.__encounter = None
199 # if not self.__initiate_active_encounter(allow_user_interaction = allow_user_interaction):
200 # raise gmExceptions.ConstructorError("cannot activate an encounter for patient [%s]" % self.pk_patient)
201 #
202 ## # FIXME: delegate to worker thread
203 # gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
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 #--------------------------------------------------------
222 if self.__calculator is None:
223 from Gnumed.business.gmClinicalCalculator import cClinicalCalculator
224 self.__calculator = cClinicalCalculator()
225 from Gnumed.business.gmPerson import gmCurrentPatient
226 curr_pat = gmCurrentPatient()
227 if curr_pat.ID == self.pk_patient:
228 self.__calculator.patient = curr_pat
229 else:
230 from Gnumed.business.gmPerson import cPatient
231 self.__calculator.patient = cPatient(self.pk_patient)
232 return self.__calculator
233
234 calculator = property(_get_calculator, lambda x:x)
235
236 #--------------------------------------------------------
237 # messaging
238 #--------------------------------------------------------
240 #gmDispatcher.connect(signal = u'clin.encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
241 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self.db_modification_callback)
242
243 return True
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 # get the current encounter as an extra instance
260 # from the database to check for changes
261 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
262
263 # the encounter just retrieved and the active encounter
264 # have got the same transaction ID so there's no change
265 # in the database, there could be a local change in
266 # the active encounter but that doesn't matter because
267 # no one else can have written to the DB so far
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 # there must have been a change to the active encounter
277 # committed to the database from elsewhere,
278 # we must fail propagating the change, however, if
279 # there are local changes pending
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 # don't do this: same_payload() does not compare _all_ fields
288 # so we can get into a reality disconnect if we don't
289 # announce the mod
290 # if self.current_encounter.same_payload(another_object = curr_enc_in_db):
291 # _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
292 # return True
293
294 # there was a change in the database from elsewhere,
295 # locally, however, we don't have any pending changes,
296 # therefore we can propagate the remote change locally
297 # without losing anything
298 # this really should be the standard case
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 # get the current encounter as an extra instance
310 # from the database to check for changes
311 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
312
313 # the encounter just retrieved and the active encounter
314 # have got the same transaction ID so there's no change
315 # in the database, there could be a local change in
316 # the active encounter but that doesn't matter because
317 # no one else can have written to the DB so far
318 # THIS DOES NOT WORK
319 # if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
320 # return True
321
322 # there must have been a change to the active encounter
323 # committed to the database from elsewhere,
324 # we must fail propagating the change, however, if
325 # there are local changes
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 # there was a change in the database from elsewhere,
339 # locally, however, we don't have any changes, therefore
340 # we can propagate the remote change locally without
341 # losing anything
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 # API: family history
351 #--------------------------------------------------------
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 #--------------------------------------------------------
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 # API: pregnancy
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
391 self.__gender = gender
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
406 self.__dob = dob
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 # mother too young
445 if (self.dob + pydt.timedelta(weeks = 5 * 52)) > now:
446 return True
447 # mother too old
448 if (self.dob + pydt.timedelta(weeks = 55 * 52)) < now:
449 return True
450 # Beulah Hunter, 375 days (http://www.reference.com/motif/health/longest-human-pregnancy-on-record)
451 # EDC too far in the future
452 if (edc - pydt.timedelta(days = 380)) > now:
453 return True
454 # even if the pregnancy would have *started* when it
455 # was documented to *end* it would be over by now by
456 # all accounts
457 # EDC too far in the past
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 # valid ?
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 # valid ?
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 # valid ?
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 # caching does not take into account status changes from elsewhere
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 # disentangle smoking
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 # set fields
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 #--------------------------------------------------------
613 - def format_harmful_substance_use(self, include_tobacco=True, include_alcohol=True, include_drugs=True, include_nonuse=True, include_unknown=True):
614 use = self.harmful_substance_use
615 if use is None:
616 return []
617
618 lines = []
619
620 if include_tobacco:
621 status, details = use['tobacco']
622 add_details = False
623 if status is None:
624 if include_unknown:
625 lines.append(_('unknown smoking status'))
626 elif status == 0:
627 if include_nonuse:
628 lines.append('%s (%s)' % (_('non-smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
629 add_details = True
630 elif status == 1: # now or previous
631 if details['quit_when'] is None:
632 lines.append('%s (%s)' % (_('current smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
633 add_details = True
634 elif details['quit_when'] < gmDateTime.pydt_now_here():
635 if include_nonuse:
636 lines.append('%s (%s)' % (_('ex-smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
637 add_details = True
638 else:
639 lines.append('%s (%s)' % (_('current smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
640 add_details = True
641 elif status == 2: # addicted
642 lines.append('%s (%s)' % (_('tobacco addiction'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
643 add_details = True
644 if add_details:
645 if details['quit_when'] is not None:
646 lines.append(' %s: %s' % (_('Quit date'), gmDateTime.pydt_strftime(details['quit_when'], '%Y %b %d')))
647 if details['comment'] is not None:
648 lines.append(' %s' % details['comment'])
649
650 if include_alcohol:
651 status, details = use['alcohol']
652 if status is False:
653 if include_nonuse:
654 if len(lines) > 0:
655 lines.append('')
656 lines.append(_('no or non-harmful alcohol use'))
657 lines.append(' %s' % details)
658 elif status is True:
659 if len(lines) > 0:
660 lines.append('')
661 lines.append(_('harmful alcohol use'))
662 lines.append(' %s' % details)
663 else:
664 if include_unknown:
665 if len(lines) > 0:
666 lines.append('')
667 lines.append(_('unknown alcohol use'))
668 lines.append(' %s' % details)
669
670 if include_drugs:
671 status, details = use['drugs']
672 if status is False:
673 if include_nonuse:
674 if len(lines) > 0:
675 lines.append('')
676 lines.append(_('no or non-harmful drug use'))
677 lines.append(' %s' % details)
678 elif status is True:
679 if len(lines) > 0:
680 lines.append('')
681 lines.append(_('harmful drug use'))
682 lines.append(' %s' % details)
683 else:
684 if include_unknown:
685 if len(lines) > 0:
686 lines.append('')
687 lines.append(_('unknown drug use'))
688 lines.append(' %s' % details)
689
690 return lines
691
692 #--------------------------------------------------------
694 # returns True / False / None (= unknown)
695
696 use = self.harmful_substance_use
697 # we know that at least one group is used:
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 # is True:
704 if use['tobacco'][1]['quit_when'] is None:
705 return True
706 # at this point no group is currently used for sure
707 # we don't know about some of the groups so we can NOT say: no abuse at all:
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 # at this point all groups must be FALSE, except for
715 # tobacco which can also be TRUE _but_, if so, a quit
716 # date has been set, which is considered non-abuse
717 return False
718
719 currently_abuses_substances = property(_get_currently_abuses_substances, lambda x:x)
720
721 #--------------------------------------------------------
722 # API: performed procedures
723 #--------------------------------------------------------
725
726 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
727
728 if episodes is not None:
729 procs = [ p for p in procs if p['pk_episode'] in episodes ]
730
731 if issues is not None:
732 procs = [ p for p in procs if p['pk_health_issue'] in issues ]
733
734 return procs
735
736 performed_procedures = property(get_performed_procedures, lambda x:x)
737 #--------------------------------------------------------
740 #--------------------------------------------------------
741 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
742 return gmEMRStructItems.create_performed_procedure (
743 encounter = self.current_encounter['pk_encounter'],
744 episode = episode,
745 location = location,
746 hospital_stay = hospital_stay,
747 procedure = procedure
748 )
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 # API: hospitalizations
759 #--------------------------------------------------------
761 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient, ongoing_only = ongoing_only)
762 if episodes is not None:
763 stays = [ s for s in stays if s['pk_episode'] in episodes ]
764 if issues is not None:
765 stays = [ s for s in stays if s['pk_health_issue'] in issues ]
766 return stays
767
768 hospital_stays = property(get_hospital_stays, lambda x:x)
769 #--------------------------------------------------------
772 #--------------------------------------------------------
774 return gmEMRStructItems.create_hospital_stay (
775 encounter = self.current_encounter['pk_encounter'],
776 episode = episode,
777 fk_org_unit = fk_org_unit
778 )
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 # API: narrative
807 #--------------------------------------------------------
809 enc = gmTools.coalesce (
810 encounter,
811 self.current_encounter['pk_encounter']
812 )
813 for note in notes:
814 gmClinNarrative.create_narrative_item (
815 narrative = note[1],
816 soap_cat = note[0],
817 episode_id = episode,
818 encounter_id = enc
819 )
820 return True
821
822 #--------------------------------------------------------
824 if note.strip() == '':
825 _log.info('will not create empty clinical note')
826 return None
827 if isinstance(episode, gmEMRStructItems.cEpisode):
828 episode = episode['pk_episode']
829 instance = gmClinNarrative.create_narrative_item (
830 link_obj = link_obj,
831 narrative = note,
832 soap_cat = soap_cat,
833 episode_id = episode,
834 encounter_id = self.current_encounter['pk_encounter']
835 )
836 return instance
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 """ # case sensitive
939 rows, idx = gmPG2.run_ro_queries(queries = [
940 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
941 ])
942 return rows
943 #--------------------------------------------------------
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 # handle constraint conditions
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 # FIXME: these are interrelated, eg if we constrain encounter
970 # we automatically constrain issue/episode, so handle that,
971 # encounters
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 # episodes
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 # health issues
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 # -- sort the data --
1003 # FIXME: by issue/encounter/episode, eg formatting
1004 # aggregate by src_table for item retrieval
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 # get mapping for issue/episode IDs
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 # get item data from all source tables
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 # we don't know anything about the columns of
1029 # the source tables but, hey, this is a dump
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 # skip this table
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 # skip this table
1044 continue
1045 rows = curs.fetchall()
1046 table_col_idx = gmPG.get_col_indices(curs)
1047 # format per-table items
1048 for row in rows:
1049 # FIXME: make this get_pkey_name()
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 # format metadata
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 # format table specific data columns
1075 # - ignore those, they are metadata, some
1076 # are in clin.v_pat_items data already
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 #--------------------------------------------------------
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 # active and approved substances == medication
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 #--------------------------------------------------------
1161 return _(
1162 'Medical problems: %(problems)s\n'
1163 'Total encounters: %(encounters)s\n'
1164 'Total EMR entries: %(items)s\n'
1165 'Active medications: %(active_drugs)s\n'
1166 'Documents: %(documents)s\n'
1167 'Test results: %(results)s\n'
1168 'Hospitalizations: %(stays)s\n'
1169 'Procedures: %(procedures)s\n'
1170 'Vaccinations: %(vaccinations)s'
1171 ) % self.get_statistics()
1172 #--------------------------------------------------------
1174
1175 cmd = "SELECT dob FROM dem.v_all_persons WHERE pk_identity = %(pk)s"
1176 args = {'pk': self.pk_patient}
1177 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1178 dob = rows[0]['dob']
1179
1180 stats = self.get_statistics()
1181 first = self.get_first_encounter()
1182 last = self.get_last_encounter()
1183 probs = self.get_problems()
1184
1185 txt = ''
1186 if len(probs) > 0:
1187 txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems']
1188 else:
1189 txt += _(' %s known problems\n') % stats['problems']
1190 for prob in probs:
1191 if not prob['clinically_relevant']:
1192 continue
1193 txt += ' \u00BB%s\u00AB (%s)\n' % (
1194 prob['problem'],
1195 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
1196 )
1197 txt += '\n'
1198 txt += _(' %s encounters from %s to %s\n') % (
1199 stats['encounters'],
1200 gmDateTime.pydt_strftime(first['started'], '%Y %b %d'),
1201 gmDateTime.pydt_strftime(last['started'], '%Y %b %d')
1202 )
1203 txt += _(' %s active medications\n') % stats['active_drugs']
1204 txt += _(' %s documents\n') % stats['documents']
1205 txt += _(' %s test results\n') % stats['results']
1206 txt += _(' %s hospitalizations') % stats['stays']
1207 if stats['stays'] == 0:
1208 txt += '\n'
1209 else:
1210 txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3)
1211 # FIXME: perhaps only count "ongoing ones"
1212 txt += _(' %s performed procedures') % stats['procedures']
1213 if stats['procedures'] == 0:
1214 txt += '\n'
1215 else:
1216 txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3)
1217
1218 txt += '\n'
1219 txt += _('Allergies and Intolerances\n')
1220
1221 allg_state = self.allergy_state
1222 txt += (' ' + allg_state.state_string)
1223 if allg_state['last_confirmed'] is not None:
1224 txt += _(' (last confirmed %s)') % gmDateTime.pydt_strftime(allg_state['last_confirmed'], '%Y %b %d')
1225 txt += '\n'
1226 txt += gmTools.coalesce(allg_state['comment'], '', ' %s\n')
1227 for allg in self.get_allergies():
1228 txt += ' %s: %s\n' % (
1229 allg['descriptor'],
1230 gmTools.coalesce(allg['reaction'], _('unknown reaction'))
1231 )
1232
1233 meds = self.get_current_medications(order_by = 'intake_is_approved_of DESC, substance')
1234 if len(meds) > 0:
1235 txt += '\n'
1236 txt += _('Medications and Substances')
1237 txt += '\n'
1238 for m in meds:
1239 txt += '%s\n' % m.format_as_single_line(left_margin = 1)
1240
1241 fhx = self.get_family_history()
1242 if len(fhx) > 0:
1243 txt += '\n'
1244 txt += _('Family History')
1245 txt += '\n'
1246 for f in fhx:
1247 txt += '%s\n' % f.format(left_margin = 1)
1248
1249 jobs = get_occupations(pk_identity = self.pk_patient)
1250 if len(jobs) > 0:
1251 txt += '\n'
1252 txt += _('Occupations')
1253 txt += '\n'
1254 for job in jobs:
1255 txt += ' %s%s\n' % (
1256 job['l10n_occupation'],
1257 gmTools.coalesce(job['activities'], '', ': %s')
1258 )
1259
1260 vaccs = self.get_latest_vaccinations()
1261 if len(vaccs) > 0:
1262 txt += '\n'
1263 txt += _('Vaccinations')
1264 txt += '\n'
1265 inds = sorted(vaccs.keys())
1266 for ind in inds:
1267 ind_count, vacc = vaccs[ind]
1268 if dob is None:
1269 age_given = ''
1270 else:
1271 age_given = ' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
1272 start = dob,
1273 end = vacc['date_given']
1274 ))
1275 since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given'])
1276 txt += ' %s (%s%s): %s%s (%s %s%s%s)\n' % (
1277 ind,
1278 gmTools.u_sum,
1279 ind_count,
1280 #gmDateTime.pydt_strftime(vacc['date_given'], '%b %Y'),
1281 since,
1282 age_given,
1283 vacc['vaccine'],
1284 gmTools.u_left_double_angle_quote,
1285 vacc['batch_no'],
1286 gmTools.u_right_double_angle_quote
1287 )
1288
1289 care = self.get_external_care_items(order_by = 'issue, organization, unit, provider', exclude_inactive = True)
1290 if len(care) > 0:
1291 txt += '\n'
1292 txt += _('External care')
1293 txt += '\n'
1294 for item in care:
1295 txt += ' %s: %s\n' % (
1296 item['issue'],
1297 gmTools.coalesce (
1298 item['provider'],
1299 '%s@%s' % (item['unit'], item['organization']),
1300 '%%s (%s@%s)' % (item['unit'], item['organization'])
1301 )
1302 )
1303
1304 return txt
1305
1306 #--------------------------------------------------------
1308 txt = ''
1309 for enc in self.get_encounters(skip_empty = True):
1310 txt += gmTools.u_box_horiz_4dashes * 70 + '\n'
1311 txt += enc.format (
1312 episodes = None, # means: each touched upon
1313 left_margin = left_margin,
1314 patient = patient,
1315 fancy_header = False,
1316 with_soap = True,
1317 with_docs = True,
1318 with_tests = True,
1319 with_vaccinations = True,
1320 with_co_encountlet_hints = False, # irrelevant
1321 with_rfe_aoe = True,
1322 with_family_history = True,
1323 by_episode = True
1324 )
1325
1326 return txt
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):
1346 if use_active_encounter:
1347 active_encounter = self.active_encounter
1348 else:
1349 active_encounter = None
1350 return gmGenericEMRItem.get_generic_emr_items (
1351 patient = self.pk_patient,
1352 encounters = pk_encounters,
1353 episodes = pk_episodes,
1354 issues = pk_health_issues,
1355 active_encounter = active_encounter,
1356 order_by = order_by
1357 )
1358
1359 #--------------------------------------------------------
1360 # API: allergy
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 # ok, let's constrain our list
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 # better fail here contrary to what we do elsewhere
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 #--------------------------------------------------------
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 # we don't know the state
1438 if self.allergy_state is None:
1439 return None
1440
1441 # we know there's no allergies
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 #--------------------------------------------------------
1485
1486 if state not in gmAllergy.allergy_states:
1487 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
1488
1489 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
1490 allg_state['has_allergy'] = state
1491 allg_state.save_payload()
1492 return True
1493
1496
1497 allergy_state = property(_get_allergy_state, _set_allergy_state)
1498 #--------------------------------------------------------
1499 # API: external care
1500 #--------------------------------------------------------
1502 return gmExternalCare.get_external_care_items (
1503 pk_identity = self.pk_patient,
1504 order_by = order_by,
1505 exclude_inactive = exclude_inactive
1506 )
1507
1508 external_care_items = property(get_external_care_items, lambda x:x)
1509
1510 #--------------------------------------------------------
1511 # API: episodes
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 # try to find the episode with the most recently modified clinical item
1596
1597 issue_where = gmTools.coalesce(issue, '', 'and pk_health_issue = %(issue)s')
1598
1599 cmd = """
1600 SELECT pk
1601 from clin.episode
1602 WHERE pk = (
1603 SELECT distinct on(pk_episode) pk_episode
1604 from clin.v_pat_items
1605 WHERE
1606 pk_patient = %%(pat)s
1607 and
1608 modified_when = (
1609 SELECT max(vpi.modified_when)
1610 from clin.v_pat_items vpi
1611 WHERE vpi.pk_patient = %%(pat)s
1612 )
1613 %s
1614 -- guard against several episodes created at the same moment of time
1615 limit 1
1616 )""" % issue_where
1617 rows, idx = gmPG2.run_ro_queries(queries = [
1618 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1619 ])
1620 if len(rows) != 0:
1621 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1622
1623 # no clinical items recorded, so try to find
1624 # the youngest episode for this patient
1625 cmd = """
1626 SELECT vpe0.pk_episode
1627 from
1628 clin.v_pat_episodes vpe0
1629 WHERE
1630 vpe0.pk_patient = %%(pat)s
1631 and
1632 vpe0.episode_modified_when = (
1633 SELECT max(vpe1.episode_modified_when)
1634 from clin.v_pat_episodes vpe1
1635 WHERE vpe1.pk_episode = vpe0.pk_episode
1636 )
1637 %s""" % issue_where
1638 rows, idx = gmPG2.run_ro_queries(queries = [
1639 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1640 ])
1641 if len(rows) != 0:
1642 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1643
1644 return None
1645 #--------------------------------------------------------
1648 #--------------------------------------------------------
1649 # API: problems
1650 #--------------------------------------------------------
1651 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1652 """Retrieve a patient's problems.
1653
1654 "Problems" are the UNION of:
1655
1656 - issues which are .clinically_relevant
1657 - episodes which are .is_open
1658
1659 Therefore, both an issue and the open episode
1660 thereof can each be listed as a problem.
1661
1662 include_closed_episodes/include_irrelevant_issues will
1663 include those -- which departs from the definition of
1664 the problem list being "active" items only ...
1665
1666 episodes - episodes' PKs to filter problems by
1667 issues - health issues' PKs to filter problems by
1668 """
1669 # FIXME: this could use a good measure of streamlining, probably
1670
1671 args = {'pat': self.pk_patient}
1672
1673 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1674 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1675
1676 # Instantiate problem items
1677 problems = []
1678 for row in rows:
1679 pk_args = {
1680 'pk_patient': self.pk_patient,
1681 'pk_health_issue': row['pk_health_issue'],
1682 'pk_episode': row['pk_episode']
1683 }
1684 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1685
1686 # include non-problems ?
1687 other_rows = []
1688 if include_closed_episodes:
1689 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1690 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1691 other_rows.extend(rows)
1692
1693 if include_irrelevant_issues:
1694 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1695 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1696 other_rows.extend(rows)
1697
1698 if len(other_rows) > 0:
1699 for row in other_rows:
1700 pk_args = {
1701 'pk_patient': self.pk_patient,
1702 'pk_health_issue': row['pk_health_issue'],
1703 'pk_episode': row['pk_episode']
1704 }
1705 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1706
1707 # filter
1708 if issues is not None:
1709 problems = [ p for p in problems if p['pk_health_issue'] in issues ]
1710 if episodes is not None:
1711 problems = [ p for p in problems if p['pk_episode'] in episodes ]
1712
1713 return problems
1714
1715 #--------------------------------------------------------
1718
1719 #--------------------------------------------------------
1722
1723 #--------------------------------------------------------
1726
1727 #--------------------------------------------------------
1729 cmd = "SELECT * FROM clin.v_candidate_diagnoses WHERE pk_patient = %(pat)s"
1730 rows, idx = gmPG2.run_ro_queries (
1731 queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}],
1732 get_col_idx = False
1733 )
1734 return rows
1735
1736 candidate_diagnoses = property(get_candidate_diagnoses)
1737
1738 #--------------------------------------------------------
1739 # API: health issues
1740 #--------------------------------------------------------
1742
1743 cmd = "SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1744 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1745 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1746
1747 if id_list is None:
1748 return issues
1749
1750 if len(id_list) == 0:
1751 raise ValueError('id_list to filter by is empty, most likely a programming error')
1752
1753 filtered_issues = []
1754 for issue in issues:
1755 if issue['pk_health_issue'] in id_list:
1756 filtered_issues.append(issue)
1757
1758 return filtered_issues
1759
1760 health_issues = property(get_health_issues, lambda x:x)
1761
1762 #------------------------------------------------------------------
1764 """Adds patient health issue."""
1765 return gmEMRStructItems.create_health_issue (
1766 description = issue_name,
1767 encounter = self.current_encounter['pk_encounter'],
1768 patient = self.pk_patient
1769 )
1770 #--------------------------------------------------------
1773 #--------------------------------------------------------
1774 # API: substance intake
1775 #--------------------------------------------------------
1776 - def get_current_medications(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1777 return self._get_current_substance_intakes (
1778 include_inactive = include_inactive,
1779 include_unapproved = include_unapproved,
1780 order_by = order_by,
1781 episodes = episodes,
1782 issues = issues,
1783 exclude_medications = False,
1784 exclude_potential_abuses = True
1785 )
1786
1787 #--------------------------------------------------------
1789 return self._get_current_substance_intakes (
1790 include_inactive = True,
1791 include_unapproved = True,
1792 order_by = order_by,
1793 episodes = None,
1794 issues = None,
1795 exclude_medications = True,
1796 exclude_potential_abuses = False
1797 )
1798
1799 abused_substances = property(_get_abused_substances, lambda x:x)
1800
1801 #--------------------------------------------------------
1802 - 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):
1803
1804 where_parts = ['pk_patient = %(pat)s']
1805 args = {'pat': self.pk_patient}
1806
1807 if not include_inactive:
1808 where_parts.append('is_currently_active IN (TRUE, NULL)')
1809
1810 if not include_unapproved:
1811 where_parts.append('intake_is_approved_of IN (TRUE, NULL)')
1812
1813 if exclude_potential_abuses:
1814 where_parts.append('harmful_use_type IS NULL')
1815
1816 if exclude_medications:
1817 where_parts.append('harmful_use_type IS NOT NULL')
1818
1819 if order_by is None:
1820 order_by = ''
1821 else:
1822 order_by = 'ORDER BY %s' % order_by
1823
1824 cmd = "SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1825 '\nAND '.join(where_parts),
1826 order_by
1827 )
1828 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1829 intakes = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1830
1831 if episodes is not None:
1832 intakes = [ i for i in intakes if i['pk_episode'] in episodes ]
1833
1834 if issues is not None:
1835 intakes = [ i for i in intakes if i ['pk_health_issue'] in issues ]
1836
1837 return intakes
1838
1839 #--------------------------------------------------------
1840 - def add_substance_intake(self, pk_component=None, pk_episode=None, pk_drug_product=None, pk_health_issue=None):
1841 pk_enc = self.current_encounter['pk_encounter']
1842 if pk_episode is None:
1843 pk_episode = gmMedication.create_default_medication_history_episode (
1844 pk_health_issue = pk_health_issue,
1845 encounter = pk_enc
1846 )
1847 return gmMedication.create_substance_intake (
1848 pk_component = pk_component,
1849 pk_encounter = pk_enc,
1850 pk_episode = pk_episode,
1851 pk_drug_product = pk_drug_product
1852 )
1853
1854 #--------------------------------------------------------
1855 - def substance_intake_exists(self, pk_component=None, pk_substance=None, pk_drug_product=None):
1856 return gmMedication.substance_intake_exists (
1857 pk_component = pk_component,
1858 pk_substance = pk_substance,
1859 pk_identity = self.pk_patient,
1860 pk_drug_product = pk_drug_product
1861 )
1862
1863 #--------------------------------------------------------
1864 # API: vaccinations
1865 #--------------------------------------------------------
1867 return gmVaccination.create_vaccination (
1868 encounter = self.current_encounter['pk_encounter'],
1869 episode = episode,
1870 vaccine = vaccine,
1871 batch_no = batch_no
1872 )
1873
1874 #--------------------------------------------------------
1876 """Returns latest given vaccination for each vaccinated indication.
1877
1878 as a dict {'l10n_indication': cVaccination instance}
1879
1880 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1881 """
1882 args = {'pat': self.pk_patient}
1883 where_parts = ['c_v_shots.pk_patient = %(pat)s']
1884
1885 if (episodes is not None) and (len(episodes) > 0):
1886 where_parts.append('c_v_shots.pk_episode IN %(epis)s')
1887 args['epis'] = tuple(episodes)
1888
1889 if (issues is not None) and (len(issues) > 0):
1890 where_parts.append('c_v_shots.pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1891 args['issues'] = tuple(issues)
1892
1893 if (atc_indications is not None) and (len(atc_indications) > 0):
1894 where_parts.append('c_v_plv4i.atc_indication IN %(atc_inds)s')
1895 args['atc_inds'] = tuple(atc_indications)
1896
1897 # find the shots
1898 cmd = """
1899 SELECT
1900 c_v_shots.*,
1901 c_v_plv4i.l10n_indication,
1902 c_v_plv4i.no_of_shots
1903 FROM
1904 clin.v_vaccinations c_v_shots
1905 JOIN clin.v_pat_last_vacc4indication c_v_plv4i ON (c_v_shots.pk_vaccination = c_v_plv4i.pk_vaccination)
1906 WHERE %s
1907 """ % '\nAND '.join(where_parts)
1908 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1909
1910 # none found
1911 if len(rows) == 0:
1912 return {}
1913
1914 # turn them into vaccinations
1915 # (idx is constant)
1916 vaccs = {}
1917 for shot_row in rows:
1918 vaccs[shot_row['l10n_indication']] = (
1919 shot_row['no_of_shots'],
1920 gmVaccination.cVaccination(row = {'idx': idx, 'data': shot_row, 'pk_field': 'pk_vaccination'})
1921 )
1922
1923 return vaccs
1924
1925 #--------------------------------------------------------
1927 return gmVaccination.get_vaccinations (
1928 pk_identity = self.pk_patient,
1929 pk_episodes = episodes,
1930 pk_health_issues = issues,
1931 pk_encounters = encounters,
1932 order_by = order_by,
1933 return_pks = False
1934 )
1935
1936 vaccinations = property(get_vaccinations, lambda x:x)
1937
1938 #--------------------------------------------------------
1939 # old/obsolete:
1940 #--------------------------------------------------------
1942 """Retrieves vaccination regimes the patient is on.
1943
1944 optional:
1945 * ID - PK of the vaccination regime
1946 * indications - indications we want to retrieve vaccination
1947 regimes for, must be primary language, not l10n_indication
1948 """
1949 # FIXME: use course, not regime
1950 # retrieve vaccination regimes definitions
1951 cmd = """SELECT distinct on(pk_course) pk_course
1952 FROM clin.v_vaccs_scheduled4pat
1953 WHERE pk_patient=%s"""
1954 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1955 if rows is None:
1956 _log.error('cannot retrieve scheduled vaccination courses')
1957 return None
1958 # Instantiate vaccination items and keep cache
1959 for row in rows:
1960 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1961
1962 # ok, let's constrain our list
1963 filtered_regimes = []
1964 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1965 if ID is not None:
1966 filtered_regimes = [ r for r in filtered_regimes if r['pk_course'] == ID ]
1967 if len(filtered_regimes) == 0:
1968 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1969 return []
1970 else:
1971 return filtered_regimes[0]
1972 if indications is not None:
1973 filtered_regimes = [ r for r in filtered_regimes if r['indication'] in indications ]
1974
1975 return filtered_regimes
1976 #--------------------------------------------------------
1977 # def get_vaccinated_indications(self):
1978 # """Retrieves patient vaccinated indications list.
1979 #
1980 # Note that this does NOT rely on the patient being on
1981 # some schedule or other but rather works with what the
1982 # patient has ACTUALLY been vaccinated against. This is
1983 # deliberate !
1984 # """
1985 # # most likely, vaccinations will be fetched close
1986 # # by so it makes sense to count on the cache being
1987 # # filled (or fill it for nearby use)
1988 # vaccinations = self.get_vaccinations()
1989 # if vaccinations is None:
1990 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1991 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
1992 # if len(vaccinations) == 0:
1993 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
1994 # v_indications = []
1995 # for vacc in vaccinations:
1996 # tmp = [vacc['indication'], vacc['l10n_indication']]
1997 # # remove duplicates
1998 # if tmp in v_indications:
1999 # continue
2000 # v_indications.append(tmp)
2001 # return (True, v_indications)
2002 #--------------------------------------------------------
2003 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2004 """Retrieves list of vaccinations the patient has received.
2005
2006 optional:
2007 * ID - PK of a vaccination
2008 * indications - indications we want to retrieve vaccination
2009 items for, must be primary language, not l10n_indication
2010 * since - initial date for allergy items
2011 * until - final date for allergy items
2012 * encounters - list of encounters whose allergies are to be retrieved
2013 * episodes - list of episodes whose allergies are to be retrieved
2014 * issues - list of health issues whose allergies are to be retrieved
2015 """
2016 try:
2017 self.__db_cache['vaccinations']['vaccinated']
2018 except KeyError:
2019 self.__db_cache['vaccinations']['vaccinated'] = []
2020 # Important fetch ordering by indication, date to know if a vaccination is booster
2021 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
2022 WHERE pk_patient=%s
2023 order by indication, date"""
2024 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2025 if rows is None:
2026 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
2027 del self.__db_cache['vaccinations']['vaccinated']
2028 return None
2029 # Instantiate vaccination items
2030 vaccs_by_ind = {}
2031 for row in rows:
2032 vacc_row = {
2033 'pk_field': 'pk_vaccination',
2034 'idx': idx,
2035 'data': row
2036 }
2037 vacc = gmVaccination.cVaccination(row=vacc_row)
2038 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
2039 # keep them, ordered by indication
2040 try:
2041 vaccs_by_ind[vacc['indication']].append(vacc)
2042 except KeyError:
2043 vaccs_by_ind[vacc['indication']] = [vacc]
2044
2045 # calculate sequence number and is_booster
2046 for ind in vaccs_by_ind.keys():
2047 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
2048 for vacc in vaccs_by_ind[ind]:
2049 # due to the "order by indication, date" the vaccinations are in the
2050 # right temporal order inside the indication-keyed dicts
2051 seq_no = vaccs_by_ind[ind].index(vacc) + 1
2052 vacc['seq_no'] = seq_no
2053 # if no active schedule for indication we cannot
2054 # check for booster status (eg. seq_no > max_shot)
2055 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
2056 continue
2057 if seq_no > vacc_regimes[0]['shots']:
2058 vacc['is_booster'] = True
2059 del vaccs_by_ind
2060
2061 # ok, let's constrain our list
2062 filtered_shots = []
2063 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
2064 if ID is not None:
2065 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
2066 if len(filtered_shots) == 0:
2067 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
2068 return None
2069 else:
2070 return filtered_shots[0]
2071 if since is not None:
2072 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
2073 if until is not None:
2074 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
2075 if issues is not None:
2076 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
2077 if episodes is not None:
2078 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
2079 if encounters is not None:
2080 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
2081 if indications is not None:
2082 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2083 return filtered_shots
2084 #--------------------------------------------------------
2086 """Retrieves vaccinations scheduled for a regime a patient is on.
2087
2088 The regime is referenced by its indication (not l10n)
2089
2090 * indications - List of indications (not l10n) of regimes we want scheduled
2091 vaccinations to be fetched for
2092 """
2093 try:
2094 self.__db_cache['vaccinations']['scheduled']
2095 except KeyError:
2096 self.__db_cache['vaccinations']['scheduled'] = []
2097 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
2098 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2099 if rows is None:
2100 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
2101 del self.__db_cache['vaccinations']['scheduled']
2102 return None
2103 # Instantiate vaccination items
2104 for row in rows:
2105 vacc_row = {
2106 'pk_field': 'pk_vacc_def',
2107 'idx': idx,
2108 'data': row
2109 }
2110 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
2111
2112 # ok, let's constrain our list
2113 if indications is None:
2114 return self.__db_cache['vaccinations']['scheduled']
2115 filtered_shots = []
2116 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
2117 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2118 return filtered_shots
2119 #--------------------------------------------------------
2121 try:
2122 self.__db_cache['vaccinations']['missing']
2123 except KeyError:
2124 self.__db_cache['vaccinations']['missing'] = {}
2125 # 1) non-booster
2126 self.__db_cache['vaccinations']['missing']['due'] = []
2127 # get list of (indication, seq_no) tuples
2128 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
2129 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2130 if rows is None:
2131 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
2132 return None
2133 pk_args = {'pat_id': self.pk_patient}
2134 if rows is not None:
2135 for row in rows:
2136 pk_args['indication'] = row[0]
2137 pk_args['seq_no'] = row[1]
2138 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
2139
2140 # 2) boosters
2141 self.__db_cache['vaccinations']['missing']['boosters'] = []
2142 # get list of indications
2143 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
2144 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2145 if rows is None:
2146 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
2147 return None
2148 pk_args = {'pat_id': self.pk_patient}
2149 if rows is not None:
2150 for row in rows:
2151 pk_args['indication'] = row[0]
2152 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
2153
2154 # if any filters ...
2155 if indications is None:
2156 return self.__db_cache['vaccinations']['missing']
2157 if len(indications) == 0:
2158 return self.__db_cache['vaccinations']['missing']
2159 # ... apply them
2160 filtered_shots = {
2161 'due': [],
2162 'boosters': []
2163 }
2164 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
2165 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
2166 filtered_shots['due'].append(due_shot)
2167 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
2168 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
2169 filtered_shots['boosters'].append(due_shot)
2170 return filtered_shots
2171
2172 #------------------------------------------------------------------
2173 # API: encounters
2174 #------------------------------------------------------------------
2177
2179 # first ever setting ? -> fast path
2180 if self.__encounter is None:
2181 _log.debug('first setting of active encounter in this clinical record instance')
2182 encounter.lock(exclusive = False) # lock new
2183 self.__encounter = encounter
2184 gmDispatcher.send('current_encounter_switched')
2185 return True
2186
2187 # real switch -> slow path
2188 _log.debug('switching of active encounter')
2189 # fail if the currently active encounter has unsaved changes
2190 if self.__encounter.is_modified():
2191 gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
2192 _log.error('current in client: %s', self.__encounter)
2193 raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
2194 self.__encounter['pk_encounter'],
2195 encounter['pk_encounter']
2196 ))
2197
2198 prev_enc = self.__encounter
2199 encounter.lock(exclusive = False) # lock new
2200 self.__encounter = encounter
2201 prev_enc.unlock(exclusive = False) # unlock old
2202 gmDispatcher.send('current_encounter_switched')
2203
2204 return True
2205
2206 current_encounter = property(_get_current_encounter, _set_current_encounter)
2207 active_encounter = property(_get_current_encounter, _set_current_encounter)
2208
2209 #--------------------------------------------------------
2211 _log.debug('setting up active encounter for identity [%s]', self.pk_patient)
2212
2213 # log access to patient record (HIPAA, for example)
2214 _delayed_execute(self.log_access, action = 'pulling chart for identity [%s]' % self.pk_patient)
2215
2216 # cleanup (not async, because we don't want recent encounters
2217 # to become the active one just because they are recent)
2218 self.remove_empty_encounters()
2219
2220 # activate very recent encounter if available
2221 if self.__activate_very_recent_encounter():
2222 return
2223
2224 fairly_recent_enc = self.__get_fairly_recent_encounter()
2225
2226 # create new encounter for the time being
2227 self.start_new_encounter()
2228
2229 if fairly_recent_enc is None:
2230 return
2231
2232 # but check whether user wants to continue a "fairly recent" one
2233 gmDispatcher.send (
2234 signal = 'ask_for_encounter_continuation',
2235 new_encounter = self.__encounter,
2236 fairly_recent_encounter = fairly_recent_enc
2237 )
2238
2239 #------------------------------------------------------------------
2241 """Try to attach to a "very recent" encounter if there is one.
2242
2243 returns:
2244 False: no "very recent" encounter
2245 True: success
2246 """
2247 cfg_db = gmCfg.cCfgSQL()
2248 min_ttl = cfg_db.get2 (
2249 option = 'encounter.minimum_ttl',
2250 workplace = _here.active_workplace,
2251 bias = 'user',
2252 default = '1 hour 30 minutes'
2253 )
2254 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2255 SELECT pk_encounter
2256 FROM clin.v_most_recent_encounters
2257 WHERE
2258 pk_patient = %s
2259 and
2260 last_affirmed > (now() - %s::interval)
2261 ORDER BY
2262 last_affirmed DESC
2263 LIMIT 1
2264 )"""
2265 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}], get_col_idx = True)
2266
2267 # none found
2268 if len(enc_rows) == 0:
2269 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
2270 return False
2271
2272 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0]['pk_encounter'])
2273
2274 # attach to existing
2275 self.current_encounter = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2276 return True
2277
2278 #------------------------------------------------------------------
2280 cfg_db = gmCfg.cCfgSQL()
2281 min_ttl = cfg_db.get2 (
2282 option = 'encounter.minimum_ttl',
2283 workplace = _here.active_workplace,
2284 bias = 'user',
2285 default = '1 hour 30 minutes'
2286 )
2287 max_ttl = cfg_db.get2 (
2288 option = 'encounter.maximum_ttl',
2289 workplace = _here.active_workplace,
2290 bias = 'user',
2291 default = '6 hours'
2292 )
2293
2294 # do we happen to have a "fairly recent" candidate ?
2295 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2296 SELECT pk_encounter
2297 FROM clin.v_most_recent_encounters
2298 WHERE
2299 pk_patient=%s
2300 AND
2301 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2302 ORDER BY
2303 last_affirmed DESC
2304 LIMIT 1
2305 )"""
2306 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2307
2308 # none found
2309 if len(enc_rows) == 0:
2310 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2311 return None
2312
2313 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2314 return gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2315
2316 # #------------------------------------------------------------------
2317 # def __check_for_fairly_recent_encounter(self):
2318 #
2319 # cfg_db = gmCfg.cCfgSQL()
2320 # min_ttl = cfg_db.get2 (
2321 # option = u'encounter.minimum_ttl',
2322 # workplace = _here.active_workplace,
2323 # bias = u'user',
2324 # default = u'1 hour 30 minutes'
2325 # )
2326 # max_ttl = cfg_db.get2 (
2327 # option = u'encounter.maximum_ttl',
2328 # workplace = _here.active_workplace,
2329 # bias = u'user',
2330 # default = u'6 hours'
2331 # )
2332 #
2333 # # do we happen to have a "fairly recent" candidate ?
2334 # cmd = gmEMRStructItems.SQL_get_encounters % u"""pk_encounter = (
2335 # SELECT pk_encounter
2336 # FROM clin.v_most_recent_encounters
2337 # WHERE
2338 # pk_patient=%s
2339 # AND
2340 # last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2341 # ORDER BY
2342 # last_affirmed DESC
2343 # LIMIT 1
2344 # )"""
2345 # enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2346 #
2347 # # none found
2348 # if len(enc_rows) == 0:
2349 # _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2350 # return
2351 #
2352 # _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2353 # fairly_recent_enc = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2354 # gmDispatcher.send(u'ask_for_encounter_continuation', current = self.__encounter, fairly_recent_encounter = fairly_recent_enc)
2355
2356 # #------------------------------------------------------------------
2357 # def __activate_fairly_recent_encounter(self, allow_user_interaction=True):
2358 # """Try to attach to a "fairly recent" encounter if there is one.
2359 #
2360 # returns:
2361 # False: no "fairly recent" encounter, create new one
2362 # True: success
2363 # """
2364 # if _func_ask_user is None:
2365 # _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
2366 # return False
2367 #
2368 # if not allow_user_interaction:
2369 # _log.exception('user interaction not desired, not looking for fairly recent encounter')
2370 # return False
2371 #
2372 # cfg_db = gmCfg.cCfgSQL()
2373 # min_ttl = cfg_db.get2 (
2374 # option = u'encounter.minimum_ttl',
2375 # workplace = _here.active_workplace,
2376 # bias = u'user',
2377 # default = u'1 hour 30 minutes'
2378 # )
2379 # max_ttl = cfg_db.get2 (
2380 # option = u'encounter.maximum_ttl',
2381 # workplace = _here.active_workplace,
2382 # bias = u'user',
2383 # default = u'6 hours'
2384 # )
2385 # cmd = u"""
2386 # SELECT pk_encounter
2387 # FROM clin.v_most_recent_encounters
2388 # WHERE
2389 # pk_patient=%s
2390 # AND
2391 # last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2392 # ORDER BY
2393 # last_affirmed DESC"""
2394 # enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
2395 # # none found
2396 # if len(enc_rows) == 0:
2397 # _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2398 # return False
2399 #
2400 # _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
2401 #
2402 # encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
2403 # # ask user whether to attach or not
2404 # cmd = u"""
2405 # SELECT title, firstnames, lastnames, gender, dob
2406 # FROM dem.v_all_persons WHERE pk_identity=%s"""
2407 # pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
2408 # pat = pats[0]
2409 # pat_str = u'%s %s %s (%s), %s [#%s]' % (
2410 # gmTools.coalesce(pat[0], u'')[:5],
2411 # pat[1][:15],
2412 # pat[2][:15],
2413 # pat[3],
2414 # gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
2415 # self.pk_patient
2416 # )
2417 # msg = _(
2418 # '%s\n'
2419 # '\n'
2420 # "This patient's chart was worked on only recently:\n"
2421 # '\n'
2422 # ' %s %s - %s (%s)\n'
2423 # '\n'
2424 # ' Reason for Encounter:\n'
2425 # ' %s\n'
2426 # ' Assessment of Encounter:\n'
2427 # ' %s\n'
2428 # '\n'
2429 # 'Do you want to continue that consultation\n'
2430 # 'or do you want to start a new one ?\n'
2431 # ) % (
2432 # pat_str,
2433 # gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
2434 # gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
2435 # encounter['l10n_type'],
2436 # gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
2437 # gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
2438 # )
2439 # attach = False
2440 # try:
2441 # attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
2442 # except:
2443 # _log.exception('cannot ask user for guidance, not attaching to existing encounter')
2444 # return False
2445 # if not attach:
2446 # return False
2447 #
2448 # # attach to existing
2449 # self.current_encounter = encounter
2450 # _log.debug('"fairly recent" encounter re-activated')
2451 # return True
2452
2453 #------------------------------------------------------------------
2455 cfg_db = gmCfg.cCfgSQL()
2456 enc_type = cfg_db.get2 (
2457 option = 'encounter.default_type',
2458 workplace = _here.active_workplace,
2459 bias = 'user'
2460 )
2461 if enc_type is None:
2462 enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
2463 if enc_type is None:
2464 enc_type = 'in surgery'
2465 enc = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
2466 enc['pk_org_unit'] = _here['pk_org_unit']
2467 enc.save()
2468 self.current_encounter = enc
2469 _log.debug('new encounter [%s] activated', enc['pk_encounter'])
2470
2471 #------------------------------------------------------------------
2472 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False, order_by=None, max_encounters=None):
2473 """Retrieves patient's encounters.
2474
2475 id_list - PKs of encounters to fetch
2476 since - initial date for encounter items, DateTime instance
2477 until - final date for encounter items, DateTime instance
2478 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
2479 issues - PKs of the health issues the encounters belong to (many-to-many relation)
2480 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
2481
2482 NOTE: if you specify *both* issues and episodes
2483 you will get the *aggregate* of all encounters even
2484 if the episodes all belong to the health issues listed.
2485 IOW, the issues broaden the episode list rather than
2486 the episode list narrowing the episodes-from-issues
2487 list.
2488 Rationale: If it was the other way round it would be
2489 redundant to specify the list of issues at all.
2490 """
2491 # if issues are given, translate them to their episodes
2492 if (issues is not None) and (len(issues) > 0):
2493 # - find episodes corresponding to the health issues in question
2494 cmd = "SELECT distinct pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue in %(issue_pks)s AND pk_patient = %(pat)s"
2495 args = {'issue_pks': tuple(issues), 'pat': self.pk_patient}
2496 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2497 epis4issues_pks = [ r['pk_episode'] for r in rows ]
2498 if episodes is None:
2499 episodes = []
2500 episodes.extend(epis4issues_pks)
2501
2502 if (episodes is not None) and (len(episodes) > 0):
2503 # since the episodes to filter by belong to the patient in question so will
2504 # the encounters found with them - hence we don't need a WHERE on the patient ...
2505 # but, better safe than sorry ...
2506 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2507 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)"
2508 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2509 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2510 if id_list is None:
2511 id_list = []
2512 id_list.extend(encs4epis_pks)
2513
2514 where_parts = ['c_vpe.pk_patient = %(pat)s']
2515 args = {'pat': self.pk_patient}
2516
2517 if skip_empty:
2518 where_parts.append("""NOT (
2519 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
2520 AND
2521 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
2522 AND
2523 NOT EXISTS (
2524 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
2525 UNION ALL
2526 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
2527 ))""")
2528
2529 if since is not None:
2530 where_parts.append('c_vpe.started >= %(start)s')
2531 args['start'] = since
2532
2533 if until is not None:
2534 where_parts.append('c_vpe.last_affirmed <= %(end)s')
2535 args['end'] = since
2536
2537 if (id_list is not None) and (len(id_list) > 0):
2538 where_parts.append('c_vpe.pk_encounter IN %(enc_pks)s')
2539 args['enc_pks'] = tuple(id_list)
2540
2541 if order_by is None:
2542 order_by = 'c_vpe.started'
2543
2544 if max_encounters is None:
2545 limit = ''
2546 else:
2547 limit = 'LIMIT %s' % max_encounters
2548
2549 cmd = """
2550 SELECT * FROM clin.v_pat_encounters c_vpe
2551 WHERE
2552 %s
2553 ORDER BY %s %s
2554 """ % (
2555 ' AND '.join(where_parts),
2556 order_by,
2557 limit
2558 )
2559 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2560 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
2561
2562 # we've got the encounters, start filtering
2563 filtered_encounters = []
2564 filtered_encounters.extend(encounters)
2565
2566 if (episodes is not None) and (len(episodes) > 0):
2567 # since the episodes to filter by belong to the patient in question so will
2568 # the encounters found with them - hence we don't need a WHERE on the patient ...
2569 # but, better safe than sorry ...
2570 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2571 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)"
2572 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2573 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2574 filtered_encounters = [ enc for enc in filtered_encounters if enc['pk_encounter'] in encs4epis_pks ]
2575
2576 return filtered_encounters
2577
2578 #--------------------------------------------------------
2580 """Retrieves first encounter for a particular issue and/or episode.
2581
2582 issue_id - First encounter associated health issue
2583 episode - First encounter associated episode
2584 """
2585 if issue_id is None:
2586 issues = None
2587 else:
2588 issues = [issue_id]
2589
2590 if episode_id is None:
2591 episodes = None
2592 else:
2593 episodes = [episode_id]
2594
2595 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started', max_encounters = 1)
2596 if len(encounters) == 0:
2597 return None
2598
2599 return encounters[0]
2600
2601 first_encounter = property(get_first_encounter, lambda x:x)
2602
2603 #--------------------------------------------------------
2605 args = {'pat': self.pk_patient}
2606 cmd = """
2607 SELECT MIN(earliest) FROM (
2608 (
2609 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
2610
2611 ) UNION ALL (
2612
2613 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
2614
2615 ) UNION ALL (
2616
2617 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2618
2619 ) UNION ALL (
2620
2621 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2622
2623 ) UNION ALL (
2624
2625 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2626
2627 ) UNION ALL (
2628
2629 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2630
2631 ) UNION ALL (
2632
2633 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2634
2635 )
2636 ) AS candidates"""
2637 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2638 return rows[0][0]
2639
2640 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2641
2642 #--------------------------------------------------------
2644 encounters = self.get_encounters(order_by = 'started DESC', max_encounters = 1)
2645 if len(encounters) == 0:
2646 return None
2647 return encounters[0]['last_affirmed']
2648
2649 most_recent_care_date = property(get_most_recent_care_date)
2650
2651 #--------------------------------------------------------
2653 """Retrieves last encounter for a concrete issue and/or episode
2654
2655 issue_id - Last encounter associated health issue
2656 episode_id - Last encounter associated episode
2657 """
2658 if issue_id is None:
2659 issues = None
2660 else:
2661 issues = [issue_id]
2662
2663 if episode_id is None:
2664 episodes = None
2665 else:
2666 episodes = [episode_id]
2667
2668 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started DESC', max_encounters = 1)
2669 if len(encounters) == 0:
2670 return None
2671
2672 return encounters[0]
2673
2674 last_encounter = property(get_last_encounter, lambda x:x)
2675
2676 #------------------------------------------------------------------
2678 args = {'pat': self.pk_patient, 'range': cover_period}
2679 where_parts = ['pk_patient = %(pat)s']
2680 if cover_period is not None:
2681 where_parts.append('last_affirmed > now() - %(range)s')
2682
2683 cmd = """
2684 SELECT l10n_type, count(1) AS frequency
2685 FROM clin.v_pat_encounters
2686 WHERE
2687 %s
2688 GROUP BY l10n_type
2689 ORDER BY frequency DESC
2690 """ % ' AND '.join(where_parts)
2691 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2692 return rows
2693
2694 #------------------------------------------------------------------
2696
2697 args = {'pat': self.pk_patient}
2698
2699 if (issue_id is None) and (episode_id is None):
2700 cmd = """
2701 SELECT * FROM clin.v_pat_encounters
2702 WHERE pk_patient = %(pat)s
2703 ORDER BY started DESC
2704 LIMIT 2
2705 """
2706 else:
2707 where_parts = []
2708
2709 if issue_id is not None:
2710 where_parts.append('pk_health_issue = %(issue)s')
2711 args['issue'] = issue_id
2712
2713 if episode_id is not None:
2714 where_parts.append('pk_episode = %(epi)s')
2715 args['epi'] = episode_id
2716
2717 cmd = """
2718 SELECT *
2719 FROM clin.v_pat_encounters
2720 WHERE
2721 pk_patient = %%(pat)s
2722 AND
2723 pk_encounter IN (
2724 SELECT distinct pk_encounter
2725 FROM clin.v_narrative
2726 WHERE
2727 %s
2728 )
2729 ORDER BY started DESC
2730 LIMIT 2
2731 """ % ' AND '.join(where_parts)
2732
2733 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2734
2735 if len(rows) == 0:
2736 return None
2737
2738 # just one encounter within the above limits
2739 if len(rows) == 1:
2740 # is it the current encounter ?
2741 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2742 # yes
2743 return None
2744 # no
2745 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2746
2747 # more than one encounter
2748 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2749 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2750
2751 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2752
2753 last_but_one_encounter = property(get_last_but_one_encounter, lambda x:x)
2754
2755 #------------------------------------------------------------------
2757 _log.debug('removing empty encounters for pk_identity [%s]', self.pk_patient)
2758 cfg_db = gmCfg.cCfgSQL()
2759 ttl = cfg_db.get2 (
2760 option = 'encounter.ttl_if_empty',
2761 workplace = _here.active_workplace,
2762 bias = 'user',
2763 default = '1 week'
2764 )
2765 # # FIXME: this should be done async
2766 cmd = "SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
2767 args = {'pat': self.pk_patient, 'ttl': ttl}
2768 try:
2769 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
2770 except:
2771 _log.exception('error deleting empty encounters')
2772 return False
2773
2774 if not rows[0][0]:
2775 _log.debug('no encounters deleted (less than 2 exist)')
2776
2777 return True
2778
2779 #------------------------------------------------------------------
2780 # API: measurements / test results
2781 #------------------------------------------------------------------
2783 return gmPathLab.get_most_recent_results_for_patient (
2784 no_of_results = no_of_results,
2785 patient = self.pk_patient
2786 )
2787
2788 #------------------------------------------------------------------
2789 - def get_most_recent_results_in_loinc_group(self, loincs=None, no_of_results=1, consider_meta_type=False):
2790 return gmPathLab.get_most_recent_results_in_loinc_group (
2791 loincs = loincs,
2792 no_of_results = no_of_results,
2793 consider_meta_type = consider_meta_type,
2794 patient = self.pk_patient
2795 )
2796
2797 #------------------------------------------------------------------
2799 return gmPathLab.get_most_recent_results_for_test_type (
2800 test_type = test_type,
2801 no_of_results = no_of_results,
2802 patient = self.pk_patient
2803 )
2804
2805 #------------------------------------------------------------------
2807 return gmPathLab.get_most_recent_result_for_test_types (
2808 pk_test_types = pk_test_types,
2809 pk_patient = self.pk_patient
2810 )
2811
2812 #------------------------------------------------------------------
2813 - def get_result_at_timestamp(self, timestamp=None, test_type=None, loinc=None, tolerance_interval='12 hours'):
2814 return gmPathLab.get_result_at_timestamp (
2815 timestamp = timestamp,
2816 test_type = test_type,
2817 loinc = loinc,
2818 tolerance_interval = tolerance_interval,
2819 patient = self.pk_patient
2820 )
2821
2822 #------------------------------------------------------------------
2824 return gmPathLab.get_results_for_day (
2825 timestamp = timestamp,
2826 patient = self.pk_patient,
2827 order_by = order_by
2828 )
2829
2830 #------------------------------------------------------------------
2832 return gmPathLab.get_results_for_issue (
2833 pk_health_issue = pk_health_issue,
2834 order_by = order_by
2835 )
2836
2837 #------------------------------------------------------------------
2840
2841 #------------------------------------------------------------------
2843 if order_by is None:
2844 order_by = ''
2845 else:
2846 order_by = 'ORDER BY %s' % order_by
2847 cmd = """
2848 SELECT * FROM clin.v_test_results
2849 WHERE
2850 pk_patient = %%(pat)s
2851 AND
2852 reviewed IS FALSE
2853 %s""" % order_by
2854 args = {'pat': self.pk_patient}
2855 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2856 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2857
2858 #------------------------------------------------------------------
2859 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
2861 """Retrieve data about test types for which this patient has results."""
2862 if order_by is None:
2863 order_by = ''
2864 else:
2865 order_by = 'ORDER BY %s' % order_by
2866
2867 if unique_meta_types:
2868 cmd = """
2869 SELECT * FROM clin.v_test_types c_vtt
2870 WHERE c_vtt.pk_test_type IN (
2871 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
2872 FROM clin.v_test_results c_vtr1
2873 WHERE
2874 c_vtr1.pk_patient = %%(pat)s
2875 AND
2876 c_vtr1.pk_meta_test_type IS NOT NULL
2877 UNION ALL
2878 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
2879 FROM clin.v_test_results c_vtr2
2880 WHERE
2881 c_vtr2.pk_patient = %%(pat)s
2882 AND
2883 c_vtr2.pk_meta_test_type IS NULL
2884 )
2885 %s""" % order_by
2886 else:
2887 cmd = """
2888 SELECT * FROM clin.v_test_types c_vtt
2889 WHERE c_vtt.pk_test_type IN (
2890 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
2891 FROM clin.v_test_results c_vtr
2892 WHERE c_vtr.pk_patient = %%(pat)s
2893 )
2894 %s""" % order_by
2895
2896 args = {'pat': self.pk_patient}
2897 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2898 return [ gmPathLab.cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
2899
2900 #------------------------------------------------------------------
2902 """Get the dates for which we have results."""
2903 where_parts = ['pk_patient = %(pat)s']
2904 args = {'pat': self.pk_patient}
2905
2906 if tests is not None:
2907 where_parts.append('pk_test_type IN %(tests)s')
2908 args['tests'] = tuple(tests)
2909
2910 cmd = """
2911 SELECT DISTINCT ON (clin_when_day)
2912 clin_when_day,
2913 is_reviewed
2914 FROM (
2915 SELECT
2916 date_trunc('day', clin_when)
2917 AS clin_when_day,
2918 bool_and(reviewed)
2919 AS is_reviewed
2920 FROM (
2921 SELECT
2922 clin_when,
2923 reviewed,
2924 pk_patient,
2925 pk_test_result
2926 FROM clin.v_test_results
2927 WHERE %s
2928 )
2929 AS patient_tests
2930 GROUP BY clin_when_day
2931 )
2932 AS grouped_days
2933 ORDER BY clin_when_day %s
2934 """ % (
2935 ' AND '.join(where_parts),
2936 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
2937 )
2938 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2939 return rows
2940
2941 #------------------------------------------------------------------
2943 """Get the issues/episodes for which we have results."""
2944 where_parts = ['pk_patient = %(pat)s']
2945 args = {'pat': self.pk_patient}
2946
2947 if tests is not None:
2948 where_parts.append('pk_test_type IN %(tests)s')
2949 args['tests'] = tuple(tests)
2950 where = ' AND '.join(where_parts)
2951 cmd = """
2952 SELECT * FROM ((
2953 -- issues, each including all it"s episodes
2954 SELECT
2955 health_issue AS problem,
2956 pk_health_issue,
2957 NULL::integer AS pk_episode,
2958 1 AS rank
2959 FROM clin.v_test_results
2960 WHERE pk_health_issue IS NOT NULL AND %s
2961 GROUP BY pk_health_issue, problem
2962 ) UNION ALL (
2963 -- episodes w/o issue
2964 SELECT
2965 episode AS problem,
2966 NULL::integer AS pk_health_issue,
2967 pk_episode,
2968 2 AS rank
2969 FROM clin.v_test_results
2970 WHERE pk_health_issue IS NULL AND %s
2971 GROUP BY pk_episode, problem
2972 )) AS grouped_union
2973 ORDER BY rank, problem
2974 """ % (where, where)
2975 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2976 return rows
2977
2978 #------------------------------------------------------------------
2980 return gmPathLab.get_test_results (
2981 pk_patient = self.pk_patient,
2982 encounters = encounters,
2983 episodes = episodes,
2984 order_by = order_by
2985 )
2986 #------------------------------------------------------------------
2987 - def get_test_results_by_date(self, encounter=None, episodes=None, tests=None, reverse_chronological=True):
2988
2989 where_parts = ['pk_patient = %(pat)s']
2990 args = {'pat': self.pk_patient}
2991
2992 if tests is not None:
2993 where_parts.append('pk_test_type IN %(tests)s')
2994 args['tests'] = tuple(tests)
2995
2996 if encounter is not None:
2997 where_parts.append('pk_encounter = %(enc)s')
2998 args['enc'] = encounter
2999
3000 if episodes is not None:
3001 where_parts.append('pk_episode IN %(epis)s')
3002 args['epis'] = tuple(episodes)
3003
3004 cmd = """
3005 SELECT * FROM clin.v_test_results
3006 WHERE %s
3007 ORDER BY clin_when %s, pk_episode, unified_name
3008 """ % (
3009 ' AND '.join(where_parts),
3010 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
3011 )
3012 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3013
3014 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
3015
3016 return tests
3017 #------------------------------------------------------------------
3018 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
3019
3020 try:
3021 epi = int(episode)
3022 except:
3023 epi = episode['pk_episode']
3024
3025 try:
3026 type = int(type)
3027 except:
3028 type = type['pk_test_type']
3029
3030 tr = gmPathLab.create_test_result (
3031 link_obj = link_obj,
3032 encounter = self.current_encounter['pk_encounter'],
3033 episode = epi,
3034 type = type,
3035 intended_reviewer = intended_reviewer,
3036 val_num = val_num,
3037 val_alpha = val_alpha,
3038 unit = unit
3039 )
3040
3041 return tr
3042
3043 #------------------------------------------------------------------
3045 where = 'pk_org_unit IN (%s)' % """
3046 SELECT DISTINCT fk_org_unit FROM clin.test_org WHERE pk IN (
3047 SELECT DISTINCT pk_test_org FROM clin.v_test_results where pk_patient = %(pat)s
3048 )"""
3049 args = {'pat': self.pk_patient}
3050 cmd = gmOrganization._SQL_get_org_unit % where
3051 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3052 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
3053
3054 #------------------------------------------------------------------
3056
3057 measured_gfr = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
3058 crea = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1)
3059
3060 if (measured_gfr is None) and (crea is None):
3061 return None
3062
3063 if (measured_gfr is not None) and (crea is None):
3064 return measured_gfr
3065
3066 # from here, Crea cannot be None anymore
3067 if measured_gfr is None:
3068 eGFR = self.calculator.eGFR
3069 if eGFR.numeric_value is None:
3070 return crea
3071 return eGFR
3072
3073 # from here, measured_gfr cannot be None anymore, either
3074 two_weeks = pydt.timedelta(weeks = 2)
3075 gfr_too_old = (crea['clin_when'] - measured_gfr['clin_when']) > two_weeks
3076 if not gfr_too_old:
3077 return measured_gfr
3078
3079 # from here, measured_gfr is considered too
3080 # old, so attempt a more timely estimate
3081 eGFR = self.calculator.eGFR
3082 if eGFR.numeric_value is None:
3083 # return crea since we cannot get a
3084 # better estimate for some reason
3085 return crea
3086
3087 return eGFR
3088
3089 best_gfr_or_crea = property(_get_best_gfr_or_crea, lambda x:x)
3090
3091 #------------------------------------------------------------------
3094
3095 bmi = property(_get_bmi, lambda x:x)
3096
3097 #------------------------------------------------------------------
3099 return gmAutoHints.get_hints_for_patient(pk_identity = self.pk_patient, pk_encounter = self.current_encounter['pk_encounter'])
3100
3101 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
3102
3103 #------------------------------------------------------------------
3104 #------------------------------------------------------------------
3105 #------------------------------------------------------------------
3107 # FIXME: verify that it is our patient ? ...
3108 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
3109 return req
3110 #------------------------------------------------------------------
3112 if encounter_id is None:
3113 encounter_id = self.current_encounter['pk_encounter']
3114 status, data = gmPathLab.create_lab_request(
3115 lab=lab,
3116 req_id=req_id,
3117 pat_id=self.pk_patient,
3118 encounter_id=encounter_id,
3119 episode_id=episode_id
3120 )
3121 if not status:
3122 _log.error(str(data))
3123 return None
3124 return data
3125
3126 #============================================================
3127 # main
3128 #------------------------------------------------------------
3129 if __name__ == "__main__":
3130
3131 if len(sys.argv) == 1:
3132 sys.exit()
3133
3134 if sys.argv[1] != 'test':
3135 sys.exit()
3136
3137 from Gnumed.pycommon import gmLog2
3138
3139 from Gnumed.business import gmPraxis
3140 branches = gmPraxis.get_praxis_branches()
3141 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
3142
3147
3148 set_delayed_executor(_do_delayed)
3149
3150 #-----------------------------------------
3152 emr = cClinicalRecord(aPKey=1)
3153 state = emr.allergy_state
3154 print("allergy state is:", state)
3155
3156 print("setting state to 0")
3157 emr.allergy_state = 0
3158
3159 print("setting state to None")
3160 emr.allergy_state = None
3161
3162 print("setting state to 'abc'")
3163 emr.allergy_state = 'abc'
3164
3165 #-----------------------------------------
3167 emr = cClinicalRecord(aPKey = 6)
3168 rows = emr.get_test_types_for_results(unique_meta_types = True)
3169 print("test result names:", len(rows))
3170 # for row in rows:
3171 # print row
3172
3173 #-----------------------------------------
3175 emr = cClinicalRecord(aPKey=12)
3176 rows = emr.get_dates_for_results()
3177 print("test result dates:")
3178 for row in rows:
3179 print(row)
3180
3181 #-----------------------------------------
3183 emr = cClinicalRecord(aPKey=12)
3184 rows, idx = emr.get_measurements_by_date()
3185 print("test results:")
3186 for row in rows:
3187 print(row)
3188
3189 #-----------------------------------------
3191 emr = cClinicalRecord(aPKey=12)
3192 tests = emr.get_test_results_by_date()
3193 print("test results:")
3194 for test in tests:
3195 print(test)
3196
3197 #-----------------------------------------
3199 emr = cClinicalRecord(aPKey=12)
3200 for key, item in emr.get_statistics().items():
3201 print(key, ":", item)
3202
3203 #-----------------------------------------
3205 emr = cClinicalRecord(aPKey=12)
3206
3207 probs = emr.get_problems()
3208 print("normal probs (%s):" % len(probs))
3209 for p in probs:
3210 print('%s (%s)' % (p['problem'], p['type']))
3211
3212 probs = emr.get_problems(include_closed_episodes=True)
3213 print("probs + closed episodes (%s):" % len(probs))
3214 for p in probs:
3215 print('%s (%s)' % (p['problem'], p['type']))
3216
3217 probs = emr.get_problems(include_irrelevant_issues=True)
3218 print("probs + issues (%s):" % len(probs))
3219 for p in probs:
3220 print('%s (%s)' % (p['problem'], p['type']))
3221
3222 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
3223 print("probs + issues + epis (%s):" % len(probs))
3224 for p in probs:
3225 print('%s (%s)' % (p['problem'], p['type']))
3226
3227 #-----------------------------------------
3229 emr = cClinicalRecord(aPKey=12)
3230 tr = emr.add_test_result (
3231 episode = 1,
3232 intended_reviewer = 1,
3233 type = 1,
3234 val_num = 75,
3235 val_alpha = 'somewhat obese',
3236 unit = 'kg'
3237 )
3238 print(tr)
3239
3240 #-----------------------------------------
3244
3245 #-----------------------------------------
3247 emr = cClinicalRecord(aPKey=12)
3248 print(emr.get_last_encounter(issue_id=2))
3249 print(emr.get_last_but_one_encounter(issue_id=2))
3250
3251 #-----------------------------------------
3253 emr = cClinicalRecord(aPKey = 5)
3254 print(emr.get_first_encounter(episode_id = 1638))
3255 print(emr.get_last_encounter(episode_id = 1638))
3256
3257 #-----------------------------------------
3259 emr = cClinicalRecord(aPKey = 12)
3260 for issue in emr.health_issues:
3261 print(issue['description'])
3262
3263 #-----------------------------------------
3268
3269 #-----------------------------------------
3274
3275 #-----------------------------------------
3277 emr = cClinicalRecord(aPKey=12)
3278 for med in emr.abused_substances:
3279 print(med.format(single_line = True))
3280
3281 #-----------------------------------------
3283 emr = cClinicalRecord(aPKey = 12)
3284 print(emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), product_name = sys.argv[2]))
3285
3286 #-----------------------------------------
3288 emr = cClinicalRecord(aPKey = 12)
3289 for journal_line in emr.get_as_journal():
3290 #print journal_line.keys()
3291 print('%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line)
3292 print("")
3293
3294 #-----------------------------------------
3298
3299 #-----------------------------------------
3301 emr = cClinicalRecord(aPKey=12)
3302 print("episodes:", emr.episodes)
3303 print("unlinked:", emr.unlinked_episodes)
3304
3305 #-----------------------------------------
3307 emr = cClinicalRecord(aPKey=12)
3308 from Gnumed.business.gmPerson import cPatient
3309 pat = cPatient(aPK_obj = 12)
3310 print(emr.format_as_journal(left_margin = 1, patient = pat))
3311
3312 #-----------------------------------------
3314 emr = cClinicalRecord(aPKey=12)
3315 #print emr.is_or_was_smoker
3316 smoking, details = emr.smoking_status
3317 print('status:', smoking)
3318 print('details:')
3319 print(details)
3320 emr.smoking_status = (True, {'comment': '2', 'last_confirmed': gmDateTime.pydt_now_here()})
3321 print(emr.smoking_status)
3322 print(emr.alcohol_status)
3323 print(emr.drugs_status)
3324
3325 #-----------------------------------------
3326
3327 #test_allergy_state()
3328 #test_is_allergic_to()
3329
3330 #test_get_test_names()
3331 #test_get_dates_for_results()
3332 #test_get_measurements()
3333 #test_get_test_results_by_date()
3334 #test_get_statistics()
3335 #test_get_problems()
3336 #test_add_test_result()
3337 #test_get_most_recent_episode()
3338 #test_get_almost_recent_encounter()
3339 #test_get_meds()
3340 #test_get_as_journal()
3341 #test_get_most_recent()
3342 #test_episodes()
3343 #test_format_as_journal()
3344 #test_smoking()
3345 #test_get_abuses()
3346 #test_get_encounters()
3347 #test_get_issues()
3348 #test_get_dx()
3349
3350 emr = cClinicalRecord(aPKey = 12)
3351
3352 # # Vacc regimes
3353 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
3354 # print '\nVaccination regimes: '
3355 # for a_regime in vacc_regimes:
3356 # pass
3357 # #print a_regime
3358 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
3359 # #print vacc_regime
3360
3361 # # vaccination regimes and vaccinations for regimes
3362 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
3363 # print 'Vaccinations for the regime:'
3364 # for a_scheduled_vacc in scheduled_vaccs:
3365 # pass
3366 # #print ' %s' %(a_scheduled_vacc)
3367
3368 # # vaccination next shot and booster
3369 v1 = emr.vaccinations
3370 print(v1)
3371 v2 = gmVaccination.get_vaccinations(pk_identity = 12, return_pks = True)
3372 print(v2)
3373 for v in v1:
3374 if v['pk_vaccination'] not in v2:
3375 print('ERROR')
3376
3377 # for a_vacc in vaccinations:
3378 # print '\nVaccination %s , date: %s, booster: %s, seq no: %s' %(a_vacc['batch_no'], a_vacc['date'].strftime('%Y-%m-%d'), a_vacc['is_booster'], a_vacc['seq_no'])
3379
3380 # # first and last encounters
3381 # first_encounter = emr.get_first_encounter(issue_id = 1)
3382 # print '\nFirst encounter: ' + str(first_encounter)
3383 # last_encounter = emr.get_last_encounter(episode_id = 1)
3384 # print '\nLast encounter: ' + str(last_encounter)
3385 # print ''
3386
3387 #dump = record.get_missing_vaccinations()
3388 #f = io.open('vaccs.lst', 'wb')
3389 #if dump is not None:
3390 # print "=== due ==="
3391 # f.write(u"=== due ===\n")
3392 # for row in dump['due']:
3393 # print row
3394 # f.write(repr(row))
3395 # f.write(u'\n')
3396 # print "=== overdue ==="
3397 # f.write(u"=== overdue ===\n")
3398 # for row in dump['overdue']:
3399 # print row
3400 # f.write(repr(row))
3401 # f.write(u'\n')
3402 #f.close()
3403
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Jul 28 01:55:29 2019 | http://epydoc.sourceforge.net |