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

Source Code for Module Gnumed.business.gmClinicalRecord

   1  # -*- coding: utf8 -*- 
   2  """GNUmed clinical patient record. 
   3   
   4  This is a clinical record object intended to let a useful 
   5  client-side API crystallize from actual use in true XP fashion. 
   6   
   7  Make sure to call set_func_ask_user() and set_encounter_ttl() 
   8  early on in your code (before cClinicalRecord.__init__() is 
   9  called for the first time). 
  10  """ 
  11  #============================================================ 
  12  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  13  __license__ = "GPL v2 or later" 
  14   
  15  #=================================================== 
  16  # TODO 
  17  # Basically we'll probably have to: 
  18  # 
  19  # a) serialize access to re-getting data from the cache so 
  20  #   that later-but-concurrent cache accesses spin until 
  21  #   the first one completes the refetch from the database 
  22  # 
  23  # b) serialize access to the cache per-se such that cache 
  24  #    flushes vs. cache regets happen atomically (where 
  25  #    flushes would abort/restart current regets) 
  26  #=================================================== 
  27   
  28  # standard libs 
  29  import sys 
  30  import logging 
  31   
  32   
  33  if __name__ == '__main__': 
  34          sys.path.insert(0, '../../') 
  35          from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N 
  36          gmI18N.activate_locale() 
  37          gmI18N.install_domain() 
  38          gmDateTime.init() 
  39   
  40  from Gnumed.pycommon import gmExceptions 
  41  from Gnumed.pycommon import gmPG2 
  42  from Gnumed.pycommon import gmDispatcher 
  43  from Gnumed.pycommon import gmI18N 
  44  from Gnumed.pycommon import gmCfg 
  45  from Gnumed.pycommon import gmTools 
  46  from Gnumed.pycommon import gmDateTime 
  47   
  48  from Gnumed.business import gmAllergy 
  49  from Gnumed.business import gmPathLab 
  50  from Gnumed.business import gmLOINC 
  51  from Gnumed.business import gmClinNarrative 
  52  from Gnumed.business import gmEMRStructItems 
  53  from Gnumed.business import gmMedication 
  54  from Gnumed.business import gmVaccination 
  55  from Gnumed.business import gmFamilyHistory 
  56  from Gnumed.business.gmDemographicRecord import get_occupations 
  57   
  58   
  59  _log = logging.getLogger('gm.emr') 
  60   
  61  _me = None 
  62  _here = None 
  63  #============================================================ 
  64  # helper functions 
  65  #------------------------------------------------------------ 
  66  _func_ask_user = None 
  67   
