| 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 -- CHANGE BACK IN V23:
933 --FROM clin.v_narrative4search vn4s
934 FROM v_narrative4search vn4s
935 WHERE
936 pk_patient = %(pat)s and
937 vn4s.narrative ~ %(term)s
938 order by
939 encounter_started
940 """ # case sensitive
941 #rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}])
942 queries = [
943 {'cmd': gmClinNarrative._VIEW_clin_v_narrative4search},
944 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
945 ]
946 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
947 return rows
948
949 #--------------------------------------------------------
951 fields = [
952 'age',
953 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
954 'modified_by',
955 'clin_when',
956 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
957 'pk_item',
958 'pk_encounter',
959 'pk_episode',
960 'pk_health_issue',
961 'src_table'
962 ]
963 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
964 # handle constraint conditions
965 where_snippets = []
966 params = {}
967 where_snippets.append('pk_patient=%(pat_id)s')
968 params['pat_id'] = self.pk_patient
969 if not since is None:
970 where_snippets.append('clin_when >= %(since)s')
971 params['since'] = since
972 if not until is None:
973 where_snippets.append('clin_when <= %(until)s')
974 params['until'] = until
975 # FIXME: these are interrelated, eg if we constrain encounter
976 # we automatically constrain issue/episode, so handle that,
977 # encounters
978 if not encounters is None and len(encounters) > 0:
979 params['enc'] = encounters
980 if len(encounters) > 1:
981 where_snippets.append('fk_encounter in %(enc)s')
982 else:
983 where_snippets.append('fk_encounter=%(enc)s')
984 # episodes
985 if not episodes is None and len(episodes) > 0:
986 params['epi'] = episodes
987 if len(episodes) > 1:
988 where_snippets.append('fk_episode in %(epi)s')
989 else:
990 where_snippets.append('fk_episode=%(epi)s')
991 # health issues
992 if not issues is None and len(issues) > 0:
993 params['issue'] = issues
994 if len(issues) > 1:
995 where_snippets.append('fk_health_issue in %(issue)s')
996 else:
997 where_snippets.append('fk_health_issue=%(issue)s')
998
999 where_clause = ' and '.join(where_snippets)
1000 order_by = 'order by src_table, age'
1001 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
1002
1003 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
1004 if rows is None:
1005 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
1006 return None
1007
1008 # -- sort the data --
1009 # FIXME: by issue/encounter/episode, eg formatting
1010 # aggregate by src_table for item retrieval
1011 items_by_table = {}
1012 for item in rows:
1013 src_table = item[view_col_idx['src_table']]
1014 pk_item = item[view_col_idx['pk_item']]
1015 if src_table not in items_by_table:
1016 items_by_table[src_table] = {}
1017 items_by_table[src_table][pk_item] = item
1018
1019 # get mapping for issue/episode IDs
1020 issues = self.get_health_issues()
1021 issue_map = {}
1022 for issue in issues:
1023 issue_map[issue['pk_health_issue']] = issue['description']
1024 episodes = self.get_episodes()
1025 episode_map = {}
1026 for episode in episodes:
1027 episode_map[episode['pk_episode']] = episode['description']
1028 emr_data = {}
1029 # get item data from all source tables
1030 ro_conn = self._conn_pool.GetConnection('historica')
1031 curs = ro_conn.cursor()
1032 for src_table in items_by_table.keys():
1033 item_ids = items_by_table[src_table].keys()
1034 # we don't know anything about the columns of
1035 # the source tables but, hey, this is a dump
1036 if len(item_ids) == 0:
1037 _log.info('no items in table [%s] ?!?' % src_table)
1038 continue
1039 elif len(item_ids) == 1:
1040 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
1041 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
1042 _log.error('cannot load items from table [%s]' % src_table)
1043 # skip this table
1044 continue
1045 elif len(item_ids) > 1:
1046 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
1047 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
1048 _log.error('cannot load items from table [%s]' % src_table)
1049 # skip this table
1050 continue
1051 rows = curs.fetchall()
1052 table_col_idx = gmPG.get_col_indices(curs)
1053 # format per-table items
1054 for row in rows:
1055 # FIXME: make this get_pkey_name()
1056 pk_item = row[table_col_idx['pk_item']]
1057 view_row = items_by_table[src_table][pk_item]
1058 age = view_row[view_col_idx['age']]
1059 # format metadata
1060 try:
1061 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
1062 except Exception:
1063 episode_name = view_row[view_col_idx['pk_episode']]
1064 try:
1065 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
1066 except Exception:
1067 issue_name = view_row[view_col_idx['pk_health_issue']]
1068
1069 if age not in emr_data:
1070 emr_data[age] = []
1071
1072 emr_data[age].append(
1073 _('%s: encounter (%s)') % (
1074 view_row[view_col_idx['clin_when']],
1075 view_row[view_col_idx['pk_encounter']]
1076 )
1077 )
1078 emr_data[age].append(_('health issue: %s') % issue_name)
1079 emr_data[age].append(_('episode : %s') % episode_name)
1080 # format table specific data columns
1081 # - ignore those, they are metadata, some
1082 # are in clin.v_pat_items data already
1083 cols2ignore = [
1084 'pk_audit', 'row_version', 'modified_when', 'modified_by',
1085 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
1086 ]
1087 col_data = []
1088 for col_name in table_col_idx.keys():
1089 if col_name in cols2ignore:
1090 continue
1091 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
1092 emr_data[age].append("----------------------------------------------------")
1093 emr_data[age].append("-- %s from table %s" % (
1094 view_row[view_col_idx['modified_string']],
1095 src_table
1096 ))
1097 emr_data[age].append("-- written %s by %s" % (
1098 view_row[view_col_idx['modified_when']],
1099 view_row[view_col_idx['modified_by']]
1100 ))
1101 emr_data[age].append("----------------------------------------------------")
1102 curs.close()
1103 return emr_data
1104 #--------------------------------------------------------
1107 #--------------------------------------------------------
1109 union_query = '\n union all\n'.join ([
1110 """
1111 SELECT ((
1112 -- all relevant health issues + active episodes WITH health issue
1113 SELECT COUNT(1)
1114 FROM clin.v_problem_list
1115 WHERE
1116 pk_patient = %(pat)s
1117 AND
1118 pk_health_issue is not null
1119 ) + (
1120 -- active episodes WITHOUT health issue
1121 SELECT COUNT(1)
1122 FROM clin.v_problem_list
1123 WHERE
1124 pk_patient = %(pat)s
1125 AND
1126 pk_health_issue is null
1127 ))""",
1128 'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
1129 'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
1130 'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
1131 'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
1132 'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
1133 'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
1134 # active and approved substances == medication
1135 """
1136 SELECT count(1)
1137 FROM clin.v_substance_intakes
1138 WHERE
1139 pk_patient = %(pat)s
1140 AND
1141 is_currently_active IN (null, true)
1142 AND
1143 intake_is_approved_of IN (null, true)""",
1144 'SELECT count(1) FROM clin.v_vaccinations WHERE pk_patient = %(pat)s'
1145 ])
1146
1147 rows, idx = gmPG2.run_ro_queries (
1148 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
1149 get_col_idx = False
1150 )
1151
1152 stats = dict (
1153 problems = rows[0][0],
1154 encounters = rows[1][0],
1155 items = rows[2][0],
1156 documents = rows[3][0],
1157 results = rows[4][0],
1158 stays = rows[5][0],
1159 procedures = rows[6][0],
1160 active_drugs = rows[7][0],
1161 vaccinations = rows[8][0]
1162 )
1163
1164 return stats
1165 #--------------------------------------------------------
1167 return _(
1168 'Medical problems: %(problems)s\n'
1169 'Total encounters: %(encounters)s\n'
1170 'Total EMR entries: %(items)s\n'
1171 'Active medications: %(active_drugs)s\n'
1172 'Documents: %(documents)s\n'
1173 'Test results: %(results)s\n'
1174 'Hospitalizations: %(stays)s\n'
1175 'Procedures: %(procedures)s\n'
1176 'Vaccinations: %(vaccinations)s'
1177 ) % self.get_statistics()
1178 #--------------------------------------------------------
1180
1181 cmd = "SELECT dob FROM dem.v_all_persons WHERE pk_identity = %(pk)s"
1182 args = {'pk': self.pk_patient}
1183 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1184 dob = rows[0]['dob']
1185
1186 stats = self.get_statistics()
1187 first = self.get_first_encounter()
1188 last = self.get_last_encounter()
1189 probs = self.get_problems()
1190
1191 txt = ''
1192 if len(probs) > 0:
1193 txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems']
1194 else:
1195 txt += _(' %s known problems\n') % stats['problems']
1196 for prob in probs:
1197 if not prob['clinically_relevant']:
1198 continue
1199 txt += ' \u00BB%s\u00AB (%s)\n' % (
1200 prob['problem'],
1201 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
1202 )
1203 txt += '\n'
1204 txt += _(' %s encounters from %s to %s\n') % (
1205 stats['encounters'],
1206 gmDateTime.pydt_strftime(first['started'], '%Y %b %d'),
1207 gmDateTime.pydt_strftime(last['started'], '%Y %b %d')
1208 )
1209 txt += _(' %s active medications\n') % stats['active_drugs']
1210 txt += _(' %s documents\n') % stats['documents']
1211 txt += _(' %s test results\n') % stats['results']
1212 txt += _(' %s hospitalizations') % stats['stays']
1213 if stats['stays'] == 0:
1214 txt += '\n'
1215 else:
1216 txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3)
1217 # FIXME: perhaps only count "ongoing ones"
1218 txt += _(' %s performed procedures') % stats['procedures']
1219 if stats['procedures'] == 0:
1220 txt += '\n'
1221 else:
1222 txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3)
1223
1224 txt += '\n'
1225 txt += _('Allergies and Intolerances\n')
1226
1227 allg_state = self.allergy_state
1228 txt += (' ' + allg_state.state_string)
1229 if allg_state['last_confirmed'] is not None:
1230 txt += _(' (last confirmed %s)') % gmDateTime.pydt_strftime(allg_state['last_confirmed'], '%Y %b %d')
1231 txt += '\n'
1232 txt += gmTools.coalesce(allg_state['comment'], '', ' %s\n')
1233 for allg in self.get_allergies():
1234 txt += ' %s: %s\n' % (
1235 allg['descriptor'],
1236 gmTools.coalesce(allg['reaction'], _('unknown reaction'))
1237 )
1238
1239 meds = self.get_current_medications(order_by = 'intake_is_approved_of DESC, substance')
1240 if len(meds) > 0:
1241 txt += '\n'
1242 txt += _('Medications and Substances')
1243 txt += '\n'
1244 for m in meds:
1245 txt += '%s\n' % m.format_as_single_line(left_margin = 1)
1246
1247 fhx = self.get_family_history()
1248 if len(fhx) > 0:
1249 txt += '\n'
1250 txt += _('Family History')
1251 txt += '\n'
1252 for f in fhx:
1253 txt += '%s\n' % f.format(left_margin = 1)
1254
1255 jobs = get_occupations(pk_identity = self.pk_patient)
1256 if len(jobs) > 0:
1257 txt += '\n'
1258 txt += _('Occupations')
1259 txt += '\n'
1260 for job in jobs:
1261 txt += ' %s%s\n' % (
1262 job['l10n_occupation'],
1263 gmTools.coalesce(job['activities'], '', ': %s')
1264 )
1265
1266 vaccs = self.get_latest_vaccinations()
1267 if len(vaccs) > 0:
1268 txt += '\n'
1269 txt += _('Vaccinations')
1270 txt += '\n'
1271 inds = sorted(vaccs.keys())
1272 for ind in inds:
1273 ind_count, vacc = vaccs[ind]
1274 if dob is None:
1275 age_given = ''
1276 else:
1277 age_given = ' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
1278 start = dob,
1279 end = vacc['date_given']
1280 ))
1281 since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given'])
1282 txt += ' %s (%s%s): %s%s (%s %s%s%s)\n' % (
1283 ind,
1284 gmTools.u_sum,
1285 ind_count,
1286 #gmDateTime.pydt_strftime(vacc['date_given'], '%b %Y'),
1287 since,
1288 age_given,
1289 vacc['vaccine'],
1290 gmTools.u_left_double_angle_quote,
1291 vacc['batch_no'],
1292 gmTools.u_right_double_angle_quote
1293 )
1294
1295 care = self.get_external_care_items(order_by = 'issue, organization, unit, provider', exclude_inactive = True)
1296 if len(care) > 0:
1297 txt += '\n'
1298 txt += _('External care')
1299 txt += '\n'
1300 for item in care:
1301 txt += ' %s: %s\n' % (
1302 item['issue'],
1303 gmTools.coalesce (
1304 item['provider'],
1305 '%s@%s' % (item['unit'], item['organization']),
1306 '%%s (%s@%s)' % (item['unit'], item['organization'])
1307 )
1308 )
1309
1310 return txt
1311
1312 #--------------------------------------------------------
1314 txt = ''
1315 for enc in self.get_encounters(skip_empty = True):
1316 txt += gmTools.u_box_horiz_4dashes * 70 + '\n'
1317 txt += enc.format (
1318 episodes = None, # means: each touched upon
1319 left_margin = left_margin,
1320 patient = patient,
1321 fancy_header = False,
1322 with_soap = True,
1323 with_docs = True,
1324 with_tests = True,
1325 with_vaccinations = True,
1326 with_co_encountlet_hints = False, # irrelevant
1327 with_rfe_aoe = True,
1328 with_family_history = True,
1329 by_episode = True
1330 )
1331
1332 return txt
1333
1334 #--------------------------------------------------------
1335 - def get_as_journal(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None):
1336 return gmClinNarrative.get_as_journal (
1337 patient = self.pk_patient,
1338 since = since,
1339 until = until,
1340 encounters = encounters,
1341 episodes = episodes,
1342 issues = issues,
1343 soap_cats = soap_cats,
1344 providers = providers,
1345 order_by = order_by,
1346 time_range = time_range,
1347 active_encounter = self.active_encounter
1348 )
1349
1350 #------------------------------------------------------------------
1351 - def get_generic_emr_items(self, pk_encounters=None, pk_episodes=None, pk_health_issues=None, use_active_encounter=False, order_by=None):
1352 if use_active_encounter:
1353 active_encounter = self.active_encounter
1354 else:
1355 active_encounter = None
1356 return gmGenericEMRItem.get_generic_emr_items (
1357 patient = self.pk_patient,
1358 encounters = pk_encounters,
1359 episodes = pk_episodes,
1360 issues = pk_health_issues,
1361 active_encounter = active_encounter,
1362 order_by = order_by
1363 )
1364
1365 #--------------------------------------------------------
1366 # API: allergy
1367 #--------------------------------------------------------
1368 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
1369 """Retrieves patient allergy items.
1370
1371 remove_sensitivities
1372 - retrieve real allergies only, without sensitivities
1373 since
1374 - initial date for allergy items
1375 until
1376 - final date for allergy items
1377 encounters
1378 - list of encounters whose allergies are to be retrieved
1379 episodes
1380 - list of episodes whose allergies are to be retrieved
1381 issues
1382 - list of health issues whose allergies are to be retrieved
1383 """
1384 cmd = "SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
1385 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
1386 filtered_allergies = []
1387 for r in rows:
1388 filtered_allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
1389
1390 # ok, let's constrain our list
1391 if ID_list is not None:
1392 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_allergy'] in ID_list ]
1393 if len(filtered_allergies) == 0:
1394 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
1395 # better fail here contrary to what we do elsewhere
1396 return None
1397 else:
1398 return filtered_allergies
1399
1400 if remove_sensitivities:
1401 filtered_allergies = [ allg for allg in filtered_allergies if allg['type'] == 'allergy' ]
1402 if since is not None:
1403 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] >= since ]
1404 if until is not None:
1405 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] < until ]
1406 if issues is not None:
1407 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_health_issue'] in issues ]
1408 if episodes is not None:
1409 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_episode'] in episodes ]
1410 if encounters is not None:
1411 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_encounter'] in encounters ]
1412
1413 return filtered_allergies
1414 #--------------------------------------------------------
1416 if encounter_id is None:
1417 encounter_id = self.current_encounter['pk_encounter']
1418
1419 if episode_id is None:
1420 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
1421 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
1422 episode_id = epi['pk_episode']
1423
1424 new_allergy = gmAllergy.create_allergy (
1425 allergene = allergene,
1426 allg_type = allg_type,
1427 encounter_id = encounter_id,
1428 episode_id = episode_id
1429 )
1430
1431 return new_allergy
1432 #--------------------------------------------------------
1434 cmd = 'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1435 args = {'pk_allg': pk_allergy}
1436 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1437
1438 #--------------------------------------------------------
1440 """Cave: only use with one potential allergic agent
1441 otherwise you won't know which of the agents the allergy is to."""
1442
1443 # we don't know the state
1444 if self.allergy_state is None:
1445 return None
1446
1447 # we know there's no allergies
1448 if self.allergy_state == 0:
1449 return False
1450
1451 args = {
1452 'atcs': atcs,
1453 'inns': inns,
1454 'prod_name': product_name,
1455 'pat': self.pk_patient
1456 }
1457 allergenes = []
1458 where_parts = []
1459
1460 if len(atcs) == 0:
1461 atcs = None
1462 if atcs is not None:
1463 where_parts.append('atc_code in %(atcs)s')
1464 if len(inns) == 0:
1465 inns = None
1466 if inns is not None:
1467 where_parts.append('generics in %(inns)s')
1468 allergenes.extend(inns)
1469 if product_name is not None:
1470 where_parts.append('substance = %(prod_name)s')
1471 allergenes.append(product_name)
1472
1473 if len(allergenes) != 0:
1474 where_parts.append('allergene in %(allgs)s')
1475 args['allgs'] = tuple(allergenes)
1476
1477 cmd = """
1478 SELECT * FROM clin.v_pat_allergies
1479 WHERE
1480 pk_patient = %%(pat)s
1481 AND ( %s )""" % ' OR '.join(where_parts)
1482
1483 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1484
1485 if len(rows) == 0:
1486 return False
1487
1488 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1489 #--------------------------------------------------------
1491
1492 if state not in gmAllergy.allergy_states:
1493 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
1494
1495 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
1496 allg_state['has_allergy'] = state
1497 allg_state.save_payload()
1498 return True
1499
1502
1503 allergy_state = property(_get_allergy_state, _set_allergy_state)
1504 #--------------------------------------------------------
1505 # API: external care
1506 #--------------------------------------------------------
1508 return gmExternalCare.get_external_care_items (
1509 pk_identity = self.pk_patient,
1510 order_by = order_by,
1511 exclude_inactive = exclude_inactive
1512 )
1513
1514 external_care_items = property(get_external_care_items, lambda x:x)
1515
1516 #--------------------------------------------------------
1517 # API: episodes
1518 #--------------------------------------------------------
1519 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1520 """Fetches from backend patient episodes.
1521
1522 id_list - Episodes' PKs list
1523 issues - Health issues' PKs list to filter episodes by
1524 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1525 """
1526 if (unlinked_only is True) and (issues is not None):
1527 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1528
1529 if order_by is None:
1530 order_by = ''
1531 else:
1532 order_by = 'ORDER BY %s' % order_by
1533
1534 args = {
1535 'pat': self.pk_patient,
1536 'open': open_status
1537 }
1538 where_parts = ['pk_patient = %(pat)s']
1539
1540 if open_status is not None:
1541 where_parts.append('episode_open IS %(open)s')
1542
1543 if unlinked_only:
1544 where_parts.append('pk_health_issue is NULL')
1545
1546 if issues is not None:
1547 where_parts.append('pk_health_issue IN %(issues)s')
1548 args['issues'] = tuple(issues)
1549
1550 if id_list is not None:
1551 where_parts.append('pk_episode IN %(epis)s')
1552 args['epis'] = tuple(id_list)
1553
1554 cmd = "SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1555 ' AND '.join(where_parts),
1556 order_by
1557 )
1558 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1559
1560 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1561
1562 episodes = property(get_episodes, lambda x:x)
1563 #------------------------------------------------------------------
1565 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1566
1567 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1568 #------------------------------------------------------------------
1570 cmd = """SELECT distinct pk_episode
1571 from clin.v_pat_items
1572 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1573 args = {
1574 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1575 'pat': self.pk_patient
1576 }
1577 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1578 if len(rows) == 0:
1579 return []
1580 epis = []
1581 for row in rows:
1582 epis.append(row[0])
1583 return self.get_episodes(id_list=epis)
1584 #------------------------------------------------------------------
1585 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False, allow_dupes=False, link_obj=None):
1586 """Add episode 'episode_name' for a patient's health issue.
1587
1588 - silently returns if episode already exists
1589 """
1590 episode = gmEMRStructItems.create_episode (
1591 link_obj = link_obj,
1592 pk_health_issue = pk_health_issue,
1593 episode_name = episode_name,
1594 is_open = is_open,
1595 encounter = self.current_encounter['pk_encounter'],
1596 allow_dupes = allow_dupes
1597 )
1598 return episode
1599 #--------------------------------------------------------
1601 # try to find the episode with the most recently modified clinical item
1602 issue_where = gmTools.coalesce (
1603 value2test = issue,
1604 return_instead = '',
1605 value2return = 'and pk_health_issue = %(issue)s'
1606 )
1607 cmd = """
1608 SELECT pk
1609 from clin.episode
1610 WHERE pk = (
1611 SELECT distinct on(pk_episode) pk_episode
1612 from clin.v_pat_items
1613 WHERE
1614 pk_patient = %%(pat)s
1615 and
1616 modified_when = (
1617 SELECT max(vpi.modified_when)
1618 from clin.v_pat_items vpi
1619 WHERE vpi.pk_patient = %%(pat)s
1620 )
1621 %s
1622 -- guard against several episodes created at the same moment of time
1623 limit 1
1624 )""" % issue_where
1625 rows, idx = gmPG2.run_ro_queries(queries = [
1626 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1627 ])
1628 if len(rows) != 0:
1629 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1630
1631 # no clinical items recorded, so try to find
1632 # the youngest episode for this patient
1633 cmd = """
1634 SELECT vpe0.pk_episode
1635 from
1636 clin.v_pat_episodes vpe0
1637 WHERE
1638 vpe0.pk_patient = %%(pat)s
1639 and
1640 vpe0.episode_modified_when = (
1641 SELECT max(vpe1.episode_modified_when)
1642 from clin.v_pat_episodes vpe1
1643 WHERE vpe1.pk_episode = vpe0.pk_episode
1644 )
1645 %s""" % issue_where
1646 rows, idx = gmPG2.run_ro_queries(queries = [
1647 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1648 ])
1649 if len(rows) != 0:
1650 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1651
1652 return None
1653 #--------------------------------------------------------
1656 #--------------------------------------------------------
1657 # API: problems
1658 #--------------------------------------------------------
1659 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1660 """Retrieve a patient's problems.
1661
1662 "Problems" are the UNION of:
1663
1664 - issues which are .clinically_relevant
1665 - episodes which are .is_open
1666
1667 Therefore, both an issue and the open episode
1668 thereof can each be listed as a problem.
1669
1670 include_closed_episodes/include_irrelevant_issues will
1671 include those -- which departs from the definition of
1672 the problem list being "active" items only ...
1673
1674 episodes - episodes' PKs to filter problems by
1675 issues - health issues' PKs to filter problems by
1676 """
1677 # FIXME: this could use a good measure of streamlining, probably
1678
1679 args = {'pat': self.pk_patient}
1680
1681 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1682 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1683
1684 # Instantiate problem items
1685 problems = []
1686 for row in rows:
1687 pk_args = {
1688 'pk_patient': self.pk_patient,
1689 'pk_health_issue': row['pk_health_issue'],
1690 'pk_episode': row['pk_episode']
1691 }
1692 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1693
1694 # include non-problems ?
1695 other_rows = []
1696 if include_closed_episodes:
1697 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1698 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1699 other_rows.extend(rows)
1700
1701 if include_irrelevant_issues:
1702 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1703 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1704 other_rows.extend(rows)
1705
1706 if len(other_rows) > 0:
1707 for row in other_rows:
1708 pk_args = {
1709 'pk_patient': self.pk_patient,
1710 'pk_health_issue': row['pk_health_issue'],
1711 'pk_episode': row['pk_episode']
1712 }
1713 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1714
1715 # filter
1716 if issues is not None:
1717 problems = [ p for p in problems if p['pk_health_issue'] in issues ]
1718 if episodes is not None:
1719 problems = [ p for p in problems if p['pk_episode'] in episodes ]
1720
1721 return problems
1722
1723 #--------------------------------------------------------
1726
1727 #--------------------------------------------------------
1730
1731 #--------------------------------------------------------
1734
1735 #--------------------------------------------------------
1737 cmd = "SELECT * FROM clin.v_candidate_diagnoses WHERE pk_patient = %(pat)s"
1738 rows, idx = gmPG2.run_ro_queries (
1739 queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}],
1740 get_col_idx = False
1741 )
1742 return rows
1743
1744 candidate_diagnoses = property(get_candidate_diagnoses)
1745
1746 #--------------------------------------------------------
1747 # API: health issues
1748 #--------------------------------------------------------
1750
1751 cmd = "SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1752 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1753 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1754
1755 if id_list is None:
1756 return issues
1757
1758 if len(id_list) == 0:
1759 raise ValueError('id_list to filter by is empty, most likely a programming error')
1760
1761 filtered_issues = []
1762 for issue in issues:
1763 if issue['pk_health_issue'] in id_list:
1764 filtered_issues.append(issue)
1765
1766 return filtered_issues
1767
1768 health_issues = property(get_health_issues, lambda x:x)
1769
1770 #------------------------------------------------------------------
1772 """Adds patient health issue."""
1773 return gmEMRStructItems.create_health_issue (
1774 description = issue_name,
1775 encounter = self.current_encounter['pk_encounter'],
1776 patient = self.pk_patient
1777 )
1778 #--------------------------------------------------------
1781 #--------------------------------------------------------
1782 # API: substance intake
1783 #--------------------------------------------------------
1784 - def get_current_medications(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1785 return self._get_current_substance_intakes (
1786 include_inactive = include_inactive,
1787 include_unapproved = include_unapproved,
1788 order_by = order_by,
1789 episodes = episodes,
1790 issues = issues,
1791 exclude_medications = False,
1792 exclude_potential_abuses = True
1793 )
1794
1795 #--------------------------------------------------------
1797 return self._get_current_substance_intakes (
1798 include_inactive = True,
1799 include_unapproved = True,
1800 order_by = order_by,
1801 episodes = None,
1802 issues = None,
1803 exclude_medications = True,
1804 exclude_potential_abuses = False
1805 )
1806
1807 abused_substances = property(_get_abused_substances, lambda x:x)
1808
1809 #--------------------------------------------------------
1810 - def _get_current_substance_intakes(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None, exclude_potential_abuses=False, exclude_medications=False):
1811
1812 where_parts = ['pk_patient = %(pat)s']
1813 args = {'pat': self.pk_patient}
1814
1815 if not include_inactive:
1816 where_parts.append('is_currently_active IN (TRUE, NULL)')
1817
1818 if not include_unapproved:
1819 where_parts.append('intake_is_approved_of IN (TRUE, NULL)')
1820
1821 if exclude_potential_abuses:
1822 where_parts.append('harmful_use_type IS NULL')
1823
1824 if exclude_medications:
1825 where_parts.append('harmful_use_type IS NOT NULL')
1826
1827 if order_by is None:
1828 order_by = ''
1829 else:
1830 order_by = 'ORDER BY %s' % order_by
1831
1832 cmd = "SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1833 '\nAND '.join(where_parts),
1834 order_by
1835 )
1836 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1837 intakes = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1838
1839 if episodes is not None:
1840 intakes = [ i for i in intakes if i['pk_episode'] in episodes ]
1841
1842 if issues is not None:
1843 intakes = [ i for i in intakes if i ['pk_health_issue'] in issues ]
1844
1845 return intakes
1846
1847 #--------------------------------------------------------
1848 - def add_substance_intake(self, pk_component=None, pk_episode=None, pk_drug_product=None, pk_health_issue=None):
1849 pk_enc = self.current_encounter['pk_encounter']
1850 if pk_episode is None:
1851 pk_episode = gmMedication.create_default_medication_history_episode (
1852 pk_health_issue = pk_health_issue,
1853 encounter = pk_enc
1854 )
1855 return gmMedication.create_substance_intake (
1856 pk_component = pk_component,
1857 pk_encounter = pk_enc,
1858 pk_episode = pk_episode,
1859 pk_drug_product = pk_drug_product
1860 )
1861
1862 #--------------------------------------------------------
1863 - def substance_intake_exists(self, pk_component=None, pk_substance=None, pk_drug_product=None):
1864 return gmMedication.substance_intake_exists (
1865 pk_component = pk_component,
1866 pk_substance = pk_substance,
1867 pk_identity = self.pk_patient,
1868 pk_drug_product = pk_drug_product
1869 )
1870
1871 #--------------------------------------------------------
1872 # API: vaccinations
1873 #--------------------------------------------------------
1875 return gmVaccination.create_vaccination (
1876 encounter = self.current_encounter['pk_encounter'],
1877 episode = episode,
1878 vaccine = vaccine,
1879 batch_no = batch_no
1880 )
1881
1882 #--------------------------------------------------------
1884 """Returns latest given vaccination for each vaccinated indication.
1885
1886 as a dict {'l10n_indication': cVaccination instance}
1887
1888 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1889 """
1890 args = {'pat': self.pk_patient}
1891 where_parts = ['c_v_shots.pk_patient = %(pat)s']
1892
1893 if (episodes is not None) and (len(episodes) > 0):
1894 where_parts.append('c_v_shots.pk_episode IN %(epis)s')
1895 args['epis'] = tuple(episodes)
1896
1897 if (issues is not None) and (len(issues) > 0):
1898 where_parts.append('c_v_shots.pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1899 args['issues'] = tuple(issues)
1900
1901 if (atc_indications is not None) and (len(atc_indications) > 0):
1902 where_parts.append('c_v_plv4i.atc_indication IN %(atc_inds)s')
1903 args['atc_inds'] = tuple(atc_indications)
1904
1905 # find the shots
1906 cmd = """
1907 SELECT
1908 c_v_shots.*,
1909 c_v_plv4i.l10n_indication,
1910 c_v_plv4i.no_of_shots
1911 FROM
1912 clin.v_vaccinations c_v_shots
1913 JOIN clin.v_pat_last_vacc4indication c_v_plv4i ON (c_v_shots.pk_vaccination = c_v_plv4i.pk_vaccination)
1914 WHERE %s
1915 """ % '\nAND '.join(where_parts)
1916 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1917
1918 # none found
1919 if len(rows) == 0:
1920 return {}
1921
1922 # turn them into vaccinations
1923 # (idx is constant)
1924 vaccs = {}
1925 for shot_row in rows:
1926 vaccs[shot_row['l10n_indication']] = (
1927 shot_row['no_of_shots'],
1928 gmVaccination.cVaccination(row = {'idx': idx, 'data': shot_row, 'pk_field': 'pk_vaccination'})
1929 )
1930
1931 return vaccs
1932
1933 #--------------------------------------------------------
1935 return gmVaccination.get_vaccinations (
1936 pk_identity = self.pk_patient,
1937 pk_episodes = episodes,
1938 pk_health_issues = issues,
1939 pk_encounters = encounters,
1940 order_by = order_by,
1941 return_pks = False
1942 )
1943
1944 vaccinations = property(get_vaccinations, lambda x:x)
1945
1946 #--------------------------------------------------------
1947 # old/obsolete:
1948 #--------------------------------------------------------
1950 """Retrieves vaccination regimes the patient is on.
1951
1952 optional:
1953 * ID - PK of the vaccination regime
1954 * indications - indications we want to retrieve vaccination
1955 regimes for, must be primary language, not l10n_indication
1956 """
1957 # FIXME: use course, not regime
1958 # retrieve vaccination regimes definitions
1959 cmd = """SELECT distinct on(pk_course) pk_course
1960 FROM clin.v_vaccs_scheduled4pat
1961 WHERE pk_patient=%s"""
1962 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1963 if rows is None:
1964 _log.error('cannot retrieve scheduled vaccination courses')
1965 return None
1966 # Instantiate vaccination items and keep cache
1967 for row in rows:
1968 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1969
1970 # ok, let's constrain our list
1971 filtered_regimes = []
1972 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1973 if ID is not None:
1974 filtered_regimes = [ r for r in filtered_regimes if r['pk_course'] == ID ]
1975 if len(filtered_regimes) == 0:
1976 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1977 return []
1978 else:
1979 return filtered_regimes[0]
1980 if indications is not None:
1981 filtered_regimes = [ r for r in filtered_regimes if r['indication'] in indications ]
1982
1983 return filtered_regimes
1984 #--------------------------------------------------------
1985 # def get_vaccinated_indications(self):
1986 # """Retrieves patient vaccinated indications list.
1987 #
1988 # Note that this does NOT rely on the patient being on
1989 # some schedule or other but rather works with what the
1990 # patient has ACTUALLY been vaccinated against. This is
1991 # deliberate !
1992 # """
1993 # # most likely, vaccinations will be fetched close
1994 # # by so it makes sense to count on the cache being
1995 # # filled (or fill it for nearby use)
1996 # vaccinations = self.get_vaccinations()
1997 # if vaccinations is None:
1998 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1999 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
2000 # if len(vaccinations) == 0:
2001 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
2002 # v_indications = []
2003 # for vacc in vaccinations:
2004 # tmp = [vacc['indication'], vacc['l10n_indication']]
2005 # # remove duplicates
2006 # if tmp in v_indications:
2007 # continue
2008 # v_indications.append(tmp)
2009 # return (True, v_indications)
2010 #--------------------------------------------------------
2011 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2012 """Retrieves list of vaccinations the patient has received.
2013
2014 optional:
2015 * ID - PK of a vaccination
2016 * indications - indications we want to retrieve vaccination
2017 items for, must be primary language, not l10n_indication
2018 * since - initial date for allergy items
2019 * until - final date for allergy items
2020 * encounters - list of encounters whose allergies are to be retrieved
2021 * episodes - list of episodes whose allergies are to be retrieved
2022 * issues - list of health issues whose allergies are to be retrieved
2023 """
2024 try:
2025 self.__db_cache['vaccinations']['vaccinated']
2026 except KeyError:
2027 self.__db_cache['vaccinations']['vaccinated'] = []
2028 # Important fetch ordering by indication, date to know if a vaccination is booster
2029 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
2030 WHERE pk_patient=%s
2031 order by indication, date"""
2032 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2033 if rows is None:
2034 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
2035 del self.__db_cache['vaccinations']['vaccinated']
2036 return None
2037 # Instantiate vaccination items
2038 vaccs_by_ind = {}
2039 for row in rows:
2040 vacc_row = {
2041 'pk_field': 'pk_vaccination',
2042 'idx': idx,
2043 'data': row
2044 }
2045 vacc = gmVaccination.cVaccination(row=vacc_row)
2046 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
2047 # keep them, ordered by indication
2048 try:
2049 vaccs_by_ind[vacc['indication']].append(vacc)
2050 except KeyError:
2051 vaccs_by_ind[vacc['indication']] = [vacc]
2052
2053 # calculate sequence number and is_booster
2054 for ind in vaccs_by_ind.keys():
2055 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
2056 for vacc in vaccs_by_ind[ind]:
2057 # due to the "order by indication, date" the vaccinations are in the
2058 # right temporal order inside the indication-keyed dicts
2059 seq_no = vaccs_by_ind[ind].index(vacc) + 1
2060 vacc['seq_no'] = seq_no
2061 # if no active schedule for indication we cannot
2062 # check for booster status (eg. seq_no > max_shot)
2063 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
2064 continue
2065 if seq_no > vacc_regimes[0]['shots']:
2066 vacc['is_booster'] = True
2067 del vaccs_by_ind
2068
2069 # ok, let's constrain our list
2070 filtered_shots = []
2071 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
2072 if ID is not None:
2073 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
2074 if len(filtered_shots) == 0:
2075 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
2076 return None
2077 else:
2078 return filtered_shots[0]
2079 if since is not None:
2080 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
2081 if until is not None:
2082 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
2083 if issues is not None:
2084 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
2085 if episodes is not None:
2086 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
2087 if encounters is not None:
2088 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
2089 if indications is not None:
2090 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2091 return filtered_shots
2092 #--------------------------------------------------------
2094 """Retrieves vaccinations scheduled for a regime a patient is on.
2095
2096 The regime is referenced by its indication (not l10n)
2097
2098 * indications - List of indications (not l10n) of regimes we want scheduled
2099 vaccinations to be fetched for
2100 """
2101 try:
2102 self.__db_cache['vaccinations']['scheduled']
2103 except KeyError:
2104 self.__db_cache['vaccinations']['scheduled'] = []
2105 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
2106 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2107 if rows is None:
2108 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
2109 del self.__db_cache['vaccinations']['scheduled']
2110 return None
2111 # Instantiate vaccination items
2112 for row in rows:
2113 vacc_row = {
2114 'pk_field': 'pk_vacc_def',
2115 'idx': idx,
2116 'data': row
2117 }
2118 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
2119
2120 # ok, let's constrain our list
2121 if indications is None:
2122 return self.__db_cache['vaccinations']['scheduled']
2123 filtered_shots = []
2124 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
2125 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2126 return filtered_shots
2127 #--------------------------------------------------------
2129 try:
2130 self.__db_cache['vaccinations']['missing']
2131 except KeyError:
2132 self.__db_cache['vaccinations']['missing'] = {}
2133 # 1) non-booster
2134 self.__db_cache['vaccinations']['missing']['due'] = []
2135 # get list of (indication, seq_no) tuples
2136 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
2137 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2138 if rows is None:
2139 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
2140 return None
2141 pk_args = {'pat_id': self.pk_patient}
2142 if rows is not None:
2143 for row in rows:
2144 pk_args['indication'] = row[0]
2145 pk_args['seq_no'] = row[1]
2146 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
2147
2148 # 2) boosters
2149 self.__db_cache['vaccinations']['missing']['boosters'] = []
2150 # get list of indications
2151 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
2152 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2153 if rows is None:
2154 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
2155 return None
2156 pk_args = {'pat_id': self.pk_patient}
2157 if rows is not None:
2158 for row in rows:
2159 pk_args['indication'] = row[0]
2160 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
2161
2162 # if any filters ...
2163 if indications is None:
2164 return self.__db_cache['vaccinations']['missing']
2165 if len(indications) == 0:
2166 return self.__db_cache['vaccinations']['missing']
2167 # ... apply them
2168 filtered_shots = {
2169 'due': [],
2170 'boosters': []
2171 }
2172 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
2173 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
2174 filtered_shots['due'].append(due_shot)
2175 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
2176 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
2177 filtered_shots['boosters'].append(due_shot)
2178 return filtered_shots
2179
2180 #------------------------------------------------------------------
2181 # API: encounters
2182 #------------------------------------------------------------------
2185
2187 # first ever setting ? -> fast path
2188 if self.__encounter is None:
2189 _log.debug('first setting of active encounter in this clinical record instance')
2190 encounter.lock(exclusive = False) # lock new
2191 self.__encounter = encounter
2192 gmDispatcher.send('current_encounter_switched')
2193 return True
2194
2195 # real switch -> slow path
2196 _log.debug('switching of active encounter')
2197 # fail if the currently active encounter has unsaved changes
2198 if self.__encounter.is_modified():
2199 gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
2200 _log.error('current in client: %s', self.__encounter)
2201 raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
2202 self.__encounter['pk_encounter'],
2203 encounter['pk_encounter']
2204 ))
2205
2206 prev_enc = self.__encounter
2207 encounter.lock(exclusive = False) # lock new
2208 self.__encounter = encounter
2209 prev_enc.unlock(exclusive = False) # unlock old
2210 gmDispatcher.send('current_encounter_switched')
2211
2212 return True
2213
2214 current_encounter = property(_get_current_encounter, _set_current_encounter)
2215 active_encounter = property(_get_current_encounter, _set_current_encounter)
2216
2217 #--------------------------------------------------------
2219 _log.debug('setting up active encounter for identity [%s]', self.pk_patient)
2220
2221 # log access to patient record (HIPAA, for example)
2222 _delayed_execute(self.log_access, action = 'pulling chart for identity [%s]' % self.pk_patient)
2223
2224 # cleanup (not async, because we don't want recent encounters
2225 # to become the active one just because they are recent)
2226 self.remove_empty_encounters()
2227
2228 # activate very recent encounter if available
2229 if self.__activate_very_recent_encounter():
2230 return
2231
2232 fairly_recent_enc = self.__get_fairly_recent_encounter()
2233
2234 # create new encounter for the time being
2235 self.start_new_encounter()
2236
2237 if fairly_recent_enc is None:
2238 return
2239
2240 # but check whether user wants to continue a "fairly recent" one
2241 gmDispatcher.send (
2242 signal = 'ask_for_encounter_continuation',
2243 new_encounter = self.__encounter,
2244 fairly_recent_encounter = fairly_recent_enc
2245 )
2246
2247 #------------------------------------------------------------------
2249 """Try to attach to a "very recent" encounter if there is one.
2250
2251 returns:
2252 False: no "very recent" encounter
2253 True: success
2254 """
2255 cfg_db = gmCfg.cCfgSQL()
2256 min_ttl = cfg_db.get2 (
2257 option = 'encounter.minimum_ttl',
2258 workplace = _here.active_workplace,
2259 bias = 'user',
2260 default = '1 hour 30 minutes'
2261 )
2262 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2263 SELECT pk_encounter
2264 FROM clin.v_most_recent_encounters
2265 WHERE
2266 pk_patient = %s
2267 and
2268 last_affirmed > (now() - %s::interval)
2269 ORDER BY
2270 last_affirmed DESC
2271 LIMIT 1
2272 )"""
2273 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}], get_col_idx = True)
2274
2275 # none found
2276 if len(enc_rows) == 0:
2277 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
2278 return False
2279
2280 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0]['pk_encounter'])
2281
2282 # attach to existing
2283 self.current_encounter = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2284 return True
2285
2286 #------------------------------------------------------------------
2288 cfg_db = gmCfg.cCfgSQL()
2289 min_ttl = cfg_db.get2 (
2290 option = 'encounter.minimum_ttl',
2291 workplace = _here.active_workplace,
2292 bias = 'user',
2293 default = '1 hour 30 minutes'
2294 )
2295 max_ttl = cfg_db.get2 (
2296 option = 'encounter.maximum_ttl',
2297 workplace = _here.active_workplace,
2298 bias = 'user',
2299 default = '6 hours'
2300 )
2301
2302 # do we happen to have a "fairly recent" candidate ?
2303 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2304 SELECT pk_encounter
2305 FROM clin.v_most_recent_encounters
2306 WHERE
2307 pk_patient=%s
2308 AND
2309 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2310 ORDER BY
2311 last_affirmed DESC
2312 LIMIT 1
2313 )"""
2314 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2315
2316 # none found
2317 if len(enc_rows) == 0:
2318 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2319 return None
2320
2321 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2322 return gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2323
2324 # #------------------------------------------------------------------
2325 # def __check_for_fairly_recent_encounter(self):
2326 #
2327 # cfg_db = gmCfg.cCfgSQL()
2328 # min_ttl = cfg_db.get2 (
2329 # option = u'encounter.minimum_ttl',
2330 # workplace = _here.active_workplace,
2331 # bias = u'user',
2332 # default = u'1 hour 30 minutes'
2333 # )
2334 # max_ttl = cfg_db.get2 (
2335 # option = u'encounter.maximum_ttl',
2336 # workplace = _here.active_workplace,
2337 # bias = u'user',
2338 # default = u'6 hours'
2339 # )
2340 #
2341 # # do we happen to have a "fairly recent" candidate ?
2342 # cmd = gmEMRStructItems.SQL_get_encounters % u"""pk_encounter = (
2343 # SELECT pk_encounter
2344 # FROM clin.v_most_recent_encounters
2345 # WHERE
2346 # pk_patient=%s
2347 # AND
2348 # last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2349 # ORDER BY
2350 # last_affirmed DESC
2351 # LIMIT 1
2352 # )"""
2353 # enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2354 #
2355 # # none found
2356 # if len(enc_rows) == 0:
2357 # _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2358 # return
2359 #
2360 # _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2361 # fairly_recent_enc = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2362 # gmDispatcher.send(u'ask_for_encounter_continuation', current = self.__encounter, fairly_recent_encounter = fairly_recent_enc)
2363
2364 # #------------------------------------------------------------------
2365 # def __activate_fairly_recent_encounter(self, allow_user_interaction=True):
2366 # """Try to attach to a "fairly recent" encounter if there is one.
2367 #
2368 # returns:
2369 # False: no "fairly recent" encounter, create new one
2370 # True: success
2371 # """
2372 # if _func_ask_user is None:
2373 # _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
2374 # return False
2375 #
2376 # if not allow_user_interaction:
2377 # _log.exception('user interaction not desired, not looking for fairly recent encounter')
2378 # return False
2379 #
2380 # cfg_db = gmCfg.cCfgSQL()
2381 # min_ttl = cfg_db.get2 (
2382 # option = u'encounter.minimum_ttl',
2383 # workplace = _here.active_workplace,
2384 # bias = u'user',
2385 # default = u'1 hour 30 minutes'
2386 # )
2387 # max_ttl = cfg_db.get2 (
2388 # option = u'encounter.maximum_ttl',
2389 # workplace = _here.active_workplace,
2390 # bias = u'user',
2391 # default = u'6 hours'
2392 # )
2393 # cmd = u"""
2394 # SELECT pk_encounter
2395 # FROM clin.v_most_recent_encounters
2396 # WHERE
2397 # pk_patient=%s
2398 # AND
2399 # last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2400 # ORDER BY
2401 # last_affirmed DESC"""
2402 # enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
2403 # # none found
2404 # if len(enc_rows) == 0:
2405 # _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2406 # return False
2407 #
2408 # _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
2409 #
2410 # encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
2411 # # ask user whether to attach or not
2412 # cmd = u"""
2413 # SELECT title, firstnames, lastnames, gender, dob
2414 # FROM dem.v_all_persons WHERE pk_identity=%s"""
2415 # pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
2416 # pat = pats[0]
2417 # pat_str = u'%s %s %s (%s), %s [#%s]' % (
2418 # gmTools.coalesce(pat[0], u'')[:5],
2419 # pat[1][:15],
2420 # pat[2][:15],
2421 # pat[3],
2422 # gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
2423 # self.pk_patient
2424 # )
2425 # msg = _(
2426 # '%s\n'
2427 # '\n'
2428 # "This patient's chart was worked on only recently:\n"
2429 # '\n'
2430 # ' %s %s - %s (%s)\n'
2431 # '\n'
2432 # ' Reason for Encounter:\n'
2433 # ' %s\n'
2434 # ' Assessment of Encounter:\n'
2435 # ' %s\n'
2436 # '\n'
2437 # 'Do you want to continue that consultation\n'
2438 # 'or do you want to start a new one ?\n'
2439 # ) % (
2440 # pat_str,
2441 # gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
2442 # gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
2443 # encounter['l10n_type'],
2444 # gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
2445 # gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
2446 # )
2447 # attach = False
2448 # try:
2449 # attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
2450 # except Exception:
2451 # _log.exception('cannot ask user for guidance, not attaching to existing encounter')
2452 # return False
2453 # if not attach:
2454 # return False
2455 #
2456 # # attach to existing
2457 # self.current_encounter = encounter
2458 # _log.debug('"fairly recent" encounter re-activated')
2459 # return True
2460
2461 #------------------------------------------------------------------
2463 cfg_db = gmCfg.cCfgSQL()
2464 enc_type = cfg_db.get2 (
2465 option = 'encounter.default_type',
2466 workplace = _here.active_workplace,
2467 bias = 'user'
2468 )
2469 if enc_type is None:
2470 enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
2471 if enc_type is None:
2472 enc_type = 'in surgery'
2473 enc = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
2474 enc['pk_org_unit'] = _here['pk_org_unit']
2475 enc.save()
2476 self.current_encounter = enc
2477 _log.debug('new encounter [%s] activated', enc['pk_encounter'])
2478
2479 #------------------------------------------------------------------
2480 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False, order_by=None, max_encounters=None):
2481 """Retrieves patient's encounters.
2482
2483 id_list - PKs of encounters to fetch
2484 since - initial date for encounter items, DateTime instance
2485 until - final date for encounter items, DateTime instance
2486 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
2487 issues - PKs of the health issues the encounters belong to (many-to-many relation)
2488 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
2489
2490 NOTE: if you specify *both* issues and episodes
2491 you will get the *aggregate* of all encounters even
2492 if the episodes all belong to the health issues listed.
2493 IOW, the issues broaden the episode list rather than
2494 the episode list narrowing the episodes-from-issues
2495 list.
2496 Rationale: If it was the other way round it would be
2497 redundant to specify the list of issues at all.
2498 """
2499 # if issues are given, translate them to their episodes
2500 if (issues is not None) and (len(issues) > 0):
2501 # - find episodes corresponding to the health issues in question
2502 cmd = "SELECT distinct pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue in %(issue_pks)s AND pk_patient = %(pat)s"
2503 args = {'issue_pks': tuple(issues), 'pat': self.pk_patient}
2504 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2505 epis4issues_pks = [ r['pk_episode'] for r in rows ]
2506 if episodes is None:
2507 episodes = []
2508 episodes.extend(epis4issues_pks)
2509
2510 if (episodes is not None) and (len(episodes) > 0):
2511 # since the episodes to filter by belong to the patient in question so will
2512 # the encounters found with them - hence we don't need a WHERE on the patient ...
2513 # but, better safe than sorry ...
2514 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2515 cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
2516 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2517 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2518 if id_list is None:
2519 id_list = []
2520 id_list.extend(encs4epis_pks)
2521
2522 where_parts = ['c_vpe.pk_patient = %(pat)s']
2523 args = {'pat': self.pk_patient}
2524
2525 if skip_empty:
2526 where_parts.append("""NOT (
2527 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
2528 AND
2529 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
2530 AND
2531 NOT EXISTS (
2532 SELECT 1 FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_patient = %(pat)s AND c_vpi.pk_encounter = c_vpe.pk_encounter
2533 UNION ALL
2534 SELECT 1 FROM blobs.v_doc_med b_vdm WHERE b_vdm.pk_patient = %(pat)s AND b_vdm.pk_encounter = c_vpe.pk_encounter
2535 ))""")
2536
2537 if since is not None:
2538 where_parts.append('c_vpe.started >= %(start)s')
2539 args['start'] = since
2540
2541 if until is not None:
2542 where_parts.append('c_vpe.last_affirmed <= %(end)s')
2543 args['end'] = since
2544
2545 if (id_list is not None) and (len(id_list) > 0):
2546 where_parts.append('c_vpe.pk_encounter IN %(enc_pks)s')
2547 args['enc_pks'] = tuple(id_list)
2548
2549 if order_by is None:
2550 order_by = 'c_vpe.started'
2551
2552 if max_encounters is None:
2553 limit = ''
2554 else:
2555 limit = 'LIMIT %s' % max_encounters
2556
2557 cmd = """
2558 SELECT * FROM clin.v_pat_encounters c_vpe
2559 WHERE
2560 %s
2561 ORDER BY %s %s
2562 """ % (
2563 ' AND '.join(where_parts),
2564 order_by,
2565 limit
2566 )
2567 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2568 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
2569
2570 # we've got the encounters, start filtering
2571 filtered_encounters = []
2572 filtered_encounters.extend(encounters)
2573
2574 if (episodes is not None) and (len(episodes) > 0):
2575 # since the episodes to filter by belong to the patient in question so will
2576 # the encounters found with them - hence we don't need a WHERE on the patient ...
2577 # but, better safe than sorry ...
2578 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2579 cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
2580 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2581 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2582 filtered_encounters = [ enc for enc in filtered_encounters if enc['pk_encounter'] in encs4epis_pks ]
2583
2584 return filtered_encounters
2585
2586 #--------------------------------------------------------
2588 """Retrieves first encounter for a particular issue and/or episode.
2589
2590 issue_id - First encounter associated health issue
2591 episode - First encounter associated episode
2592 """
2593 if issue_id is None:
2594 issues = None
2595 else:
2596 issues = [issue_id]
2597
2598 if episode_id is None:
2599 episodes = None
2600 else:
2601 episodes = [episode_id]
2602
2603 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started', max_encounters = 1)
2604 if len(encounters) == 0:
2605 return None
2606
2607 return encounters[0]
2608
2609 first_encounter = property(get_first_encounter, lambda x:x)
2610
2611 #--------------------------------------------------------
2613 args = {'pat': self.pk_patient}
2614 cmd = """
2615 SELECT MIN(earliest) FROM (
2616 (
2617 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
2618
2619 ) UNION ALL (
2620
2621 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
2622
2623 ) UNION ALL (
2624
2625 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2626
2627 ) UNION ALL (
2628
2629 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2630
2631 ) UNION ALL (
2632
2633 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2634
2635 ) UNION ALL (
2636
2637 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2638
2639 ) UNION ALL (
2640
2641 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2642
2643 )
2644 ) AS candidates"""
2645 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2646 return rows[0][0]
2647
2648 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2649
2650 #--------------------------------------------------------
2652 encounters = self.get_encounters(order_by = 'started DESC', max_encounters = 1)
2653 if len(encounters) == 0:
2654 return None
2655 return encounters[0]['last_affirmed']
2656
2657 most_recent_care_date = property(get_most_recent_care_date)
2658
2659 #--------------------------------------------------------
2661 """Retrieves last encounter for a concrete issue and/or episode
2662
2663 issue_id - Last encounter associated health issue
2664 episode_id - Last encounter associated episode
2665 """
2666 if issue_id is None:
2667 issues = None
2668 else:
2669 issues = [issue_id]
2670
2671 if episode_id is None:
2672 episodes = None
2673 else:
2674 episodes = [episode_id]
2675
2676 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started DESC', max_encounters = 1)
2677 if len(encounters) == 0:
2678 return None
2679
2680 return encounters[0]
2681
2682 last_encounter = property(get_last_encounter, lambda x:x)
2683
2684 #------------------------------------------------------------------
2686 args = {'pat': self.pk_patient, 'range': cover_period}
2687 where_parts = ['pk_patient = %(pat)s']
2688 if cover_period is not None:
2689 where_parts.append('last_affirmed > now() - %(range)s')
2690
2691 cmd = """
2692 SELECT l10n_type, count(1) AS frequency
2693 FROM clin.v_pat_encounters
2694 WHERE
2695 %s
2696 GROUP BY l10n_type
2697 ORDER BY frequency DESC
2698 """ % ' AND '.join(where_parts)
2699 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2700 return rows
2701
2702 #------------------------------------------------------------------
2704
2705 args = {'pat': self.pk_patient}
2706
2707 if (issue_id is None) and (episode_id is None):
2708 cmd = """
2709 SELECT * FROM clin.v_pat_encounters
2710 WHERE pk_patient = %(pat)s
2711 ORDER BY started DESC
2712 LIMIT 2
2713 """
2714 else:
2715 where_parts = []
2716
2717 if issue_id is not None:
2718 where_parts.append('pk_health_issue = %(issue)s')
2719 args['issue'] = issue_id
2720
2721 if episode_id is not None:
2722 where_parts.append('pk_episode = %(epi)s')
2723 args['epi'] = episode_id
2724
2725 cmd = """
2726 SELECT *
2727 FROM clin.v_pat_encounters
2728 WHERE
2729 pk_patient = %%(pat)s
2730 AND
2731 pk_encounter IN (
2732 SELECT distinct pk_encounter
2733 FROM clin.v_narrative
2734 WHERE
2735 %s
2736 )
2737 ORDER BY started DESC
2738 LIMIT 2
2739 """ % ' AND '.join(where_parts)
2740
2741 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2742
2743 if len(rows) == 0:
2744 return None
2745
2746 # just one encounter within the above limits
2747 if len(rows) == 1:
2748 # is it the current encounter ?
2749 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2750 # yes
2751 return None
2752 # no
2753 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2754
2755 # more than one encounter
2756 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2757 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2758
2759 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2760
2761 last_but_one_encounter = property(get_last_but_one_encounter, lambda x:x)
2762
2763 #------------------------------------------------------------------
2765 _log.debug('removing empty encounters for pk_identity [%s]', self.pk_patient)
2766 cfg_db = gmCfg.cCfgSQL()
2767 ttl = cfg_db.get2 (
2768 option = 'encounter.ttl_if_empty',
2769 workplace = _here.active_workplace,
2770 bias = 'user',
2771 default = '1 week'
2772 )
2773 # # FIXME: this should be done async
2774 cmd = "SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
2775 args = {'pat': self.pk_patient, 'ttl': ttl}
2776 try:
2777 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
2778 except Exception:
2779 _log.exception('error deleting empty encounters')
2780 return False
2781
2782 if not rows[0][0]:
2783 _log.debug('no encounters deleted (less than 2 exist)')
2784
2785 return True
2786
2787 #------------------------------------------------------------------
2788 # API: measurements / test results
2789 #------------------------------------------------------------------
2791 return gmPathLab.get_most_recent_results_for_patient (
2792 no_of_results = no_of_results,
2793 patient = self.pk_patient
2794 )
2795
2796 #------------------------------------------------------------------
2797 - def get_most_recent_results_in_loinc_group(self, loincs=None, max_no_of_results=1, consider_indirect_matches=False):
2798 return gmPathLab.get_most_recent_results_in_loinc_group (
2799 loincs = loincs,
2800 max_no_of_results = max_no_of_results,
2801 patient = self.pk_patient,
2802 consider_indirect_matches = consider_indirect_matches
2803 )
2804
2805 #------------------------------------------------------------------
2807 return gmPathLab.get_most_recent_results_for_test_type (
2808 test_type = test_type,
2809 max_no_of_results = max_no_of_results,
2810 patient = self.pk_patient
2811 )
2812
2813 #------------------------------------------------------------------
2815 return gmPathLab.get_most_recent_result_for_test_types (
2816 pk_test_types = pk_test_types,
2817 pk_patient = self.pk_patient
2818 )
2819
2820 #------------------------------------------------------------------
2821 - def get_result_at_timestamp(self, timestamp=None, test_type=None, loinc=None, tolerance_interval='12 hours'):
2822 return gmPathLab.get_result_at_timestamp (
2823 timestamp = timestamp,
2824 test_type = test_type,
2825 loinc = loinc,
2826 tolerance_interval = tolerance_interval,
2827 patient = self.pk_patient
2828 )
2829
2830 #------------------------------------------------------------------
2832 return gmPathLab.get_results_for_day (
2833 timestamp = timestamp,
2834 patient = self.pk_patient,
2835 order_by = order_by
2836 )
2837
2838 #------------------------------------------------------------------
2840 return gmPathLab.get_results_for_issue (
2841 pk_health_issue = pk_health_issue,
2842 order_by = order_by
2843 )
2844
2845 #------------------------------------------------------------------
2848
2849 #------------------------------------------------------------------
2851 if order_by is None:
2852 order_by = ''
2853 else:
2854 order_by = 'ORDER BY %s' % order_by
2855 cmd = """
2856 SELECT * FROM clin.v_test_results
2857 WHERE
2858 pk_patient = %%(pat)s
2859 AND
2860 reviewed IS FALSE
2861 %s""" % order_by
2862 args = {'pat': self.pk_patient}
2863 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2864 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2865
2866 #------------------------------------------------------------------
2867 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
2869 """Retrieve data about test types for which this patient has results."""
2870 if order_by is None:
2871 order_by = ''
2872 else:
2873 order_by = 'ORDER BY %s' % order_by
2874
2875 if unique_meta_types:
2876 cmd = """
2877 SELECT * FROM clin.v_test_types c_vtt
2878 WHERE c_vtt.pk_test_type IN (
2879 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
2880 FROM clin.v_test_results c_vtr1
2881 WHERE
2882 c_vtr1.pk_patient = %%(pat)s
2883 AND
2884 c_vtr1.pk_meta_test_type IS NOT NULL
2885 UNION ALL
2886 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
2887 FROM clin.v_test_results c_vtr2
2888 WHERE
2889 c_vtr2.pk_patient = %%(pat)s
2890 AND
2891 c_vtr2.pk_meta_test_type IS NULL
2892 )
2893 %s""" % order_by
2894 else:
2895 cmd = """
2896 SELECT * FROM clin.v_test_types c_vtt
2897 WHERE c_vtt.pk_test_type IN (
2898 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
2899 FROM clin.v_test_results c_vtr
2900 WHERE c_vtr.pk_patient = %%(pat)s
2901 )
2902 %s""" % order_by
2903
2904 args = {'pat': self.pk_patient}
2905 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2906 return [ gmPathLab.cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
2907
2908 #------------------------------------------------------------------
2910 """Get the dates for which we have results."""
2911 where_parts = ['pk_patient = %(pat)s']
2912 args = {'pat': self.pk_patient}
2913
2914 if tests is not None:
2915 where_parts.append('pk_test_type IN %(tests)s')
2916 args['tests'] = tuple(tests)
2917
2918 cmd = """
2919 SELECT DISTINCT ON (clin_when_day)
2920 clin_when_day,
2921 is_reviewed
2922 FROM (
2923 SELECT
2924 date_trunc('day', clin_when)
2925 AS clin_when_day,
2926 bool_and(reviewed)
2927 AS is_reviewed
2928 FROM (
2929 SELECT
2930 clin_when,
2931 reviewed,
2932 pk_patient,
2933 pk_test_result
2934 FROM clin.v_test_results
2935 WHERE %s
2936 )
2937 AS patient_tests
2938 GROUP BY clin_when_day
2939 )
2940 AS grouped_days
2941 ORDER BY clin_when_day %s
2942 """ % (
2943 ' AND '.join(where_parts),
2944 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
2945 )
2946 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2947 return rows
2948
2949 #------------------------------------------------------------------
2951 """Get the issues/episodes for which we have results."""
2952 where_parts = ['pk_patient = %(pat)s']
2953 args = {'pat': self.pk_patient}
2954
2955 if tests is not None:
2956 where_parts.append('pk_test_type IN %(tests)s')
2957 args['tests'] = tuple(tests)
2958 where = ' AND '.join(where_parts)
2959 cmd = """
2960 SELECT * FROM ((
2961 -- issues, each including all it"s episodes
2962 SELECT
2963 health_issue AS problem,
2964 pk_health_issue,
2965 NULL::integer AS pk_episode,
2966 1 AS rank
2967 FROM clin.v_test_results
2968 WHERE pk_health_issue IS NOT NULL AND %s
2969 GROUP BY pk_health_issue, problem
2970 ) UNION ALL (
2971 -- episodes w/o issue
2972 SELECT
2973 episode AS problem,
2974 NULL::integer AS pk_health_issue,
2975 pk_episode,
2976 2 AS rank
2977 FROM clin.v_test_results
2978 WHERE pk_health_issue IS NULL AND %s
2979 GROUP BY pk_episode, problem
2980 )) AS grouped_union
2981 ORDER BY rank, problem
2982 """ % (where, where)
2983 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2984 return rows
2985
2986 #------------------------------------------------------------------
2988 return gmPathLab.get_test_results (
2989 pk_patient = self.pk_patient,
2990 encounters = encounters,
2991 episodes = episodes,
2992 order_by = order_by
2993 )
2994 #------------------------------------------------------------------
2995 - def get_test_results_by_date(self, encounter=None, episodes=None, tests=None, reverse_chronological=True):
2996
2997 where_parts = ['pk_patient = %(pat)s']
2998 args = {'pat': self.pk_patient}
2999
3000 if tests is not None:
3001 where_parts.append('pk_test_type IN %(tests)s')
3002 args['tests'] = tuple(tests)
3003
3004 if encounter is not None:
3005 where_parts.append('pk_encounter = %(enc)s')
3006 args['enc'] = encounter
3007
3008 if episodes is not None:
3009 where_parts.append('pk_episode IN %(epis)s')
3010 args['epis'] = tuple(episodes)
3011
3012 cmd = """
3013 SELECT * FROM clin.v_test_results
3014 WHERE %s
3015 ORDER BY clin_when %s, pk_episode, unified_name
3016 """ % (
3017 ' AND '.join(where_parts),
3018 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
3019 )
3020 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3021
3022 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
3023
3024 return tests
3025 #------------------------------------------------------------------
3026 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
3027
3028 try:
3029 epi = int(episode)
3030 except Exception:
3031 epi = episode['pk_episode']
3032
3033 try:
3034 type = int(type)
3035 except Exception:
3036 type = type['pk_test_type']
3037
3038 tr = gmPathLab.create_test_result (
3039 link_obj = link_obj,
3040 encounter = self.current_encounter['pk_encounter'],
3041 episode = epi,
3042 type = type,
3043 intended_reviewer = intended_reviewer,
3044 val_num = val_num,
3045 val_alpha = val_alpha,
3046 unit = unit
3047 )
3048
3049 return tr
3050
3051 #------------------------------------------------------------------
3053 where = 'pk_org_unit IN (%s)' % """
3054 SELECT DISTINCT fk_org_unit FROM clin.test_org WHERE pk IN (
3055 SELECT DISTINCT pk_test_org FROM clin.v_test_results where pk_patient = %(pat)s
3056 )"""
3057 args = {'pat': self.pk_patient}
3058 cmd = gmOrganization._SQL_get_org_unit % where
3059 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3060 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
3061
3062 #------------------------------------------------------------------
3064 measured_gfrs = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, max_no_of_results = 1)
3065 measured_gfr = measured_gfrs[0] if len(measured_gfrs) > 0 else None
3066 creas = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
3067 crea = creas[0] if len(creas) > 0 else None
3068
3069 if (measured_gfr is None) and (crea is None):
3070 return None
3071
3072 if (measured_gfr is not None) and (crea is None):
3073 return measured_gfr
3074
3075 # from here, Crea cannot be None anymore
3076 if measured_gfr is None:
3077 eGFR = self.calculator.eGFR
3078 if eGFR.numeric_value is None:
3079 return crea
3080 return eGFR
3081
3082 # from here, measured_gfr cannot be None anymore, either
3083 two_weeks = pydt.timedelta(weeks = 2)
3084 gfr_too_old = (crea['clin_when'] - measured_gfr['clin_when']) > two_weeks
3085 if not gfr_too_old:
3086 return measured_gfr
3087
3088 # from here, measured_gfr is considered too
3089 # old, so attempt a more timely estimate
3090 eGFR = self.calculator.eGFR
3091 if eGFR.numeric_value is None:
3092 # return crea since we cannot get a
3093 # better estimate for some reason
3094 return crea
3095
3096 return eGFR
3097
3098 best_gfr_or_crea = property(_get_best_gfr_or_crea, lambda x:x)
3099
3100 #------------------------------------------------------------------
3103
3104 bmi = property(_get_bmi, lambda x:x)
3105
3106 #------------------------------------------------------------------
3108 return gmAutoHints.get_hints_for_patient(pk_identity = self.pk_patient, pk_encounter = self.current_encounter['pk_encounter'])
3109
3110 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
3111
3112 #------------------------------------------------------------------
3113 #------------------------------------------------------------------
3114 #------------------------------------------------------------------
3116 # FIXME: verify that it is our patient ? ...
3117 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
3118 return req
3119 #------------------------------------------------------------------
3121 if encounter_id is None:
3122 encounter_id = self.current_encounter['pk_encounter']
3123 status, data = gmPathLab.create_lab_request(
3124 lab=lab,
3125 req_id=req_id,
3126 pat_id=self.pk_patient,
3127 encounter_id=encounter_id,
3128 episode_id=episode_id
3129 )
3130 if not status:
3131 _log.error(str(data))
3132 return None
3133 return data
3134
3135 #============================================================
3136 # main
3137 #------------------------------------------------------------
3138 if __name__ == "__main__":
3139
3140 if len(sys.argv) == 1:
3141 sys.exit()
3142
3143 if sys.argv[1] != 'test':
3144 sys.exit()
3145
3146 from Gnumed.pycommon import gmLog2
3147
3148 from Gnumed.business import gmPraxis
3149 branches = gmPraxis.get_praxis_branches()
3150 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
3151
3156
3157 set_delayed_executor(_do_delayed)
3158
3159 #-----------------------------------------
3161 emr = cClinicalRecord(aPKey=1)
3162 state = emr.allergy_state
3163 print("allergy state is:", state)
3164
3165 print("setting state to 0")
3166 emr.allergy_state = 0
3167
3168 print("setting state to None")
3169 emr.allergy_state = None
3170
3171 print("setting state to 'abc'")
3172 emr.allergy_state = 'abc'
3173
3174 #-----------------------------------------
3176 emr = cClinicalRecord(aPKey = 6)
3177 rows = emr.get_test_types_for_results(unique_meta_types = True)
3178 print("test result names:", len(rows))
3179 # for row in rows:
3180 # print row
3181
3182 #-----------------------------------------
3184 emr = cClinicalRecord(aPKey=12)
3185 rows = emr.get_dates_for_results()
3186 print("test result dates:")
3187 for row in rows:
3188 print(row)
3189
3190 #-----------------------------------------
3192 emr = cClinicalRecord(aPKey=12)
3193 rows, idx = emr.get_measurements_by_date()
3194 print("test results:")
3195 for row in rows:
3196 print(row)
3197
3198 #-----------------------------------------
3200 emr = cClinicalRecord(aPKey=12)
3201 tests = emr.get_test_results_by_date()
3202 print("test results:")
3203 for test in tests:
3204 print(test)
3205
3206 #-----------------------------------------
3208 emr = cClinicalRecord(aPKey=12)
3209 for key, item in emr.get_statistics().items():
3210 print(key, ":", item)
3211
3212 #-----------------------------------------
3214 emr = cClinicalRecord(aPKey=12)
3215
3216 probs = emr.get_problems()
3217 print("normal probs (%s):" % len(probs))
3218 for p in probs:
3219 print('%s (%s)' % (p['problem'], p['type']))
3220
3221 probs = emr.get_problems(include_closed_episodes=True)
3222 print("probs + closed episodes (%s):" % len(probs))
3223 for p in probs:
3224 print('%s (%s)' % (p['problem'], p['type']))
3225
3226 probs = emr.get_problems(include_irrelevant_issues=True)
3227 print("probs + issues (%s):" % len(probs))
3228 for p in probs:
3229 print('%s (%s)' % (p['problem'], p['type']))
3230
3231 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
3232 print("probs + issues + epis (%s):" % len(probs))
3233 for p in probs:
3234 print('%s (%s)' % (p['problem'], p['type']))
3235
3236 #-----------------------------------------
3238 emr = cClinicalRecord(aPKey=12)
3239 tr = emr.add_test_result (
3240 episode = 1,
3241 intended_reviewer = 1,
3242 type = 1,
3243 val_num = 75,
3244 val_alpha = 'somewhat obese',
3245 unit = 'kg'
3246 )
3247 print(tr)
3248
3249 #-----------------------------------------
3253
3254 #-----------------------------------------
3256 emr = cClinicalRecord(aPKey=12)
3257 print(emr.get_last_encounter(issue_id=2))
3258 print(emr.get_last_but_one_encounter(issue_id=2))
3259
3260 #-----------------------------------------
3262 emr = cClinicalRecord(aPKey = 5)
3263 print(emr.get_first_encounter(episode_id = 1638))
3264 print(emr.get_last_encounter(episode_id = 1638))
3265
3266 #-----------------------------------------
3268 emr = cClinicalRecord(aPKey = 12)
3269 for issue in emr.health_issues:
3270 print(issue['description'])
3271
3272 #-----------------------------------------
3277
3278 #-----------------------------------------
3283
3284 #-----------------------------------------
3286 emr = cClinicalRecord(aPKey=12)
3287 for med in emr.abused_substances:
3288 print(med.format(single_line = True))
3289
3290 #-----------------------------------------
3292 emr = cClinicalRecord(aPKey = 12)
3293 print(emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), product_name = sys.argv[2]))
3294
3295 #-----------------------------------------
3297 emr = cClinicalRecord(aPKey = 12)
3298 for journal_line in emr.get_as_journal():
3299 #print journal_line.keys()
3300 print('%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line)
3301 print("")
3302
3303 #-----------------------------------------
3307
3308 #-----------------------------------------
3310 emr = cClinicalRecord(aPKey=12)
3311 print("episodes:", emr.episodes)
3312 print("unlinked:", emr.unlinked_episodes)
3313
3314 #-----------------------------------------
3316 emr = cClinicalRecord(aPKey=12)
3317 from Gnumed.business.gmPerson import cPatient
3318 pat = cPatient(aPK_obj = 12)
3319 print(emr.format_as_journal(left_margin = 1, patient = pat))
3320
3321 #-----------------------------------------
3323 emr = cClinicalRecord(aPKey=12)
3324 #print emr.is_or_was_smoker
3325 smoking, details = emr.smoking_status
3326 print('status:', smoking)
3327 print('details:')
3328 print(details)
3329 emr.smoking_status = (True, {'comment': '2', 'last_confirmed': gmDateTime.pydt_now_here()})
3330 print(emr.smoking_status)
3331 print(emr.alcohol_status)
3332 print(emr.drugs_status)
3333
3334 #-----------------------------------------
3335
3336 #test_allergy_state()
3337 #test_is_allergic_to()
3338
3339 #test_get_test_names()
3340 #test_get_dates_for_results()
3341 #test_get_measurements()
3342 #test_get_test_results_by_date()
3343 #test_get_statistics()
3344 #test_get_problems()
3345 #test_add_test_result()
3346 #test_get_most_recent_episode()
3347 #test_get_almost_recent_encounter()
3348 #test_get_meds()
3349 #test_get_as_journal()
3350 #test_get_most_recent()
3351 #test_episodes()
3352 #test_format_as_journal()
3353 #test_smoking()
3354 #test_get_abuses()
3355 #test_get_encounters()
3356 #test_get_issues()
3357 #test_get_dx()
3358
3359 emr = cClinicalRecord(aPKey = 12)
3360
3361 # # Vacc regimes
3362 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
3363 # print '\nVaccination regimes: '
3364 # for a_regime in vacc_regimes:
3365 # pass
3366 # #print a_regime
3367 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
3368 # #print vacc_regime
3369
3370 # # vaccination regimes and vaccinations for regimes
3371 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
3372 # print 'Vaccinations for the regime:'
3373 # for a_scheduled_vacc in scheduled_vaccs:
3374 # pass
3375 # #print ' %s' %(a_scheduled_vacc)
3376
3377 # # vaccination next shot and booster
3378 v1 = emr.vaccinations
3379 print(v1)
3380 v2 = gmVaccination.get_vaccinations(pk_identity = 12, return_pks = True)
3381 print(v2)
3382 for v in v1:
3383 if v['pk_vaccination'] not in v2:
3384 print('ERROR')
3385
3386 # for a_vacc in vaccinations:
3387 # 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'])
3388
3389 # # first and last encounters
3390 # first_encounter = emr.get_first_encounter(issue_id = 1)
3391 # print '\nFirst encounter: ' + str(first_encounter)
3392 # last_encounter = emr.get_last_encounter(episode_id = 1)
3393 # print '\nLast encounter: ' + str(last_encounter)
3394 # print ''
3395
3396 #dump = record.get_missing_vaccinations()
3397 #f = io.open('vaccs.lst', 'wb')
3398 #if dump is not None:
3399 # print "=== due ==="
3400 # f.write(u"=== due ===\n")
3401 # for row in dump['due']:
3402 # print row
3403 # f.write(repr(row))
3404 # f.write(u'\n')
3405 # print "=== overdue ==="
3406 # f.write(u"=== overdue ===\n")
3407 # for row in dump['overdue']:
3408 # print row
3409 # f.write(repr(row))
3410 # f.write(u'\n')
3411 #f.close()
3412
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |