| 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 gmAllergy
32 from Gnumed.business import gmPathLab
33 from Gnumed.business import gmLOINC
34 from Gnumed.business import gmClinNarrative
35 from Gnumed.business import gmSoapDefs
36 from Gnumed.business import gmEMRStructItems
37 from Gnumed.business import gmMedication
38 from Gnumed.business import gmVaccination
39 from Gnumed.business import gmFamilyHistory
40 from Gnumed.business import gmExternalCare
41 from Gnumed.business import gmOrganization
42 from Gnumed.business import gmAutoHints
43 from Gnumed.business.gmDemographicRecord import get_occupations
44
45
46 _log = logging.getLogger('gm.emr')
47
48 _here = None
49 #============================================================
50 # helper functions
51 #------------------------------------------------------------
52 #_func_ask_user = None
53 #
54 #def set_func_ask_user(a_func = None):
55 # if not callable(a_func):
56 # _log.error('[%] not callable, not setting _func_ask_user', a_func)
57 # return False
58 #
59 # _log.debug('setting _func_ask_user to [%s]', a_func)
60 #
61 # global _func_ask_user
62 # _func_ask_user = a_func
63
64 #============================================================
65 _map_clin_root_item2type_str = {
66 'clin.encounter': _('Encounter'),
67 'clin.episode': _('Episode'),
68 'clin.health_issue': _('Health issue'),
69 'clin.external_care': _('External care'),
70 'clin.vaccination': _('Vaccination'),
71 'clin.clin_narrative': _('Clinical narrative'),
72 'clin.test_result': _('Test result'),
73 'clin.substance_intake': _('Substance intake'),
74 'clin.hospital_stay': _('Hospital stay'),
75 'clin.procedure': _('Performed procedure'),
76 'clin.allergy': _('Allergy'),
77 'clin.allergy_state': _('Allergy state'),
78 'clin.family_history': _('Family history'),
79 'blobs.doc_med': _('Document'),
80 'dem.message_inbox': _('Inbox message'),
81 'ref.auto_hint': _('Dynamic hint')
82 }
83
85 try:
86 return _map_clin_root_item2type_str[table]
87 except KeyError:
88 return _('unmapped entry type from table [%s]') % table
89
90 #------------------------------------------------------------
91 from Gnumed.business.gmDocuments import cDocument
92 from Gnumed.business.gmProviderInbox import cInboxMessage
93
94 _map_table2class = {
95 'clin.encounter': gmEMRStructItems.cEncounter,
96 'clin.episode': gmEMRStructItems.cEpisode,
97 'clin.health_issue': gmEMRStructItems.cHealthIssue,
98 'clin.external_care': gmExternalCare.cExternalCareItem,
99 'clin.vaccination': gmVaccination.cVaccination,
100 'clin.clin_narrative': gmClinNarrative.cNarrative,
101 'clin.test_result': gmPathLab.cTestResult,
102 'clin.substance_intake': gmMedication.cSubstanceIntakeEntry,
103 'clin.hospital_stay': gmEMRStructItems.cHospitalStay,
104 'clin.procedure': gmEMRStructItems.cPerformedProcedure,
105 'clin.allergy': gmAllergy.cAllergy,
106 'clin.allergy_state': gmAllergy.cAllergyState,
107 'clin.family_history': gmFamilyHistory.cFamilyHistory,
108 'clin.suppressed_hint': gmAutoHints.cSuppressedHint,
109 'blobs.doc_med': cDocument,
110 'dem.message_inbox': cInboxMessage,
111 'ref.auto_hint': gmAutoHints.cDynamicHint
112 }
113
115 try:
116 item_class = _map_table2class[table]
117 except KeyError:
118 _log.error('unmapped clin_root_item entry [%s], cannot instantiate', table)
119 return None
120
121 return item_class(aPK_obj = pk)
122
123 #------------------------------------------------------------
125
126 instance = instantiate_clin_root_item(table, pk)
127 if instance is None:
128 return _('cannot instantiate clinical root item <%s(%s)>' % (table, pk))
129
130 # if patient is not None:
131 # if patient.ID != instance['pk_patient']:
132 # raise ValueError(u'patient passed in: [%s], but instance is: [%s:%s:%s]' % (patient.ID, table, pk, instance['pk_patient']))
133
134 if hasattr(instance, 'format_maximum_information'):
135 return '\n'.join(instance.format_maximum_information(patient = patient))
136
137 if hasattr(instance, 'format'):
138 try:
139 formatted = instance.format(patient = patient)
140 except TypeError:
141 formatted = instance.format()
142 if type(formatted) == type([]):
143 return '\n'.join(formatted)
144 return formatted
145
146 d = instance.fields_as_dict (
147 date_format = '%Y %b %d %H:%M',
148 none_string = gmTools.u_diameter,
149 escape_style = None,
150 bool_strings = [_('True'), _('False')]
151 )
152 return gmTools.format_dict_like(d, tabular = True, value_delimiters = None)
153
154 #============================================================
157
158
159 _delayed_execute = __noop_delayed_execute
160
161
163 if not callable(executor):
164 raise TypeError('executor <%s> is not callable' % executor)
165 global _delayed_execute
166 _delayed_execute = executor
167 _log.debug('setting delayed executor to <%s>', executor)
168
169 #------------------------------------------------------------
171
173 """Fails if
174
175 - no connection to database possible
176 - patient referenced by aPKey does not exist
177 """
178 self.pk_patient = aPKey # == identity.pk == primary key
179 self.gender = None
180 self.dob = None
181
182 from Gnumed.business import gmPraxis
183 global _here
184 if _here is None:
185 _here = gmPraxis.gmCurrentPraxisBranch()
186
187 self.__encounter = None
188 self.__setup_active_encounter()
189
190 # register backend notification interests
191 # (keep this last so we won't hang on threads when
192 # failing this constructor for other reasons ...)
193 if not self._register_interests():
194 raise gmExceptions.ConstructorError("cannot register signal interests")
195
196 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
197 #_delayed_execute(gmAllergy.ensure_has_allergy_state, encounter = self.current_encounter['pk_encounter'])
198
199 self.__calculator = None
200
201 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
202
203 # #--------------------------------------------------------
204 # def __old_style_init(self, allow_user_interaction=True):
205 #
206 # _log.error('%s.__old_style_init() used', self.__class__.__name__)
207 # print u'*** GNUmed [%s]: __old_style_init() used ***' % self.__class__.__name__
208 #
209 # # FIXME: delegate to worker thread
210 # # log access to patient record (HIPAA, for example)
211 # cmd = u'SELECT gm.log_access2emr(%(todo)s)'
212 # args = {'todo': u'patient [%s]' % self.pk_patient}
213 # gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
214 #
215 # # load current or create new encounter
216 # if _func_ask_user is None:
217 # _log.error('[_func_ask_user] is None')
218 # print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__
219 #
220 ## # FIXME: delegate to worker thread ?
221 # self.remove_empty_encounters()
222 #
223 # self.__encounter = None
224 # if not self.__initiate_active_encounter(allow_user_interaction = allow_user_interaction):
225 # raise gmExceptions.ConstructorError("cannot activate an encounter for patient [%s]" % self.pk_patient)
226 #
227 ## # FIXME: delegate to worker thread
228 # gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
229
230 #--------------------------------------------------------
232 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
233 if self.__encounter is not None:
234 self.__encounter.unlock(exclusive = False)
235 return True
236
237 #--------------------------------------------------------
239 if action is None:
240 action = 'EMR access for pk_identity [%s]' % self.pk_patient
241 args = {'action': action}
242 cmd = 'SELECT gm.log_access2emr(%(action)s)'
243 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
244
245 #--------------------------------------------------------
247 if self.__calculator is None:
248 from Gnumed.business.gmClinicalCalculator import cClinicalCalculator
249 self.__calculator = cClinicalCalculator()
250 from Gnumed.business.gmPerson import gmCurrentPatient
251 curr_pat = gmCurrentPatient()
252 if curr_pat.ID == self.pk_patient:
253 self.__calculator.patient = curr_pat
254 else:
255 from Gnumed.business.gmPerson import cPatient
256 self.__calculator.patient = cPatient(self.pk_patient)
257 return self.__calculator
258
259 calculator = property(_get_calculator, lambda x:x)
260
261 #--------------------------------------------------------
262 # messaging
263 #--------------------------------------------------------
265 #gmDispatcher.connect(signal = u'clin.encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
266 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self.db_modification_callback)
267
268 return True
269
270 #--------------------------------------------------------
272
273 if kwds['table'] != 'clin.encounter':
274 return True
275 if self.current_encounter is None:
276 _log.debug('no local current-encounter, ignoring encounter modification signal')
277 return True
278 if int(kwds['pk_of_row']) != self.current_encounter['pk_encounter']:
279 _log.debug('modified encounter [%s] != local encounter [%s], ignoring signal', kwds['pk_of_row'], self.current_encounter['pk_encounter'])
280 return True
281
282 _log.debug('modification of our encounter (%s) signalled (%s)', self.current_encounter['pk_encounter'], kwds['pk_of_row'])
283
284 # get the current encounter as an extra instance
285 # from the database to check for changes
286 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
287
288 # the encounter just retrieved and the active encounter
289 # have got the same transaction ID so there's no change
290 # in the database, there could be a local change in
291 # the active encounter but that doesn't matter because
292 # no one else can have written to the DB so far
293 if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
294 _log.debug('same XMIN, no difference between DB and in-client instance of current encounter expected')
295 if self.current_encounter.is_modified():
296 _log.error('encounter modification signal from DB with same XMIN as in local in-client instance of encounter BUT local instance ALSO has .is_modified()=True')
297 _log.error('this hints at an error in .is_modified handling')
298 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
299 return True
300
301 # there must have been a change to the active encounter
302 # committed to the database from elsewhere,
303 # we must fail propagating the change, however, if
304 # there are local changes pending
305 if self.current_encounter.is_modified():
306 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'signalled enc loaded from DB')
307 raise ValueError('unsaved changes in locally active encounter [%s], cannot switch to DB state of encounter [%s]' % (
308 self.current_encounter['pk_encounter'],
309 curr_enc_in_db['pk_encounter']
310 ))
311
312 # don't do this: same_payload() does not compare _all_ fields
313 # so we can get into a reality disconnect if we don't
314 # announce the mod
315 # if self.current_encounter.same_payload(another_object = curr_enc_in_db):
316 # _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
317 # return True
318
319 # there was a change in the database from elsewhere,
320 # locally, however, we don't have any pending changes,
321 # therefore we can propagate the remote change locally
322 # without losing anything
323 # this really should be the standard case
324 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
325 _log.debug('active encounter modified remotely, no locally pending changes, reloading from DB and locally announcing the remote modification')
326 self.current_encounter.refetch_payload()
327 gmDispatcher.send('current_encounter_modified')
328
329 return True
330
331 #--------------------------------------------------------
333
334 # get the current encounter as an extra instance
335 # from the database to check for changes
336 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
337
338 # the encounter just retrieved and the active encounter
339 # have got the same transaction ID so there's no change
340 # in the database, there could be a local change in
341 # the active encounter but that doesn't matter because
342 # no one else can have written to the DB so far
343 # THIS DOES NOT WORK
344 # if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
345 # return True
346
347 # there must have been a change to the active encounter
348 # committed to the database from elsewhere,
349 # we must fail propagating the change, however, if
350 # there are local changes
351 if self.current_encounter.is_modified():
352 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
353 _log.error('current in client: %s', self.current_encounter)
354 raise ValueError('unsaved changes in active encounter [%s], cannot switch [%s]' % (
355 self.current_encounter['pk_encounter'],
356 curr_enc_in_db['pk_encounter']
357 ))
358
359 if self.current_encounter.same_payload(another_object = curr_enc_in_db):
360 _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
361 return True
362
363 # there was a change in the database from elsewhere,
364 # locally, however, we don't have any changes, therefore
365 # we can propagate the remote change locally without
366 # losing anything
367 gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
368 _log.debug('active encounter modified remotely, reloading from DB and locally announcing the modification')
369 self.current_encounter.refetch_payload()
370 gmDispatcher.send('current_encounter_modified')
371
372 return True
373
374 #--------------------------------------------------------
375 # API: family history
376 #--------------------------------------------------------
378 fhx = gmFamilyHistory.get_family_history (
379 order_by = 'l10n_relation, condition',
380 patient = self.pk_patient
381 )
382
383 if episodes is not None:
384 fhx = [ f for f in fhx if f['pk_episode'] in episodes ]
385
386 if issues is not None:
387 fhx = [ f for f in fhx if f['pk_health_issue'] in issues ]
388
389 if encounters is not None:
390 fhx = [ f for f in fhx if f['pk_encounter'] in encounters ]
391
392 return fhx
393
394 #--------------------------------------------------------
396 return gmFamilyHistory.create_family_history (
397 encounter = self.current_encounter['pk_encounter'],
398 episode = episode,
399 condition = condition,
400 relation = relation
401 )
402
403 #--------------------------------------------------------
404 # API: pregnancy
405 #--------------------------------------------------------
407 if self.__gender is not None:
408 return self.__gender
409 cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
410 args = {'pat': self.pk_patient}
411 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
412 self.__gender = rows[0]['gender']
413 self.__dob = rows[0]['dob']
414
416 self.__gender = gender
417
418 gender = property(_get_gender, _set_gender)
419
420 #--------------------------------------------------------
422 if self.__dob is not None:
423 return self.__dob
424 cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
425 args = {'pat': self.pk_patient}
426 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
427 self.__gender = rows[0]['gender']
428 self.__dob = rows[0]['dob']
429
431 self.__dob = dob
432
433 dob = property(_get_dob, _set_dob)
434
435 #--------------------------------------------------------
437 cmd = 'SELECT edc FROM clin.patient WHERE fk_identity = %(pat)s'
438 args = {'pat': self.pk_patient}
439 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
440 if len(rows) == 0:
441 return None
442 return rows[0]['edc']
443
445 cmd = """
446 INSERT INTO clin.patient (fk_identity, edc) SELECT
447 %(pat)s,
448 %(edc)s
449 WHERE NOT EXISTS (
450 SELECT 1 FROM clin.patient WHERE fk_identity = %(pat)s
451 )
452 RETURNING pk"""
453 args = {'pat': self.pk_patient, 'edc': edc}
454 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
455 if len(rows) == 0:
456 cmd = 'UPDATE clin.patient SET edc = %(edc)s WHERE fk_identity = %(pat)s'
457 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
458
459 EDC = property(_get_EDC, _set_EDC)
460
461 #--------------------------------------------------------
463 edc = self.EDC
464 if edc is None:
465 return False
466 if self.gender != 'f':
467 return True
468 now = gmDateTime.pydt_now_here()
469 # mother too young
470 if (self.dob + pydt.timedelta(weeks = 5 * 52)) > now:
471 return True
472 # mother too old
473 if (self.dob + pydt.timedelta(weeks = 55 * 52)) < now:
474 return True
475 # Beulah Hunter, 375 days (http://www.reference.com/motif/health/longest-human-pregnancy-on-record)
476 # EDC too far in the future
477 if (edc - pydt.timedelta(days = 380)) > now:
478 return True
479 # even if the pregnancy would have *started* when it
480 # was documented to *end* it would be over by now by
481 # all accounts
482 # EDC too far in the past
483 if edc < (now - pydt.timedelta(days = 380)):
484 return True
485
486 EDC_is_fishy = property(_EDC_is_fishy, lambda x:x)
487
488 #--------------------------------------------------------
490 try:
491 details['quit_when']
492 except KeyError:
493 details['quit_when'] = None
494
495 try:
496 details['last_confirmed']
497 if details['last_confirmed'] is None:
498 details['last_confirmed'] = gmDateTime.pydt_now_here()
499 except KeyError:
500 details['last_confirmed'] = gmDateTime.pydt_now_here()
501
502 try:
503 details['comment']
504 if details['comment'].strip() == '':
505 details['comment'] = None
506 except KeyError:
507 details['comment'] = None
508
509 return details
510
511 #--------------------------------------------------------
517
519 # valid ?
520 status_flag, details = status
521 self.__harmful_substance_use = None
522 args = {
523 'pat': self.pk_patient,
524 'status': status_flag
525 }
526 if status_flag is None:
527 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = NULL WHERE fk_identity = %(pat)s'
528 elif status_flag == 0:
529 details['quit_when'] = None
530 args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
531 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
532 else:
533 args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
534 cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
535 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
536
537 smoking_status = property(_get_smoking_status, _set_smoking_status)
538
539 #--------------------------------------------------------
545
547 # valid ?
548 harmful, details = status
549 self.__harmful_substance_use = None
550 args = {'pat': self.pk_patient}
551 if harmful is None:
552 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = NULL, c2_details = NULL WHERE fk_identity = %(pat)s'
553 elif harmful is False:
554 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = FALSE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
555 else:
556 cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = TRUE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
557 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
558
559 alcohol_status = property(_get_alcohol_status, _set_alcohol_status)
560
561 #--------------------------------------------------------
567
569 # valid ?
570 harmful, details = status
571 self.__harmful_substance_use = None
572 args = {'pat': self.pk_patient}
573 if harmful is None:
574 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = NULL, drugs_details = NULL WHERE fk_identity = %(pat)s'
575 elif harmful is False:
576 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = FALSE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
577 else:
578 cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = TRUE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
579 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
580
581 drugs_status = property(_get_drugs_status, _set_drugs_status)
582
583 #--------------------------------------------------------
585 # caching does not take into account status changes from elsewhere
586 try:
587 self.__harmful_substance_use
588 except AttributeError:
589 self.__harmful_substance_use = None
590
591 if self.__harmful_substance_use is not None:
592 return self.__harmful_substance_use
593
594 args = {'pat': self.pk_patient}
595 cmd = """
596 SELECT
597 -- tobacco use
598 smoking_status,
599 smoking_details,
600 (smoking_details->>'last_confirmed')::timestamp with time zone
601 AS ts_last,
602 (smoking_details->>'quit_when')::timestamp with time zone
603 AS ts_quit,
604 -- c2 use
605 c2_currently_harmful_use,
606 c2_details,
607 -- other drugs use
608 drugs_currently_harmful_use,
609 drugs_details
610 FROM clin.patient
611 WHERE fk_identity = %(pat)s
612 """
613 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
614 if len(rows) == 0:
615 return None
616 # disentangle smoking
617 status = rows[0]['smoking_status']
618 details = rows[0]['smoking_details']
619 if status is not None:
620 details['last_confirmed'] = rows[0]['ts_last']
621 details['quit_when'] = rows[0]['ts_quit']
622 # set fields
623 self.__harmful_substance_use = {
624 'tobacco': (status, details),
625 'alcohol': (rows[0]['c2_currently_harmful_use'], rows[0]['c2_details']),
626 'drugs': (rows[0]['drugs_currently_harmful_use'], rows[0]['drugs_details'])
627 }
628
629 return self.__harmful_substance_use
630
631
633 cmd = 'SELECT * FROM clin.v_substance_intakes WHERE harmful_use_type = %s'
634
635 harmful_substance_use = property(_get_harmful_substance_use, lambda x:x)
636
637 #--------------------------------------------------------
638 - def format_harmful_substance_use(self, include_tobacco=True, include_alcohol=True, include_drugs=True, include_nonuse=True, include_unknown=True):
639 use = self.harmful_substance_use
640 if use is None:
641 return []
642
643 lines = []
644
645 if include_tobacco:
646 status, details = use['tobacco']
647 add_details = False
648 if status is None:
649 if include_unknown:
650 lines.append(_('unknown smoking status'))
651 elif status == 0:
652 if include_nonuse:
653 lines.append('%s (%s)' % (_('non-smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
654 add_details = True
655 elif status == 1: # now or previous
656 if details['quit_when'] is None:
657 lines.append('%s (%s)' % (_('current smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
658 add_details = True
659 elif details['quit_when'] < gmDateTime.pydt_now_here():
660 if include_nonuse:
661 lines.append('%s (%s)' % (_('ex-smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
662 add_details = True
663 else:
664 lines.append('%s (%s)' % (_('current smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
665 add_details = True
666 elif status == 2: # addicted
667 lines.append('%s (%s)' % (_('tobacco addiction'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
668 add_details = True
669 if add_details:
670 if details['quit_when'] is not None:
671 lines.append(' %s: %s' % (_('Quit date'), gmDateTime.pydt_strftime(details['quit_when'], '%Y %b %d')))
672 if details['comment'] is not None:
673 lines.append(' %s' % details['comment'])
674
675 if include_alcohol:
676 status, details = use['alcohol']
677 if status is False:
678 if include_nonuse:
679 if len(lines) > 0:
680 lines.append('')
681 lines.append(_('no or non-harmful alcohol use'))
682 lines.append(' %s' % details)
683 elif status is True:
684 if len(lines) > 0:
685 lines.append('')
686 lines.append(_('harmful alcohol use'))
687 lines.append(' %s' % details)
688 else:
689 if include_unknown:
690 if len(lines) > 0:
691 lines.append('')
692 lines.append(_('unknown alcohol use'))
693 lines.append(' %s' % details)
694
695 if include_drugs:
696 status, details = use['drugs']
697 if status is False:
698 if include_nonuse:
699 if len(lines) > 0:
700 lines.append('')
701 lines.append(_('no or non-harmful drug use'))
702 lines.append(' %s' % details)
703 elif status is True:
704 if len(lines) > 0:
705 lines.append('')
706 lines.append(_('harmful drug use'))
707 lines.append(' %s' % details)
708 else:
709 if include_unknown:
710 if len(lines) > 0:
711 lines.append('')
712 lines.append(_('unknown drug use'))
713 lines.append(' %s' % details)
714
715 return lines
716
717 #--------------------------------------------------------
719 # returns True / False / None (= unknown)
720
721 use = self.harmful_substance_use
722 # we know that at least one group is used:
723 if use['alcohol'][0] is True:
724 return True
725 if use['drugs'][0] is True:
726 return True
727 if use['tobacco'][0] > 0:
728 # is True:
729 if use['tobacco'][1]['quit_when'] is None:
730 return True
731 # at this point no group is currently used for sure
732 # we don't know about some of the groups so we can NOT say: no abuse at all:
733 if use['alcohol'][0] is None:
734 return None
735 if use['drugs'][0] is None:
736 return None
737 if use['tobacco'][0] is None:
738 return None
739 # at this point all groups must be FALSE, except for
740 # tobacco which can also be TRUE _but_, if so, a quit
741 # date has been set, which is considered non-abuse
742 return False
743
744 currently_abuses_substances = property(_get_currently_abuses_substances, lambda x:x)
745
746 #--------------------------------------------------------
747 # API: performed procedures
748 #--------------------------------------------------------
750
751 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
752
753 if episodes is not None:
754 procs = [ p for p in procs if p['pk_episode'] in episodes ]
755
756 if issues is not None:
757 procs = [ p for p in procs if p['pk_health_issue'] in issues ]
758
759 return procs
760
761 performed_procedures = property(get_performed_procedures, lambda x:x)
762 #--------------------------------------------------------
765 #--------------------------------------------------------
766 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
767 return gmEMRStructItems.create_performed_procedure (
768 encounter = self.current_encounter['pk_encounter'],
769 episode = episode,
770 location = location,
771 hospital_stay = hospital_stay,
772 procedure = procedure
773 )
774 #--------------------------------------------------------
776 where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_procedures_not_at_hospital WHERE pk_patient = %(pat)s)'
777 args = {'pat': self.pk_patient}
778 cmd = gmOrganization._SQL_get_org_unit % where
779 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
780 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
781
782 #--------------------------------------------------------
783 # API: hospitalizations
784 #--------------------------------------------------------
786 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient, ongoing_only = ongoing_only)
787 if episodes is not None:
788 stays = [ s for s in stays if s['pk_episode'] in episodes ]
789 if issues is not None:
790 stays = [ s for s in stays if s['pk_health_issue'] in issues ]
791 return stays
792
793 hospital_stays = property(get_hospital_stays, lambda x:x)
794 #--------------------------------------------------------
797 #--------------------------------------------------------
799 return gmEMRStructItems.create_hospital_stay (
800 encounter = self.current_encounter['pk_encounter'],
801 episode = episode,
802 fk_org_unit = fk_org_unit
803 )
804 #--------------------------------------------------------
806 args = {'pat': self.pk_patient, 'range': cover_period}
807 where_parts = ['pk_patient = %(pat)s']
808 if cover_period is not None:
809 where_parts.append('discharge > (now() - %(range)s)')
810
811 cmd = """
812 SELECT hospital, count(1) AS frequency
813 FROM clin.v_hospital_stays
814 WHERE
815 %s
816 GROUP BY hospital
817 ORDER BY frequency DESC
818 """ % ' AND '.join(where_parts)
819
820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
821 return rows
822 #--------------------------------------------------------
824 where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s)'
825 args = {'pat': self.pk_patient}
826 cmd = gmOrganization._SQL_get_org_unit % where
827 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
828 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
829
830 #--------------------------------------------------------
831 # API: narrative
832 #--------------------------------------------------------
834 enc = gmTools.coalesce (
835 encounter,
836 self.current_encounter['pk_encounter']
837 )
838 for note in notes:
839 gmClinNarrative.create_narrative_item (
840 narrative = note[1],
841 soap_cat = note[0],
842 episode_id = episode,
843 encounter_id = enc
844 )
845 return True
846
847 #--------------------------------------------------------
849 if note.strip() == '':
850 _log.info('will not create empty clinical note')
851 return None
852 if isinstance(episode, gmEMRStructItems.cEpisode):
853 episode = episode['pk_episode']
854 instance = gmClinNarrative.create_narrative_item (
855 link_obj = link_obj,
856 narrative = note,
857 soap_cat = soap_cat,
858 episode_id = episode,
859 encounter_id = self.current_encounter['pk_encounter']
860 )
861 return instance
862
863 #--------------------------------------------------------
864 - def get_clin_narrative(self, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
865 """Get SOAP notes pertinent to this encounter.
866
867 encounters
868 - list of encounters the narrative of which are to be retrieved
869 episodes
870 - list of episodes the narrative of which are to be retrieved
871 issues
872 - list of health issues the narrative of which are to be retrieved
873 soap_cats
874 - list of SOAP categories of the narrative to be retrieved
875 """
876 where_parts = ['pk_patient = %(pat)s']
877 args = {'pat': self.pk_patient}
878
879 if issues is not None:
880 where_parts.append('pk_health_issue IN %(issues)s')
881 if len(issues) == 0:
882 args['issues'] = tuple()
883 else:
884 if isinstance(issues[0], gmEMRStructItems.cHealthIssue):
885 args['issues'] = tuple([ i['pk_health_issue'] for i in issues ])
886 elif isinstance(issues[0], int):
887 args['issues'] = tuple(issues)
888 else:
889 raise ValueError('<issues> must be list of type int (=pk) or cHealthIssue, but 1st issue is: %s' % issues[0])
890
891 if episodes is not None:
892 where_parts.append('pk_episode IN %(epis)s')
893 if len(episodes) == 0:
894 args['epis'] = tuple()
895 else:
896 if isinstance(episodes[0], gmEMRStructItems.cEpisode):
897 args['epis'] = tuple([ e['pk_episode'] for e in episodes ])
898 elif isinstance(episodes[0], int):
899 args['epis'] = tuple(episodes)
900 else:
901 raise ValueError('<episodes> must be list of type int (=pk) or cEpisode, but 1st episode is: %s' % episodes[0])
902
903 if encounters is not None:
904 where_parts.append('pk_encounter IN %(encs)s')
905 if len(encounters) == 0:
906 args['encs'] = tuple()
907 else:
908 if isinstance(encounters[0], gmEMRStructItems.cEncounter):
909 args['encs'] = tuple([ e['pk_encounter'] for e in encounters ])
910 elif isinstance(encounters[0], int):
911 args['encs'] = tuple(encounters)
912 else:
913 raise ValueError('<encounters> must be list of type int (=pk) or cEncounter, but 1st encounter is: %s' % encounters[0])
914
915 if soap_cats is not None:
916 where_parts.append('c_vn.soap_cat IN %(cats)s')
917 args['cats'] = tuple(gmSoapDefs.soap_cats2list(soap_cats))
918
919 if providers is not None:
920 where_parts.append('c_vn.modified_by IN %(docs)s')
921 args['docs'] = tuple(providers)
922
923 cmd = """
924 SELECT
925 c_vn.*,
926 c_scr.rank AS soap_rank
927 FROM
928 clin.v_narrative c_vn
929 LEFT JOIN clin.soap_cat_ranks c_scr on c_vn.soap_cat = c_scr.soap_cat
930 WHERE %s
931 ORDER BY date, soap_rank
932 """ % ' AND '.join(where_parts)
933
934 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
935 return [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
936
937 #--------------------------------------------------------
938 - def get_as_journal(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None):
939 return gmClinNarrative.get_as_journal (
940 patient = self.pk_patient,
941 since = since,
942 until = until,
943 encounters = encounters,
944 episodes = episodes,
945 issues = issues,
946 soap_cats = soap_cats,
947 providers = providers,
948 order_by = order_by,
949 time_range = time_range,
950 active_encounter = self.active_encounter
951 )
952
953 #--------------------------------------------------------
955
956 search_term = search_term.strip()
957 if search_term == '':
958 return []
959
960 cmd = """
961 SELECT
962 *,
963 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
964 as episode,
965 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
966 as health_issue,
967 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
968 as encounter_started,
969 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
970 as encounter_ended,
971 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
972 as encounter_type
973 from clin.v_narrative4search vn4s
974 WHERE
975 pk_patient = %(pat)s and
976 vn4s.narrative ~ %(term)s
977 order by
978 encounter_started
979 """ # case sensitive
980 rows, idx = gmPG2.run_ro_queries(queries = [
981 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
982 ])
983 return rows
984 #--------------------------------------------------------
986 fields = [
987 'age',
988 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
989 'modified_by',
990 'clin_when',
991 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
992 'pk_item',
993 'pk_encounter',
994 'pk_episode',
995 'pk_health_issue',
996 'src_table'
997 ]
998 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
999 # handle constraint conditions
1000 where_snippets = []
1001 params = {}
1002 where_snippets.append('pk_patient=%(pat_id)s')
1003 params['pat_id'] = self.pk_patient
1004 if not since is None:
1005 where_snippets.append('clin_when >= %(since)s')
1006 params['since'] = since
1007 if not until is None:
1008 where_snippets.append('clin_when <= %(until)s')
1009 params['until'] = until
1010 # FIXME: these are interrelated, eg if we constrain encounter
1011 # we automatically constrain issue/episode, so handle that,
1012 # encounters
1013 if not encounters is None and len(encounters) > 0:
1014 params['enc'] = encounters
1015 if len(encounters) > 1:
1016 where_snippets.append('fk_encounter in %(enc)s')
1017 else:
1018 where_snippets.append('fk_encounter=%(enc)s')
1019 # episodes
1020 if not episodes is None and len(episodes) > 0:
1021 params['epi'] = episodes
1022 if len(episodes) > 1:
1023 where_snippets.append('fk_episode in %(epi)s')
1024 else:
1025 where_snippets.append('fk_episode=%(epi)s')
1026 # health issues
1027 if not issues is None and len(issues) > 0:
1028 params['issue'] = issues
1029 if len(issues) > 1:
1030 where_snippets.append('fk_health_issue in %(issue)s')
1031 else:
1032 where_snippets.append('fk_health_issue=%(issue)s')
1033
1034 where_clause = ' and '.join(where_snippets)
1035 order_by = 'order by src_table, age'
1036 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
1037
1038 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
1039 if rows is None:
1040 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
1041 return None
1042
1043 # -- sort the data --
1044 # FIXME: by issue/encounter/episode, eg formatting
1045 # aggregate by src_table for item retrieval
1046 items_by_table = {}
1047 for item in rows:
1048 src_table = item[view_col_idx['src_table']]
1049 pk_item = item[view_col_idx['pk_item']]
1050 if src_table not in items_by_table:
1051 items_by_table[src_table] = {}
1052 items_by_table[src_table][pk_item] = item
1053
1054 # get mapping for issue/episode IDs
1055 issues = self.get_health_issues()
1056 issue_map = {}
1057 for issue in issues:
1058 issue_map[issue['pk_health_issue']] = issue['description']
1059 episodes = self.get_episodes()
1060 episode_map = {}
1061 for episode in episodes:
1062 episode_map[episode['pk_episode']] = episode['description']
1063 emr_data = {}
1064 # get item data from all source tables
1065 ro_conn = self._conn_pool.GetConnection('historica')
1066 curs = ro_conn.cursor()
1067 for src_table in items_by_table.keys():
1068 item_ids = items_by_table[src_table].keys()
1069 # we don't know anything about the columns of
1070 # the source tables but, hey, this is a dump
1071 if len(item_ids) == 0:
1072 _log.info('no items in table [%s] ?!?' % src_table)
1073 continue
1074 elif len(item_ids) == 1:
1075 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
1076 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
1077 _log.error('cannot load items from table [%s]' % src_table)
1078 # skip this table
1079 continue
1080 elif len(item_ids) > 1:
1081 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
1082 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
1083 _log.error('cannot load items from table [%s]' % src_table)
1084 # skip this table
1085 continue
1086 rows = curs.fetchall()
1087 table_col_idx = gmPG.get_col_indices(curs)
1088 # format per-table items
1089 for row in rows:
1090 # FIXME: make this get_pkey_name()
1091 pk_item = row[table_col_idx['pk_item']]
1092 view_row = items_by_table[src_table][pk_item]
1093 age = view_row[view_col_idx['age']]
1094 # format metadata
1095 try:
1096 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
1097 except:
1098 episode_name = view_row[view_col_idx['pk_episode']]
1099 try:
1100 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
1101 except:
1102 issue_name = view_row[view_col_idx['pk_health_issue']]
1103
1104 if age not in emr_data:
1105 emr_data[age] = []
1106
1107 emr_data[age].append(
1108 _('%s: encounter (%s)') % (
1109 view_row[view_col_idx['clin_when']],
1110 view_row[view_col_idx['pk_encounter']]
1111 )
1112 )
1113 emr_data[age].append(_('health issue: %s') % issue_name)
1114 emr_data[age].append(_('episode : %s') % episode_name)
1115 # format table specific data columns
1116 # - ignore those, they are metadata, some
1117 # are in clin.v_pat_items data already
1118 cols2ignore = [
1119 'pk_audit', 'row_version', 'modified_when', 'modified_by',
1120 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
1121 ]
1122 col_data = []
1123 for col_name in table_col_idx.keys():
1124 if col_name in cols2ignore:
1125 continue
1126 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
1127 emr_data[age].append("----------------------------------------------------")
1128 emr_data[age].append("-- %s from table %s" % (
1129 view_row[view_col_idx['modified_string']],
1130 src_table
1131 ))
1132 emr_data[age].append("-- written %s by %s" % (
1133 view_row[view_col_idx['modified_when']],
1134 view_row[view_col_idx['modified_by']]
1135 ))
1136 emr_data[age].append("----------------------------------------------------")
1137 curs.close()
1138 return emr_data
1139 #--------------------------------------------------------
1142 #--------------------------------------------------------
1144 union_query = '\n union all\n'.join ([
1145 """
1146 SELECT ((
1147 -- all relevant health issues + active episodes WITH health issue
1148 SELECT COUNT(1)
1149 FROM clin.v_problem_list
1150 WHERE
1151 pk_patient = %(pat)s
1152 AND
1153 pk_health_issue is not null
1154 ) + (
1155 -- active episodes WITHOUT health issue
1156 SELECT COUNT(1)
1157 FROM clin.v_problem_list
1158 WHERE
1159 pk_patient = %(pat)s
1160 AND
1161 pk_health_issue is null
1162 ))""",
1163 'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
1164 'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
1165 'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
1166 'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
1167 'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
1168 'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
1169 # active and approved substances == medication
1170 """
1171 SELECT count(1)
1172 FROM clin.v_substance_intakes
1173 WHERE
1174 pk_patient = %(pat)s
1175 AND
1176 is_currently_active IN (null, true)
1177 AND
1178 intake_is_approved_of IN (null, true)""",
1179 'SELECT count(1) FROM clin.v_vaccinations WHERE pk_patient = %(pat)s'
1180 ])
1181
1182 rows, idx = gmPG2.run_ro_queries (
1183 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
1184 get_col_idx = False
1185 )
1186
1187 stats = dict (
1188 problems = rows[0][0],
1189 encounters = rows[1][0],
1190 items = rows[2][0],
1191 documents = rows[3][0],
1192 results = rows[4][0],
1193 stays = rows[5][0],
1194 procedures = rows[6][0],
1195 active_drugs = rows[7][0],
1196 vaccinations = rows[8][0]
1197 )
1198
1199 return stats
1200 #--------------------------------------------------------
1202 return _(
1203 'Medical problems: %(problems)s\n'
1204 'Total encounters: %(encounters)s\n'
1205 'Total EMR entries: %(items)s\n'
1206 'Active medications: %(active_drugs)s\n'
1207 'Documents: %(documents)s\n'
1208 'Test results: %(results)s\n'
1209 'Hospitalizations: %(stays)s\n'
1210 'Procedures: %(procedures)s\n'
1211 'Vaccinations: %(vaccinations)s'
1212 ) % self.get_statistics()
1213 #--------------------------------------------------------
1215
1216 cmd = "SELECT dob FROM dem.v_all_persons WHERE pk_identity = %(pk)s"
1217 args = {'pk': self.pk_patient}
1218 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1219 dob = rows[0]['dob']
1220
1221 stats = self.get_statistics()
1222 first = self.get_first_encounter()
1223 last = self.get_last_encounter()
1224 probs = self.get_problems()
1225
1226 txt = ''
1227 if len(probs) > 0:
1228 txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems']
1229 else:
1230 txt += _(' %s known problems\n') % stats['problems']
1231 for prob in probs:
1232 if not prob['clinically_relevant']:
1233 continue
1234 txt += ' \u00BB%s\u00AB (%s)\n' % (
1235 prob['problem'],
1236 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
1237 )
1238 txt += '\n'
1239 txt += _(' %s encounters from %s to %s\n') % (
1240 stats['encounters'],
1241 gmDateTime.pydt_strftime(first['started'], '%Y %b %d'),
1242 gmDateTime.pydt_strftime(last['started'], '%Y %b %d')
1243 )
1244 txt += _(' %s active medications\n') % stats['active_drugs']
1245 txt += _(' %s documents\n') % stats['documents']
1246 txt += _(' %s test results\n') % stats['results']
1247 txt += _(' %s hospitalizations') % stats['stays']
1248 if stats['stays'] == 0:
1249 txt += '\n'
1250 else:
1251 txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3)
1252 # FIXME: perhaps only count "ongoing ones"
1253 txt += _(' %s performed procedures') % stats['procedures']
1254 if stats['procedures'] == 0:
1255 txt += '\n'
1256 else:
1257 txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3)
1258
1259 txt += '\n'
1260 txt += _('Allergies and Intolerances\n')
1261
1262 allg_state = self.allergy_state
1263 txt += (' ' + allg_state.state_string)
1264 if allg_state['last_confirmed'] is not None:
1265 txt += _(' (last confirmed %s)') % gmDateTime.pydt_strftime(allg_state['last_confirmed'], '%Y %b %d')
1266 txt += '\n'
1267 txt += gmTools.coalesce(allg_state['comment'], '', ' %s\n')
1268 for allg in self.get_allergies():
1269 txt += ' %s: %s\n' % (
1270 allg['descriptor'],
1271 gmTools.coalesce(allg['reaction'], _('unknown reaction'))
1272 )
1273
1274 meds = self.get_current_medications(order_by = 'intake_is_approved_of DESC, substance')
1275 if len(meds) > 0:
1276 txt += '\n'
1277 txt += _('Medications and Substances')
1278 txt += '\n'
1279 for m in meds:
1280 txt += '%s\n' % m.format_as_single_line(left_margin = 1)
1281
1282 fhx = self.get_family_history()
1283 if len(fhx) > 0:
1284 txt += '\n'
1285 txt += _('Family History')
1286 txt += '\n'
1287 for f in fhx:
1288 txt += '%s\n' % f.format(left_margin = 1)
1289
1290 jobs = get_occupations(pk_identity = self.pk_patient)
1291 if len(jobs) > 0:
1292 txt += '\n'
1293 txt += _('Occupations')
1294 txt += '\n'
1295 for job in jobs:
1296 txt += ' %s%s\n' % (
1297 job['l10n_occupation'],
1298 gmTools.coalesce(job['activities'], '', ': %s')
1299 )
1300
1301 vaccs = self.get_latest_vaccinations()
1302 if len(vaccs) > 0:
1303 txt += '\n'
1304 txt += _('Vaccinations')
1305 txt += '\n'
1306 inds = sorted(vaccs.keys())
1307 for ind in inds:
1308 ind_count, vacc = vaccs[ind]
1309 if dob is None:
1310 age_given = ''
1311 else:
1312 age_given = ' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
1313 start = dob,
1314 end = vacc['date_given']
1315 ))
1316 since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given'])
1317 txt += ' %s (%s%s): %s%s (%s %s%s%s)\n' % (
1318 ind,
1319 gmTools.u_sum,
1320 ind_count,
1321 #gmDateTime.pydt_strftime(vacc['date_given'], '%b %Y'),
1322 since,
1323 age_given,
1324 vacc['vaccine'],
1325 gmTools.u_left_double_angle_quote,
1326 vacc['batch_no'],
1327 gmTools.u_right_double_angle_quote
1328 )
1329
1330 care = self.get_external_care_items(order_by = 'issue, organization, unit, provider', exclude_inactive = True)
1331 if len(care) > 0:
1332 txt += '\n'
1333 txt += _('External care')
1334 txt += '\n'
1335 for item in care:
1336 txt += ' %s: %s\n' % (
1337 item['issue'],
1338 gmTools.coalesce (
1339 item['provider'],
1340 '%s@%s' % (item['unit'], item['organization']),
1341 '%%s (%s@%s)' % (item['unit'], item['organization'])
1342 )
1343 )
1344
1345 return txt
1346
1347 #--------------------------------------------------------
1349 txt = ''
1350 for enc in self.get_encounters(skip_empty = True):
1351 txt += gmTools.u_box_horiz_4dashes * 70 + '\n'
1352 txt += enc.format (
1353 episodes = None, # means: each touched upon
1354 left_margin = left_margin,
1355 patient = patient,
1356 fancy_header = False,
1357 with_soap = True,
1358 with_docs = True,
1359 with_tests = True,
1360 with_vaccinations = True,
1361 with_co_encountlet_hints = False, # irrelevant
1362 with_rfe_aoe = True,
1363 with_family_history = True,
1364 by_episode = True
1365 )
1366
1367 return txt
1368 #--------------------------------------------------------
1369 # API: allergy
1370 #--------------------------------------------------------
1371 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
1372 """Retrieves patient allergy items.
1373
1374 remove_sensitivities
1375 - retrieve real allergies only, without sensitivities
1376 since
1377 - initial date for allergy items
1378 until
1379 - final date for allergy items
1380 encounters
1381 - list of encounters whose allergies are to be retrieved
1382 episodes
1383 - list of episodes whose allergies are to be retrieved
1384 issues
1385 - list of health issues whose allergies are to be retrieved
1386 """
1387 cmd = "SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
1388 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
1389 filtered_allergies = []
1390 for r in rows:
1391 filtered_allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
1392
1393 # ok, let's constrain our list
1394 if ID_list is not None:
1395 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_allergy'] in ID_list ]
1396 if len(filtered_allergies) == 0:
1397 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
1398 # better fail here contrary to what we do elsewhere
1399 return None
1400 else:
1401 return filtered_allergies
1402
1403 if remove_sensitivities:
1404 filtered_allergies = [ allg for allg in filtered_allergies if allg['type'] == 'allergy' ]
1405 if since is not None:
1406 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] >= since ]
1407 if until is not None:
1408 filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] < until ]
1409 if issues is not None:
1410 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_health_issue'] in issues ]
1411 if episodes is not None:
1412 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_episode'] in episodes ]
1413 if encounters is not None:
1414 filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_encounter'] in encounters ]
1415
1416 return filtered_allergies
1417 #--------------------------------------------------------
1419 if encounter_id is None:
1420 encounter_id = self.current_encounter['pk_encounter']
1421
1422 if episode_id is None:
1423 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
1424 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
1425 episode_id = epi['pk_episode']
1426
1427 new_allergy = gmAllergy.create_allergy (
1428 allergene = allergene,
1429 allg_type = allg_type,
1430 encounter_id = encounter_id,
1431 episode_id = episode_id
1432 )
1433
1434 return new_allergy
1435 #--------------------------------------------------------
1437 cmd = 'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1438 args = {'pk_allg': pk_allergy}
1439 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1440
1441 #--------------------------------------------------------
1443 """Cave: only use with one potential allergic agent
1444 otherwise you won't know which of the agents the allergy is to."""
1445
1446 # we don't know the state
1447 if self.allergy_state is None:
1448 return None
1449
1450 # we know there's no allergies
1451 if self.allergy_state == 0:
1452 return False
1453
1454 args = {
1455 'atcs': atcs,
1456 'inns': inns,
1457 'prod_name': product_name,
1458 'pat': self.pk_patient
1459 }
1460 allergenes = []
1461 where_parts = []
1462
1463 if len(atcs) == 0:
1464 atcs = None
1465 if atcs is not None:
1466 where_parts.append('atc_code in %(atcs)s')
1467 if len(inns) == 0:
1468 inns = None
1469 if inns is not None:
1470 where_parts.append('generics in %(inns)s')
1471 allergenes.extend(inns)
1472 if product_name is not None:
1473 where_parts.append('substance = %(prod_name)s')
1474 allergenes.append(product_name)
1475
1476 if len(allergenes) != 0:
1477 where_parts.append('allergene in %(allgs)s')
1478 args['allgs'] = tuple(allergenes)
1479
1480 cmd = """
1481 SELECT * FROM clin.v_pat_allergies
1482 WHERE
1483 pk_patient = %%(pat)s
1484 AND ( %s )""" % ' OR '.join(where_parts)
1485
1486 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1487
1488 if len(rows) == 0:
1489 return False
1490
1491 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1492 #--------------------------------------------------------
1494
1495 if state not in gmAllergy.allergy_states:
1496 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
1497
1498 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
1499 allg_state['has_allergy'] = state
1500 allg_state.save_payload()
1501 return True
1502
1505
1506 allergy_state = property(_get_allergy_state, _set_allergy_state)
1507 #--------------------------------------------------------
1508 # API: external care
1509 #--------------------------------------------------------
1511 return gmExternalCare.get_external_care_items (
1512 pk_identity = self.pk_patient,
1513 order_by = order_by,
1514 exclude_inactive = exclude_inactive
1515 )
1516
1517 external_care_items = property(get_external_care_items, lambda x:x)
1518
1519 #--------------------------------------------------------
1520 # API: episodes
1521 #--------------------------------------------------------
1522 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1523 """Fetches from backend patient episodes.
1524
1525 id_list - Episodes' PKs list
1526 issues - Health issues' PKs list to filter episodes by
1527 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1528 """
1529 if (unlinked_only is True) and (issues is not None):
1530 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1531
1532 if order_by is None:
1533 order_by = ''
1534 else:
1535 order_by = 'ORDER BY %s' % order_by
1536
1537 args = {
1538 'pat': self.pk_patient,
1539 'open': open_status
1540 }
1541 where_parts = ['pk_patient = %(pat)s']
1542
1543 if open_status is not None:
1544 where_parts.append('episode_open IS %(open)s')
1545
1546 if unlinked_only:
1547 where_parts.append('pk_health_issue is NULL')
1548
1549 if issues is not None:
1550 where_parts.append('pk_health_issue IN %(issues)s')
1551 args['issues'] = tuple(issues)
1552
1553 if id_list is not None:
1554 where_parts.append('pk_episode IN %(epis)s')
1555 args['epis'] = tuple(id_list)
1556
1557 cmd = "SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1558 ' AND '.join(where_parts),
1559 order_by
1560 )
1561 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1562
1563 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1564
1565 episodes = property(get_episodes, lambda x:x)
1566 #------------------------------------------------------------------
1568 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1569
1570 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1571 #------------------------------------------------------------------
1573 cmd = """SELECT distinct pk_episode
1574 from clin.v_pat_items
1575 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1576 args = {
1577 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1578 'pat': self.pk_patient
1579 }
1580 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1581 if len(rows) == 0:
1582 return []
1583 epis = []
1584 for row in rows:
1585 epis.append(row[0])
1586 return self.get_episodes(id_list=epis)
1587 #------------------------------------------------------------------
1588 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False, allow_dupes=False, link_obj=None):
1589 """Add episode 'episode_name' for a patient's health issue.
1590
1591 - silently returns if episode already exists
1592 """
1593 episode = gmEMRStructItems.create_episode (
1594 link_obj = link_obj,
1595 pk_health_issue = pk_health_issue,
1596 episode_name = episode_name,
1597 is_open = is_open,
1598 encounter = self.current_encounter['pk_encounter'],
1599 allow_dupes = allow_dupes
1600 )
1601 return episode
1602 #--------------------------------------------------------
1604 # try to find the episode with the most recently modified clinical item
1605
1606 issue_where = gmTools.coalesce(issue, '', 'and pk_health_issue = %(issue)s')
1607
1608 cmd = """
1609 SELECT pk
1610 from clin.episode
1611 WHERE pk = (
1612 SELECT distinct on(pk_episode) pk_episode
1613 from clin.v_pat_items
1614 WHERE
1615 pk_patient = %%(pat)s
1616 and
1617 modified_when = (
1618 SELECT max(vpi.modified_when)
1619 from clin.v_pat_items vpi
1620 WHERE vpi.pk_patient = %%(pat)s
1621 )
1622 %s
1623 -- guard against several episodes created at the same moment of time
1624 limit 1
1625 )""" % issue_where
1626 rows, idx = gmPG2.run_ro_queries(queries = [
1627 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1628 ])
1629 if len(rows) != 0:
1630 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1631
1632 # no clinical items recorded, so try to find
1633 # the youngest episode for this patient
1634 cmd = """
1635 SELECT vpe0.pk_episode
1636 from
1637 clin.v_pat_episodes vpe0
1638 WHERE
1639 vpe0.pk_patient = %%(pat)s
1640 and
1641 vpe0.episode_modified_when = (
1642 SELECT max(vpe1.episode_modified_when)
1643 from clin.v_pat_episodes vpe1
1644 WHERE vpe1.pk_episode = vpe0.pk_episode
1645 )
1646 %s""" % issue_where
1647 rows, idx = gmPG2.run_ro_queries(queries = [
1648 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1649 ])
1650 if len(rows) != 0:
1651 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1652
1653 return None
1654 #--------------------------------------------------------
1657 #--------------------------------------------------------
1658 # API: problems
1659 #--------------------------------------------------------
1660 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1661 """Retrieve a patient's problems.
1662
1663 "Problems" are the UNION of:
1664
1665 - issues which are .clinically_relevant
1666 - episodes which are .is_open
1667
1668 Therefore, both an issue and the open episode
1669 thereof can each be listed as a problem.
1670
1671 include_closed_episodes/include_irrelevant_issues will
1672 include those -- which departs from the definition of
1673 the problem list being "active" items only ...
1674
1675 episodes - episodes' PKs to filter problems by
1676 issues - health issues' PKs to filter problems by
1677 """
1678 # FIXME: this could use a good measure of streamlining, probably
1679
1680 args = {'pat': self.pk_patient}
1681
1682 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1683 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1684
1685 # Instantiate problem items
1686 problems = []
1687 for row in rows:
1688 pk_args = {
1689 'pk_patient': self.pk_patient,
1690 'pk_health_issue': row['pk_health_issue'],
1691 'pk_episode': row['pk_episode']
1692 }
1693 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1694
1695 # include non-problems ?
1696 other_rows = []
1697 if include_closed_episodes:
1698 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1699 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1700 other_rows.extend(rows)
1701
1702 if include_irrelevant_issues:
1703 cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1704 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1705 other_rows.extend(rows)
1706
1707 if len(other_rows) > 0:
1708 for row in other_rows:
1709 pk_args = {
1710 'pk_patient': self.pk_patient,
1711 'pk_health_issue': row['pk_health_issue'],
1712 'pk_episode': row['pk_episode']
1713 }
1714 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1715
1716 # filter
1717 if issues is not None:
1718 problems = [ p for p in problems if p['pk_health_issue'] in issues ]
1719 if episodes is not None:
1720 problems = [ p for p in problems if p['pk_episode'] in episodes ]
1721
1722 return problems
1723
1724 #--------------------------------------------------------
1727
1728 #--------------------------------------------------------
1731
1732 #--------------------------------------------------------
1735
1736 #--------------------------------------------------------
1738 cmd = "SELECT * FROM clin.v_candidate_diagnoses WHERE pk_patient = %(pat)s"
1739 rows, idx = gmPG2.run_ro_queries (
1740 queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}],
1741 get_col_idx = False
1742 )
1743 return rows
1744
1745 candidate_diagnoses = property(get_candidate_diagnoses)
1746
1747 #--------------------------------------------------------
1748 # API: health issues
1749 #--------------------------------------------------------
1751
1752 cmd = "SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1753 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1754 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1755
1756 if id_list is None:
1757 return issues
1758
1759 if len(id_list) == 0:
1760 raise ValueError('id_list to filter by is empty, most likely a programming error')
1761
1762 filtered_issues = []
1763 for issue in issues:
1764 if issue['pk_health_issue'] in id_list:
1765 filtered_issues.append(issue)
1766
1767 return filtered_issues
1768
1769 health_issues = property(get_health_issues, lambda x:x)
1770
1771 #------------------------------------------------------------------
1773 """Adds patient health issue."""
1774 return gmEMRStructItems.create_health_issue (
1775 description = issue_name,
1776 encounter = self.current_encounter['pk_encounter'],
1777 patient = self.pk_patient
1778 )
1779 #--------------------------------------------------------
1782 #--------------------------------------------------------
1783 # API: substance intake
1784 #--------------------------------------------------------
1785 - def get_current_medications(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1786 return self._get_current_substance_intakes (
1787 include_inactive = include_inactive,
1788 include_unapproved = include_unapproved,
1789 order_by = order_by,
1790 episodes = episodes,
1791 issues = issues,
1792 exclude_medications = False,
1793 exclude_potential_abuses = True
1794 )
1795
1796 #--------------------------------------------------------
1798 return self._get_current_substance_intakes (
1799 include_inactive = True,
1800 include_unapproved = True,
1801 order_by = order_by,
1802 episodes = None,
1803 issues = None,
1804 exclude_medications = True,
1805 exclude_potential_abuses = False
1806 )
1807
1808 abused_substances = property(_get_abused_substances, lambda x:x)
1809
1810 #--------------------------------------------------------
1811 - def _get_current_substance_intakes(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None, exclude_potential_abuses=False, exclude_medications=False):
1812
1813 where_parts = ['pk_patient = %(pat)s']
1814 args = {'pat': self.pk_patient}
1815
1816 if not include_inactive:
1817 where_parts.append('is_currently_active IN (TRUE, NULL)')
1818
1819 if not include_unapproved:
1820 where_parts.append('intake_is_approved_of IN (TRUE, NULL)')
1821
1822 if exclude_potential_abuses:
1823 where_parts.append('harmful_use_type IS NULL')
1824
1825 if exclude_medications:
1826 where_parts.append('harmful_use_type IS NOT NULL')
1827
1828 if order_by is None:
1829 order_by = ''
1830 else:
1831 order_by = 'ORDER BY %s' % order_by
1832
1833 cmd = "SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1834 '\nAND '.join(where_parts),
1835 order_by
1836 )
1837 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1838 intakes = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1839
1840 if episodes is not None:
1841 intakes = [ i for i in intakes if i['pk_episode'] in episodes ]
1842
1843 if issues is not None:
1844 intakes = [ i for i in intakes if i ['pk_health_issue'] in issues ]
1845
1846 return intakes
1847
1848 #--------------------------------------------------------
1849 - def add_substance_intake(self, pk_component=None, pk_episode=None, pk_drug_product=None, pk_health_issue=None):
1850 pk_enc = self.current_encounter['pk_encounter']
1851 if pk_episode is None:
1852 pk_episode = gmMedication.create_default_medication_history_episode (
1853 pk_health_issue = pk_health_issue,
1854 encounter = pk_enc
1855 )
1856 return gmMedication.create_substance_intake (
1857 pk_component = pk_component,
1858 pk_encounter = pk_enc,
1859 pk_episode = pk_episode,
1860 pk_drug_product = pk_drug_product
1861 )
1862
1863 #--------------------------------------------------------
1864 - def substance_intake_exists(self, pk_component=None, pk_substance=None, pk_drug_product=None):
1865 return gmMedication.substance_intake_exists (
1866 pk_component = pk_component,
1867 pk_substance = pk_substance,
1868 pk_identity = self.pk_patient,
1869 pk_drug_product = pk_drug_product
1870 )
1871
1872 #--------------------------------------------------------
1873 # API: vaccinations
1874 #--------------------------------------------------------
1876 return gmVaccination.create_vaccination (
1877 encounter = self.current_encounter['pk_encounter'],
1878 episode = episode,
1879 vaccine = vaccine,
1880 batch_no = batch_no
1881 )
1882
1883 #--------------------------------------------------------
1885 """Returns latest given vaccination for each vaccinated indication.
1886
1887 as a dict {'l10n_indication': cVaccination instance}
1888
1889 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1890 """
1891 args = {'pat': self.pk_patient}
1892 where_parts = ['c_v_shots.pk_patient = %(pat)s']
1893
1894 if (episodes is not None) and (len(episodes) > 0):
1895 where_parts.append('c_v_shots.pk_episode IN %(epis)s')
1896 args['epis'] = tuple(episodes)
1897
1898 if (issues is not None) and (len(issues) > 0):
1899 where_parts.append('c_v_shots.pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1900 args['issues'] = tuple(issues)
1901
1902 if (atc_indications is not None) and (len(atc_indications) > 0):
1903 where_parts.append('c_v_plv4i.atc_indication IN %(atc_inds)s')
1904 args['atc_inds'] = tuple(atc_indications)
1905
1906 # find the shots
1907 cmd = """
1908 SELECT
1909 c_v_shots.*,
1910 c_v_plv4i.l10n_indication,
1911 c_v_plv4i.no_of_shots
1912 FROM
1913 clin.v_vaccinations c_v_shots
1914 JOIN clin.v_pat_last_vacc4indication c_v_plv4i ON (c_v_shots.pk_vaccination = c_v_plv4i.pk_vaccination)
1915 WHERE %s
1916 """ % '\nAND '.join(where_parts)
1917 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1918
1919 # none found
1920 if len(rows) == 0:
1921 return {}
1922
1923 # turn them into vaccinations
1924 # (idx is constant)
1925 vaccs = {}
1926 for shot_row in rows:
1927 vaccs[shot_row['l10n_indication']] = (
1928 shot_row['no_of_shots'],
1929 gmVaccination.cVaccination(row = {'idx': idx, 'data': shot_row, 'pk_field': 'pk_vaccination'})
1930 )
1931
1932 return vaccs
1933
1934 #--------------------------------------------------------
1936
1937 args = {'pat': self.pk_patient}
1938 where_parts = ['pk_patient = %(pat)s']
1939
1940 if order_by is None:
1941 order_by = ''
1942 else:
1943 order_by = 'ORDER BY %s' % order_by
1944
1945 if (episodes is not None) and (len(episodes) > 0):
1946 where_parts.append('pk_episode IN %(epis)s')
1947 args['epis'] = tuple(episodes)
1948
1949 if (issues is not None) and (len(issues) > 0):
1950 where_parts.append('pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1951 args['issues'] = tuple(issues)
1952
1953 if (encounters is not None) and (len(encounters) > 0):
1954 where_parts.append('pk_encounter IN %(encs)s')
1955 args['encs'] = tuple(encounters)
1956
1957 cmd = '%s %s' % (
1958 gmVaccination._SQL_get_vaccination_fields % '\nAND '.join(where_parts),
1959 order_by
1960 )
1961 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1962 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1963
1964 return vaccs
1965
1966 vaccinations = property(get_vaccinations, lambda x:x)
1967
1968 #--------------------------------------------------------
1969 # old/obsolete:
1970 #--------------------------------------------------------
1972 """Retrieves vaccination regimes the patient is on.
1973
1974 optional:
1975 * ID - PK of the vaccination regime
1976 * indications - indications we want to retrieve vaccination
1977 regimes for, must be primary language, not l10n_indication
1978 """
1979 # FIXME: use course, not regime
1980 # retrieve vaccination regimes definitions
1981 cmd = """SELECT distinct on(pk_course) pk_course
1982 FROM clin.v_vaccs_scheduled4pat
1983 WHERE pk_patient=%s"""
1984 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1985 if rows is None:
1986 _log.error('cannot retrieve scheduled vaccination courses')
1987 return None
1988 # Instantiate vaccination items and keep cache
1989 for row in rows:
1990 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1991
1992 # ok, let's constrain our list
1993 filtered_regimes = []
1994 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1995 if ID is not None:
1996 filtered_regimes = [ r for r in filtered_regimes if r['pk_course'] == ID ]
1997 if len(filtered_regimes) == 0:
1998 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1999 return []
2000 else:
2001 return filtered_regimes[0]
2002 if indications is not None:
2003 filtered_regimes = [ r for r in filtered_regimes if r['indication'] in indications ]
2004
2005 return filtered_regimes
2006 #--------------------------------------------------------
2007 # def get_vaccinated_indications(self):
2008 # """Retrieves patient vaccinated indications list.
2009 #
2010 # Note that this does NOT rely on the patient being on
2011 # some schedule or other but rather works with what the
2012 # patient has ACTUALLY been vaccinated against. This is
2013 # deliberate !
2014 # """
2015 # # most likely, vaccinations will be fetched close
2016 # # by so it makes sense to count on the cache being
2017 # # filled (or fill it for nearby use)
2018 # vaccinations = self.get_vaccinations()
2019 # if vaccinations is None:
2020 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
2021 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
2022 # if len(vaccinations) == 0:
2023 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
2024 # v_indications = []
2025 # for vacc in vaccinations:
2026 # tmp = [vacc['indication'], vacc['l10n_indication']]
2027 # # remove duplicates
2028 # if tmp in v_indications:
2029 # continue
2030 # v_indications.append(tmp)
2031 # return (True, v_indications)
2032 #--------------------------------------------------------
2033 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2034 """Retrieves list of vaccinations the patient has received.
2035
2036 optional:
2037 * ID - PK of a vaccination
2038 * indications - indications we want to retrieve vaccination
2039 items for, must be primary language, not l10n_indication
2040 * since - initial date for allergy items
2041 * until - final date for allergy items
2042 * encounters - list of encounters whose allergies are to be retrieved
2043 * episodes - list of episodes whose allergies are to be retrieved
2044 * issues - list of health issues whose allergies are to be retrieved
2045 """
2046 try:
2047 self.__db_cache['vaccinations']['vaccinated']
2048 except KeyError:
2049 self.__db_cache['vaccinations']['vaccinated'] = []
2050 # Important fetch ordering by indication, date to know if a vaccination is booster
2051 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
2052 WHERE pk_patient=%s
2053 order by indication, date"""
2054 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2055 if rows is None:
2056 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
2057 del self.__db_cache['vaccinations']['vaccinated']
2058 return None
2059 # Instantiate vaccination items
2060 vaccs_by_ind = {}
2061 for row in rows:
2062 vacc_row = {
2063 'pk_field': 'pk_vaccination',
2064 'idx': idx,
2065 'data': row
2066 }
2067 vacc = gmVaccination.cVaccination(row=vacc_row)
2068 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
2069 # keep them, ordered by indication
2070 try:
2071 vaccs_by_ind[vacc['indication']].append(vacc)
2072 except KeyError:
2073 vaccs_by_ind[vacc['indication']] = [vacc]
2074
2075 # calculate sequence number and is_booster
2076 for ind in vaccs_by_ind.keys():
2077 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
2078 for vacc in vaccs_by_ind[ind]:
2079 # due to the "order by indication, date" the vaccinations are in the
2080 # right temporal order inside the indication-keyed dicts
2081 seq_no = vaccs_by_ind[ind].index(vacc) + 1
2082 vacc['seq_no'] = seq_no
2083 # if no active schedule for indication we cannot
2084 # check for booster status (eg. seq_no > max_shot)
2085 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
2086 continue
2087 if seq_no > vacc_regimes[0]['shots']:
2088 vacc['is_booster'] = True
2089 del vaccs_by_ind
2090
2091 # ok, let's constrain our list
2092 filtered_shots = []
2093 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
2094 if ID is not None:
2095 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
2096 if len(filtered_shots) == 0:
2097 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
2098 return None
2099 else:
2100 return filtered_shots[0]
2101 if since is not None:
2102 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
2103 if until is not None:
2104 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
2105 if issues is not None:
2106 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
2107 if episodes is not None:
2108 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
2109 if encounters is not None:
2110 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
2111 if indications is not None:
2112 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2113 return filtered_shots
2114 #--------------------------------------------------------
2116 """Retrieves vaccinations scheduled for a regime a patient is on.
2117
2118 The regime is referenced by its indication (not l10n)
2119
2120 * indications - List of indications (not l10n) of regimes we want scheduled
2121 vaccinations to be fetched for
2122 """
2123 try:
2124 self.__db_cache['vaccinations']['scheduled']
2125 except KeyError:
2126 self.__db_cache['vaccinations']['scheduled'] = []
2127 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
2128 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2129 if rows is None:
2130 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
2131 del self.__db_cache['vaccinations']['scheduled']
2132 return None
2133 # Instantiate vaccination items
2134 for row in rows:
2135 vacc_row = {
2136 'pk_field': 'pk_vacc_def',
2137 'idx': idx,
2138 'data': row
2139 }
2140 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
2141
2142 # ok, let's constrain our list
2143 if indications is None:
2144 return self.__db_cache['vaccinations']['scheduled']
2145 filtered_shots = []
2146 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
2147 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
2148 return filtered_shots
2149 #--------------------------------------------------------
2151 try:
2152 self.__db_cache['vaccinations']['missing']
2153 except KeyError:
2154 self.__db_cache['vaccinations']['missing'] = {}
2155 # 1) non-booster
2156 self.__db_cache['vaccinations']['missing']['due'] = []
2157 # get list of (indication, seq_no) tuples
2158 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
2159 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2160 if rows is None:
2161 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
2162 return None
2163 pk_args = {'pat_id': self.pk_patient}
2164 if rows is not None:
2165 for row in rows:
2166 pk_args['indication'] = row[0]
2167 pk_args['seq_no'] = row[1]
2168 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
2169
2170 # 2) boosters
2171 self.__db_cache['vaccinations']['missing']['boosters'] = []
2172 # get list of indications
2173 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
2174 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
2175 if rows is None:
2176 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
2177 return None
2178 pk_args = {'pat_id': self.pk_patient}
2179 if rows is not None:
2180 for row in rows:
2181 pk_args['indication'] = row[0]
2182 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
2183
2184 # if any filters ...
2185 if indications is None:
2186 return self.__db_cache['vaccinations']['missing']
2187 if len(indications) == 0:
2188 return self.__db_cache['vaccinations']['missing']
2189 # ... apply them
2190 filtered_shots = {
2191 'due': [],
2192 'boosters': []
2193 }
2194 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
2195 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
2196 filtered_shots['due'].append(due_shot)
2197 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
2198 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
2199 filtered_shots['boosters'].append(due_shot)
2200 return filtered_shots
2201
2202 #------------------------------------------------------------------
2203 # API: encounters
2204 #------------------------------------------------------------------
2207
2209 # first ever setting ? -> fast path
2210 if self.__encounter is None:
2211 _log.debug('first setting of active encounter in this clinical record instance')
2212 encounter.lock(exclusive = False) # lock new
2213 self.__encounter = encounter
2214 gmDispatcher.send('current_encounter_switched')
2215 return True
2216
2217 # real switch -> slow path
2218 _log.debug('switching of active encounter')
2219 # fail if the currently active encounter has unsaved changes
2220 if self.__encounter.is_modified():
2221 gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
2222 _log.error('current in client: %s', self.__encounter)
2223 raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
2224 self.__encounter['pk_encounter'],
2225 encounter['pk_encounter']
2226 ))
2227
2228 prev_enc = self.__encounter
2229 encounter.lock(exclusive = False) # lock new
2230 self.__encounter = encounter
2231 prev_enc.unlock(exclusive = False) # unlock old
2232 gmDispatcher.send('current_encounter_switched')
2233
2234 return True
2235
2236 current_encounter = property(_get_current_encounter, _set_current_encounter)
2237 active_encounter = property(_get_current_encounter, _set_current_encounter)
2238
2239 #--------------------------------------------------------
2241 _log.debug('setting up active encounter for identity [%s]', self.pk_patient)
2242
2243 # log access to patient record (HIPAA, for example)
2244 _delayed_execute(self.log_access, action = 'pulling chart for identity [%s]' % self.pk_patient)
2245
2246 # cleanup (not async, because we don't want recent encounters
2247 # to become the active one just because they are recent)
2248 self.remove_empty_encounters()
2249
2250 # activate very recent encounter if available
2251 if self.__activate_very_recent_encounter():
2252 return
2253
2254 fairly_recent_enc = self.__get_fairly_recent_encounter()
2255
2256 # create new encounter for the time being
2257 self.start_new_encounter()
2258
2259 if fairly_recent_enc is None:
2260 return
2261
2262 # but check whether user wants to continue a "fairly recent" one
2263 gmDispatcher.send (
2264 signal = 'ask_for_encounter_continuation',
2265 new_encounter = self.__encounter,
2266 fairly_recent_encounter = fairly_recent_enc
2267 )
2268
2269 #------------------------------------------------------------------
2271 """Try to attach to a "very recent" encounter if there is one.
2272
2273 returns:
2274 False: no "very recent" encounter
2275 True: success
2276 """
2277 cfg_db = gmCfg.cCfgSQL()
2278 min_ttl = cfg_db.get2 (
2279 option = 'encounter.minimum_ttl',
2280 workplace = _here.active_workplace,
2281 bias = 'user',
2282 default = '1 hour 30 minutes'
2283 )
2284 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2285 SELECT pk_encounter
2286 FROM clin.v_most_recent_encounters
2287 WHERE
2288 pk_patient = %s
2289 and
2290 last_affirmed > (now() - %s::interval)
2291 ORDER BY
2292 last_affirmed DESC
2293 LIMIT 1
2294 )"""
2295 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}], get_col_idx = True)
2296
2297 # none found
2298 if len(enc_rows) == 0:
2299 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
2300 return False
2301
2302 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0]['pk_encounter'])
2303
2304 # attach to existing
2305 self.current_encounter = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2306 return True
2307
2308 #------------------------------------------------------------------
2310 cfg_db = gmCfg.cCfgSQL()
2311 min_ttl = cfg_db.get2 (
2312 option = 'encounter.minimum_ttl',
2313 workplace = _here.active_workplace,
2314 bias = 'user',
2315 default = '1 hour 30 minutes'
2316 )
2317 max_ttl = cfg_db.get2 (
2318 option = 'encounter.maximum_ttl',
2319 workplace = _here.active_workplace,
2320 bias = 'user',
2321 default = '6 hours'
2322 )
2323
2324 # do we happen to have a "fairly recent" candidate ?
2325 cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
2326 SELECT pk_encounter
2327 FROM clin.v_most_recent_encounters
2328 WHERE
2329 pk_patient=%s
2330 AND
2331 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2332 ORDER BY
2333 last_affirmed DESC
2334 LIMIT 1
2335 )"""
2336 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2337
2338 # none found
2339 if len(enc_rows) == 0:
2340 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2341 return None
2342
2343 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2344 return gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2345
2346 # #------------------------------------------------------------------
2347 # def __check_for_fairly_recent_encounter(self):
2348 #
2349 # cfg_db = gmCfg.cCfgSQL()
2350 # min_ttl = cfg_db.get2 (
2351 # option = u'encounter.minimum_ttl',
2352 # workplace = _here.active_workplace,
2353 # bias = u'user',
2354 # default = u'1 hour 30 minutes'
2355 # )
2356 # max_ttl = cfg_db.get2 (
2357 # option = u'encounter.maximum_ttl',
2358 # workplace = _here.active_workplace,
2359 # bias = u'user',
2360 # default = u'6 hours'
2361 # )
2362 #
2363 # # do we happen to have a "fairly recent" candidate ?
2364 # cmd = gmEMRStructItems.SQL_get_encounters % u"""pk_encounter = (
2365 # SELECT pk_encounter
2366 # FROM clin.v_most_recent_encounters
2367 # WHERE
2368 # pk_patient=%s
2369 # AND
2370 # last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2371 # ORDER BY
2372 # last_affirmed DESC
2373 # LIMIT 1
2374 # )"""
2375 # enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
2376 #
2377 # # none found
2378 # if len(enc_rows) == 0:
2379 # _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2380 # return
2381 #
2382 # _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
2383 # fairly_recent_enc = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2384 # gmDispatcher.send(u'ask_for_encounter_continuation', current = self.__encounter, fairly_recent_encounter = fairly_recent_enc)
2385
2386 # #------------------------------------------------------------------
2387 # def __activate_fairly_recent_encounter(self, allow_user_interaction=True):
2388 # """Try to attach to a "fairly recent" encounter if there is one.
2389 #
2390 # returns:
2391 # False: no "fairly recent" encounter, create new one
2392 # True: success
2393 # """
2394 # if _func_ask_user is None:
2395 # _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
2396 # return False
2397 #
2398 # if not allow_user_interaction:
2399 # _log.exception('user interaction not desired, not looking for fairly recent encounter')
2400 # return False
2401 #
2402 # cfg_db = gmCfg.cCfgSQL()
2403 # min_ttl = cfg_db.get2 (
2404 # option = u'encounter.minimum_ttl',
2405 # workplace = _here.active_workplace,
2406 # bias = u'user',
2407 # default = u'1 hour 30 minutes'
2408 # )
2409 # max_ttl = cfg_db.get2 (
2410 # option = u'encounter.maximum_ttl',
2411 # workplace = _here.active_workplace,
2412 # bias = u'user',
2413 # default = u'6 hours'
2414 # )
2415 # cmd = u"""
2416 # SELECT pk_encounter
2417 # FROM clin.v_most_recent_encounters
2418 # WHERE
2419 # pk_patient=%s
2420 # AND
2421 # last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
2422 # ORDER BY
2423 # last_affirmed DESC"""
2424 # enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
2425 # # none found
2426 # if len(enc_rows) == 0:
2427 # _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
2428 # return False
2429 #
2430 # _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
2431 #
2432 # encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
2433 # # ask user whether to attach or not
2434 # cmd = u"""
2435 # SELECT title, firstnames, lastnames, gender, dob
2436 # FROM dem.v_all_persons WHERE pk_identity=%s"""
2437 # pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
2438 # pat = pats[0]
2439 # pat_str = u'%s %s %s (%s), %s [#%s]' % (
2440 # gmTools.coalesce(pat[0], u'')[:5],
2441 # pat[1][:15],
2442 # pat[2][:15],
2443 # pat[3],
2444 # gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
2445 # self.pk_patient
2446 # )
2447 # msg = _(
2448 # '%s\n'
2449 # '\n'
2450 # "This patient's chart was worked on only recently:\n"
2451 # '\n'
2452 # ' %s %s - %s (%s)\n'
2453 # '\n'
2454 # ' Reason for Encounter:\n'
2455 # ' %s\n'
2456 # ' Assessment of Encounter:\n'
2457 # ' %s\n'
2458 # '\n'
2459 # 'Do you want to continue that consultation\n'
2460 # 'or do you want to start a new one ?\n'
2461 # ) % (
2462 # pat_str,
2463 # gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
2464 # gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
2465 # encounter['l10n_type'],
2466 # gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
2467 # gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
2468 # )
2469 # attach = False
2470 # try:
2471 # attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
2472 # except:
2473 # _log.exception('cannot ask user for guidance, not attaching to existing encounter')
2474 # return False
2475 # if not attach:
2476 # return False
2477 #
2478 # # attach to existing
2479 # self.current_encounter = encounter
2480 # _log.debug('"fairly recent" encounter re-activated')
2481 # return True
2482
2483 #------------------------------------------------------------------
2485 cfg_db = gmCfg.cCfgSQL()
2486 enc_type = cfg_db.get2 (
2487 option = 'encounter.default_type',
2488 workplace = _here.active_workplace,
2489 bias = 'user'
2490 )
2491 if enc_type is None:
2492 enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
2493 if enc_type is None:
2494 enc_type = 'in surgery'
2495 enc = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
2496 enc['pk_org_unit'] = _here['pk_org_unit']
2497 enc.save()
2498 self.current_encounter = enc
2499 _log.debug('new encounter [%s] activated', enc['pk_encounter'])
2500
2501 #------------------------------------------------------------------
2502 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False, order_by=None, max_encounters=None):
2503 """Retrieves patient's encounters.
2504
2505 id_list - PKs of encounters to fetch
2506 since - initial date for encounter items, DateTime instance
2507 until - final date for encounter items, DateTime instance
2508 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
2509 issues - PKs of the health issues the encounters belong to (many-to-many relation)
2510 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
2511
2512 NOTE: if you specify *both* issues and episodes
2513 you will get the *aggregate* of all encounters even
2514 if the episodes all belong to the health issues listed.
2515 IOW, the issues broaden the episode list rather than
2516 the episode list narrowing the episodes-from-issues
2517 list.
2518 Rationale: If it was the other way round it would be
2519 redundant to specify the list of issues at all.
2520 """
2521 # if issues are given, translate them to their episodes
2522 if (issues is not None) and (len(issues) > 0):
2523 # - find episodes corresponding to the health issues in question
2524 cmd = "SELECT distinct pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue in %(issue_pks)s AND pk_patient = %(pat)s"
2525 args = {'issue_pks': tuple(issues), 'pat': self.pk_patient}
2526 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2527 epis4issues_pks = [ r['pk_episode'] for r in rows ]
2528 if episodes is None:
2529 episodes = []
2530 episodes.extend(epis4issues_pks)
2531
2532 if (episodes is not None) and (len(episodes) > 0):
2533 # since the episodes to filter by belong to the patient in question so will
2534 # the encounters found with them - hence we don't need a WHERE on the patient ...
2535 # but, better safe than sorry ...
2536 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2537 cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
2538 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2539 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2540 if id_list is None:
2541 id_list = []
2542 id_list.extend(encs4epis_pks)
2543
2544 where_parts = ['c_vpe.pk_patient = %(pat)s']
2545 args = {'pat': self.pk_patient}
2546
2547 if skip_empty:
2548 where_parts.append("""NOT (
2549 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
2550 AND
2551 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
2552 AND
2553 NOT EXISTS (
2554 SELECT 1 FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_patient = %(pat)s AND c_vpi.pk_encounter = c_vpe.pk_encounter
2555 UNION ALL
2556 SELECT 1 FROM blobs.v_doc_med b_vdm WHERE b_vdm.pk_patient = %(pat)s AND b_vdm.pk_encounter = c_vpe.pk_encounter
2557 ))""")
2558
2559 if since is not None:
2560 where_parts.append('c_vpe.started >= %(start)s')
2561 args['start'] = since
2562
2563 if until is not None:
2564 where_parts.append('c_vpe.last_affirmed <= %(end)s')
2565 args['end'] = since
2566
2567 if (id_list is not None) and (len(id_list) > 0):
2568 where_parts.append('c_vpe.pk_encounter IN %(enc_pks)s')
2569 args['enc_pks'] = tuple(id_list)
2570
2571 if order_by is None:
2572 order_by = 'c_vpe.started'
2573
2574 if max_encounters is None:
2575 limit = ''
2576 else:
2577 limit = 'LIMIT %s' % max_encounters
2578
2579 cmd = """
2580 SELECT * FROM clin.v_pat_encounters c_vpe
2581 WHERE
2582 %s
2583 ORDER BY %s %s
2584 """ % (
2585 ' AND '.join(where_parts),
2586 order_by,
2587 limit
2588 )
2589 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2590 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
2591
2592 # we've got the encounters, start filtering
2593 filtered_encounters = []
2594 filtered_encounters.extend(encounters)
2595
2596 if (episodes is not None) and (len(episodes) > 0):
2597 # since the episodes to filter by belong to the patient in question so will
2598 # the encounters found with them - hence we don't need a WHERE on the patient ...
2599 # but, better safe than sorry ...
2600 args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
2601 cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
2602 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2603 encs4epis_pks = [ r['fk_encounter'] for r in rows ]
2604 filtered_encounters = [ enc for enc in filtered_encounters if enc['pk_encounter'] in encs4epis_pks ]
2605
2606 return filtered_encounters
2607
2608 #--------------------------------------------------------
2610 """Retrieves first encounter for a particular issue and/or episode.
2611
2612 issue_id - First encounter associated health issue
2613 episode - First encounter associated episode
2614 """
2615 if issue_id is None:
2616 issues = None
2617 else:
2618 issues = [issue_id]
2619
2620 if episode_id is None:
2621 episodes = None
2622 else:
2623 episodes = [episode_id]
2624
2625 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started', max_encounters = 1)
2626 if len(encounters) == 0:
2627 return None
2628
2629 return encounters[0]
2630
2631 first_encounter = property(get_first_encounter, lambda x:x)
2632
2633 #--------------------------------------------------------
2635 args = {'pat': self.pk_patient}
2636 cmd = """
2637 SELECT MIN(earliest) FROM (
2638 (
2639 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
2640
2641 ) UNION ALL (
2642
2643 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
2644
2645 ) UNION ALL (
2646
2647 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2648
2649 ) UNION ALL (
2650
2651 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2652
2653 ) UNION ALL (
2654
2655 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2656
2657 ) UNION ALL (
2658
2659 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2660
2661 ) UNION ALL (
2662
2663 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2664
2665 )
2666 ) AS candidates"""
2667 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2668 return rows[0][0]
2669
2670 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2671
2672 #--------------------------------------------------------
2674 encounters = self.get_encounters(order_by = 'started DESC', max_encounters = 1)
2675 if len(encounters) == 0:
2676 return None
2677 return encounters[0]['last_affirmed']
2678
2679 most_recent_care_date = property(get_most_recent_care_date)
2680
2681 #--------------------------------------------------------
2683 """Retrieves last encounter for a concrete issue and/or episode
2684
2685 issue_id - Last encounter associated health issue
2686 episode_id - Last encounter associated episode
2687 """
2688 if issue_id is None:
2689 issues = None
2690 else:
2691 issues = [issue_id]
2692
2693 if episode_id is None:
2694 episodes = None
2695 else:
2696 episodes = [episode_id]
2697
2698 encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started DESC', max_encounters = 1)
2699 if len(encounters) == 0:
2700 return None
2701
2702 return encounters[0]
2703
2704 last_encounter = property(get_last_encounter, lambda x:x)
2705
2706 #------------------------------------------------------------------
2708 args = {'pat': self.pk_patient, 'range': cover_period}
2709 where_parts = ['pk_patient = %(pat)s']
2710 if cover_period is not None:
2711 where_parts.append('last_affirmed > now() - %(range)s')
2712
2713 cmd = """
2714 SELECT l10n_type, count(1) AS frequency
2715 FROM clin.v_pat_encounters
2716 WHERE
2717 %s
2718 GROUP BY l10n_type
2719 ORDER BY frequency DESC
2720 """ % ' AND '.join(where_parts)
2721 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2722 return rows
2723
2724 #------------------------------------------------------------------
2726
2727 args = {'pat': self.pk_patient}
2728
2729 if (issue_id is None) and (episode_id is None):
2730 cmd = """
2731 SELECT * FROM clin.v_pat_encounters
2732 WHERE pk_patient = %(pat)s
2733 ORDER BY started DESC
2734 LIMIT 2
2735 """
2736 else:
2737 where_parts = []
2738
2739 if issue_id is not None:
2740 where_parts.append('pk_health_issue = %(issue)s')
2741 args['issue'] = issue_id
2742
2743 if episode_id is not None:
2744 where_parts.append('pk_episode = %(epi)s')
2745 args['epi'] = episode_id
2746
2747 cmd = """
2748 SELECT *
2749 FROM clin.v_pat_encounters
2750 WHERE
2751 pk_patient = %%(pat)s
2752 AND
2753 pk_encounter IN (
2754 SELECT distinct pk_encounter
2755 FROM clin.v_narrative
2756 WHERE
2757 %s
2758 )
2759 ORDER BY started DESC
2760 LIMIT 2
2761 """ % ' AND '.join(where_parts)
2762
2763 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2764
2765 if len(rows) == 0:
2766 return None
2767
2768 # just one encounter within the above limits
2769 if len(rows) == 1:
2770 # is it the current encounter ?
2771 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2772 # yes
2773 return None
2774 # no
2775 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2776
2777 # more than one encounter
2778 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2779 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2780
2781 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2782
2783 last_but_one_encounter = property(get_last_but_one_encounter, lambda x:x)
2784
2785 #------------------------------------------------------------------
2787 _log.debug('removing empty encounters for pk_identity [%s]', self.pk_patient)
2788 cfg_db = gmCfg.cCfgSQL()
2789 ttl = cfg_db.get2 (
2790 option = 'encounter.ttl_if_empty',
2791 workplace = _here.active_workplace,
2792 bias = 'user',
2793 default = '1 week'
2794 )
2795 # # FIXME: this should be done async
2796 cmd = "SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
2797 args = {'pat': self.pk_patient, 'ttl': ttl}
2798 try:
2799 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
2800 except:
2801 _log.exception('error deleting empty encounters')
2802 return False
2803
2804 if not rows[0][0]:
2805 _log.debug('no encounters deleted (less than 2 exist)')
2806
2807 return True
2808
2809 #------------------------------------------------------------------
2810 # API: measurements / test results
2811 #------------------------------------------------------------------
2813 return gmPathLab.get_most_recent_results_for_patient (
2814 no_of_results = no_of_results,
2815 patient = self.pk_patient
2816 )
2817
2818 #------------------------------------------------------------------
2819 - def get_most_recent_results_in_loinc_group(self, loincs=None, no_of_results=1, consider_meta_type=False):
2820 return gmPathLab.get_most_recent_results_in_loinc_group (
2821 loincs = loincs,
2822 no_of_results = no_of_results,
2823 consider_meta_type = consider_meta_type,
2824 patient = self.pk_patient
2825 )
2826
2827 #------------------------------------------------------------------
2829 return gmPathLab.get_most_recent_results_for_test_type (
2830 test_type = test_type,
2831 no_of_results = no_of_results,
2832 patient = self.pk_patient
2833 )
2834
2835 #------------------------------------------------------------------
2837 return gmPathLab.get_most_recent_result_for_test_types (
2838 pk_test_types = pk_test_types,
2839 pk_patient = self.pk_patient
2840 )
2841
2842 #------------------------------------------------------------------
2843 - def get_result_at_timestamp(self, timestamp=None, test_type=None, loinc=None, tolerance_interval='12 hours'):
2844 return gmPathLab.get_result_at_timestamp (
2845 timestamp = timestamp,
2846 test_type = test_type,
2847 loinc = loinc,
2848 tolerance_interval = tolerance_interval,
2849 patient = self.pk_patient
2850 )
2851
2852 #------------------------------------------------------------------
2854 return gmPathLab.get_results_for_day (
2855 timestamp = timestamp,
2856 patient = self.pk_patient,
2857 order_by = order_by
2858 )
2859
2860 #------------------------------------------------------------------
2862 return gmPathLab.get_results_for_issue (
2863 pk_health_issue = pk_health_issue,
2864 order_by = order_by
2865 )
2866
2867 #------------------------------------------------------------------
2870
2871 #------------------------------------------------------------------
2873 if order_by is None:
2874 order_by = ''
2875 else:
2876 order_by = 'ORDER BY %s' % order_by
2877 cmd = """
2878 SELECT * FROM clin.v_test_results
2879 WHERE
2880 pk_patient = %%(pat)s
2881 AND
2882 reviewed IS FALSE
2883 %s""" % order_by
2884 args = {'pat': self.pk_patient}
2885 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2886 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2887
2888 #------------------------------------------------------------------
2889 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
2891 """Retrieve data about test types for which this patient has results."""
2892 if order_by is None:
2893 order_by = ''
2894 else:
2895 order_by = 'ORDER BY %s' % order_by
2896
2897 if unique_meta_types:
2898 cmd = """
2899 SELECT * FROM clin.v_test_types c_vtt
2900 WHERE c_vtt.pk_test_type IN (
2901 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
2902 FROM clin.v_test_results c_vtr1
2903 WHERE
2904 c_vtr1.pk_patient = %%(pat)s
2905 AND
2906 c_vtr1.pk_meta_test_type IS NOT NULL
2907 UNION ALL
2908 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
2909 FROM clin.v_test_results c_vtr2
2910 WHERE
2911 c_vtr2.pk_patient = %%(pat)s
2912 AND
2913 c_vtr2.pk_meta_test_type IS NULL
2914 )
2915 %s""" % order_by
2916 else:
2917 cmd = """
2918 SELECT * FROM clin.v_test_types c_vtt
2919 WHERE c_vtt.pk_test_type IN (
2920 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
2921 FROM clin.v_test_results c_vtr
2922 WHERE c_vtr.pk_patient = %%(pat)s
2923 )
2924 %s""" % order_by
2925
2926 args = {'pat': self.pk_patient}
2927 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2928 return [ gmPathLab.cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
2929
2930 #------------------------------------------------------------------
2932 """Get the dates for which we have results."""
2933 where_parts = ['pk_patient = %(pat)s']
2934 args = {'pat': self.pk_patient}
2935
2936 if tests is not None:
2937 where_parts.append('pk_test_type IN %(tests)s')
2938 args['tests'] = tuple(tests)
2939
2940 cmd = """
2941 SELECT DISTINCT ON (clin_when_day)
2942 clin_when_day,
2943 is_reviewed
2944 FROM (
2945 SELECT
2946 date_trunc('day', clin_when)
2947 AS clin_when_day,
2948 bool_and(reviewed)
2949 AS is_reviewed
2950 FROM (
2951 SELECT
2952 clin_when,
2953 reviewed,
2954 pk_patient,
2955 pk_test_result
2956 FROM clin.v_test_results
2957 WHERE %s
2958 )
2959 AS patient_tests
2960 GROUP BY clin_when_day
2961 )
2962 AS grouped_days
2963 ORDER BY clin_when_day %s
2964 """ % (
2965 ' AND '.join(where_parts),
2966 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
2967 )
2968 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2969 return rows
2970
2971 #------------------------------------------------------------------
2973 """Get the issues/episodes for which we have results."""
2974 where_parts = ['pk_patient = %(pat)s']
2975 args = {'pat': self.pk_patient}
2976
2977 if tests is not None:
2978 where_parts.append('pk_test_type IN %(tests)s')
2979 args['tests'] = tuple(tests)
2980 where = ' AND '.join(where_parts)
2981 cmd = """
2982 SELECT * FROM ((
2983 -- issues, each including all it"s episodes
2984 SELECT
2985 health_issue AS problem,
2986 pk_health_issue,
2987 NULL::integer AS pk_episode,
2988 1 AS rank
2989 FROM clin.v_test_results
2990 WHERE pk_health_issue IS NOT NULL AND %s
2991 GROUP BY pk_health_issue, problem
2992 ) UNION ALL (
2993 -- episodes w/o issue
2994 SELECT
2995 episode AS problem,
2996 NULL::integer AS pk_health_issue,
2997 pk_episode,
2998 2 AS rank
2999 FROM clin.v_test_results
3000 WHERE pk_health_issue IS NULL AND %s
3001 GROUP BY pk_episode, problem
3002 )) AS grouped_union
3003 ORDER BY rank, problem
3004 """ % (where, where)
3005 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
3006 return rows
3007
3008 #------------------------------------------------------------------
3010 return gmPathLab.get_test_results (
3011 pk_patient = self.pk_patient,
3012 encounters = encounters,
3013 episodes = episodes,
3014 order_by = order_by
3015 )
3016 #------------------------------------------------------------------
3017 - def get_test_results_by_date(self, encounter=None, episodes=None, tests=None, reverse_chronological=True):
3018
3019 where_parts = ['pk_patient = %(pat)s']
3020 args = {'pat': self.pk_patient}
3021
3022 if tests is not None:
3023 where_parts.append('pk_test_type IN %(tests)s')
3024 args['tests'] = tuple(tests)
3025
3026 if encounter is not None:
3027 where_parts.append('pk_encounter = %(enc)s')
3028 args['enc'] = encounter
3029
3030 if episodes is not None:
3031 where_parts.append('pk_episode IN %(epis)s')
3032 args['epis'] = tuple(episodes)
3033
3034 cmd = """
3035 SELECT * FROM clin.v_test_results
3036 WHERE %s
3037 ORDER BY clin_when %s, pk_episode, unified_name
3038 """ % (
3039 ' AND '.join(where_parts),
3040 gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
3041 )
3042 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3043
3044 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
3045
3046 return tests
3047 #------------------------------------------------------------------
3048 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
3049
3050 try:
3051 epi = int(episode)
3052 except:
3053 epi = episode['pk_episode']
3054
3055 try:
3056 type = int(type)
3057 except:
3058 type = type['pk_test_type']
3059
3060 tr = gmPathLab.create_test_result (
3061 link_obj = link_obj,
3062 encounter = self.current_encounter['pk_encounter'],
3063 episode = epi,
3064 type = type,
3065 intended_reviewer = intended_reviewer,
3066 val_num = val_num,
3067 val_alpha = val_alpha,
3068 unit = unit
3069 )
3070
3071 return tr
3072
3073 #------------------------------------------------------------------
3075 where = 'pk_org_unit IN (%s)' % """
3076 SELECT DISTINCT fk_org_unit FROM clin.test_org WHERE pk IN (
3077 SELECT DISTINCT pk_test_org FROM clin.v_test_results where pk_patient = %(pat)s
3078 )"""
3079 args = {'pat': self.pk_patient}
3080 cmd = gmOrganization._SQL_get_org_unit % where
3081 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3082 return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
3083
3084 #------------------------------------------------------------------
3086
3087 measured_gfr = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
3088 crea = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1)
3089
3090 if (measured_gfr is None) and (crea is None):
3091 return None
3092
3093 if (measured_gfr is not None) and (crea is None):
3094 return measured_gfr
3095
3096 # from here, Crea cannot be None anymore
3097 if measured_gfr is None:
3098 eGFR = self.calculator.eGFR
3099 if eGFR.numeric_value is None:
3100 return crea
3101 return eGFR
3102
3103 # from here, measured_gfr cannot be None anymore, either
3104 two_weeks = pydt.timedelta(weeks = 2)
3105 gfr_too_old = (crea['clin_when'] - measured_gfr['clin_when']) > two_weeks
3106 if not gfr_too_old:
3107 return measured_gfr
3108
3109 # from here, measured_gfr is considered too
3110 # old, so attempt a more timely estimate
3111 eGFR = self.calculator.eGFR
3112 if eGFR.numeric_value is None:
3113 # return crea since we cannot get a
3114 # better estimate for some reason
3115 return crea
3116
3117 return eGFR
3118
3119 best_gfr_or_crea = property(_get_best_gfr_or_crea, lambda x:x)
3120
3121 #------------------------------------------------------------------
3124
3125 bmi = property(_get_bmi, lambda x:x)
3126
3127 #------------------------------------------------------------------
3129 return gmAutoHints.get_hints_for_patient(pk_identity = self.pk_patient, pk_encounter = self.current_encounter['pk_encounter'])
3130
3131 dynamic_hints = property(_get_dynamic_hints, lambda x:x)
3132
3133 #------------------------------------------------------------------
3134 #------------------------------------------------------------------
3135 #------------------------------------------------------------------
3137 # FIXME: verify that it is our patient ? ...
3138 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
3139 return req
3140 #------------------------------------------------------------------
3142 if encounter_id is None:
3143 encounter_id = self.current_encounter['pk_encounter']
3144 status, data = gmPathLab.create_lab_request(
3145 lab=lab,
3146 req_id=req_id,
3147 pat_id=self.pk_patient,
3148 encounter_id=encounter_id,
3149 episode_id=episode_id
3150 )
3151 if not status:
3152 _log.error(str(data))
3153 return None
3154 return data
3155
3156 #============================================================
3157 # main
3158 #------------------------------------------------------------
3159 if __name__ == "__main__":
3160
3161 if len(sys.argv) == 1:
3162 sys.exit()
3163
3164 if sys.argv[1] != 'test':
3165 sys.exit()
3166
3167 from Gnumed.pycommon import gmLog2
3168
3169 from Gnumed.business import gmPraxis
3170 branches = gmPraxis.get_praxis_branches()
3171 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
3172
3177
3178 set_delayed_executor(_do_delayed)
3179
3180 #-----------------------------------------
3182 emr = cClinicalRecord(aPKey=1)
3183 state = emr.allergy_state
3184 print("allergy state is:", state)
3185
3186 print("setting state to 0")
3187 emr.allergy_state = 0
3188
3189 print("setting state to None")
3190 emr.allergy_state = None
3191
3192 print("setting state to 'abc'")
3193 emr.allergy_state = 'abc'
3194
3195 #-----------------------------------------
3197 emr = cClinicalRecord(aPKey = 6)
3198 rows = emr.get_test_types_for_results(unique_meta_types = True)
3199 print("test result names:", len(rows))
3200 # for row in rows:
3201 # print row
3202
3203 #-----------------------------------------
3205 emr = cClinicalRecord(aPKey=12)
3206 rows = emr.get_dates_for_results()
3207 print("test result dates:")
3208 for row in rows:
3209 print(row)
3210
3211 #-----------------------------------------
3213 emr = cClinicalRecord(aPKey=12)
3214 rows, idx = emr.get_measurements_by_date()
3215 print("test results:")
3216 for row in rows:
3217 print(row)
3218
3219 #-----------------------------------------
3221 emr = cClinicalRecord(aPKey=12)
3222 tests = emr.get_test_results_by_date()
3223 print("test results:")
3224 for test in tests:
3225 print(test)
3226
3227 #-----------------------------------------
3229 emr = cClinicalRecord(aPKey=12)
3230 for key, item in emr.get_statistics().items():
3231 print(key, ":", item)
3232
3233 #-----------------------------------------
3235 emr = cClinicalRecord(aPKey=12)
3236
3237 probs = emr.get_problems()
3238 print("normal probs (%s):" % len(probs))
3239 for p in probs:
3240 print('%s (%s)' % (p['problem'], p['type']))
3241
3242 probs = emr.get_problems(include_closed_episodes=True)
3243 print("probs + closed episodes (%s):" % len(probs))
3244 for p in probs:
3245 print('%s (%s)' % (p['problem'], p['type']))
3246
3247 probs = emr.get_problems(include_irrelevant_issues=True)
3248 print("probs + issues (%s):" % len(probs))
3249 for p in probs:
3250 print('%s (%s)' % (p['problem'], p['type']))
3251
3252 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
3253 print("probs + issues + epis (%s):" % len(probs))
3254 for p in probs:
3255 print('%s (%s)' % (p['problem'], p['type']))
3256
3257 #-----------------------------------------
3259 emr = cClinicalRecord(aPKey=12)
3260 tr = emr.add_test_result (
3261 episode = 1,
3262 intended_reviewer = 1,
3263 type = 1,
3264 val_num = 75,
3265 val_alpha = 'somewhat obese',
3266 unit = 'kg'
3267 )
3268 print(tr)
3269
3270 #-----------------------------------------
3274
3275 #-----------------------------------------
3277 emr = cClinicalRecord(aPKey=12)
3278 print(emr.get_last_encounter(issue_id=2))
3279 print(emr.get_last_but_one_encounter(issue_id=2))
3280
3281 #-----------------------------------------
3283 emr = cClinicalRecord(aPKey = 5)
3284 print(emr.get_first_encounter(episode_id = 1638))
3285 print(emr.get_last_encounter(episode_id = 1638))
3286
3287 #-----------------------------------------
3289 emr = cClinicalRecord(aPKey = 12)
3290 for issue in emr.health_issues:
3291 print(issue['description'])
3292
3293 #-----------------------------------------
3298
3299 #-----------------------------------------
3304
3305 #-----------------------------------------
3307 emr = cClinicalRecord(aPKey=12)
3308 for med in emr.abused_substances:
3309 print(med.format(single_line = True))
3310
3311 #-----------------------------------------
3313 emr = cClinicalRecord(aPKey = 12)
3314 print(emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), product_name = sys.argv[2]))
3315
3316 #-----------------------------------------
3318 emr = cClinicalRecord(aPKey = 12)
3319 for journal_line in emr.get_as_journal():
3320 #print journal_line.keys()
3321 print('%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line)
3322 print("")
3323
3324 #-----------------------------------------
3328
3329 #-----------------------------------------
3331 emr = cClinicalRecord(aPKey=12)
3332 print("episodes:", emr.episodes)
3333 print("unlinked:", emr.unlinked_episodes)
3334
3335 #-----------------------------------------
3337 emr = cClinicalRecord(aPKey=12)
3338 from Gnumed.business.gmPerson import cPatient
3339 pat = cPatient(aPK_obj = 12)
3340 print(emr.format_as_journal(left_margin = 1, patient = pat))
3341
3342 #-----------------------------------------
3344 emr = cClinicalRecord(aPKey=12)
3345 #print emr.is_or_was_smoker
3346 smoking, details = emr.smoking_status
3347 print('status:', smoking)
3348 print('details:')
3349 print(details)
3350 emr.smoking_status = (True, {'comment': '2', 'last_confirmed': gmDateTime.pydt_now_here()})
3351 print(emr.smoking_status)
3352 print(emr.alcohol_status)
3353 print(emr.drugs_status)
3354
3355 #-----------------------------------------
3356
3357 #test_allergy_state()
3358 #test_is_allergic_to()
3359
3360 #test_get_test_names()
3361 #test_get_dates_for_results()
3362 #test_get_measurements()
3363 #test_get_test_results_by_date()
3364 #test_get_statistics()
3365 #test_get_problems()
3366 #test_add_test_result()
3367 #test_get_most_recent_episode()
3368 #test_get_almost_recent_encounter()
3369 #test_get_meds()
3370 #test_get_as_journal()
3371 #test_get_most_recent()
3372 #test_episodes()
3373 #test_format_as_journal()
3374 #test_smoking()
3375 #test_get_abuses()
3376 #test_get_encounters()
3377 #test_get_issues()
3378 test_get_dx()
3379
3380 # emr = cClinicalRecord(aPKey = 12)
3381
3382 # # Vacc regimes
3383 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
3384 # print '\nVaccination regimes: '
3385 # for a_regime in vacc_regimes:
3386 # pass
3387 # #print a_regime
3388 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
3389 # #print vacc_regime
3390
3391 # # vaccination regimes and vaccinations for regimes
3392 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
3393 # print 'Vaccinations for the regime:'
3394 # for a_scheduled_vacc in scheduled_vaccs:
3395 # pass
3396 # #print ' %s' %(a_scheduled_vacc)
3397
3398 # # vaccination next shot and booster
3399 # vaccinations = emr.get_vaccinations()
3400 # for a_vacc in vaccinations:
3401 # 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'])
3402
3403 # # first and last encounters
3404 # first_encounter = emr.get_first_encounter(issue_id = 1)
3405 # print '\nFirst encounter: ' + str(first_encounter)
3406 # last_encounter = emr.get_last_encounter(episode_id = 1)
3407 # print '\nLast encounter: ' + str(last_encounter)
3408 # print ''
3409
3410 #dump = record.get_missing_vaccinations()
3411 #f = io.open('vaccs.lst', 'wb')
3412 #if dump is not None:
3413 # print "=== due ==="
3414 # f.write(u"=== due ===\n")
3415 # for row in dump['due']:
3416 # print row
3417 # f.write(repr(row))
3418 # f.write(u'\n')
3419 # print "=== overdue ==="
3420 # f.write(u"=== overdue ===\n")
3421 # for row in dump['overdue']:
3422 # print row
3423 # f.write(repr(row))
3424 # f.write(u'\n')
3425 #f.close()
3426
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jan 25 02:55:27 2019 | http://epydoc.sourceforge.net |