68 -def set_func_ask_user(a_func = None):
69 if not callable(a_func): 70 _log.error('[%] not callable, not setting _func_ask_user', a_func) 71 return False 72 73 _log.debug('setting _func_ask_user to [%s]', a_func) 74 75 global _func_ask_user 76 _func_ask_user = a_func
77 78 #============================================================
79 -class cClinicalRecord(object):
80 81 _clin_root_item_children_union_query = None 82
83 - def __init__(self, aPKey=None, allow_user_interaction=True):
84 """Fails if 85 86 - no connection to database possible 87 - patient referenced by aPKey does not exist 88 """ 89 self.pk_patient = aPKey # == identity.pk == primary key 90 91 # log access to patient record (HIPAA, for example) 92 cmd = u'SELECT gm.log_access2emr(%(todo)s)' 93 args = {'todo': u'patient [%s]' % aPKey} 94 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 95 96 from Gnumed.business import gmPraxis, gmStaff 97 global _me 98 if _me is None: 99 _me = gmStaff.gmCurrentProvider() 100 global _here 101 if _here is None: 102 _here = gmPraxis.gmCurrentPraxisBranch() 103 104 # ........................................... 105 # this is a hack to speed up get_encounters() 106 clin_root_item_children = gmPG2.get_child_tables('clin', 'clin_root_item') 107 if cClinicalRecord._clin_root_item_children_union_query is None: 108 union_phrase = u""" 109 SELECT fk_encounter from 110 %s.%s cn 111 inner join 112 (SELECT pk FROM clin.episode ep WHERE ep.fk_health_issue in %%s) as epi 113 on (cn.fk_episode = epi.pk) 114 """ 115 cClinicalRecord._clin_root_item_children_union_query = u'union\n'.join ( 116 [ union_phrase % (child[0], child[1]) for child in clin_root_item_children ] 117 ) 118 # ........................................... 119 120 self.__db_cache = {} 121 122 # load current or create new encounter 123 if _func_ask_user is None: 124 _log.error('[_func_ask_user] is None') 125 print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__ 126 self.remove_empty_encounters() 127 self.__encounter = None 128 if not self.__initiate_active_encounter(allow_user_interaction = allow_user_interaction): 129 raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey 130 131 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter']) 132 133 # register backend notification interests 134 # (keep this last so we won't hang on threads when 135 # failing this constructor for other reasons ...) 136 if not self._register_interests(): 137 raise gmExceptions.ConstructorError, "cannot register signal interests" 138 139 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
140 #--------------------------------------------------------
141 - def __del__(self):
142 pass
143 #--------------------------------------------------------
144 - def cleanup(self):
145 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient) 146 return True
147 #-------------------------------------------------------- 148 # messaging 149 #--------------------------------------------------------
150 - def _register_interests(self):
151 gmDispatcher.connect(signal = u'encounter_mod_db', receiver = self.db_callback_encounter_mod_db) 152 153 return True
154 #--------------------------------------------------------
155 - def db_callback_encounter_mod_db(self, **kwds):
156 157 # get the current encounter as an extra instance 158 # from the database to check for changes 159 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter']) 160 161 # the encounter just retrieved and the active encounter 162 # have got the same transaction ID so there's no change 163 # in the database, there could be a local change in 164 # the active encounter but that doesn't matter 165 # THIS DOES NOT WORK 166 # if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']: 167 # return True 168 169 # there must have been a change to the active encounter 170 # committed to the database from elsewhere, 171 # we must fail propagating the change, however, if 172 # there are local changes 173 if self.current_encounter.is_modified(): 174 _log.debug('unsaved changes in active encounter, cannot switch to another one') 175 raise ValueError('unsaved changes in active encounter, cannot switch to another one') 176 177 if self.current_encounter.same_payload(another_object = curr_enc_in_db): 178 _log.debug('encounter_mod_db received but no change to active encounter payload') 179 return True 180 181 # there was a change in the database from elsewhere, 182 # locally, however, we don't have any changes, therefore 183 # we can propagate the remote change locally without 184 # losing anything 185 _log.debug('active encounter modified remotely, reloading and announcing the modification') 186 self.current_encounter.refetch_payload() 187 gmDispatcher.send(u'current_encounter_modified') 188 189 return True
190 #--------------------------------------------------------
191 - def db_callback_vaccs_modified(self, **kwds):
192 return True
193 #--------------------------------------------------------
194 - def _health_issues_modified(self):
195 try: 196 del self.__db_cache['health issues'] 197 except KeyError: 198 pass 199 return 1
200 #--------------------------------------------------------
202 # try: 203 # del self.__db_cache['episodes'] 204 # except KeyError: 205 # pass 206 return 1
207 #--------------------------------------------------------
208 - def _clin_item_modified(self):
209 _log.debug('DB: clin_root_item modification')
210 #-------------------------------------------------------- 211 # API: family history 212 #--------------------------------------------------------
213 - def get_family_history(self, episodes=None, issues=None, encounters=None):
214 fhx = gmFamilyHistory.get_family_history ( 215 order_by = u'l10n_relation, condition', 216 patient = self.pk_patient 217 ) 218 219 if episodes is not None: 220 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx) 221 222 if issues is not None: 223 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx) 224 225 if encounters is not None: 226 fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx) 227 228 return fhx
229 #--------------------------------------------------------
230 - def add_family_history(self, episode=None, condition=None, relation=None):
231 return gmFamilyHistory.create_family_history ( 232 encounter = self.current_encounter['pk_encounter'], 233 episode = episode, 234 condition = condition, 235 relation = relation 236 )
237 #-------------------------------------------------------- 238 # API: performed procedures 239 #--------------------------------------------------------
240 - def get_performed_procedures(self, episodes=None, issues=None):
241 242 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient) 243 244 if episodes is not None: 245 procs = filter(lambda p: p['pk_episode'] in episodes, procs) 246 247 if issues is not None: 248 procs = filter(lambda p: p['pk_health_issue'] in issues, procs) 249 250 return procs
251 252 performed_procedures = property(get_performed_procedures, lambda x:x) 253 #-------------------------------------------------------- 256 #--------------------------------------------------------
257 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
258 return gmEMRStructItems.create_performed_procedure ( 259 encounter = self.current_encounter['pk_encounter'], 260 episode = episode, 261 location = location, 262 hospital_stay = hospital_stay, 263 procedure = procedure 264 )
265 #-------------------------------------------------------- 266 # API: hospitalizations 267 #--------------------------------------------------------
268 - def get_hospital_stays(self, episodes=None, issues=None, ongoing_only=False):
269 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient, ongoing_only = ongoing_only) 270 if episodes is not None: 271 stays = filter(lambda s: s['pk_episode'] in episodes, stays) 272 if issues is not None: 273 stays = filter(lambda s: s['pk_health_issue'] in issues, stays) 274 return stays
275 276 hospital_stays = property(get_hospital_stays, lambda x:x) 277 #--------------------------------------------------------
278 - def get_latest_hospital_stay(self):
280 #--------------------------------------------------------
281 - def add_hospital_stay(self, episode=None):
282 return gmEMRStructItems.create_hospital_stay ( 283 encounter = self.current_encounter['pk_encounter'], 284 episode = episode 285 )
286 #--------------------------------------------------------
287 - def get_hospital_stay_stats_by_hospital(self, cover_period=None):
288 args = {'pat': self.pk_patient, 'range': cover_period} 289 where_parts = [u'pk_patient = %(pat)s'] 290 if cover_period is not None: 291 where_parts.append(u'discharge > (now() - %(range)s)') 292 293 cmd = u""" 294 SELECT hospital, count(1) AS frequency 295 FROM clin.v_pat_hospital_stays 296 WHERE 297 %s 298 GROUP BY hospital 299 ORDER BY frequency DESC 300 """ % u' AND '.join(where_parts) 301 302 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 303 return rows
304 #-------------------------------------------------------- 305 # API: narrative 306 #--------------------------------------------------------
307 - def add_notes(self, notes=None, episode=None, encounter=None):
308 309 enc = gmTools.coalesce ( 310 encounter, 311 self.current_encounter['pk_encounter'] 312 ) 313 314 for note in notes: 315 success, data = gmClinNarrative.create_clin_narrative ( 316 narrative = note[1], 317 soap_cat = note[0], 318 episode_id = episode, 319 encounter_id = enc 320 ) 321 322 return True
323 #--------------------------------------------------------
324 - def add_clin_narrative(self, note='', soap_cat='s', episode=None):
325 if note.strip() == '': 326 _log.info('will not create empty clinical note') 327 return None 328 if isinstance(episode, gmEMRStructItems.cEpisode): 329 episode = episode['pk_episode'] 330 status, data = gmClinNarrative.create_clin_narrative ( 331 narrative = note, 332 soap_cat = soap_cat, 333 episode_id = episode, 334 encounter_id = self.current_encounter['pk_encounter'] 335 ) 336 if not status: 337 _log.error(str(data)) 338 return None 339 return data
340 #--------------------------------------------------------
341 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
342 """Get SOAP notes pertinent to this encounter. 343 344 since 345 - initial date for narrative items 346 until 347 - final date for narrative items 348 encounters 349 - list of encounters whose narrative are to be retrieved 350 episodes 351 - list of episodes whose narrative are to be retrieved 352 issues 353 - list of health issues whose narrative are to be retrieved 354 soap_cats 355 - list of SOAP categories of the narrative to be retrieved 356 """ 357 where_parts = [u'pk_patient = %(pat)s'] 358 args = {u'pat': self.pk_patient} 359 360 if issues is not None: 361 where_parts.append(u'pk_health_issue IN %(issues)s') 362 args['issues'] = tuple(issues) 363 364 if episodes is not None: 365 where_parts.append(u'pk_episode IN %(epis)s') 366 args['epis'] = tuple(episodes) 367 368 if encounters is not None: 369 where_parts.append(u'pk_encounter IN %(encs)s') 370 args['encs'] = tuple(encounters) 371 372 if soap_cats is not None: 373 where_parts.append(u'soap_cat IN %(cats)s') 374 soap_cats = list(soap_cats) 375 args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ] 376 if None in soap_cats: 377 args['cats'].append(None) 378 args['cats'] = tuple(args['cats']) 379 380 cmd = u""" 381 SELECT 382 c_vpn.*, 383 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = c_vpn.soap_cat) AS soap_rank 384 FROM clin.v_pat_narrative c_vpn 385 WHERE %s 386 ORDER BY date, soap_rank 387 """ % u' AND '.join(where_parts) 388 389 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 390 391 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ] 392 393 if since is not None: 394 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative) 395 396 if until is not None: 397 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative) 398 399 if providers is not None: 400 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative) 401 402 return filtered_narrative
403 #--------------------------------------------------------
404 - 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):
405 return gmClinNarrative.get_as_journal ( 406 patient = self.pk_patient, 407 since = since, 408 until = until, 409 encounters = encounters, 410 episodes = episodes, 411 issues = issues, 412 soap_cats = soap_cats, 413 providers = providers, 414 order_by = order_by, 415 time_range = time_range 416 )
417 #--------------------------------------------------------
418 - def search_narrative_simple(self, search_term=''):
419 420 search_term = search_term.strip() 421 if search_term == '': 422 return [] 423 424 cmd = u""" 425 SELECT 426 *, 427 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table) 428 as episode, 429 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table) 430 as health_issue, 431 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter) 432 as encounter_started, 433 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter) 434 as encounter_ended, 435 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter)) 436 as encounter_type 437 from clin.v_narrative4search vn4s 438 WHERE 439 pk_patient = %(pat)s and 440 vn4s.narrative ~ %(term)s 441 order by 442 encounter_started 443 """ # case sensitive 444 rows, idx = gmPG2.run_ro_queries(queries = [ 445 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}} 446 ]) 447 return rows
448 #--------------------------------------------------------
449 - def get_text_dump_old(self):
450 # don't know how to invalidate this by means of 451 # a notify without catching notifies from *all* 452 # child tables, the best solution would be if 453 # inserts in child tables would also fire triggers 454 # of ancestor tables, but oh well, 455 # until then the text dump will not be cached ... 456 try: 457 return self.__db_cache['text dump old'] 458 except KeyError: 459 pass 460 # not cached so go get it 461 fields = [ 462 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when", 463 'modified_by', 464 'clin_when', 465 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')), 466 'pk_item', 467 'pk_encounter', 468 'pk_episode', 469 'pk_health_issue', 470 'src_table' 471 ] 472 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % ', '.join(fields) 473 ro_conn = self._conn_pool.GetConnection('historica') 474 curs = ro_conn.cursor() 475 if not gmPG2.run_query(curs, None, cmd, self.pk_patient): 476 _log.error('cannot load item links for patient [%s]' % self.pk_patient) 477 curs.close() 478 return None 479 rows = curs.fetchall() 480 view_col_idx = gmPG2.get_col_indices(curs) 481 482 # aggregate by src_table for item retrieval 483 items_by_table = {} 484 for item in rows: 485 src_table = item[view_col_idx['src_table']] 486 pk_item = item[view_col_idx['pk_item']] 487 if not items_by_table.has_key(src_table): 488 items_by_table[src_table] = {} 489 items_by_table[src_table][pk_item] = item 490 491 # get mapping for issue/episode IDs 492 issues = self.get_health_issues() 493 issue_map = {} 494 for issue in issues: 495 issue_map[issue['pk']] = issue['description'] 496 episodes = self.get_episodes() 497 episode_map = {} 498 for episode in episodes: 499 episode_map[episode['pk_episode']] = episode['description'] 500 emr_data = {} 501 # get item data from all source tables 502 for src_table in items_by_table.keys(): 503 item_ids = items_by_table[src_table].keys() 504 # we don't know anything about the columns of 505 # the source tables but, hey, this is a dump 506 if len(item_ids) == 0: 507 _log.info('no items in table [%s] ?!?' % src_table) 508 continue 509 elif len(item_ids) == 1: 510 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table 511 if not gmPG2.run_query(curs, None, cmd, item_ids[0]): 512 _log.error('cannot load items from table [%s]' % src_table) 513 # skip this table 514 continue 515 elif len(item_ids) > 1: 516 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table 517 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)): 518 _log.error('cannot load items from table [%s]' % src_table) 519 # skip this table 520 continue 521 rows = curs.fetchall() 522 table_col_idx = gmPG.get_col_indices(curs) 523 # format per-table items 524 for row in rows: 525 # FIXME: make this get_pkey_name() 526 pk_item = row[table_col_idx['pk_item']] 527 view_row = items_by_table[src_table][pk_item] 528 age = view_row[view_col_idx['age']] 529 # format metadata 530 try: 531 episode_name = episode_map[view_row[view_col_idx['pk_episode']]] 532 except: 533 episode_name = view_row[view_col_idx['pk_episode']] 534 try: 535 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]] 536 except: 537 issue_name = view_row[view_col_idx['pk_health_issue']] 538 539 if not emr_data.has_key(age): 540 emr_data[age] = [] 541 542 emr_data[age].append( 543 _('%s: encounter (%s)') % ( 544 view_row[view_col_idx['clin_when']], 545 view_row[view_col_idx['pk_encounter']] 546 ) 547 ) 548 emr_data[age].append(_('health issue: %s') % issue_name) 549 emr_data[age].append(_('episode : %s') % episode_name) 550 # format table specific data columns 551 # - ignore those, they are metadata, some 552 # are in clin.v_pat_items data already 553 cols2ignore = [ 554 'pk_audit', 'row_version', 'modified_when', 'modified_by', 555 'pk_item', 'id', 'fk_encounter', 'fk_episode' 556 ] 557 col_data = [] 558 for col_name in table_col_idx.keys(): 559 if col_name in cols2ignore: 560 continue 561 emr_data[age].append("=> %s:" % col_name) 562 emr_data[age].append(row[table_col_idx[col_name]]) 563 emr_data[age].append("----------------------------------------------------") 564 emr_data[age].append("-- %s from table %s" % ( 565 view_row[view_col_idx['modified_string']], 566 src_table 567 )) 568 emr_data[age].append("-- written %s by %s" % ( 569 view_row[view_col_idx['modified_when']], 570 view_row[view_col_idx['modified_by']] 571 )) 572 emr_data[age].append("----------------------------------------------------") 573 curs.close() 574 self._conn_pool.ReleaseConnection('historica') 575 return emr_data
576 #--------------------------------------------------------
577 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
578 # don't know how to invalidate this by means of 579 # a notify without catching notifies from *all* 580 # child tables, the best solution would be if 581 # inserts in child tables would also fire triggers 582 # of ancestor tables, but oh well, 583 # until then the text dump will not be cached ... 584 try: 585 return self.__db_cache['text dump'] 586 except KeyError: 587 pass 588 # not cached so go get it 589 # -- get the data -- 590 fields = [ 591 'age', 592 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when", 593 'modified_by', 594 'clin_when', 595 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')), 596 'pk_item', 597 'pk_encounter', 598 'pk_episode', 599 'pk_health_issue', 600 'src_table' 601 ] 602 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields) 603 # handle constraint conditions 604 where_snippets = [] 605 params = {} 606 where_snippets.append('pk_patient=%(pat_id)s') 607 params['pat_id'] = self.pk_patient 608 if not since is None: 609 where_snippets.append('clin_when >= %(since)s') 610 params['since'] = since 611 if not until is None: 612 where_snippets.append('clin_when <= %(until)s') 613 params['until'] = until 614 # FIXME: these are interrelated, eg if we constrain encounter 615 # we automatically constrain issue/episode, so handle that, 616 # encounters 617 if not encounters is None and len(encounters) > 0: 618 params['enc'] = encounters 619 if len(encounters) > 1: 620 where_snippets.append('fk_encounter in %(enc)s') 621 else: 622 where_snippets.append('fk_encounter=%(enc)s') 623 # episodes 624 if not episodes is None and len(episodes) > 0: 625 params['epi'] = episodes 626 if len(episodes) > 1: 627 where_snippets.append('fk_episode in %(epi)s') 628 else: 629 where_snippets.append('fk_episode=%(epi)s') 630 # health issues 631 if not issues is None and len(issues) > 0: 632 params['issue'] = issues 633 if len(issues) > 1: 634 where_snippets.append('fk_health_issue in %(issue)s') 635 else: 636 where_snippets.append('fk_health_issue=%(issue)s') 637 638 where_clause = ' and '.join(where_snippets) 639 order_by = 'order by src_table, age' 640 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by) 641 642 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params) 643 if rows is None: 644 _log.error('cannot load item links for patient [%s]' % self.pk_patient) 645 return None 646 647 # -- sort the data -- 648 # FIXME: by issue/encounter/episode, eg formatting 649 # aggregate by src_table for item retrieval 650 items_by_table = {} 651 for item in rows: 652 src_table = item[view_col_idx['src_table']] 653 pk_item = item[view_col_idx['pk_item']] 654 if not items_by_table.has_key(src_table): 655 items_by_table[src_table] = {} 656 items_by_table[src_table][pk_item] = item 657 658 # get mapping for issue/episode IDs 659 issues = self.get_health_issues() 660 issue_map = {} 661 for issue in issues: 662 issue_map[issue['pk_health_issue']] = issue['description'] 663 episodes = self.get_episodes() 664 episode_map = {} 665 for episode in episodes: 666 episode_map[episode['pk_episode']] = episode['description'] 667 emr_data = {} 668 # get item data from all source tables 669 ro_conn = self._conn_pool.GetConnection('historica') 670 curs = ro_conn.cursor() 671 for src_table in items_by_table.keys(): 672 item_ids = items_by_table[src_table].keys() 673 # we don't know anything about the columns of 674 # the source tables but, hey, this is a dump 675 if len(item_ids) == 0: 676 _log.info('no items in table [%s] ?!?' % src_table) 677 continue 678 elif len(item_ids) == 1: 679 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table 680 if not gmPG.run_query(curs, None, cmd, item_ids[0]): 681 _log.error('cannot load items from table [%s]' % src_table) 682 # skip this table 683 continue 684 elif len(item_ids) > 1: 685 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table 686 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)): 687 _log.error('cannot load items from table [%s]' % src_table) 688 # skip this table 689 continue 690 rows = curs.fetchall() 691 table_col_idx = gmPG.get_col_indices(curs) 692 # format per-table items 693 for row in rows: 694 # FIXME: make this get_pkey_name() 695 pk_item = row[table_col_idx['pk_item']] 696 view_row = items_by_table[src_table][pk_item] 697 age = view_row[view_col_idx['age']] 698 # format metadata 699 try: 700 episode_name = episode_map[view_row[view_col_idx['pk_episode']]] 701 except: 702 episode_name = view_row[view_col_idx['pk_episode']] 703 try: 704 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]] 705 except: 706 issue_name = view_row[view_col_idx['pk_health_issue']] 707 708 if not emr_data.has_key(age): 709 emr_data[age] = [] 710 711 emr_data[age].append( 712 _('%s: encounter (%s)') % ( 713 view_row[view_col_idx['clin_when']], 714 view_row[view_col_idx['pk_encounter']] 715 ) 716 ) 717 emr_data[age].append(_('health issue: %s') % issue_name) 718 emr_data[age].append(_('episode : %s') % episode_name) 719 # format table specific data columns 720 # - ignore those, they are metadata, some 721 # are in clin.v_pat_items data already 722 cols2ignore = [ 723 'pk_audit', 'row_version', 'modified_when', 'modified_by', 724 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk' 725 ] 726 col_data = [] 727 for col_name in table_col_idx.keys(): 728 if col_name in cols2ignore: 729 continue 730 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]])) 731 emr_data[age].append("----------------------------------------------------") 732 emr_data[age].append("-- %s from table %s" % ( 733 view_row[view_col_idx['modified_string']], 734 src_table 735 )) 736 emr_data[age].append("-- written %s by %s" % ( 737 view_row[view_col_idx['modified_when']], 738 view_row[view_col_idx['modified_by']] 739 )) 740 emr_data[age].append("----------------------------------------------------") 741 curs.close() 742 return emr_data
743 #--------------------------------------------------------
744 - def get_patient_ID(self):
745 return self.pk_patient
746 #--------------------------------------------------------
747 - def get_statistics(self):
748 union_query = u'\n union all\n'.join ([ 749 u""" 750 SELECT (( 751 -- all relevant health issues + active episodes WITH health issue 752 SELECT COUNT(1) 753 FROM clin.v_problem_list 754 WHERE 755 pk_patient = %(pat)s 756 AND 757 pk_health_issue is not null 758 ) + ( 759 -- active episodes WITHOUT health issue 760 SELECT COUNT(1) 761 FROM clin.v_problem_list 762 WHERE 763 pk_patient = %(pat)s 764 AND 765 pk_health_issue is null 766 ))""", 767 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s', 768 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s', 769 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s', 770 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s', 771 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s', 772 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s', 773 # active and approved substances == medication 774 u""" 775 SELECT count(1) 776 from clin.v_pat_substance_intake 777 WHERE 778 pk_patient = %(pat)s 779 and is_currently_active in (null, true) 780 and intake_is_approved_of in (null, true)""", 781 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s' 782 ]) 783 784 rows, idx = gmPG2.run_ro_queries ( 785 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}], 786 get_col_idx = False 787 ) 788 789 stats = dict ( 790 problems = rows[0][0], 791 encounters = rows[1][0], 792 items = rows[2][0], 793 documents = rows[3][0], 794 results = rows[4][0], 795 stays = rows[5][0], 796 procedures = rows[6][0], 797 active_drugs = rows[7][0], 798 vaccinations = rows[8][0] 799 ) 800 801 return stats
802 #--------------------------------------------------------
803 - def format_statistics(self):
804 return _( 805 'Medical problems: %(problems)s\n' 806 'Total encounters: %(encounters)s\n' 807 'Total EMR entries: %(items)s\n' 808 'Active medications: %(active_drugs)s\n' 809 'Documents: %(documents)s\n' 810 'Test results: %(results)s\n' 811 'Hospitalizations: %(stays)s\n' 812 'Procedures: %(procedures)s\n' 813 'Vaccinations: %(vaccinations)s' 814 ) % self.get_statistics()
815 #--------------------------------------------------------
816 - def format_summary(self, dob=None):
817 818 stats = self.get_statistics() 819 first = self.get_first_encounter() 820 last = self.get_last_encounter() 821 probs = self.get_problems() 822 823 txt = u'' 824 if len(probs) > 0: 825 txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems'] 826 else: 827 txt += _(' %s known problems\n') % stats['problems'] 828 for prob in probs: 829 if not prob['clinically_relevant']: 830 continue 831 txt += u' \u00BB%s\u00AB (%s)\n' % ( 832 prob['problem'], 833 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive')) 834 ) 835 txt += u'\n' 836 txt += _(' %s encounters from %s to %s\n') % ( 837 stats['encounters'], 838 gmDateTime.pydt_strftime(first['started'], '%Y %b %d'), 839 gmDateTime.pydt_strftime(last['started'], '%Y %b %d') 840 ) 841 txt += _(' %s active medications\n') % stats['active_drugs'] 842 txt += _(' %s documents\n') % stats['documents'] 843 txt += _(' %s test results\n') % stats['results'] 844 txt += _(' %s hospitalizations') % stats['stays'] 845 if stats['stays'] == 0: 846 txt += u'\n' 847 else: 848 txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3) 849 # FIXME: perhaps only count "ongoing ones" 850 txt += _(' %s performed procedures') % stats['procedures'] 851 if stats['procedures'] == 0: 852 txt += u'\n' 853 else: 854 txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3) 855 856 txt += u'\n' 857 txt += _('Allergies and Intolerances\n') 858 859 allg_state = self.allergy_state 860 txt += (u' ' + allg_state.state_string) 861 if allg_state['last_confirmed'] is not None: 862 txt += _(' (last confirmed %s)') % gmDateTime.pydt_strftime(allg_state['last_confirmed'], '%Y %b %d') 863 txt += u'\n' 864 txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n') 865 for allg in self.get_allergies(): 866 txt += u' %s: %s\n' % ( 867 allg['descriptor'], 868 gmTools.coalesce(allg['reaction'], _('unknown reaction')) 869 ) 870 871 txt += u'\n' 872 txt += _('Family History') 873 txt += u'\n' 874 fhx = self.get_family_history() 875 for f in fhx: 876 txt += u'%s\n' % f.format(left_margin = 1) 877 878 txt += u'\n' 879 txt += _('Occupations') 880 txt += u'\n' 881 jobs = get_occupations(pk_identity = self.pk_patient) 882 for job in jobs: 883 txt += u' %s%s\n' % ( 884 job['l10n_occupation'], 885 gmTools.coalesce(job['activities'], u'', u': %s') 886 ) 887 888 txt += u'\n' 889 txt += _('Vaccinations') 890 txt += u'\n' 891 vaccs = self.get_latest_vaccinations() 892 inds = sorted(vaccs.keys()) 893 for ind in inds: 894 ind_count, vacc = vaccs[ind] 895 if dob is None: 896 age_given = u'' 897 else: 898 age_given = u' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age ( 899 start = dob, 900 end = vacc['date_given'] 901 )) 902 since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given']) 903 txt += u' %s (%s%s): %s%s (%s %s%s%s)\n' % ( 904 ind, 905 gmTools.u_sum, 906 ind_count, 907 #gmDateTime.pydt_strftime(vacc['date_given'], '%b %Y'), 908 since, 909 age_given, 910 vacc['vaccine'], 911 gmTools.u_left_double_angle_quote, 912 vacc['batch_no'], 913 gmTools.u_right_double_angle_quote 914 ) 915 916 return txt
917 #--------------------------------------------------------
918 - def format_as_journal(self, left_margin=0, patient=None):
919 txt = u'' 920 for enc in self.get_encounters(skip_empty = True): 921 txt += gmTools.u_box_horiz_4dashes * 70 + u'\n' 922 txt += enc.format ( 923 episodes = None, # means: each touched upon 924 left_margin = left_margin, 925 patient = patient, 926 fancy_header = False, 927 with_soap = True, 928 with_docs = True, 929 with_tests = True, 930 with_vaccinations = True, 931 with_co_encountlet_hints = False, # irrelevant 932 with_rfe_aoe = True, 933 with_family_history = True, 934 by_episode = True 935 ) 936 937 return txt
938 #-------------------------------------------------------- 939 # API: allergy 940 #--------------------------------------------------------
941 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
942 """Retrieves patient allergy items. 943 944 remove_sensitivities 945 - retrieve real allergies only, without sensitivities 946 since 947 - initial date for allergy items 948 until 949 - final date for allergy items 950 encounters 951 - list of encounters whose allergies are to be retrieved 952 episodes 953 - list of episodes whose allergies are to be retrieved 954 issues 955 - list of health issues whose allergies are to be retrieved 956 """ 957 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor" 958 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True) 959 allergies = [] 960 for r in rows: 961 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'})) 962 963 # ok, let's constrain our list 964 filtered_allergies = [] 965 filtered_allergies.extend(allergies) 966 967 if ID_list is not None: 968 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies) 969 if len(filtered_allergies) == 0: 970 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient)) 971 # better fail here contrary to what we do elsewhere 972 return None 973 else: 974 return filtered_allergies 975 976 if remove_sensitivities: 977 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies) 978 if since is not None: 979 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies) 980 if until is not None: 981 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies) 982 if issues is not None: 983 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies) 984 if episodes is not None: 985 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies) 986 if encounters is not None: 987 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies) 988 989 return filtered_allergies
990 #--------------------------------------------------------
991 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
992 if encounter_id is None: 993 encounter_id = self.current_encounter['pk_encounter'] 994 995 if episode_id is None: 996 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances')) 997 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue']) 998 episode_id = epi['pk_episode'] 999 1000 new_allergy = gmAllergy.create_allergy ( 1001 allergene = allergene, 1002 allg_type = allg_type, 1003 encounter_id = encounter_id, 1004 episode_id = episode_id 1005 ) 1006 1007 return new_allergy
1008 #--------------------------------------------------------
1009 - def delete_allergy(self, pk_allergy=None):
1010 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s' 1011 args = {'pk_allg': pk_allergy} 1012 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1013 #--------------------------------------------------------
1014 - def is_allergic_to(self, atcs=None, inns=None, brand=None):
1015 """Cave: only use with one potential allergic agent 1016 otherwise you won't know which of the agents the allergy is to.""" 1017 1018 # we don't know the state 1019 if self.allergy_state is None: 1020 return None 1021 1022 # we know there's no allergies 1023 if self.allergy_state == 0: 1024 return False 1025 1026 args = { 1027 'atcs': atcs, 1028 'inns': inns, 1029 'brand': brand, 1030 'pat': self.pk_patient 1031 } 1032 allergenes = [] 1033 where_parts = [] 1034 1035 if len(atcs) == 0: 1036 atcs = None 1037 if atcs is not None: 1038 where_parts.append(u'atc_code in %(atcs)s') 1039 if len(inns) == 0: 1040 inns = None 1041 if inns is not None: 1042 where_parts.append(u'generics in %(inns)s') 1043 allergenes.extend(inns) 1044 if brand is not None: 1045 where_parts.append(u'substance = %(brand)s') 1046 allergenes.append(brand) 1047 1048 if len(allergenes) != 0: 1049 where_parts.append(u'allergene in %(allgs)s') 1050 args['allgs'] = tuple(allergenes) 1051 1052 cmd = u""" 1053 SELECT * FROM clin.v_pat_allergies 1054 WHERE 1055 pk_patient = %%(pat)s 1056 AND ( %s )""" % u' OR '.join(where_parts) 1057 1058 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1059 1060 if len(rows) == 0: 1061 return False 1062 1063 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1064 #--------------------------------------------------------
1065 - def _set_allergy_state(self, state):
1066 1067 if state not in gmAllergy.allergy_states: 1068 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states)) 1069 1070 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter']) 1071 allg_state['has_allergy'] = state 1072 allg_state.save_payload() 1073 return True
1074
1075 - def _get_allergy_state(self):
1076 return gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
1077 1078 allergy_state = property(_get_allergy_state, _set_allergy_state) 1079 #-------------------------------------------------------- 1080 # API: episodes 1081 #--------------------------------------------------------
1082 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1083 """Fetches from backend patient episodes. 1084 1085 id_list - Episodes' PKs list 1086 issues - Health issues' PKs list to filter episodes by 1087 open_status - return all (None) episodes, only open (True) or closed (False) one(s) 1088 """ 1089 if (unlinked_only is True) and (issues is not None): 1090 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None') 1091 1092 if order_by is None: 1093 order_by = u'' 1094 else: 1095 order_by = u'ORDER BY %s' % order_by 1096 1097 args = { 1098 'pat': self.pk_patient, 1099 'open': open_status 1100 } 1101 where_parts = [u'pk_patient = %(pat)s'] 1102 1103 if open_status is not None: 1104 where_parts.append(u'episode_open IS %(open)s') 1105 1106 if unlinked_only: 1107 where_parts.append(u'pk_health_issue is NULL') 1108 1109 if issues is not None: 1110 where_parts.append(u'pk_health_issue IN %(issues)s') 1111 args['issues'] = tuple(issues) 1112 1113 if id_list is not None: 1114 where_parts.append(u'pk_episode IN %(epis)s') 1115 args['epis'] = tuple(id_list) 1116 1117 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % ( 1118 u' AND '.join(where_parts), 1119 order_by 1120 ) 1121 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1122 1123 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1124 1125 episodes = property(get_episodes, lambda x:x) 1126 #------------------------------------------------------------------
1127 - def get_unlinked_episodes(self, open_status=None, order_by=None):
1128 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1129 1130 unlinked_episodes = property(get_unlinked_episodes, lambda x:x) 1131 #------------------------------------------------------------------
1132 - def get_episodes_by_encounter(self, pk_encounter=None):
1133 cmd = u"""SELECT distinct pk_episode 1134 from clin.v_pat_items 1135 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s""" 1136 args = { 1137 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']), 1138 'pat': self.pk_patient 1139 } 1140 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1141 if len(rows) == 0: 1142 return [] 1143 epis = [] 1144 for row in rows: 1145 epis.append(row[0]) 1146 return self.get_episodes(id_list=epis)
1147 #------------------------------------------------------------------
1148 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1149 """Add episode 'episode_name' for a patient's health issue. 1150 1151 - silently returns if episode already exists 1152 """ 1153 episode = gmEMRStructItems.create_episode ( 1154 pk_health_issue = pk_health_issue, 1155 episode_name = episode_name, 1156 is_open = is_open, 1157 encounter = self.current_encounter['pk_encounter'] 1158 ) 1159 return episode
1160 #--------------------------------------------------------
1161 - def get_most_recent_episode(self, issue=None):
1162 # try to find the episode with the most recently modified clinical item 1163 1164 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s') 1165 1166 cmd = u""" 1167 SELECT pk 1168 from clin.episode 1169 WHERE pk = ( 1170 SELECT distinct on(pk_episode) pk_episode 1171 from clin.v_pat_items 1172 WHERE 1173 pk_patient = %%(pat)s 1174 and 1175 modified_when = ( 1176 SELECT max(vpi.modified_when) 1177 from clin.v_pat_items vpi 1178 WHERE vpi.pk_patient = %%(pat)s 1179 ) 1180 %s 1181 -- guard against several episodes created at the same moment of time 1182 limit 1 1183 )""" % issue_where 1184 rows, idx = gmPG2.run_ro_queries(queries = [ 1185 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}} 1186 ]) 1187 if len(rows) != 0: 1188 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0]) 1189 1190 # no clinical items recorded, so try to find 1191 # the youngest episode for this patient 1192 cmd = u""" 1193 SELECT vpe0.pk_episode 1194 from 1195 clin.v_pat_episodes vpe0 1196 WHERE 1197 vpe0.pk_patient = %%(pat)s 1198 and 1199 vpe0.episode_modified_when = ( 1200 SELECT max(vpe1.episode_modified_when) 1201 from clin.v_pat_episodes vpe1 1202 WHERE vpe1.pk_episode = vpe0.pk_episode 1203 ) 1204 %s""" % issue_where 1205 rows, idx = gmPG2.run_ro_queries(queries = [ 1206 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}} 1207 ]) 1208 if len(rows) != 0: 1209 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0]) 1210 1211 return None
1212 #--------------------------------------------------------
1213 - def episode2problem(self, episode=None):
1214 return gmEMRStructItems.episode2problem(episode=episode)
1215 #-------------------------------------------------------- 1216 # API: problems 1217 #--------------------------------------------------------
1218 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1219 """Retrieve a patient's problems. 1220 1221 "Problems" are the UNION of: 1222 1223 - issues which are .clinically_relevant 1224 - episodes which are .is_open 1225 1226 Therefore, both an issue and the open episode 1227 thereof can each be listed as a problem. 1228 1229 include_closed_episodes/include_irrelevant_issues will 1230 include those -- which departs from the definition of 1231 the problem list being "active" items only ... 1232 1233 episodes - episodes' PKs to filter problems by 1234 issues - health issues' PKs to filter problems by 1235 """ 1236 # FIXME: this could use a good measure of streamlining, probably 1237 1238 args = {'pat': self.pk_patient} 1239 1240 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem""" 1241 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1242 1243 # Instantiate problem items 1244 problems = [] 1245 for row in rows: 1246 pk_args = { 1247 u'pk_patient': self.pk_patient, 1248 u'pk_health_issue': row['pk_health_issue'], 1249 u'pk_episode': row['pk_episode'] 1250 } 1251 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False)) 1252 1253 # include non-problems ? 1254 other_rows = [] 1255 if include_closed_episodes: 1256 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'""" 1257 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1258 other_rows.extend(rows) 1259 1260 if include_irrelevant_issues: 1261 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'""" 1262 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1263 other_rows.extend(rows) 1264 1265 if len(other_rows) > 0: 1266 for row in other_rows: 1267 pk_args = { 1268 u'pk_patient': self.pk_patient, 1269 u'pk_health_issue': row['pk_health_issue'], 1270 u'pk_episode': row['pk_episode'] 1271 } 1272 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True)) 1273 1274 # filter ? 1275 if (episodes is None) and (issues is None): 1276 return problems 1277 1278 # filter 1279 if issues is not None: 1280 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems) 1281 if episodes is not None: 1282 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems) 1283 1284 return problems
1285 #--------------------------------------------------------
1286 - def problem2episode(self, problem=None):
1287 return gmEMRStructItems.problem2episode(problem = problem)
1288 #--------------------------------------------------------
1289 - def problem2issue(self, problem=None):
1290 return gmEMRStructItems.problem2issue(problem = problem)
1291 #--------------------------------------------------------
1292 - def reclass_problem(self, problem):
1293 return gmEMRStructItems.reclass_problem(problem = problem)
1294 #-------------------------------------------------------- 1295 # API: health issues 1296 #--------------------------------------------------------
1297 - def get_health_issues(self, id_list = None):
1298 1299 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description" 1300 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True) 1301 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ] 1302 1303 if id_list is None: 1304 return issues 1305 1306 if len(id_list) == 0: 1307 raise ValueError('id_list to filter by is empty, most likely a programming error') 1308 1309 filtered_issues = [] 1310 for issue in issues: 1311 if issue['pk_health_issue'] in id_list: 1312 filtered_issues.append(issue) 1313 1314 return filtered_issues
1315 1316 health_issues = property(get_health_issues, lambda x:x) 1317 #------------------------------------------------------------------
1318 - def add_health_issue(self, issue_name=None):
1319 """Adds patient health issue.""" 1320 return gmEMRStructItems.create_health_issue ( 1321 description = issue_name, 1322 encounter = self.current_encounter['pk_encounter'], 1323 patient = self.pk_patient 1324 )
1325 #--------------------------------------------------------
1326 - def health_issue2problem(self, issue=None):
1327 return gmEMRStructItems.health_issue2problem(issue = issue)
1328 #-------------------------------------------------------- 1329 # API: substance intake 1330 #--------------------------------------------------------
1331 - def get_current_substance_intake(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1332 1333 where_parts = [u'pk_patient = %(pat)s'] 1334 1335 if not include_inactive: 1336 where_parts.append(u'is_currently_active in (true, null)') 1337 1338 if not include_unapproved: 1339 where_parts.append(u'intake_is_approved_of in (true, null)') 1340 1341 if order_by is None: 1342 order_by = u'' 1343 else: 1344 order_by = u'order by %s' % order_by 1345 1346 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % ( 1347 u'\nand '.join(where_parts), 1348 order_by 1349 ) 1350 1351 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True) 1352 1353 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ] 1354 1355 if episodes is not None: 1356 meds = filter(lambda s: s['pk_episode'] in episodes, meds) 1357 1358 if issues is not None: 1359 meds = filter(lambda s: s['pk_health_issue'] in issues, meds) 1360 1361 return meds
1362 #--------------------------------------------------------
1363 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1364 return gmMedication.create_substance_intake ( 1365 pk_substance = pk_substance, 1366 pk_component = pk_component, 1367 encounter = self.current_encounter['pk_encounter'], 1368 episode = episode, 1369 preparation = preparation 1370 )
1371 #--------------------------------------------------------
1372 - def substance_intake_exists(self, pk_component=None, pk_substance=None):
1373 return gmMedication.substance_intake_exists ( 1374 pk_component = pk_component, 1375 pk_substance = pk_substance, 1376 pk_identity = self.pk_patient 1377 )
1378 #-------------------------------------------------------- 1379 # API: vaccinations 1380 #--------------------------------------------------------
1381 - def add_vaccination(self, episode=None, vaccine=None, batch_no=None):
1382 return gmVaccination.create_vaccination ( 1383 encounter = self.current_encounter['pk_encounter'], 1384 episode = episode, 1385 vaccine = vaccine, 1386 batch_no = batch_no 1387 )
1388 #--------------------------------------------------------
1389 - def get_latest_vaccinations(self, episodes=None, issues=None):
1390 """Returns latest given vaccination for each vaccinated indication. 1391 1392 as a dict {'l10n_indication': cVaccination instance} 1393 1394 Note that this will produce duplicate vaccination instances on combi-indication vaccines ! 1395 """ 1396 # find the PKs 1397 args = {'pat': self.pk_patient} 1398 where_parts = [u'pk_patient = %(pat)s'] 1399 1400 if (episodes is not None) and (len(episodes) > 0): 1401 where_parts.append(u'pk_episode IN %(epis)s') 1402 args['epis'] = tuple(episodes) 1403 1404 if (issues is not None) and (len(issues) > 0): 1405 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)') 1406 args['issues'] = tuple(issues) 1407 1408 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts) 1409 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1410 1411 # none found 1412 if len(rows) == 0: 1413 return {} 1414 1415 vpks = [ ind['pk_vaccination'] for ind in rows ] 1416 vinds = [ ind['l10n_indication'] for ind in rows ] 1417 ind_counts = [ ind['indication_count'] for ind in rows ] 1418 1419 # turn them into vaccinations 1420 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s' 1421 args = {'pks': tuple(vpks)} 1422 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1423 1424 vaccs = {} 1425 for idx in range(len(vpks)): 1426 pk = vpks[idx] 1427 ind_count = ind_counts[idx] 1428 for r in rows: 1429 if r['pk_vaccination'] == pk: 1430 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'})) 1431 1432 return vaccs
1433 #--------------------------------------------------------
1434 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1435 1436 args = {'pat': self.pk_patient} 1437 where_parts = [u'pk_patient = %(pat)s'] 1438 1439 if order_by is None: 1440 order_by = u'' 1441 else: 1442 order_by = u'ORDER BY %s' % order_by 1443 1444 if (episodes is not None) and (len(episodes) > 0): 1445 where_parts.append(u'pk_episode IN %(epis)s') 1446 args['epis'] = tuple(episodes) 1447 1448 if (issues is not None) and (len(issues) > 0): 1449 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)') 1450 args['issues'] = tuple(issues) 1451 1452 if (encounters is not None) and (len(encounters) > 0): 1453 where_parts.append(u'pk_encounter IN %(encs)s') 1454 args['encs'] = tuple(encounters) 1455 1456 cmd = u'%s %s' % ( 1457 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts), 1458 order_by 1459 ) 1460 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1461 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ] 1462 1463 return vaccs
1464 1465 vaccinations = property(get_vaccinations, lambda x:x) 1466 #-------------------------------------------------------- 1467 # old/obsolete: 1468 #--------------------------------------------------------
1469 - def get_scheduled_vaccination_regimes(self, ID=None, indications=None):
1470 """Retrieves vaccination regimes the patient is on. 1471 1472 optional: 1473 * ID - PK of the vaccination regime 1474 * indications - indications we want to retrieve vaccination 1475 regimes for, must be primary language, not l10n_indication 1476 """ 1477 # FIXME: use course, not regime 1478 try: 1479 self.__db_cache['vaccinations']['scheduled regimes'] 1480 except KeyError: 1481 # retrieve vaccination regimes definitions 1482 self.__db_cache['vaccinations']['scheduled regimes'] = [] 1483 cmd = """SELECT distinct on(pk_course) pk_course 1484 FROM clin.v_vaccs_scheduled4pat 1485 WHERE pk_patient=%s""" 1486 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1487 if rows is None: 1488 _log.error('cannot retrieve scheduled vaccination courses') 1489 del self.__db_cache['vaccinations']['scheduled regimes'] 1490 return None 1491 # Instantiate vaccination items and keep cache 1492 for row in rows: 1493 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0])) 1494 1495 # ok, let's constrain our list 1496 filtered_regimes = [] 1497 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes']) 1498 if ID is not None: 1499 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes) 1500 if len(filtered_regimes) == 0: 1501 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient)) 1502 return [] 1503 else: 1504 return filtered_regimes[0] 1505 if indications is not None: 1506 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes) 1507 1508 return filtered_regimes
1509 #-------------------------------------------------------- 1510 # def get_vaccinated_indications(self): 1511 # """Retrieves patient vaccinated indications list. 1512 # 1513 # Note that this does NOT rely on the patient being on 1514 # some schedule or other but rather works with what the 1515 # patient has ACTUALLY been vaccinated against. This is 1516 # deliberate ! 1517 # """ 1518 # # most likely, vaccinations will be fetched close 1519 # # by so it makes sense to count on the cache being 1520 # # filled (or fill it for nearby use) 1521 # vaccinations = self.get_vaccinations() 1522 # if vaccinations is None: 1523 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient) 1524 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]]) 1525 # if len(vaccinations) == 0: 1526 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]]) 1527 # v_indications = [] 1528 # for vacc in vaccinations: 1529 # tmp = [vacc['indication'], vacc['l10n_indication']] 1530 # # remove duplicates 1531 # if tmp in v_indications: 1532 # continue 1533 # v_indications.append(tmp) 1534 # return (True, v_indications) 1535 #--------------------------------------------------------
1536 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1537 """Retrieves list of vaccinations the patient has received. 1538 1539 optional: 1540 * ID - PK of a vaccination 1541 * indications - indications we want to retrieve vaccination 1542 items for, must be primary language, not l10n_indication 1543 * since - initial date for allergy items 1544 * until - final date for allergy items 1545 * encounters - list of encounters whose allergies are to be retrieved 1546 * episodes - list of episodes whose allergies are to be retrieved 1547 * issues - list of health issues whose allergies are to be retrieved 1548 """ 1549 try: 1550 self.__db_cache['vaccinations']['vaccinated'] 1551 except KeyError: 1552 self.__db_cache['vaccinations']['vaccinated'] = [] 1553 # Important fetch ordering by indication, date to know if a vaccination is booster 1554 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication 1555 WHERE pk_patient=%s 1556 order by indication, date""" 1557 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient) 1558 if rows is None: 1559 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient) 1560 del self.__db_cache['vaccinations']['vaccinated'] 1561 return None 1562 # Instantiate vaccination items 1563 vaccs_by_ind = {} 1564 for row in rows: 1565 vacc_row = { 1566 'pk_field': 'pk_vaccination', 1567 'idx': idx, 1568 'data': row 1569 } 1570 vacc = gmVaccination.cVaccination(row=vacc_row) 1571 self.__db_cache['vaccinations']['vaccinated'].append(vacc) 1572 # keep them, ordered by indication 1573 try: 1574 vaccs_by_ind[vacc['indication']].append(vacc) 1575 except KeyError: 1576 vaccs_by_ind[vacc['indication']] = [vacc] 1577 1578 # calculate sequence number and is_booster 1579 for ind in vaccs_by_ind.keys(): 1580 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind]) 1581 for vacc in vaccs_by_ind[ind]: 1582 # due to the "order by indication, date" the vaccinations are in the 1583 # right temporal order inside the indication-keyed dicts 1584 seq_no = vaccs_by_ind[ind].index(vacc) + 1 1585 vacc['seq_no'] = seq_no 1586 # if no active schedule for indication we cannot 1587 # check for booster status (eg. seq_no > max_shot) 1588 if (vacc_regimes is None) or (len(vacc_regimes) == 0): 1589 continue 1590 if seq_no > vacc_regimes[0]['shots']: 1591 vacc['is_booster'] = True 1592 del vaccs_by_ind 1593 1594 # ok, let's constrain our list 1595 filtered_shots = [] 1596 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated']) 1597 if ID is not None: 1598 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots) 1599 if len(filtered_shots) == 0: 1600 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient)) 1601 return None 1602 else: 1603 return filtered_shots[0] 1604 if since is not None: 1605 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots) 1606 if until is not None: 1607 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots) 1608 if issues is not None: 1609 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots) 1610 if episodes is not None: 1611 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots) 1612 if encounters is not None: 1613 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots) 1614 if indications is not None: 1615 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots) 1616 return filtered_shots
1617 #--------------------------------------------------------
1618 - def get_scheduled_vaccinations(self, indications=None):
1619 """Retrieves vaccinations scheduled for a regime a patient is on. 1620 1621 The regime is referenced by its indication (not l10n) 1622 1623 * indications - List of indications (not l10n) of regimes we want scheduled 1624 vaccinations to be fetched for 1625 """ 1626 try: 1627 self.__db_cache['vaccinations']['scheduled'] 1628 except KeyError: 1629 self.__db_cache['vaccinations']['scheduled'] = [] 1630 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s""" 1631 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient) 1632 if rows is None: 1633 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient) 1634 del self.__db_cache['vaccinations']['scheduled'] 1635 return None 1636 # Instantiate vaccination items 1637 for row in rows: 1638 vacc_row = { 1639 'pk_field': 'pk_vacc_def', 1640 'idx': idx, 1641 'data': row 1642 } 1643 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row)) 1644 1645 # ok, let's constrain our list 1646 if indications is None: 1647 return self.__db_cache['vaccinations']['scheduled'] 1648 filtered_shots = [] 1649 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled']) 1650 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots) 1651 return filtered_shots
1652 #--------------------------------------------------------
1653 - def get_missing_vaccinations(self, indications=None):
1654 try: 1655 self.__db_cache['vaccinations']['missing'] 1656 except KeyError: 1657 self.__db_cache['vaccinations']['missing'] = {} 1658 # 1) non-booster 1659 self.__db_cache['vaccinations']['missing']['due'] = [] 1660 # get list of (indication, seq_no) tuples 1661 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s" 1662 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1663 if rows is None: 1664 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient) 1665 return None 1666 pk_args = {'pat_id': self.pk_patient} 1667 if rows is not None: 1668 for row in rows: 1669 pk_args['indication'] = row[0] 1670 pk_args['seq_no'] = row[1] 1671 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args)) 1672 1673 # 2) boosters 1674 self.__db_cache['vaccinations']['missing']['boosters'] = [] 1675 # get list of indications 1676 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s" 1677 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1678 if rows is None: 1679 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient) 1680 return None 1681 pk_args = {'pat_id': self.pk_patient} 1682 if rows is not None: 1683 for row in rows: 1684 pk_args['indication'] = row[0] 1685 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args)) 1686 1687 # if any filters ... 1688 if indications is None: 1689 return self.__db_cache['vaccinations']['missing'] 1690 if len(indications) == 0: 1691 return self.__db_cache['vaccinations']['missing'] 1692 # ... apply them 1693 filtered_shots = { 1694 'due': [], 1695 'boosters': [] 1696 } 1697 for due_shot in self.__db_cache['vaccinations']['missing']['due']: 1698 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']: 1699 filtered_shots['due'].append(due_shot) 1700 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']: 1701 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']: 1702 filtered_shots['boosters'].append(due_shot) 1703 return filtered_shots
1704 #------------------------------------------------------------------ 1705 # API: encounters 1706 #------------------------------------------------------------------
1707 - def _get_current_encounter(self):
1708 return self.__encounter
1709
1710 - def _set_current_encounter(self, encounter):
1711 1712 # first ever setting ? 1713 if self.__encounter is None: 1714 _log.debug('first setting of active encounter in this clinical record instance') 1715 else: 1716 _log.debug('switching of active encounter') 1717 # fail if the currently active encounter has unsaved changes 1718 if self.__encounter.is_modified(): 1719 _log.debug('unsaved changes in active encounter, cannot switch to another one') 1720 raise ValueError('unsaved changes in active encounter, cannot switch to another one') 1721 1722 # set the currently active encounter and announce that change 1723 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'): 1724 now = gmDateTime.pydt_now_here() 1725 if now > encounter['started']: 1726 encounter['last_affirmed'] = now # this will trigger an "encounter_mod_db" 1727 encounter.save() 1728 self.__encounter = encounter 1729 gmDispatcher.send(u'current_encounter_switched') 1730 1731 return True
1732 1733 current_encounter = property(_get_current_encounter, _set_current_encounter) 1734 active_encounter = property(_get_current_encounter, _set_current_encounter) 1735 #------------------------------------------------------------------
1736 - def __initiate_active_encounter(self, allow_user_interaction=True):
1737 1738 # 1) "very recent" encounter recorded ? 1739 if self.__activate_very_recent_encounter(): 1740 return True 1741 1742 # 2) "fairly recent" encounter recorded ? 1743 if self.__activate_fairly_recent_encounter(allow_user_interaction = allow_user_interaction): 1744 return True 1745 1746 # 3) start a completely new encounter 1747 self.start_new_encounter() 1748 return True
1749 #------------------------------------------------------------------
1751 """Try to attach to a "very recent" encounter if there is one. 1752 1753 returns: 1754 False: no "very recent" encounter, create new one 1755 True: success 1756 """ 1757 cfg_db = gmCfg.cCfgSQL() 1758 min_ttl = cfg_db.get2 ( 1759 option = u'encounter.minimum_ttl', 1760 workplace = _here.active_workplace, 1761 bias = u'user', 1762 default = u'1 hour 30 minutes' 1763 ) 1764 cmd = u""" 1765 SELECT pk_encounter 1766 FROM clin.v_most_recent_encounters 1767 WHERE 1768 pk_patient = %s 1769 and 1770 last_affirmed > (now() - %s::interval) 1771 ORDER BY 1772 last_affirmed DESC""" 1773 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}]) 1774 # none found 1775 if len(enc_rows) == 0: 1776 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl) 1777 return False 1778 # attach to existing 1779 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0]) 1780 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0]) 1781 return True
1782 #------------------------------------------------------------------
1783 - def __activate_fairly_recent_encounter(self, allow_user_interaction=True):
1784 """Try to attach to a "fairly recent" encounter if there is one. 1785 1786 returns: 1787 False: no "fairly recent" encounter, create new one 1788 True: success 1789 """ 1790 if _func_ask_user is None: 1791 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter') 1792 return False 1793 1794 if not allow_user_interaction: 1795 _log.exception('user interaction not desired, not looking for fairly recent encounter') 1796 return False 1797 1798 cfg_db = gmCfg.cCfgSQL() 1799 min_ttl = cfg_db.get2 ( 1800 option = u'encounter.minimum_ttl', 1801 workplace = _here.active_workplace, 1802 bias = u'user', 1803 default = u'1 hour 30 minutes' 1804 ) 1805 max_ttl = cfg_db.get2 ( 1806 option = u'encounter.maximum_ttl', 1807 workplace = _here.active_workplace, 1808 bias = u'user', 1809 default = u'6 hours' 1810 ) 1811 cmd = u""" 1812 SELECT pk_encounter 1813 FROM clin.v_most_recent_encounters 1814 WHERE 1815 pk_patient=%s 1816 AND 1817 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval) 1818 ORDER BY 1819 last_affirmed DESC""" 1820 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}]) 1821 # none found 1822 if len(enc_rows) == 0: 1823 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl)) 1824 return False 1825 1826 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0]) 1827 1828 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0]) 1829 # ask user whether to attach or not 1830 cmd = u""" 1831 SELECT title, firstnames, lastnames, gender, dob 1832 FROM dem.v_basic_person WHERE pk_identity=%s""" 1833 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}]) 1834 pat = pats[0] 1835 pat_str = u'%s %s %s (%s), %s [#%s]' % ( 1836 gmTools.coalesce(pat[0], u'')[:5], 1837 pat[1][:15], 1838 pat[2][:15], 1839 pat[3], 1840 gmDateTime.pydt_strftime(pat[4], '%Y %b %d'), 1841 self.pk_patient 1842 ) 1843 msg = _( 1844 '%s\n' 1845 '\n' 1846 "This patient's chart was worked on only recently:\n" 1847 '\n' 1848 ' %s %s - %s (%s)\n' 1849 '\n' 1850 ' Request: %s\n' 1851 ' Outcome: %s\n' 1852 '\n' 1853 'Do you want to continue that consultation\n' 1854 'or do you want to start a new one ?\n' 1855 ) % ( 1856 pat_str, 1857 gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'), 1858 gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'), 1859 encounter['l10n_type'], 1860 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')), 1861 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')), 1862 ) 1863 attach = False 1864 try: 1865 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter) 1866 except: 1867 _log.exception('cannot ask user for guidance, not attaching to existing encounter') 1868 return False 1869 if not attach: 1870 return False 1871 1872 # attach to existing 1873 self.current_encounter = encounter 1874 _log.debug('"fairly recent" encounter re-activated') 1875 return True
1876 #------------------------------------------------------------------
1877 - def start_new_encounter(self):
1878 cfg_db = gmCfg.cCfgSQL() 1879 # FIXME: look for MRU/MCU encounter type config here 1880 enc_type = cfg_db.get2 ( 1881 option = u'encounter.default_type', 1882 workplace = _here.active_workplace, 1883 bias = u'user', 1884 default = u'in surgery' 1885 ) 1886 self.current_encounter = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type) 1887 _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
1888 #------------------------------------------------------------------
1889 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False):
1890 """Retrieves patient's encounters. 1891 1892 id_list - PKs of encounters to fetch 1893 since - initial date for encounter items, DateTime instance 1894 until - final date for encounter items, DateTime instance 1895 episodes - PKs of the episodes the encounters belong to (many-to-many relation) 1896 issues - PKs of the health issues the encounters belong to (many-to-many relation) 1897 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE 1898 1899 NOTE: if you specify *both* issues and episodes 1900 you will get the *aggregate* of all encounters even 1901 if the episodes all belong to the health issues listed. 1902 IOW, the issues broaden the episode list rather than 1903 the episode list narrowing the episodes-from-issues 1904 list. 1905 Rationale: If it was the other way round it would be 1906 redundant to specify the list of issues at all. 1907 """ 1908 where_parts = [u'c_vpe.pk_patient = %(pat)s'] 1909 args = {'pat': self.pk_patient} 1910 1911 if skip_empty: 1912 where_parts.append(u"""NOT ( 1913 gm.is_null_or_blank_string(c_vpe.reason_for_encounter) 1914 AND 1915 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter) 1916 AND 1917 NOT EXISTS ( 1918 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 1919 UNION ALL 1920 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 1921 ))""") 1922 1923 if since is not None: 1924 where_parts.append(u'c_vpe.started >= %(start)s') 1925 args['start'] = since 1926 1927 if until is not None: 1928 where_parts.append(u'c_vpe.last_affirmed <= %(end)s') 1929 args['end'] = since 1930 1931 cmd = u""" 1932 SELECT * 1933 FROM clin.v_pat_encounters c_vpe 1934 WHERE 1935 %s 1936 ORDER BY started 1937 """ % u' AND '.join(where_parts) 1938 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1939 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ] 1940 1941 # we've got the encounters, start filtering 1942 filtered_encounters = [] 1943 filtered_encounters.extend(encounters) 1944 1945 if id_list is not None: 1946 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters) 1947 1948 if (issues is not None) and (len(issues) > 0): 1949 issues = tuple(issues) 1950 # however, this seems like the proper approach: 1951 # - find episodes corresponding to the health issues in question 1952 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s" 1953 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}]) 1954 epi_ids = map(lambda x:x[0], rows) 1955 if episodes is None: 1956 episodes = [] 1957 episodes.extend(epi_ids) 1958 1959 if (episodes is not None) and (len(episodes) > 0): 1960 episodes = tuple(episodes) 1961 # if the episodes to filter by belong to the patient in question so will 1962 # the encounters found with them - hence we don't need a WHERE on the patient ... 1963 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s" 1964 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}]) 1965 enc_ids = map(lambda x:x[0], rows) 1966 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters) 1967 1968 return filtered_encounters
1969 #--------------------------------------------------------
1970 - def get_first_encounter(self, issue_id=None, episode_id=None):
1971 """Retrieves first encounter for a particular issue and/or episode. 1972 1973 issue_id - First encounter associated health issue 1974 episode - First encounter associated episode 1975 """ 1976 # FIXME: use direct query 1977 if issue_id is None: 1978 issues = None 1979 else: 1980 issues = [issue_id] 1981 1982 if episode_id is None: 1983 episodes = None 1984 else: 1985 episodes = [episode_id] 1986 1987 encounters = self.get_encounters(issues=issues, episodes=episodes) 1988 if len(encounters) == 0: 1989 return None 1990 1991 # FIXME: this does not scale particularly well, I assume 1992 encounters.sort(lambda x,y: cmp(x['started'], y['started'])) 1993 return encounters[0]
1994 #--------------------------------------------------------
1995 - def get_earliest_care_date(self):
1996 args = {'pat': self.pk_patient} 1997 cmd = u""" 1998 SELECT MIN(earliest) FROM ( 1999 ( 2000 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s 2001 2002 ) UNION ALL ( 2003 2004 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s 2005 2006 ) UNION ALL ( 2007 2008 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s 2009 2010 ) UNION ALL ( 2011 2012 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s 2013 2014 ) UNION ALL ( 2015 2016 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s 2017 2018 ) UNION ALL ( 2019 2020 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s 2021 2022 ) UNION ALL ( 2023 2024 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s 2025 2026 ) 2027 ) AS candidates""" 2028 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2029 return rows[0][0]
2030 2031 earliest_care_date = property(get_earliest_care_date, lambda x:x) 2032 #--------------------------------------------------------
2033 - def get_last_encounter(self, issue_id=None, episode_id=None):
2034 """Retrieves last encounter for a concrete issue and/or episode 2035 2036 issue_id - Last encounter associated health issue 2037 episode_id - Last encounter associated episode 2038 """ 2039 # FIXME: use direct query 2040 2041 if issue_id is None: 2042 issues = None 2043 else: 2044 issues = [issue_id] 2045 2046 if episode_id is None: 2047 episodes = None 2048 else: 2049 episodes = [episode_id] 2050 2051 encounters = self.get_encounters(issues=issues, episodes=episodes) 2052 if len(encounters) == 0: 2053 return None 2054 2055 # FIXME: this does not scale particularly well, I assume 2056 encounters.sort(lambda x,y: cmp(x['started'], y['started'])) 2057 return encounters[-1]
2058 2059 last_encounter = property(get_last_encounter, lambda x:x) 2060 #------------------------------------------------------------------
2061 - def get_encounter_stats_by_type(self, cover_period=None):
2062 args = {'pat': self.pk_patient, 'range': cover_period} 2063 where_parts = [u'pk_patient = %(pat)s'] 2064 if cover_period is not None: 2065 where_parts.append(u'last_affirmed > now() - %(range)s') 2066 2067 cmd = u""" 2068 SELECT l10n_type, count(1) AS frequency 2069 FROM clin.v_pat_encounters 2070 WHERE 2071 %s 2072 GROUP BY l10n_type 2073 ORDER BY frequency DESC 2074 """ % u' AND '.join(where_parts) 2075 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2076 return rows
2077 #------------------------------------------------------------------
2078 - def get_last_but_one_encounter(self, issue_id=None, episode_id=None):
2079 2080 args = {'pat': self.pk_patient} 2081 2082 if (issue_id is None) and (episode_id is None): 2083 2084 cmd = u""" 2085 SELECT * FROM clin.v_pat_encounters 2086 WHERE pk_patient = %(pat)s 2087 ORDER BY started DESC 2088 LIMIT 2 2089 """ 2090 else: 2091 where_parts = [] 2092 2093 if issue_id is not None: 2094 where_parts.append(u'pk_health_issue = %(issue)s') 2095 args['issue'] = issue_id 2096 2097 if episode_id is not None: 2098 where_parts.append(u'pk_episode = %(epi)s') 2099 args['epi'] = episode_id 2100 2101 cmd = u""" 2102 SELECT * 2103 FROM clin.v_pat_encounters 2104 WHERE 2105 pk_patient = %%(pat)s 2106 AND 2107 pk_encounter IN ( 2108 SELECT distinct pk_encounter 2109 FROM clin.v_pat_narrative 2110 WHERE 2111 %s 2112 ) 2113 ORDER BY started DESC 2114 LIMIT 2 2115 """ % u' AND '.join(where_parts) 2116 2117 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2118 2119 if len(rows) == 0: 2120 return None 2121 2122 # just one encounter within the above limits 2123 if len(rows) == 1: 2124 # is it the current encounter ? 2125 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']: 2126 # yes 2127 return None 2128 # no 2129 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'}) 2130 2131 # more than one encounter 2132 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']: 2133 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'}) 2134 2135 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2136 #------------------------------------------------------------------
2137 - def remove_empty_encounters(self):
2138 cfg_db = gmCfg.cCfgSQL() 2139 ttl = cfg_db.get2 ( 2140 option = u'encounter.ttl_if_empty', 2141 workplace = _here.active_workplace, 2142 bias = u'user', 2143 default = u'1 week' 2144 ) 2145 2146 # # FIXME: this should be done async 2147 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)" 2148 args = {'pat': self.pk_patient, 'ttl': ttl} 2149 try: 2150 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2151 except: 2152 _log.exception('error deleting empty encounters') 2153 2154 return True
2155 #------------------------------------------------------------------ 2156 # API: measurements / test results 2157 #------------------------------------------------------------------
2158 - def get_most_recent_results(self, test_type=None, loinc=None, no_of_results=1):
2159 return gmPathLab.get_most_recent_results ( 2160 test_type = test_type, 2161 loinc = loinc, 2162 no_of_results = no_of_results, 2163 patient = self.pk_patient 2164 )
2165 #------------------------------------------------------------------
2166 - def get_result_at_timestamp(self, timestamp=None, test_type=None, loinc=None, tolerance_interval='12 hours'):
2167 return gmPathLab.get_result_at_timestamp ( 2168 timestamp = timestamp, 2169 test_type = test_type, 2170 loinc = loinc, 2171 tolerance_interval = tolerance_interval, 2172 patient = self.pk_patient 2173 )
2174 #------------------------------------------------------------------
2175 - def get_unsigned_results(self, order_by=None):
2176 if order_by is None: 2177 order_by = u'' 2178 else: 2179 order_by = u'ORDER BY %s' % order_by 2180 cmd = u""" 2181 SELECT * FROM clin.v_test_results 2182 WHERE 2183 pk_patient = %%(pat)s 2184 AND 2185 reviewed IS FALSE 2186 %s""" % order_by 2187 args = {'pat': self.pk_patient} 2188 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2189 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2190 #------------------------------------------------------------------ 2191 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
2192 - def get_test_types_for_results(self):
2193 """Retrieve data about test types for which this patient has results.""" 2194 2195 cmd = u""" 2196 SELECT * FROM ( 2197 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name 2198 FROM clin.v_test_results 2199 WHERE pk_patient = %(pat)s 2200 ) AS foo 2201 ORDER BY clin_when desc, unified_name 2202 """ 2203 args = {'pat': self.pk_patient} 2204 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 2205 return [ gmPathLab.cMeasurementType(aPK_obj = row['pk_test_type']) for row in rows ]
2206 #------------------------------------------------------------------
2207 - def get_dates_for_results(self, tests=None, reverse_chronological=True):
2208 """Get the dates for which we have results.""" 2209 where_parts = [u'pk_patient = %(pat)s'] 2210 args = {'pat': self.pk_patient} 2211 2212 if tests is not None: 2213 where_parts.append(u'pk_test_type IN %(tests)s') 2214 args['tests'] = tuple(tests) 2215 2216 cmd = u""" 2217 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen 2218 FROM clin.v_test_results 2219 WHERE %s 2220 ORDER BY cwhen %s 2221 """ % ( 2222 u' AND '.join(where_parts), 2223 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC') 2224 ) 2225 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 2226 return rows
2227 #------------------------------------------------------------------
2228 - def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
2229 return gmPathLab.get_test_results ( 2230 pk_patient = self.pk_patient, 2231 encounters = encounters, 2232 episodes = episodes, 2233 order_by = order_by 2234 )
2235 #------------------------------------------------------------------
2236 - def get_test_results_by_date(self, encounter=None, episodes=None, tests=None, reverse_chronological=True):
2237 2238 where_parts = [u'pk_patient = %(pat)s'] 2239 args = {'pat': self.pk_patient} 2240 2241 if tests is not None: 2242 where_parts.append(u'pk_test_type IN %(tests)s') 2243 args['tests'] = tuple(tests) 2244 2245 if encounter is not None: 2246 where_parts.append(u'pk_encounter = %(enc)s') 2247 args['enc'] = encounter 2248 2249 if episodes is not None: 2250 where_parts.append(u'pk_episode IN %(epis)s') 2251 args['epis'] = tuple(episodes) 2252 2253 cmd = u""" 2254 SELECT * FROM clin.v_test_results 2255 WHERE %s 2256 ORDER BY clin_when %s, pk_episode, unified_name 2257 """ % ( 2258 u' AND '.join(where_parts), 2259 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC') 2260 ) 2261 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2262 2263 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ] 2264 2265 return tests
2266 #------------------------------------------------------------------
2267 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2268 2269 try: 2270 epi = int(episode) 2271 except: 2272 epi = episode['pk_episode'] 2273 2274 try: 2275 type = int(type) 2276 except: 2277 type = type['pk_test_type'] 2278 2279 if intended_reviewer is None: 2280 intended_reviewer = _me['pk_staff'] 2281 2282 tr = gmPathLab.create_test_result ( 2283 encounter = self.current_encounter['pk_encounter'], 2284 episode = epi, 2285 type = type, 2286 intended_reviewer = intended_reviewer, 2287 val_num = val_num, 2288 val_alpha = val_alpha, 2289 unit = unit 2290 ) 2291 2292 return tr
2293 #------------------------------------------------------------------ 2294 #------------------------------------------------------------------ 2295 #------------------------------------------------------------------ 2296 #------------------------------------------------------------------
2297 - def get_lab_request(self, pk=None, req_id=None, lab=None):
2298 # FIXME: verify that it is our patient ? ... 2299 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab) 2300 return req
2301 #------------------------------------------------------------------
2302 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2303 if encounter_id is None: 2304 encounter_id = self.current_encounter['pk_encounter'] 2305 status, data = gmPathLab.create_lab_request( 2306 lab=lab, 2307 req_id=req_id, 2308 pat_id=self.pk_patient, 2309 encounter_id=encounter_id, 2310 episode_id=episode_id 2311 ) 2312 if not status: 2313 _log.error(str(data)) 2314 return None 2315 return data
2316 2317 #============================================================ 2318 # main 2319 #------------------------------------------------------------ 2320 if __name__ == "__main__": 2321 2322 if len(sys.argv) == 1: 2323 sys.exit() 2324 2325 if sys.argv[1] != 'test': 2326 sys.exit() 2327 2328 from Gnumed.pycommon import gmLog2 2329 #-----------------------------------------
2330 - def test_allergy_state():
2331 emr = cClinicalRecord(aPKey=1) 2332 state = emr.allergy_state 2333 print "allergy state is:", state 2334 2335 print "setting state to 0" 2336 emr.allergy_state = 0 2337 2338 print "setting state to None" 2339 emr.allergy_state = None 2340 2341 print "setting state to 'abc'" 2342 emr.allergy_state = 'abc'
2343 #-----------------------------------------
2344 - def test_get_test_names():
2345 emr = cClinicalRecord(aPKey=12) 2346 rows = emr.get_test_types_for_results() 2347 print "test result names:" 2348 for row in rows: 2349 print row
2350 #-----------------------------------------
2351 - def test_get_dates_for_results():
2352 emr = cClinicalRecord(aPKey=12) 2353 rows = emr.get_dates_for_results() 2354 print "test result dates:" 2355 for row in rows: 2356 print row
2357 #-----------------------------------------
2358 - def test_get_measurements():
2359 emr = cClinicalRecord(aPKey=12) 2360 rows, idx = emr.get_measurements_by_date() 2361 print "test results:" 2362 for row in rows: 2363 print row
2364 #-----------------------------------------
2365 - def test_get_test_results_by_date():
2366 emr = cClinicalRecord(aPKey=12) 2367 tests = emr.get_test_results_by_date() 2368 print "test results:" 2369 for test in tests: 2370 print test
2371 #-----------------------------------------
2372 - def test_get_statistics():
2373 emr = cClinicalRecord(aPKey=12) 2374 for key, item in emr.get_statistics().iteritems(): 2375 print key, ":", item
2376 #-----------------------------------------
2377 - def test_get_problems():
2378 emr = cClinicalRecord(aPKey=12) 2379 2380 probs = emr.get_problems() 2381 print "normal probs (%s):" % len(probs) 2382 for p in probs: 2383 print u'%s (%s)' % (p['problem'], p['type']) 2384 2385 probs = emr.get_problems(include_closed_episodes=True) 2386 print "probs + closed episodes (%s):" % len(probs) 2387 for p in probs: 2388 print u'%s (%s)' % (p['problem'], p['type']) 2389 2390 probs = emr.get_problems(include_irrelevant_issues=True) 2391 print "probs + issues (%s):" % len(probs) 2392 for p in probs: 2393 print u'%s (%s)' % (p['problem'], p['type']) 2394 2395 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True) 2396 print "probs + issues + epis (%s):" % len(probs) 2397 for p in probs: 2398 print u'%s (%s)' % (p['problem'], p['type'])
2399 #-----------------------------------------
2400 - def test_add_test_result():
2401 emr = cClinicalRecord(aPKey=12) 2402 tr = emr.add_test_result ( 2403 episode = 1, 2404 intended_reviewer = 1, 2405 type = 1, 2406 val_num = 75, 2407 val_alpha = u'somewhat obese', 2408 unit = u'kg' 2409 ) 2410 print tr
2411 #-----------------------------------------
2412 - def test_get_most_recent_episode():
2413 emr = cClinicalRecord(aPKey=12) 2414 print emr.get_most_recent_episode(issue = 2)
2415 #-----------------------------------------
2416 - def test_get_almost_recent_encounter():
2417 emr = cClinicalRecord(aPKey=12) 2418 print emr.get_last_encounter(issue_id=2) 2419 print emr.get_last_but_one_encounter(issue_id=2)
2420 #-----------------------------------------
2421 - def test_get_meds():
2422 emr = cClinicalRecord(aPKey=12) 2423 for med in emr.get_current_substance_intake(): 2424 print med
2425 #-----------------------------------------
2426 - def test_is_allergic_to():
2427 emr = cClinicalRecord(aPKey = 12) 2428 print emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), brand = sys.argv[2])
2429 #-----------------------------------------
2430 - def test_get_as_journal():
2431 emr = cClinicalRecord(aPKey = 12) 2432 for journal_line in emr.get_as_journal(): 2433 #print journal_line.keys() 2434 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line 2435 print ""
2436 #-----------------------------------------
2437 - def test_get_most_recent():
2438 emr = cClinicalRecord(aPKey=12) 2439 print emr.get_most_recent_results()
2440 #-----------------------------------------
2441 - def test_episodes():
2442 emr = cClinicalRecord(aPKey=12) 2443 print "episodes:", emr.episodes 2444 print "unlinked:", emr.unlinked_episodes
2445 2446 #-----------------------------------------
2447 - def test_format_as_journal():
2448 emr = cClinicalRecord(aPKey=12) 2449 from Gnumed.business.gmPerson import cPatient 2450 pat = cPatient(aPK_obj = 12) 2451 print emr.format_as_journal(left_margin = 1, patient = pat)
2452 #----------------------------------------- 2453 2454 #test_allergy_state() 2455 #test_is_allergic_to() 2456 2457 #test_get_test_names() 2458 #test_get_dates_for_results() 2459 #test_get_measurements() 2460 #test_get_test_results_by_date() 2461 #test_get_statistics() 2462 #test_get_problems() 2463 #test_add_test_result() 2464 #test_get_most_recent_episode() 2465 #test_get_almost_recent_encounter() 2466 #test_get_meds() 2467 #test_get_as_journal() 2468 #test_get_most_recent() 2469 #test_episodes() 2470 test_format_as_journal() 2471 2472 # emr = cClinicalRecord(aPKey = 12) 2473 2474 # # Vacc regimes 2475 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus']) 2476 # print '\nVaccination regimes: ' 2477 # for a_regime in vacc_regimes: 2478 # pass 2479 # #print a_regime 2480 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10) 2481 # #print vacc_regime 2482 2483 # # vaccination regimes and vaccinations for regimes 2484 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus']) 2485 # print 'Vaccinations for the regime:' 2486 # for a_scheduled_vacc in scheduled_vaccs: 2487 # pass 2488 # #print ' %s' %(a_scheduled_vacc) 2489 2490 # # vaccination next shot and booster 2491 # vaccinations = emr.get_vaccinations() 2492 # for a_vacc in vaccinations: 2493 # 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']) 2494 2495 # # first and last encounters 2496 # first_encounter = emr.get_first_encounter(issue_id = 1) 2497 # print '\nFirst encounter: ' + str(first_encounter) 2498 # last_encounter = emr.get_last_encounter(episode_id = 1) 2499 # print '\nLast encounter: ' + str(last_encounter) 2500 # print '' 2501 2502 #dump = record.get_missing_vaccinations() 2503 #f = open('vaccs.lst', 'wb') 2504 #if dump is not None: 2505 # print "=== due ===" 2506 # f.write("=== due ===\n") 2507 # for row in dump['due']: 2508 # print row 2509 # f.write(repr(row)) 2510 # f.write('\n') 2511 # print "=== overdue ===" 2512 # f.write("=== overdue ===\n") 2513 # for row in dump['overdue']: 2514 # print row 2515 # f.write(repr(row)) 2516 # f.write('\n') 2517 #f.close() 2518