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