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  Make sure to call set_func_ask_user() and set_encounter_ttl() early on in 
   5  your code (before cClinicalRecord.__init__() is called for the first time). 
   6  """ 
   7  #============================================================ 
   8  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
   9  __license__ = "GPL v2 or later" 
  10   
  11  #=================================================== 
  12  # TODO 
  13  # Basically we'll probably have to: 
  14  # 
  15  # a) serialize access to re-getting data from the cache so 
  16  #   that later-but-concurrent cache accesses spin until 
  17  #   the first one completes the refetch from the database 
  18  # 
  19  # b) serialize access to the cache per-se such that cache 
  20  #    flushes vs. cache regets happen atomically (where 
  21  #    flushes would abort/restart current regets) 
  22  #=================================================== 
  23   
  24  # standard libs 
  25  import sys 
  26  import logging 
  27   
  28   
  29  if __name__ == '__main__': 
  30          sys.path.insert(0, '../../') 
  31          from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N 
  32          gmI18N.activate_locale() 
  33          gmI18N.install_domain() 
  34          gmDateTime.init() 
  35   
  36  from Gnumed.pycommon import gmExceptions 
  37  from Gnumed.pycommon import gmPG2 
  38  from Gnumed.pycommon import gmDispatcher 
  39  from Gnumed.pycommon import gmI18N 
  40  from Gnumed.pycommon import gmCfg 
  41  from Gnumed.pycommon import gmTools 
  42  from Gnumed.pycommon import gmDateTime 
  43   
  44  from Gnumed.business import gmAllergy 
  45  from Gnumed.business import gmPathLab 
  46  from Gnumed.business import gmLOINC 
  47  from Gnumed.business import gmClinNarrative 
  48  from Gnumed.business import gmEMRStructItems 
  49  from Gnumed.business import gmMedication 
  50  from Gnumed.business import gmVaccination 
  51  from Gnumed.business import gmFamilyHistory 
  52  from Gnumed.business.gmDemographicRecord import get_occupations 
  53   
  54   
  55  _log = logging.getLogger('gm.emr') 
  56   
  57  _me = None 
  58  _here = None 
  59  #============================================================ 
  60  # helper functions 
  61  #------------------------------------------------------------ 
  62  _func_ask_user = None 
  63   
64 -def set_func_ask_user(a_func = None):
65 if not callable(a_func): 66 _log.error('[%] not callable, not setting _func_ask_user', a_func) 67 return False 68 69 _log.debug('setting _func_ask_user to [%s]', a_func) 70 71 global _func_ask_user 72 _func_ask_user = a_func
73 74 #============================================================
75 -class cClinicalRecord(object):
76 77 _clin_root_item_children_union_query = None 78
79 - def __init__(self, aPKey=None, allow_user_interaction=True):
80 """Fails if 81 82 - no connection to database possible 83 - patient referenced by aPKey does not exist 84 """ 85 self.pk_patient = aPKey # == identity.pk == primary key 86 87 # log access to patient record (HIPAA, for example) 88 cmd = u'SELECT gm.log_access2emr(%(todo)s)' 89 args = {'todo': u'patient [%s]' % aPKey} 90 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 91 92 from Gnumed.business import gmPraxis, gmStaff 93 global _me 94 if _me is None: 95 _me = gmStaff.gmCurrentProvider() 96 global _here 97 if _here is None: 98 _here = gmPraxis.gmCurrentPraxisBranch() 99 100 # ........................................... 101 # this is a hack to speed up get_encounters() 102 clin_root_item_children = gmPG2.get_child_tables('clin', 'clin_root_item') 103 if cClinicalRecord._clin_root_item_children_union_query is None: 104 union_phrase = u""" 105 SELECT fk_encounter from 106 %s.%s cn 107 inner join 108 (SELECT pk FROM clin.episode ep WHERE ep.fk_health_issue in %%s) as epi 109 on (cn.fk_episode = epi.pk) 110 """ 111 cClinicalRecord._clin_root_item_children_union_query = u'union\n'.join ( 112 [ union_phrase % (child[0], child[1]) for child in clin_root_item_children ] 113 ) 114 # ........................................... 115 116 self.__db_cache = {} 117 118 # load current or create new encounter 119 if _func_ask_user is None: 120 _log.error('[_func_ask_user] is None') 121 print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__ 122 self.remove_empty_encounters() 123 self.__encounter = None 124 if not self.__initiate_active_encounter(allow_user_interaction = allow_user_interaction): 125 raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey 126 127 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter']) 128 129 # register backend notification interests 130 # (keep this last so we won't hang on threads when 131 # failing this constructor for other reasons ...) 132 if not self._register_interests(): 133 raise gmExceptions.ConstructorError, "cannot register signal interests" 134 135 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
136 #--------------------------------------------------------
137 - def __del__(self):
138 pass
139 #--------------------------------------------------------
140 - def cleanup(self):
141 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient) 142 return True
143 #-------------------------------------------------------- 144 # messaging 145 #--------------------------------------------------------
146 - def _register_interests(self):
147 gmDispatcher.connect(signal = u'clin.encounter_mod_db', receiver = self.db_callback_encounter_mod_db) 148 149 return True
150 #--------------------------------------------------------
151 - def db_callback_encounter_mod_db(self, **kwds):
152 153 # get the current encounter as an extra instance 154 # from the database to check for changes 155 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter']) 156 157 # the encounter just retrieved and the active encounter 158 # have got the same transaction ID so there's no change 159 # in the database, there could be a local change in 160 # the active encounter but that doesn't matter 161 # THIS DOES NOT WORK 162 # if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']: 163 # return True 164 165 # there must have been a change to the active encounter 166 # committed to the database from elsewhere, 167 # we must fail propagating the change, however, if 168 # there are local changes 169 if self.current_encounter.is_modified(): 170 _log.debug('unsaved changes in active encounter, cannot switch to another one') 171 raise ValueError('unsaved changes in active encounter, cannot switch to another one') 172 173 if self.current_encounter.same_payload(another_object = curr_enc_in_db): 174 _log.debug('clin.encounter_mod_db received but no change to active encounter payload') 175 return True 176 177 # there was a change in the database from elsewhere, 178 # locally, however, we don't have any changes, therefore 179 # we can propagate the remote change locally without 180 # losing anything 181 _log.debug('active encounter modified remotely, reloading and announcing the modification') 182 self.current_encounter.refetch_payload() 183 gmDispatcher.send(u'current_encounter_modified') 184 185 return True
186 #--------------------------------------------------------
187 - def db_callback_vaccs_modified(self, **kwds):
188 return True
189 #--------------------------------------------------------
190 - def _health_issues_modified(self):
191 try: 192 del self.__db_cache['health issues'] 193 except KeyError: 194 pass 195 return 1
196 #--------------------------------------------------------
198 # try: 199 # del self.__db_cache['episodes'] 200 # except KeyError: 201 # pass 202 return 1
203 #-------------------------------------------------------- 204 # API: family history 205 #--------------------------------------------------------
206 - def get_family_history(self, episodes=None, issues=None, encounters=None):
207 fhx = gmFamilyHistory.get_family_history ( 208 order_by = u'l10n_relation, condition', 209 patient = self.pk_patient 210 ) 211 212 if episodes is not None: 213 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx) 214 215 if issues is not None: 216 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx) 217 218 if encounters is not None: 219 fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx) 220 221 return fhx
222 #--------------------------------------------------------
223 - def add_family_history(self, episode=None, condition=None, relation=None):
224 return gmFamilyHistory.create_family_history ( 225 encounter = self.current_encounter['pk_encounter'], 226 episode = episode, 227 condition = condition, 228 relation = relation 229 )
230 #-------------------------------------------------------- 231 # API: performed procedures 232 #--------------------------------------------------------
233 - def get_performed_procedures(self, episodes=None, issues=None):
234 235 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient) 236 237 if episodes is not None: 238 procs = filter(lambda p: p['pk_episode'] in episodes, procs) 239 240 if issues is not None: 241 procs = filter(lambda p: p['pk_health_issue'] in issues, procs) 242 243 return procs
244 245 performed_procedures = property(get_performed_procedures, lambda x:x) 246 #-------------------------------------------------------- 249 #--------------------------------------------------------
250 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
251 return gmEMRStructItems.create_performed_procedure ( 252 encounter = self.current_encounter['pk_encounter'], 253 episode = episode, 254 location = location, 255 hospital_stay = hospital_stay, 256 procedure = procedure 257 )
258 #-------------------------------------------------------- 259 # API: hospitalizations 260 #--------------------------------------------------------
261 - def get_hospital_stays(self, episodes=None, issues=None, ongoing_only=False):
262 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient, ongoing_only = ongoing_only) 263 if episodes is not None: 264 stays = filter(lambda s: s['pk_episode'] in episodes, stays) 265 if issues is not None: 266 stays = filter(lambda s: s['pk_health_issue'] in issues, stays) 267 return stays
268 269 hospital_stays = property(get_hospital_stays, lambda x:x) 270 #--------------------------------------------------------
271 - def get_latest_hospital_stay(self):
273 #--------------------------------------------------------
274 - def add_hospital_stay(self, episode=None, fk_org_unit=None):
275 return gmEMRStructItems.create_hospital_stay ( 276 encounter = self.current_encounter['pk_encounter'], 277 episode = episode, 278 fk_org_unit = fk_org_unit 279 )
280 #--------------------------------------------------------
281 - def get_hospital_stay_stats_by_hospital(self, cover_period=None):
282 args = {'pat': self.pk_patient, 'range': cover_period} 283 where_parts = [u'pk_patient = %(pat)s'] 284 if cover_period is not None: 285 where_parts.append(u'discharge > (now() - %(range)s)') 286 287 cmd = u""" 288 SELECT hospital, count(1) AS frequency 289 FROM clin.v_hospital_stays 290 WHERE 291 %s 292 GROUP BY hospital 293 ORDER BY frequency DESC 294 """ % u' AND '.join(where_parts) 295 296 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 297 return rows
298 #-------------------------------------------------------- 299 # API: narrative 300 #--------------------------------------------------------
301 - def add_notes(self, notes=None, episode=None, encounter=None):
302 303 enc = gmTools.coalesce ( 304 encounter, 305 self.current_encounter['pk_encounter'] 306 ) 307 308 for note in notes: 309 success, data = gmClinNarrative.create_clin_narrative ( 310 narrative = note[1], 311 soap_cat = note[0], 312 episode_id = episode, 313 encounter_id = enc 314 ) 315 316 return True
317 #--------------------------------------------------------
318 - def add_clin_narrative(self, note='', soap_cat='s', episode=None):
319 if note.strip() == '': 320 _log.info('will not create empty clinical note') 321 return None 322 if isinstance(episode, gmEMRStructItems.cEpisode): 323 episode = episode['pk_episode'] 324 status, data = gmClinNarrative.create_clin_narrative ( 325 narrative = note, 326 soap_cat = soap_cat, 327 episode_id = episode, 328 encounter_id = self.current_encounter['pk_encounter'] 329 ) 330 if not status: 331 _log.error(str(data)) 332 return None 333 return data
334 #--------------------------------------------------------
335 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
336 """Get SOAP notes pertinent to this encounter. 337 338 since 339 - initial date for narrative items 340 until 341 - final date for narrative items 342 encounters 343 - list of encounters whose narrative are to be retrieved 344 episodes 345 - list of episodes whose narrative are to be retrieved 346 issues 347 - list of health issues whose narrative are to be retrieved 348 soap_cats 349 - list of SOAP categories of the narrative to be retrieved 350 """ 351 where_parts = [u'pk_patient = %(pat)s'] 352 args = {u'pat': self.pk_patient} 353 354 if issues is not None: 355 where_parts.append(u'pk_health_issue IN %(issues)s') 356 args['issues'] = tuple(issues) 357 358 if episodes is not None: 359 where_parts.append(u'pk_episode IN %(epis)s') 360 args['epis'] = tuple(episodes) 361 362 if encounters is not None: 363 where_parts.append(u'pk_encounter IN %(encs)s') 364 args['encs'] = tuple(encounters) 365 366 if soap_cats is not None: 367 where_parts.append(u'soap_cat IN %(cats)s') 368 soap_cats = list(soap_cats) 369 args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ] 370 if None in soap_cats: 371 args['cats'].append(None) 372 args['cats'] = tuple(args['cats']) 373 374 cmd = u""" 375 SELECT 376 c_vpn.*, 377 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = c_vpn.soap_cat) AS soap_rank 378 FROM clin.v_pat_narrative c_vpn 379 WHERE %s 380 ORDER BY date, soap_rank 381 """ % u' AND '.join(where_parts) 382 383 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 384 385 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ] 386 387 if since is not None: 388 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative) 389 390 if until is not None: 391 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative) 392 393 if providers is not None: 394 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative) 395 396 return filtered_narrative
397 #--------------------------------------------------------
398 - 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):
399 return gmClinNarrative.get_as_journal ( 400 patient = self.pk_patient, 401 since = since, 402 until = until, 403 encounters = encounters, 404 episodes = episodes, 405 issues = issues, 406 soap_cats = soap_cats, 407 providers = providers, 408 order_by = order_by, 409 time_range = time_range 410 )
411 #--------------------------------------------------------
412 - def search_narrative_simple(self, search_term=''):
413 414 search_term = search_term.strip() 415 if search_term == '': 416 return [] 417 418 cmd = u""" 419 SELECT 420 *, 421 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table) 422 as episode, 423 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table) 424 as health_issue, 425 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter) 426 as encounter_started, 427 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter) 428 as encounter_ended, 429 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter)) 430 as encounter_type 431 from clin.v_narrative4search vn4s 432 WHERE 433 pk_patient = %(pat)s and 434 vn4s.narrative ~ %(term)s 435 order by 436 encounter_started 437 """ # case sensitive 438 rows, idx = gmPG2.run_ro_queries(queries = [ 439 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}} 440 ]) 441 return rows
442 #--------------------------------------------------------
443 - def get_text_dump_old(self):
444 # don't know how to invalidate this by means of 445 # a notify without catching notifies from *all* 446 # child tables, the best solution would be if 447 # inserts in child tables would also fire triggers 448 # of ancestor tables, but oh well, 449 # until then the text dump will not be cached ... 450 try: 451 return self.__db_cache['text dump old'] 452 except KeyError: 453 pass 454 # not cached so go get it 455 fields = [ 456 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when", 457 'modified_by', 458 'clin_when', 459 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')), 460 'pk_item', 461 'pk_encounter', 462 'pk_episode', 463 'pk_health_issue', 464 'src_table' 465 ] 466 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % ', '.join(fields) 467 ro_conn = self._conn_pool.GetConnection('historica') 468 curs = ro_conn.cursor() 469 if not gmPG2.run_query(curs, None, cmd, self.pk_patient): 470 _log.error('cannot load item links for patient [%s]' % self.pk_patient) 471 curs.close() 472 return None 473 rows = curs.fetchall() 474 view_col_idx = gmPG2.get_col_indices(curs) 475 476 # aggregate by src_table for item retrieval 477 items_by_table = {} 478 for item in rows: 479 src_table = item[view_col_idx['src_table']] 480 pk_item = item[view_col_idx['pk_item']] 481 if not items_by_table.has_key(src_table): 482 items_by_table[src_table] = {} 483 items_by_table[src_table][pk_item] = item 484 485 # get mapping for issue/episode IDs 486 issues = self.get_health_issues() 487 issue_map = {} 488 for issue in issues: 489 issue_map[issue['pk']] = issue['description'] 490 episodes = self.get_episodes() 491 episode_map = {} 492 for episode in episodes: 493 episode_map[episode['pk_episode']] = episode['description'] 494 emr_data = {} 495 # get item data from all source tables 496 for src_table in items_by_table.keys(): 497 item_ids = items_by_table[src_table].keys() 498 # we don't know anything about the columns of 499 # the source tables but, hey, this is a dump 500 if len(item_ids) == 0: 501 _log.info('no items in table [%s] ?!?' % src_table) 502 continue 503 elif len(item_ids) == 1: 504 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table 505 if not gmPG2.run_query(curs, None, cmd, item_ids[0]): 506 _log.error('cannot load items from table [%s]' % src_table) 507 # skip this table 508 continue 509 elif len(item_ids) > 1: 510 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table 511 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)): 512 _log.error('cannot load items from table [%s]' % src_table) 513 # skip this table 514 continue 515 rows = curs.fetchall() 516 table_col_idx = gmPG.get_col_indices(curs) 517 # format per-table items 518 for row in rows: 519 # FIXME: make this get_pkey_name() 520 pk_item = row[table_col_idx['pk_item']] 521 view_row = items_by_table[src_table][pk_item] 522 age = view_row[view_col_idx['age']] 523 # format metadata 524 try: 525 episode_name = episode_map[view_row[view_col_idx['pk_episode']]] 526 except: 527 episode_name = view_row[view_col_idx['pk_episode']] 528 try: 529 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]] 530 except: 531 issue_name = view_row[view_col_idx['pk_health_issue']] 532 533 if not emr_data.has_key(age): 534 emr_data[age] = [] 535 536 emr_data[age].append( 537 _('%s: encounter (%s)') % ( 538 view_row[view_col_idx['clin_when']], 539 view_row[view_col_idx['pk_encounter']] 540 ) 541 ) 542 emr_data[age].append(_('health issue: %s') % issue_name) 543 emr_data[age].append(_('episode : %s') % episode_name) 544 # format table specific data columns 545 # - ignore those, they are metadata, some 546 # are in clin.v_pat_items data already 547 cols2ignore = [ 548 'pk_audit', 'row_version', 'modified_when', 'modified_by', 549 'pk_item', 'id', 'fk_encounter', 'fk_episode' 550 ] 551 col_data = [] 552 for col_name in table_col_idx.keys(): 553 if col_name in cols2ignore: 554 continue 555 emr_data[age].append("=> %s:" % col_name) 556 emr_data[age].append(row[table_col_idx[col_name]]) 557 emr_data[age].append("----------------------------------------------------") 558 emr_data[age].append("-- %s from table %s" % ( 559 view_row[view_col_idx['modified_string']], 560 src_table 561 )) 562 emr_data[age].append("-- written %s by %s" % ( 563 view_row[view_col_idx['modified_when']], 564 view_row[view_col_idx['modified_by']] 565 )) 566 emr_data[age].append("----------------------------------------------------") 567 curs.close() 568 self._conn_pool.ReleaseConnection('historica') 569 return emr_data
570 #--------------------------------------------------------
571 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
572 # don't know how to invalidate this by means of 573 # a notify without catching notifies from *all* 574 # child tables, the best solution would be if 575 # inserts in child tables would also fire triggers 576 # of ancestor tables, but oh well, 577 # until then the text dump will not be cached ... 578 try: 579 return self.__db_cache['text dump'] 580 except KeyError: 581 pass 582 # not cached so go get it 583 # -- get the data -- 584 fields = [ 585 'age', 586 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when", 587 'modified_by', 588 'clin_when', 589 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')), 590 'pk_item', 591 'pk_encounter', 592 'pk_episode', 593 'pk_health_issue', 594 'src_table' 595 ] 596 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields) 597 # handle constraint conditions 598 where_snippets = [] 599 params = {} 600 where_snippets.append('pk_patient=%(pat_id)s') 601 params['pat_id'] = self.pk_patient 602 if not since is None: 603 where_snippets.append('clin_when >= %(since)s') 604 params['since'] = since 605 if not until is None: 606 where_snippets.append('clin_when <= %(until)s') 607 params['until'] = until 608 # FIXME: these are interrelated, eg if we constrain encounter 609 # we automatically constrain issue/episode, so handle that, 610 # encounters 611 if not encounters is None and len(encounters) > 0: 612 params['enc'] = encounters 613 if len(encounters) > 1: 614 where_snippets.append('fk_encounter in %(enc)s') 615 else: 616 where_snippets.append('fk_encounter=%(enc)s') 617 # episodes 618 if not episodes is None and len(episodes) > 0: 619 params['epi'] = episodes 620 if len(episodes) > 1: 621 where_snippets.append('fk_episode in %(epi)s') 622 else: 623 where_snippets.append('fk_episode=%(epi)s') 624 # health issues 625 if not issues is None and len(issues) > 0: 626 params['issue'] = issues 627 if len(issues) > 1: 628 where_snippets.append('fk_health_issue in %(issue)s') 629 else: 630 where_snippets.append('fk_health_issue=%(issue)s') 631 632 where_clause = ' and '.join(where_snippets) 633 order_by = 'order by src_table, age' 634 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by) 635 636 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params) 637 if rows is None: 638 _log.error('cannot load item links for patient [%s]' % self.pk_patient) 639 return None 640 641 # -- sort the data -- 642 # FIXME: by issue/encounter/episode, eg formatting 643 # aggregate by src_table for item retrieval 644 items_by_table = {} 645 for item in rows: 646 src_table = item[view_col_idx['src_table']] 647 pk_item = item[view_col_idx['pk_item']] 648 if not items_by_table.has_key(src_table): 649 items_by_table[src_table] = {} 650 items_by_table[src_table][pk_item] = item 651 652 # get mapping for issue/episode IDs 653 issues = self.get_health_issues() 654 issue_map = {} 655 for issue in issues: 656 issue_map[issue['pk_health_issue']] = issue['description'] 657 episodes = self.get_episodes() 658 episode_map = {} 659 for episode in episodes: 660 episode_map[episode['pk_episode']] = episode['description'] 661 emr_data = {} 662 # get item data from all source tables 663 ro_conn = self._conn_pool.GetConnection('historica') 664 curs = ro_conn.cursor() 665 for src_table in items_by_table.keys(): 666 item_ids = items_by_table[src_table].keys() 667 # we don't know anything about the columns of 668 # the source tables but, hey, this is a dump 669 if len(item_ids) == 0: 670 _log.info('no items in table [%s] ?!?' % src_table) 671 continue 672 elif len(item_ids) == 1: 673 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table 674 if not gmPG.run_query(curs, None, cmd, item_ids[0]): 675 _log.error('cannot load items from table [%s]' % src_table) 676 # skip this table 677 continue 678 elif len(item_ids) > 1: 679 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table 680 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)): 681 _log.error('cannot load items from table [%s]' % src_table) 682 # skip this table 683 continue 684 rows = curs.fetchall() 685 table_col_idx = gmPG.get_col_indices(curs) 686 # format per-table items 687 for row in rows: 688 # FIXME: make this get_pkey_name() 689 pk_item = row[table_col_idx['pk_item']] 690 view_row = items_by_table[src_table][pk_item] 691 age = view_row[view_col_idx['age']] 692 # format metadata 693 try: 694 episode_name = episode_map[view_row[view_col_idx['pk_episode']]] 695 except: 696 episode_name = view_row[view_col_idx['pk_episode']] 697 try: 698 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]] 699 except: 700 issue_name = view_row[view_col_idx['pk_health_issue']] 701 702 if not emr_data.has_key(age): 703 emr_data[age] = [] 704 705 emr_data[age].append( 706 _('%s: encounter (%s)') % ( 707 view_row[view_col_idx['clin_when']], 708 view_row[view_col_idx['pk_encounter']] 709 ) 710 ) 711 emr_data[age].append(_('health issue: %s') % issue_name) 712 emr_data[age].append(_('episode : %s') % episode_name) 713 # format table specific data columns 714 # - ignore those, they are metadata, some 715 # are in clin.v_pat_items data already 716 cols2ignore = [ 717 'pk_audit', 'row_version', 'modified_when', 'modified_by', 718 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk' 719 ] 720 col_data = [] 721 for col_name in table_col_idx.keys(): 722 if col_name in cols2ignore: 723 continue 724 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]])) 725 emr_data[age].append("----------------------------------------------------") 726 emr_data[age].append("-- %s from table %s" % ( 727 view_row[view_col_idx['modified_string']], 728 src_table 729 )) 730 emr_data[age].append("-- written %s by %s" % ( 731 view_row[view_col_idx['modified_when']], 732 view_row[view_col_idx['modified_by']] 733 )) 734 emr_data[age].append("----------------------------------------------------") 735 curs.close() 736 return emr_data
737 #--------------------------------------------------------
738 - def get_patient_ID(self):
739 return self.pk_patient
740 #--------------------------------------------------------
741 - def get_statistics(self):
742 union_query = u'\n union all\n'.join ([ 743 u""" 744 SELECT (( 745 -- all relevant health issues + active episodes WITH health issue 746 SELECT COUNT(1) 747 FROM clin.v_problem_list 748 WHERE 749 pk_patient = %(pat)s 750 AND 751 pk_health_issue is not null 752 ) + ( 753 -- active episodes WITHOUT health issue 754 SELECT COUNT(1) 755 FROM clin.v_problem_list 756 WHERE 757 pk_patient = %(pat)s 758 AND 759 pk_health_issue is null 760 ))""", 761 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s', 762 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s', 763 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s', 764 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s', 765 u'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s', 766 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s', 767 # active and approved substances == medication 768 u""" 769 SELECT count(1) 770 from clin.v_substance_intakes 771 WHERE 772 pk_patient = %(pat)s 773 and is_currently_active in (null, true) 774 and intake_is_approved_of in (null, true)""", 775 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s' 776 ]) 777 778 rows, idx = gmPG2.run_ro_queries ( 779 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}], 780 get_col_idx = False 781 ) 782 783 stats = dict ( 784 problems = rows[0][0], 785 encounters = rows[1][0], 786 items = rows[2][0], 787 documents = rows[3][0], 788 results = rows[4][0], 789 stays = rows[5][0], 790 procedures = rows[6][0], 791 active_drugs = rows[7][0], 792 vaccinations = rows[8][0] 793 ) 794 795 return stats
796 #--------------------------------------------------------
797 - def format_statistics(self):
798 return _( 799 'Medical problems: %(problems)s\n' 800 'Total encounters: %(encounters)s\n' 801 'Total EMR entries: %(items)s\n' 802 'Active medications: %(active_drugs)s\n' 803 'Documents: %(documents)s\n' 804 'Test results: %(results)s\n' 805 'Hospitalizations: %(stays)s\n' 806 'Procedures: %(procedures)s\n' 807 'Vaccinations: %(vaccinations)s' 808 ) % self.get_statistics()
809 #--------------------------------------------------------
810 - def format_summary(self, dob=None):
811 812 stats = self.get_statistics() 813 first = self.get_first_encounter() 814 last = self.get_last_encounter() 815 probs = self.get_problems() 816 817 txt = u'' 818 if len(probs) > 0: 819 txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems'] 820 else: 821 txt += _(' %s known problems\n') % stats['problems'] 822 for prob in probs: 823 if not prob['clinically_relevant']: 824 continue 825 txt += u' \u00BB%s\u00AB (%s)\n' % ( 826 prob['problem'], 827 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive')) 828 ) 829 txt += u'\n' 830 txt += _(' %s encounters from %s to %s\n') % ( 831 stats['encounters'], 832 gmDateTime.pydt_strftime(first['started'], '%Y %b %d'), 833 gmDateTime.pydt_strftime(last['started'], '%Y %b %d') 834 ) 835 txt += _(' %s active medications\n') % stats['active_drugs'] 836 txt += _(' %s documents\n') % stats['documents'] 837 txt += _(' %s test results\n') % stats['results'] 838 txt += _(' %s hospitalizations') % stats['stays'] 839 if stats['stays'] == 0: 840 txt += u'\n' 841 else: 842 txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3) 843 # FIXME: perhaps only count "ongoing ones" 844 txt += _(' %s performed procedures') % stats['procedures'] 845 if stats['procedures'] == 0: 846 txt += u'\n' 847 else: 848 txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3) 849 850 txt += u'\n' 851 txt += _('Allergies and Intolerances\n') 852 853 allg_state = self.allergy_state 854 txt += (u' ' + allg_state.state_string) 855 if allg_state['last_confirmed'] is not None: 856 txt += _(' (last confirmed %s)') % gmDateTime.pydt_strftime(allg_state['last_confirmed'], '%Y %b %d') 857 txt += u'\n' 858 txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n') 859 for allg in self.get_allergies(): 860 txt += u' %s: %s\n' % ( 861 allg['descriptor'], 862 gmTools.coalesce(allg['reaction'], _('unknown reaction')) 863 ) 864 865 txt += u'\n' 866 txt += _('Family History') 867 txt += u'\n' 868 fhx = self.get_family_history() 869 for f in fhx: 870 txt += u'%s\n' % f.format(left_margin = 1) 871 872 txt += u'\n' 873 txt += _('Occupations') 874 txt += u'\n' 875 jobs = get_occupations(pk_identity = self.pk_patient) 876 for job in jobs: 877 txt += u' %s%s\n' % ( 878 job['l10n_occupation'], 879 gmTools.coalesce(job['activities'], u'', u': %s') 880 ) 881 882 txt += u'\n' 883 txt += _('Vaccinations') 884 txt += u'\n' 885 vaccs = self.get_latest_vaccinations() 886 inds = sorted(vaccs.keys()) 887 for ind in inds: 888 ind_count, vacc = vaccs[ind] 889 if dob is None: 890 age_given = u'' 891 else: 892 age_given = u' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age ( 893 start = dob, 894 end = vacc['date_given'] 895 )) 896 since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given']) 897 txt += u' %s (%s%s): %s%s (%s %s%s%s)\n' % ( 898 ind, 899 gmTools.u_sum, 900 ind_count, 901 #gmDateTime.pydt_strftime(vacc['date_given'], '%b %Y'), 902 since, 903 age_given, 904 vacc['vaccine'], 905 gmTools.u_left_double_angle_quote, 906 vacc['batch_no'], 907 gmTools.u_right_double_angle_quote 908 ) 909 910 return txt
911 #--------------------------------------------------------
912 - def format_as_journal(self, left_margin=0, patient=None):
913 txt = u'' 914 for enc in self.get_encounters(skip_empty = True): 915 txt += gmTools.u_box_horiz_4dashes * 70 + u'\n' 916 txt += enc.format ( 917 episodes = None, # means: each touched upon 918 left_margin = left_margin, 919 patient = patient, 920 fancy_header = False, 921 with_soap = True, 922 with_docs = True, 923 with_tests = True, 924 with_vaccinations = True, 925 with_co_encountlet_hints = False, # irrelevant 926 with_rfe_aoe = True, 927 with_family_history = True, 928 by_episode = True 929 ) 930 931 return txt
932 #-------------------------------------------------------- 933 # API: allergy 934 #--------------------------------------------------------
935 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
936 """Retrieves patient allergy items. 937 938 remove_sensitivities 939 - retrieve real allergies only, without sensitivities 940 since 941 - initial date for allergy items 942 until 943 - final date for allergy items 944 encounters 945 - list of encounters whose allergies are to be retrieved 946 episodes 947 - list of episodes whose allergies are to be retrieved 948 issues 949 - list of health issues whose allergies are to be retrieved 950 """ 951 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor" 952 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True) 953 allergies = [] 954 for r in rows: 955 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'})) 956 957 # ok, let's constrain our list 958 filtered_allergies = [] 959 filtered_allergies.extend(allergies) 960 961 if ID_list is not None: 962 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies) 963 if len(filtered_allergies) == 0: 964 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient)) 965 # better fail here contrary to what we do elsewhere 966 return None 967 else: 968 return filtered_allergies 969 970 if remove_sensitivities: 971 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies) 972 if since is not None: 973 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies) 974 if until is not None: 975 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies) 976 if issues is not None: 977 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies) 978 if episodes is not None: 979 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies) 980 if encounters is not None: 981 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies) 982 983 return filtered_allergies
984 #--------------------------------------------------------
985 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
986 if encounter_id is None: 987 encounter_id = self.current_encounter['pk_encounter'] 988 989 if episode_id is None: 990 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances')) 991 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue']) 992 episode_id = epi['pk_episode'] 993 994 new_allergy = gmAllergy.create_allergy ( 995 allergene = allergene, 996 allg_type = allg_type, 997 encounter_id = encounter_id, 998 episode_id = episode_id 999 ) 1000 1001 return new_allergy
1002 #--------------------------------------------------------
1003 - def delete_allergy(self, pk_allergy=None):
1004 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s' 1005 args = {'pk_allg': pk_allergy} 1006 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1007 #--------------------------------------------------------
1008 - def is_allergic_to(self, atcs=None, inns=None, brand=None):
1009 """Cave: only use with one potential allergic agent 1010 otherwise you won't know which of the agents the allergy is to.""" 1011 1012 # we don't know the state 1013 if self.allergy_state is None: 1014 return None 1015 1016 # we know there's no allergies 1017 if self.allergy_state == 0: 1018 return False 1019 1020 args = { 1021 'atcs': atcs, 1022 'inns': inns, 1023 'brand': brand, 1024 'pat': self.pk_patient 1025 } 1026 allergenes = [] 1027 where_parts = [] 1028 1029 if len(atcs) == 0: 1030 atcs = None 1031 if atcs is not None: 1032 where_parts.append(u'atc_code in %(atcs)s') 1033 if len(inns) == 0: 1034 inns = None 1035 if inns is not None: 1036 where_parts.append(u'generics in %(inns)s') 1037 allergenes.extend(inns) 1038 if brand is not None: 1039 where_parts.append(u'substance = %(brand)s') 1040 allergenes.append(brand) 1041 1042 if len(allergenes) != 0: 1043 where_parts.append(u'allergene in %(allgs)s') 1044 args['allgs'] = tuple(allergenes) 1045 1046 cmd = u""" 1047 SELECT * FROM clin.v_pat_allergies 1048 WHERE 1049 pk_patient = %%(pat)s 1050 AND ( %s )""" % u' OR '.join(where_parts) 1051 1052 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1053 1054 if len(rows) == 0: 1055 return False 1056 1057 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1058 #--------------------------------------------------------
1059 - def _set_allergy_state(self, state):
1060 1061 if state not in gmAllergy.allergy_states: 1062 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states)) 1063 1064 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter']) 1065 allg_state['has_allergy'] = state 1066 allg_state.save_payload() 1067 return True
1068
1069 - def _get_allergy_state(self):
1070 return gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
1071 1072 allergy_state = property(_get_allergy_state, _set_allergy_state) 1073 #-------------------------------------------------------- 1074 # API: episodes 1075 #--------------------------------------------------------
1076 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1077 """Fetches from backend patient episodes. 1078 1079 id_list - Episodes' PKs list 1080 issues - Health issues' PKs list to filter episodes by 1081 open_status - return all (None) episodes, only open (True) or closed (False) one(s) 1082 """ 1083 if (unlinked_only is True) and (issues is not None): 1084 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None') 1085 1086 if order_by is None: 1087 order_by = u'' 1088 else: 1089 order_by = u'ORDER BY %s' % order_by 1090 1091 args = { 1092 'pat': self.pk_patient, 1093 'open': open_status 1094 } 1095 where_parts = [u'pk_patient = %(pat)s'] 1096 1097 if open_status is not None: 1098 where_parts.append(u'episode_open IS %(open)s') 1099 1100 if unlinked_only: 1101 where_parts.append(u'pk_health_issue is NULL') 1102 1103 if issues is not None: 1104 where_parts.append(u'pk_health_issue IN %(issues)s') 1105 args['issues'] = tuple(issues) 1106 1107 if id_list is not None: 1108 where_parts.append(u'pk_episode IN %(epis)s') 1109 args['epis'] = tuple(id_list) 1110 1111 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % ( 1112 u' AND '.join(where_parts), 1113 order_by 1114 ) 1115 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1116 1117 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1118 1119 episodes = property(get_episodes, lambda x:x) 1120 #------------------------------------------------------------------
1121 - def get_unlinked_episodes(self, open_status=None, order_by=None):
1122 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1123 1124 unlinked_episodes = property(get_unlinked_episodes, lambda x:x) 1125 #------------------------------------------------------------------
1126 - def get_episodes_by_encounter(self, pk_encounter=None):
1127 cmd = u"""SELECT distinct pk_episode 1128 from clin.v_pat_items 1129 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s""" 1130 args = { 1131 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']), 1132 'pat': self.pk_patient 1133 } 1134 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1135 if len(rows) == 0: 1136 return [] 1137 epis = [] 1138 for row in rows: 1139 epis.append(row[0]) 1140 return self.get_episodes(id_list=epis)
1141 #------------------------------------------------------------------
1142 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1143 """Add episode 'episode_name' for a patient's health issue. 1144 1145 - silently returns if episode already exists 1146 """ 1147 episode = gmEMRStructItems.create_episode ( 1148 pk_health_issue = pk_health_issue, 1149 episode_name = episode_name, 1150 is_open = is_open, 1151 encounter = self.current_encounter['pk_encounter'] 1152 ) 1153 return episode
1154 #--------------------------------------------------------
1155 - def get_most_recent_episode(self, issue=None):
1156 # try to find the episode with the most recently modified clinical item 1157 1158 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s') 1159 1160 cmd = u""" 1161 SELECT pk 1162 from clin.episode 1163 WHERE pk = ( 1164 SELECT distinct on(pk_episode) pk_episode 1165 from clin.v_pat_items 1166 WHERE 1167 pk_patient = %%(pat)s 1168 and 1169 modified_when = ( 1170 SELECT max(vpi.modified_when) 1171 from clin.v_pat_items vpi 1172 WHERE vpi.pk_patient = %%(pat)s 1173 ) 1174 %s 1175 -- guard against several episodes created at the same moment of time 1176 limit 1 1177 )""" % issue_where 1178 rows, idx = gmPG2.run_ro_queries(queries = [ 1179 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}} 1180 ]) 1181 if len(rows) != 0: 1182 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0]) 1183 1184 # no clinical items recorded, so try to find 1185 # the youngest episode for this patient 1186 cmd = u""" 1187 SELECT vpe0.pk_episode 1188 from 1189 clin.v_pat_episodes vpe0 1190 WHERE 1191 vpe0.pk_patient = %%(pat)s 1192 and 1193 vpe0.episode_modified_when = ( 1194 SELECT max(vpe1.episode_modified_when) 1195 from clin.v_pat_episodes vpe1 1196 WHERE vpe1.pk_episode = vpe0.pk_episode 1197 ) 1198 %s""" % issue_where 1199 rows, idx = gmPG2.run_ro_queries(queries = [ 1200 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}} 1201 ]) 1202 if len(rows) != 0: 1203 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0]) 1204 1205 return None
1206 #--------------------------------------------------------
1207 - def episode2problem(self, episode=None):
1208 return gmEMRStructItems.episode2problem(episode=episode)
1209 #-------------------------------------------------------- 1210 # API: problems 1211 #--------------------------------------------------------
1212 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1213 """Retrieve a patient's problems. 1214 1215 "Problems" are the UNION of: 1216 1217 - issues which are .clinically_relevant 1218 - episodes which are .is_open 1219 1220 Therefore, both an issue and the open episode 1221 thereof can each be listed as a problem. 1222 1223 include_closed_episodes/include_irrelevant_issues will 1224 include those -- which departs from the definition of 1225 the problem list being "active" items only ... 1226 1227 episodes - episodes' PKs to filter problems by 1228 issues - health issues' PKs to filter problems by 1229 """ 1230 # FIXME: this could use a good measure of streamlining, probably 1231 1232 args = {'pat': self.pk_patient} 1233 1234 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem""" 1235 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1236 1237 # Instantiate problem items 1238 problems = [] 1239 for row in rows: 1240 pk_args = { 1241 u'pk_patient': self.pk_patient, 1242 u'pk_health_issue': row['pk_health_issue'], 1243 u'pk_episode': row['pk_episode'] 1244 } 1245 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False)) 1246 1247 # include non-problems ? 1248 other_rows = [] 1249 if include_closed_episodes: 1250 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'""" 1251 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1252 other_rows.extend(rows) 1253 1254 if include_irrelevant_issues: 1255 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'""" 1256 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1257 other_rows.extend(rows) 1258 1259 if len(other_rows) > 0: 1260 for row in other_rows: 1261 pk_args = { 1262 u'pk_patient': self.pk_patient, 1263 u'pk_health_issue': row['pk_health_issue'], 1264 u'pk_episode': row['pk_episode'] 1265 } 1266 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True)) 1267 1268 # filter ? 1269 if (episodes is None) and (issues is None): 1270 return problems 1271 1272 # filter 1273 if issues is not None: 1274 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems) 1275 if episodes is not None: 1276 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems) 1277 1278 return problems
1279 #--------------------------------------------------------
1280 - def problem2episode(self, problem=None):
1281 return gmEMRStructItems.problem2episode(problem = problem)
1282 #--------------------------------------------------------
1283 - def problem2issue(self, problem=None):
1284 return gmEMRStructItems.problem2issue(problem = problem)
1285 #--------------------------------------------------------
1286 - def reclass_problem(self, problem):
1287 return gmEMRStructItems.reclass_problem(problem = problem)
1288 #-------------------------------------------------------- 1289 # API: health issues 1290 #--------------------------------------------------------
1291 - def get_health_issues(self, id_list = None):
1292 1293 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description" 1294 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True) 1295 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ] 1296 1297 if id_list is None: 1298 return issues 1299 1300 if len(id_list) == 0: 1301 raise ValueError('id_list to filter by is empty, most likely a programming error') 1302 1303 filtered_issues = [] 1304 for issue in issues: 1305 if issue['pk_health_issue'] in id_list: 1306 filtered_issues.append(issue) 1307 1308 return filtered_issues
1309 1310 health_issues = property(get_health_issues, lambda x:x) 1311 #------------------------------------------------------------------
1312 - def add_health_issue(self, issue_name=None):
1313 """Adds patient health issue.""" 1314 return gmEMRStructItems.create_health_issue ( 1315 description = issue_name, 1316 encounter = self.current_encounter['pk_encounter'], 1317 patient = self.pk_patient 1318 )
1319 #--------------------------------------------------------
1320 - def health_issue2problem(self, issue=None):
1321 return gmEMRStructItems.health_issue2problem(issue = issue)
1322 #-------------------------------------------------------- 1323 # API: substance intake 1324 #--------------------------------------------------------
1325 - def get_current_substance_intakes(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1326 1327 where_parts = [u'pk_patient = %(pat)s'] 1328 args = {'pat': self.pk_patient} 1329 1330 if not include_inactive: 1331 where_parts.append(u'is_currently_active IN (true, null)') 1332 1333 if not include_unapproved: 1334 where_parts.append(u'intake_is_approved_of IN (true, null)') 1335 1336 if order_by is None: 1337 order_by = u'' 1338 else: 1339 order_by = u'ORDER BY %s' % order_by 1340 1341 cmd = u"SELECT * FROM clin.v_substance_intakes WHERE %s %s" % ( 1342 u'\nAND '.join(where_parts), 1343 order_by 1344 ) 1345 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1346 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ] 1347 1348 if episodes is not None: 1349 meds = filter(lambda s: s['pk_episode'] in episodes, meds) 1350 1351 if issues is not None: 1352 meds = filter(lambda s: s['pk_health_issue'] in issues, meds) 1353 1354 return meds
1355 #--------------------------------------------------------
1356 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1357 return gmMedication.create_substance_intake ( 1358 pk_substance = pk_substance, 1359 pk_component = pk_component, 1360 encounter = self.current_encounter['pk_encounter'], 1361 episode = episode, 1362 preparation = preparation 1363 )
1364 #--------------------------------------------------------
1365 - def substance_intake_exists(self, pk_component=None, pk_substance=None):
1366 return gmMedication.substance_intake_exists ( 1367 pk_component = pk_component, 1368 pk_substance = pk_substance, 1369 pk_identity = self.pk_patient 1370 )
1371 #-------------------------------------------------------- 1372 # API: vaccinations 1373 #--------------------------------------------------------
1374 - def add_vaccination(self, episode=None, vaccine=None, batch_no=None):
1375 return gmVaccination.create_vaccination ( 1376 encounter = self.current_encounter['pk_encounter'], 1377 episode = episode, 1378 vaccine = vaccine, 1379 batch_no = batch_no 1380 )
1381 #--------------------------------------------------------
1382 - def get_latest_vaccinations(self, episodes=None, issues=None):
1383 """Returns latest given vaccination for each vaccinated indication. 1384 1385 as a dict {'l10n_indication': cVaccination instance} 1386 1387 Note that this will produce duplicate vaccination instances on combi-indication vaccines ! 1388 """ 1389 # find the PKs 1390 args = {'pat': self.pk_patient} 1391 where_parts = [u'pk_patient = %(pat)s'] 1392 1393 if (episodes is not None) and (len(episodes) > 0): 1394 where_parts.append(u'pk_episode IN %(epis)s') 1395 args['epis'] = tuple(episodes) 1396 1397 if (issues is not None) and (len(issues) > 0): 1398 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)') 1399 args['issues'] = tuple(issues) 1400 1401 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts) 1402 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1403 1404 # none found 1405 if len(rows) == 0: 1406 return {} 1407 1408 vpks = [ ind['pk_vaccination'] for ind in rows ] 1409 vinds = [ ind['l10n_indication'] for ind in rows ] 1410 ind_counts = [ ind['indication_count'] for ind in rows ] 1411 1412 # turn them into vaccinations 1413 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s' 1414 args = {'pks': tuple(vpks)} 1415 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1416 1417 vaccs = {} 1418 for idx in range(len(vpks)): 1419 pk = vpks[idx] 1420 ind_count = ind_counts[idx] 1421 for r in rows: 1422 if r['pk_vaccination'] == pk: 1423 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'})) 1424 1425 return vaccs
1426 #--------------------------------------------------------
1427 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1428 1429 args = {'pat': self.pk_patient} 1430 where_parts = [u'pk_patient = %(pat)s'] 1431 1432 if order_by is None: 1433 order_by = u'' 1434 else: 1435 order_by = u'ORDER BY %s' % order_by 1436 1437 if (episodes is not None) and (len(episodes) > 0): 1438 where_parts.append(u'pk_episode IN %(epis)s') 1439 args['epis'] = tuple(episodes) 1440 1441 if (issues is not None) and (len(issues) > 0): 1442 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)') 1443 args['issues'] = tuple(issues) 1444 1445 if (encounters is not None) and (len(encounters) > 0): 1446 where_parts.append(u'pk_encounter IN %(encs)s') 1447 args['encs'] = tuple(encounters) 1448 1449 cmd = u'%s %s' % ( 1450 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts), 1451 order_by 1452 ) 1453 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1454 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ] 1455 1456 return vaccs
1457 1458 vaccinations = property(get_vaccinations, lambda x:x) 1459 #-------------------------------------------------------- 1460 # old/obsolete: 1461 #--------------------------------------------------------
1462 - def get_scheduled_vaccination_regimes(self, ID=None, indications=None):
1463 """Retrieves vaccination regimes the patient is on. 1464 1465 optional: 1466 * ID - PK of the vaccination regime 1467 * indications - indications we want to retrieve vaccination 1468 regimes for, must be primary language, not l10n_indication 1469 """ 1470 # FIXME: use course, not regime 1471 try: 1472 self.__db_cache['vaccinations']['scheduled regimes'] 1473 except KeyError: 1474 # retrieve vaccination regimes definitions 1475 self.__db_cache['vaccinations']['scheduled regimes'] = [] 1476 cmd = """SELECT distinct on(pk_course) pk_course 1477 FROM clin.v_vaccs_scheduled4pat 1478 WHERE pk_patient=%s""" 1479 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1480 if rows is None: 1481 _log.error('cannot retrieve scheduled vaccination courses') 1482 del self.__db_cache['vaccinations']['scheduled regimes'] 1483 return None 1484 # Instantiate vaccination items and keep cache 1485 for row in rows: 1486 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0])) 1487 1488 # ok, let's constrain our list 1489 filtered_regimes = [] 1490 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes']) 1491 if ID is not None: 1492 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes) 1493 if len(filtered_regimes) == 0: 1494 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient)) 1495 return [] 1496 else: 1497 return filtered_regimes[0] 1498 if indications is not None: 1499 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes) 1500 1501 return filtered_regimes
1502 #-------------------------------------------------------- 1503 # def get_vaccinated_indications(self): 1504 # """Retrieves patient vaccinated indications list. 1505 # 1506 # Note that this does NOT rely on the patient being on 1507 # some schedule or other but rather works with what the 1508 # patient has ACTUALLY been vaccinated against. This is 1509 # deliberate ! 1510 # """ 1511 # # most likely, vaccinations will be fetched close 1512 # # by so it makes sense to count on the cache being 1513 # # filled (or fill it for nearby use) 1514 # vaccinations = self.get_vaccinations() 1515 # if vaccinations is None: 1516 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient) 1517 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]]) 1518 # if len(vaccinations) == 0: 1519 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]]) 1520 # v_indications = [] 1521 # for vacc in vaccinations: 1522 # tmp = [vacc['indication'], vacc['l10n_indication']] 1523 # # remove duplicates 1524 # if tmp in v_indications: 1525 # continue 1526 # v_indications.append(tmp) 1527 # return (True, v_indications) 1528 #--------------------------------------------------------
1529 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1530 """Retrieves list of vaccinations the patient has received. 1531 1532 optional: 1533 * ID - PK of a vaccination 1534 * indications - indications we want to retrieve vaccination 1535 items for, must be primary language, not l10n_indication 1536 * since - initial date for allergy items 1537 * until - final date for allergy items 1538 * encounters - list of encounters whose allergies are to be retrieved 1539 * episodes - list of episodes whose allergies are to be retrieved 1540 * issues - list of health issues whose allergies are to be retrieved 1541 """ 1542 try: 1543 self.__db_cache['vaccinations']['vaccinated'] 1544 except KeyError: 1545 self.__db_cache['vaccinations']['vaccinated'] = [] 1546 # Important fetch ordering by indication, date to know if a vaccination is booster 1547 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication 1548 WHERE pk_patient=%s 1549 order by indication, date""" 1550 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient) 1551 if rows is None: 1552 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient) 1553 del self.__db_cache['vaccinations']['vaccinated'] 1554 return None 1555 # Instantiate vaccination items 1556 vaccs_by_ind = {} 1557 for row in rows: 1558 vacc_row = { 1559 'pk_field': 'pk_vaccination', 1560 'idx': idx, 1561 'data': row 1562 } 1563 vacc = gmVaccination.cVaccination(row=vacc_row) 1564 self.__db_cache['vaccinations']['vaccinated'].append(vacc) 1565 # keep them, ordered by indication 1566 try: 1567 vaccs_by_ind[vacc['indication']].append(vacc) 1568 except KeyError: 1569 vaccs_by_ind[vacc['indication']] = [vacc] 1570 1571 # calculate sequence number and is_booster 1572 for ind in vaccs_by_ind.keys(): 1573 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind]) 1574 for vacc in vaccs_by_ind[ind]: 1575 # due to the "order by indication, date" the vaccinations are in the 1576 # right temporal order inside the indication-keyed dicts 1577 seq_no = vaccs_by_ind[ind].index(vacc) + 1 1578 vacc['seq_no'] = seq_no 1579 # if no active schedule for indication we cannot 1580 # check for booster status (eg. seq_no > max_shot) 1581 if (vacc_regimes is None) or (len(vacc_regimes) == 0): 1582 continue 1583 if seq_no > vacc_regimes[0]['shots']: 1584 vacc['is_booster'] = True 1585 del vaccs_by_ind 1586 1587 # ok, let's constrain our list 1588 filtered_shots = [] 1589 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated']) 1590 if ID is not None: 1591 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots) 1592 if len(filtered_shots) == 0: 1593 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient)) 1594 return None 1595 else: 1596 return filtered_shots[0] 1597 if since is not None: 1598 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots) 1599 if until is not None: 1600 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots) 1601 if issues is not None: 1602 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots) 1603 if episodes is not None: 1604 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots) 1605 if encounters is not None: 1606 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots) 1607 if indications is not None: 1608 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots) 1609 return filtered_shots
1610 #--------------------------------------------------------
1611 - def get_scheduled_vaccinations(self, indications=None):
1612 """Retrieves vaccinations scheduled for a regime a patient is on. 1613 1614 The regime is referenced by its indication (not l10n) 1615 1616 * indications - List of indications (not l10n) of regimes we want scheduled 1617 vaccinations to be fetched for 1618 """ 1619 try: 1620 self.__db_cache['vaccinations']['scheduled'] 1621 except KeyError: 1622 self.__db_cache['vaccinations']['scheduled'] = [] 1623 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s""" 1624 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient) 1625 if rows is None: 1626 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient) 1627 del self.__db_cache['vaccinations']['scheduled'] 1628 return None 1629 # Instantiate vaccination items 1630 for row in rows: 1631 vacc_row = { 1632 'pk_field': 'pk_vacc_def', 1633 'idx': idx, 1634 'data': row 1635 } 1636 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row)) 1637 1638 # ok, let's constrain our list 1639 if indications is None: 1640 return self.__db_cache['vaccinations']['scheduled'] 1641 filtered_shots = [] 1642 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled']) 1643 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots) 1644 return filtered_shots
1645 #--------------------------------------------------------
1646 - def get_missing_vaccinations(self, indications=None):
1647 try: 1648 self.__db_cache['vaccinations']['missing'] 1649 except KeyError: 1650 self.__db_cache['vaccinations']['missing'] = {} 1651 # 1) non-booster 1652 self.__db_cache['vaccinations']['missing']['due'] = [] 1653 # get list of (indication, seq_no) tuples 1654 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s" 1655 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1656 if rows is None: 1657 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient) 1658 return None 1659 pk_args = {'pat_id': self.pk_patient} 1660 if rows is not None: 1661 for row in rows: 1662 pk_args['indication'] = row[0] 1663 pk_args['seq_no'] = row[1] 1664 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args)) 1665 1666 # 2) boosters 1667 self.__db_cache['vaccinations']['missing']['boosters'] = [] 1668 # get list of indications 1669 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s" 1670 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1671 if rows is None: 1672 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient) 1673 return None 1674 pk_args = {'pat_id': self.pk_patient} 1675 if rows is not None: 1676 for row in rows: 1677 pk_args['indication'] = row[0] 1678 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args)) 1679 1680 # if any filters ... 1681 if indications is None: 1682 return self.__db_cache['vaccinations']['missing'] 1683 if len(indications) == 0: 1684 return self.__db_cache['vaccinations']['missing'] 1685 # ... apply them 1686 filtered_shots = { 1687 'due': [], 1688 'boosters': [] 1689 } 1690 for due_shot in self.__db_cache['vaccinations']['missing']['due']: 1691 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']: 1692 filtered_shots['due'].append(due_shot) 1693 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']: 1694 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']: 1695 filtered_shots['boosters'].append(due_shot) 1696 return filtered_shots
1697 #------------------------------------------------------------------ 1698 # API: encounters 1699 #------------------------------------------------------------------
1700 - def _get_current_encounter(self):
1701 return self.__encounter
1702
1703 - def _set_current_encounter(self, encounter):
1704 1705 # first ever setting ? 1706 if self.__encounter is None: 1707 _log.debug('first setting of active encounter in this clinical record instance') 1708 else: 1709 _log.debug('switching of active encounter') 1710 # fail if the currently active encounter has unsaved changes 1711 if self.__encounter.is_modified(): 1712 _log.debug('unsaved changes in active encounter, cannot switch to another one') 1713 raise ValueError('unsaved changes in active encounter, cannot switch to another one') 1714 1715 # be more conservative, it seems to have brought about 1716 # races involving encounter mod signals which made GNUmed crash 1717 # # set the currently active encounter and announce that change 1718 # if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'): 1719 # now = gmDateTime.pydt_now_here() 1720 # if now > encounter['started']: 1721 # encounter['last_affirmed'] = now # this will trigger an "clin.encounter_mod_db" 1722 # encounter.save() 1723 self.__encounter = encounter 1724 gmDispatcher.send(u'current_encounter_switched') 1725 1726 return True
1727 1728 current_encounter = property(_get_current_encounter, _set_current_encounter) 1729 active_encounter = property(_get_current_encounter, _set_current_encounter) 1730 #------------------------------------------------------------------
1731 - def __initiate_active_encounter(self, allow_user_interaction=True):
1732 1733 # 1) "very recent" encounter recorded ? 1734 if self.__activate_very_recent_encounter(): 1735 return True 1736 1737 # 2) "fairly recent" encounter recorded ? 1738 if self.__activate_fairly_recent_encounter(allow_user_interaction = allow_user_interaction): 1739 return True 1740 1741 # 3) start a completely new encounter 1742 self.start_new_encounter() 1743 return True
1744 #------------------------------------------------------------------
1746 """Try to attach to a "very recent" encounter if there is one. 1747 1748 returns: 1749 False: no "very recent" encounter, create new one 1750 True: success 1751 """ 1752 cfg_db = gmCfg.cCfgSQL() 1753 min_ttl = cfg_db.get2 ( 1754 option = u'encounter.minimum_ttl', 1755 workplace = _here.active_workplace, 1756 bias = u'user', 1757 default = u'1 hour 30 minutes' 1758 ) 1759 cmd = u""" 1760 SELECT pk_encounter 1761 FROM clin.v_most_recent_encounters 1762 WHERE 1763 pk_patient = %s 1764 and 1765 last_affirmed > (now() - %s::interval) 1766 ORDER BY 1767 last_affirmed DESC""" 1768 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}]) 1769 # none found 1770 if len(enc_rows) == 0: 1771 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl) 1772 return False 1773 # attach to existing 1774 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0]) 1775 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0]) 1776 return True
1777 #------------------------------------------------------------------
1778 - def __activate_fairly_recent_encounter(self, allow_user_interaction=True):
1779 """Try to attach to a "fairly recent" encounter if there is one. 1780 1781 returns: 1782 False: no "fairly recent" encounter, create new one 1783 True: success 1784 """ 1785 if _func_ask_user is None: 1786 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter') 1787 return False 1788 1789 if not allow_user_interaction: 1790 _log.exception('user interaction not desired, not looking for fairly recent encounter') 1791 return False 1792 1793 cfg_db = gmCfg.cCfgSQL() 1794 min_ttl = cfg_db.get2 ( 1795 option = u'encounter.minimum_ttl', 1796 workplace = _here.active_workplace, 1797 bias = u'user', 1798 default = u'1 hour 30 minutes' 1799 ) 1800 max_ttl = cfg_db.get2 ( 1801 option = u'encounter.maximum_ttl', 1802 workplace = _here.active_workplace, 1803 bias = u'user', 1804 default = u'6 hours' 1805 ) 1806 cmd = u""" 1807 SELECT pk_encounter 1808 FROM clin.v_most_recent_encounters 1809 WHERE 1810 pk_patient=%s 1811 AND 1812 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval) 1813 ORDER BY 1814 last_affirmed DESC""" 1815 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}]) 1816 # none found 1817 if len(enc_rows) == 0: 1818 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl)) 1819 return False 1820 1821 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0]) 1822 1823 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0]) 1824 # ask user whether to attach or not 1825 cmd = u""" 1826 SELECT title, firstnames, lastnames, gender, dob 1827 FROM dem.v_basic_person WHERE pk_identity=%s""" 1828 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}]) 1829 pat = pats[0] 1830 pat_str = u'%s %s %s (%s), %s [#%s]' % ( 1831 gmTools.coalesce(pat[0], u'')[:5], 1832 pat[1][:15], 1833 pat[2][:15], 1834 pat[3], 1835 gmDateTime.pydt_strftime(pat[4], '%Y %b %d'), 1836 self.pk_patient 1837 ) 1838 msg = _( 1839 '%s\n' 1840 '\n' 1841 "This patient's chart was worked on only recently:\n" 1842 '\n' 1843 ' %s %s - %s (%s)\n' 1844 '\n' 1845 ' Request: %s\n' 1846 ' Outcome: %s\n' 1847 '\n' 1848 'Do you want to continue that consultation\n' 1849 'or do you want to start a new one ?\n' 1850 ) % ( 1851 pat_str, 1852 gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'), 1853 gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'), 1854 encounter['l10n_type'], 1855 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')), 1856 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')), 1857 ) 1858 attach = False 1859 try: 1860 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter) 1861 except: 1862 _log.exception('cannot ask user for guidance, not attaching to existing encounter') 1863 return False 1864 if not attach: 1865 return False 1866 1867 # attach to existing 1868 self.current_encounter = encounter 1869 _log.debug('"fairly recent" encounter re-activated') 1870 return True
1871 #------------------------------------------------------------------
1872 - def start_new_encounter(self):
1873 cfg_db = gmCfg.cCfgSQL() 1874 enc_type = cfg_db.get2 ( 1875 option = u'encounter.default_type', 1876 workplace = _here.active_workplace, 1877 bias = u'user' 1878 ) 1879 if enc_type is None: 1880 enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type() 1881 if enc_type is None: 1882 enc_type = u'in surgery' 1883 enc = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type) 1884 enc['pk_org_unit'] = _here['pk_org_unit'] 1885 enc.save() 1886 self.current_encounter = enc 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_intakes(): 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