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

Source Code for Module Gnumed.business.gmClinicalRecord

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