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

Source Code for Module Gnumed.business.gmClinicalRecord

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