Package Gnumed :: Package business :: Module gmClinicalRecord
[frames] | no frames]

Source Code for Module Gnumed.business.gmClinicalRecord

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