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

Source Code for Module Gnumed.business.gmClinicalRecord

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