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  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/business/gmClinicalRecord.py,v $ 
  12  # $Id: gmClinicalRecord.py,v 1.308 2009/12/03 17:44:18 ncq Exp $ 
  13  __version__ = "$Revision: 1.308 $" 
  14  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  15  __license__ = "GPL" 
  16   
  17  #=================================================== 
  18  # TODO 
  19  # Basically we'll probably have to: 
  20  # 
  21  # a) serialize access to re-getting data from the cache so 
  22  #   that later-but-concurrent cache accesses spin until 
  23  #   the first one completes the refetch from the database 
  24  # 
  25  # b) serialize access to the cache per-se such that cache 
  26  #    flushes vs. cache regets happen atomically (where 
  27  #    flushes would abort/restart current regets) 
  28  #=================================================== 
  29   
  30  # standard libs 
  31  import sys, string, time, copy, locale 
  32   
  33   
  34  # 3rd party 
  35  import mx.DateTime as mxDT, psycopg2, logging 
  36   
  37   
  38  if __name__ == '__main__': 
  39          sys.path.insert(0, '../../') 
  40          from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N 
  41          gmI18N.activate_locale() 
  42          gmI18N.install_domain() 
  43          gmDateTime.init() 
  44  from Gnumed.pycommon import gmExceptions, gmPG2, gmDispatcher, gmI18N, gmCfg, gmTools 
  45  from Gnumed.business import gmAllergy, gmEMRStructItems, gmClinNarrative, gmPathLab, gmMedication 
  46   
  47   
  48  _log = logging.getLogger('gm.emr') 
  49  _log.debug(__version__) 
  50   
  51  _me = None 
  52  _here = None 
  53   
  54  _func_ask_user = None 
  55  #============================================================ 
56 -class cClinicalRecord(object):
57 58 _clin_root_item_children_union_query = None 59
60 - def __init__(self, aPKey = None):
61 """Fails if 62 63 - no connection to database possible 64 - patient referenced by aPKey does not exist 65 """ 66 self.pk_patient = aPKey # == identity.pk == primary key 67 68 # log access to patient record (HIPAA, for example) 69 cmd = u'SELECT gm.log_access2emr(%(todo)s)' 70 args = {'todo': u'patient [%s]' % aPKey} 71 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 72 73 from Gnumed.business import gmSurgery, gmPerson 74 global _me 75 if _me is None: 76 _me = gmPerson.gmCurrentProvider() 77 global _here 78 if _here is None: 79 _here = gmSurgery.gmCurrentPractice() 80 81 # ........................................... 82 # this is a hack to speed up get_encounters() 83 clin_root_item_children = gmPG2.get_child_tables('clin', 'clin_root_item') 84 if cClinicalRecord._clin_root_item_children_union_query is None: 85 union_phrase = u""" 86 SELECT fk_encounter from 87 %s.%s cn 88 inner join 89 (SELECT pk FROM clin.episode ep WHERE ep.fk_health_issue in %%s) as epi 90 on (cn.fk_episode = epi.pk) 91 """ 92 cClinicalRecord._clin_root_item_children_union_query = u'union\n'.join ( 93 [ union_phrase % (child[0], child[1]) for child in clin_root_item_children ] 94 ) 95 # ........................................... 96 97 self.__db_cache = { 98 'vaccinations': {} 99 } 100 101 # load current or create new encounter 102 self.remove_empty_encounters() 103 self.__encounter = None 104 if not self.__initiate_active_encounter(): 105 raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey 106 107 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter']) 108 109 # register backend notification interests 110 # (keep this last so we won't hang on threads when 111 # failing this constructor for other reasons ...) 112 if not self._register_interests(): 113 raise gmExceptions.ConstructorError, "cannot register signal interests" 114 115 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
116 #--------------------------------------------------------
117 - def __del__(self):
118 pass
119 #--------------------------------------------------------
120 - def cleanup(self):
121 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient) 122 123 return True
124 #-------------------------------------------------------- 125 # messaging 126 #--------------------------------------------------------
127 - def _register_interests(self):
128 gmDispatcher.connect(signal = u'encounter_mod_db', receiver = self.db_callback_encounter_mod_db) 129 130 return True
131 #--------------------------------------------------------
132 - def db_callback_encounter_mod_db(self, **kwds):
133 # get the current encounter as an extra instance 134 # from the database to check for changes 135 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter']) 136 137 # the encounter just retrieved and the active encounter 138 # have got the same transaction ID so there's no change 139 # in the database, there could be a local change in 140 # the active encounter but that doesn't matter 141 if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']: 142 return True 143 144 # there must have been a change to the active encounter 145 # committed to the database from elsewhere, 146 # we must fail propagating the change, however, if 147 # there are local changes 148 if self.current_encounter.is_modified(): 149 _log.debug('unsaved changes in active encounter, cannot switch to another one') 150 raise ValueError('unsaved changes in active encounter, cannot switch to another one') 151 152 # there was a change in the database from elsewhere, 153 # locally, however, we don't have any changes, therefore 154 # we can propagate the remote change locally without 155 # losing anything 156 _log.debug('active encounter modified remotely, reloading and announcing the modification') 157 self.current_encounter.refetch_payload() 158 gmDispatcher.send(u'current_encounter_modified') 159 160 return True
161 #--------------------------------------------------------
162 - def db_callback_vaccs_modified(self, **kwds):
163 try: 164 self.__db_cache['vaccinations'] = {} 165 except KeyError: 166 pass 167 return True
168 #--------------------------------------------------------
169 - def _health_issues_modified(self):
170 try: 171 del self.__db_cache['health issues'] 172 except KeyError: 173 pass 174 return 1
175 #--------------------------------------------------------
177 # try: 178 # del self.__db_cache['episodes'] 179 # except KeyError: 180 # pass 181 return 1
182 #--------------------------------------------------------
183 - def _clin_item_modified(self):
184 _log.debug('DB: clin_root_item modification')
185 #-------------------------------------------------------- 186 # API: performed procedures 187 #--------------------------------------------------------
188 - def get_performed_procedures(self, episodes=None, issues=None):
189 190 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient) 191 192 if episodes is not None: 193 procs = filter(lambda p: p['pk_episode'] in episodes, procs) 194 195 if issues is not None: 196 procs = filter(lambda p: p['pk_health_issue'] in issues, procs) 197 198 return procs
199 #--------------------------------------------------------
200 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
201 return gmEMRStructItems.create_performed_procedure ( 202 encounter = self.current_encounter['pk_encounter'], 203 episode = episode, 204 location = location, 205 hospital_stay = hospital_stay, 206 procedure = procedure 207 )
208 #-------------------------------------------------------- 209 # API: hospital stays 210 #--------------------------------------------------------
211 - def get_hospital_stays(self, episodes=None, issues=None):
212 213 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient) 214 215 if episodes is not None: 216 stays = filter(lambda s: s['pk_episode'] in episodes, stays) 217 218 if issues is not None: 219 stays = filter(lambda s: s['pk_health_issue'] in issues, stays) 220 221 return stays
222 #-------------------------------------------------------- 223 # Narrative API 224 #--------------------------------------------------------
225 - def add_notes(self, notes=None, episode=None):
226 227 for note in notes: 228 success, data = gmClinNarrative.create_clin_narrative ( 229 narrative = note[1], 230 soap_cat = note[0], 231 episode_id = episode, 232 encounter_id = self.current_encounter['pk_encounter'] 233 ) 234 235 return True
236 #--------------------------------------------------------
237 - def add_clin_narrative(self, note='', soap_cat='s', episode=None):
238 if note.strip() == '': 239 _log.info('will not create empty clinical note') 240 return None 241 status, data = gmClinNarrative.create_clin_narrative ( 242 narrative = note, 243 soap_cat = soap_cat, 244 episode_id = episode['pk_episode'], 245 encounter_id = self.current_encounter['pk_encounter'] 246 ) 247 if not status: 248 _log.error(str(data)) 249 return None 250 return data
251 #--------------------------------------------------------
252 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
253 """Get SOAP notes pertinent to this encounter. 254 255 since 256 - initial date for narrative items 257 until 258 - final date for narrative items 259 encounters 260 - list of encounters whose narrative are to be retrieved 261 episodes 262 - list of episodes whose narrative are to be retrieved 263 issues 264 - list of health issues whose narrative are to be retrieved 265 soap_cats 266 - list of SOAP categories of the narrative to be retrieved 267 """ 268 cmd = u""" 269 SELECT cvpn.*, (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat) as soap_rank 270 from clin.v_pat_narrative cvpn 271 WHERE pk_patient = %s 272 order by date, soap_rank 273 """ 274 275 #xxxxxxxxxxxxxxx 276 # support row_version in narrative for display in tree 277 278 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True) 279 280 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ] 281 282 if since is not None: 283 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative) 284 285 if until is not None: 286 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative) 287 288 if issues is not None: 289 filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative) 290 291 if episodes is not None: 292 filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative) 293 294 if encounters is not None: 295 filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative) 296 297 if soap_cats is not None: 298 soap_cats = map(lambda c: c.lower(), soap_cats) 299 filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative) 300 301 if providers is not None: 302 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative) 303 304 return filtered_narrative
305 #--------------------------------------------------------
306 - def search_narrative_simple(self, search_term=''):
307 308 search_term = search_term.strip() 309 if search_term == '': 310 return [] 311 312 cmd = u""" 313 SELECT 314 *, 315 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table) 316 as episode, 317 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table) 318 as health_issue, 319 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter) 320 as encounter_started, 321 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter) 322 as encounter_ended, 323 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter)) 324 as encounter_type 325 from clin.v_narrative4search vn4s 326 WHERE 327 pk_patient = %(pat)s and 328 vn4s.narrative ~ %(term)s 329 order by 330 encounter_started 331 """ # case sensitive 332 rows, idx = gmPG2.run_ro_queries(queries = [ 333 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}} 334 ]) 335 return rows
336 #--------------------------------------------------------
337 - def get_text_dump_old(self):
338 # don't know how to invalidate this by means of 339 # a notify without catching notifies from *all* 340 # child tables, the best solution would be if 341 # inserts in child tables would also fire triggers 342 # of ancestor tables, but oh well, 343 # until then the text dump will not be cached ... 344 try: 345 return self.__db_cache['text dump old'] 346 except KeyError: 347 pass 348 # not cached so go get it 349 fields = [ 350 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when", 351 'modified_by', 352 'clin_when', 353 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')), 354 'pk_item', 355 'pk_encounter', 356 'pk_episode', 357 'pk_health_issue', 358 'src_table' 359 ] 360 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % string.join(fields, ', ') 361 ro_conn = self._conn_pool.GetConnection('historica') 362 curs = ro_conn.cursor() 363 if not gmPG2.run_query(curs, None, cmd, self.pk_patient): 364 _log.error('cannot load item links for patient [%s]' % self.pk_patient) 365 curs.close() 366 return None 367 rows = curs.fetchall() 368 view_col_idx = gmPG2.get_col_indices(curs) 369 370 # aggregate by src_table for item retrieval 371 items_by_table = {} 372 for item in rows: 373 src_table = item[view_col_idx['src_table']] 374 pk_item = item[view_col_idx['pk_item']] 375 if not items_by_table.has_key(src_table): 376 items_by_table[src_table] = {} 377 items_by_table[src_table][pk_item] = item 378 379 # get mapping for issue/episode IDs 380 issues = self.get_health_issues() 381 issue_map = {} 382 for issue in issues: 383 issue_map[issue['pk']] = issue['description'] 384 episodes = self.get_episodes() 385 episode_map = {} 386 for episode in episodes: 387 episode_map[episode['pk_episode']] = episode['description'] 388 emr_data = {} 389 # get item data from all source tables 390 for src_table in items_by_table.keys(): 391 item_ids = items_by_table[src_table].keys() 392 # we don't know anything about the columns of 393 # the source tables but, hey, this is a dump 394 if len(item_ids) == 0: 395 _log.info('no items in table [%s] ?!?' % src_table) 396 continue 397 elif len(item_ids) == 1: 398 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table 399 if not gmPG2.run_query(curs, None, cmd, item_ids[0]): 400 _log.error('cannot load items from table [%s]' % src_table) 401 # skip this table 402 continue 403 elif len(item_ids) > 1: 404 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table 405 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)): 406 _log.error('cannot load items from table [%s]' % src_table) 407 # skip this table 408 continue 409 rows = curs.fetchall() 410 table_col_idx = gmPG.get_col_indices(curs) 411 # format per-table items 412 for row in rows: 413 # FIXME: make this get_pkey_name() 414 pk_item = row[table_col_idx['pk_item']] 415 view_row = items_by_table[src_table][pk_item] 416 age = view_row[view_col_idx['age']] 417 # format metadata 418 try: 419 episode_name = episode_map[view_row[view_col_idx['pk_episode']]] 420 except: 421 episode_name = view_row[view_col_idx['pk_episode']] 422 try: 423 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]] 424 except: 425 issue_name = view_row[view_col_idx['pk_health_issue']] 426 427 if not emr_data.has_key(age): 428 emr_data[age] = [] 429 430 emr_data[age].append( 431 _('%s: encounter (%s)') % ( 432 view_row[view_col_idx['clin_when']], 433 view_row[view_col_idx['pk_encounter']] 434 ) 435 ) 436 emr_data[age].append(_('health issue: %s') % issue_name) 437 emr_data[age].append(_('episode : %s') % episode_name) 438 # format table specific data columns 439 # - ignore those, they are metadata, some 440 # are in clin.v_pat_items data already 441 cols2ignore = [ 442 'pk_audit', 'row_version', 'modified_when', 'modified_by', 443 'pk_item', 'id', 'fk_encounter', 'fk_episode' 444 ] 445 col_data = [] 446 for col_name in table_col_idx.keys(): 447 if col_name in cols2ignore: 448 continue 449 emr_data[age].append("=> %s:" % col_name) 450 emr_data[age].append(row[table_col_idx[col_name]]) 451 emr_data[age].append("----------------------------------------------------") 452 emr_data[age].append("-- %s from table %s" % ( 453 view_row[view_col_idx['modified_string']], 454 src_table 455 )) 456 emr_data[age].append("-- written %s by %s" % ( 457 view_row[view_col_idx['modified_when']], 458 view_row[view_col_idx['modified_by']] 459 )) 460 emr_data[age].append("----------------------------------------------------") 461 curs.close() 462 self._conn_pool.ReleaseConnection('historica') 463 return emr_data
464 #--------------------------------------------------------
465 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
466 # don't know how to invalidate this by means of 467 # a notify without catching notifies from *all* 468 # child tables, the best solution would be if 469 # inserts in child tables would also fire triggers 470 # of ancestor tables, but oh well, 471 # until then the text dump will not be cached ... 472 try: 473 return self.__db_cache['text dump'] 474 except KeyError: 475 pass 476 # not cached so go get it 477 # -- get the data -- 478 fields = [ 479 'age', 480 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when", 481 'modified_by', 482 'clin_when', 483 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')), 484 'pk_item', 485 'pk_encounter', 486 'pk_episode', 487 'pk_health_issue', 488 'src_table' 489 ] 490 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields) 491 # handle constraint conditions 492 where_snippets = [] 493 params = {} 494 where_snippets.append('pk_patient=%(pat_id)s') 495 params['pat_id'] = self.pk_patient 496 if not since is None: 497 where_snippets.append('clin_when >= %(since)s') 498 params['since'] = since 499 if not until is None: 500 where_snippets.append('clin_when <= %(until)s') 501 params['until'] = until 502 # FIXME: these are interrelated, eg if we constrain encounter 503 # we automatically constrain issue/episode, so handle that, 504 # encounters 505 if not encounters is None and len(encounters) > 0: 506 params['enc'] = encounters 507 if len(encounters) > 1: 508 where_snippets.append('fk_encounter in %(enc)s') 509 else: 510 where_snippets.append('fk_encounter=%(enc)s') 511 # episodes 512 if not episodes is None and len(episodes) > 0: 513 params['epi'] = episodes 514 if len(episodes) > 1: 515 where_snippets.append('fk_episode in %(epi)s') 516 else: 517 where_snippets.append('fk_episode=%(epi)s') 518 # health issues 519 if not issues is None and len(issues) > 0: 520 params['issue'] = issues 521 if len(issues) > 1: 522 where_snippets.append('fk_health_issue in %(issue)s') 523 else: 524 where_snippets.append('fk_health_issue=%(issue)s') 525 526 where_clause = ' and '.join(where_snippets) 527 order_by = 'order by src_table, age' 528 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by) 529 530 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params) 531 if rows is None: 532 _log.error('cannot load item links for patient [%s]' % self.pk_patient) 533 return None 534 535 # -- sort the data -- 536 # FIXME: by issue/encounter/episode, eg formatting 537 # aggregate by src_table for item retrieval 538 items_by_table = {} 539 for item in rows: 540 src_table = item[view_col_idx['src_table']] 541 pk_item = item[view_col_idx['pk_item']] 542 if not items_by_table.has_key(src_table): 543 items_by_table[src_table] = {} 544 items_by_table[src_table][pk_item] = item 545 546 # get mapping for issue/episode IDs 547 issues = self.get_health_issues() 548 issue_map = {} 549 for issue in issues: 550 issue_map[issue['pk_health_issue']] = issue['description'] 551 episodes = self.get_episodes() 552 episode_map = {} 553 for episode in episodes: 554 episode_map[episode['pk_episode']] = episode['description'] 555 emr_data = {} 556 # get item data from all source tables 557 ro_conn = self._conn_pool.GetConnection('historica') 558 curs = ro_conn.cursor() 559 for src_table in items_by_table.keys(): 560 item_ids = items_by_table[src_table].keys() 561 # we don't know anything about the columns of 562 # the source tables but, hey, this is a dump 563 if len(item_ids) == 0: 564 _log.info('no items in table [%s] ?!?' % src_table) 565 continue 566 elif len(item_ids) == 1: 567 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table 568 if not gmPG.run_query(curs, None, cmd, item_ids[0]): 569 _log.error('cannot load items from table [%s]' % src_table) 570 # skip this table 571 continue 572 elif len(item_ids) > 1: 573 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table 574 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)): 575 _log.error('cannot load items from table [%s]' % src_table) 576 # skip this table 577 continue 578 rows = curs.fetchall() 579 table_col_idx = gmPG.get_col_indices(curs) 580 # format per-table items 581 for row in rows: 582 # FIXME: make this get_pkey_name() 583 pk_item = row[table_col_idx['pk_item']] 584 view_row = items_by_table[src_table][pk_item] 585 age = view_row[view_col_idx['age']] 586 # format metadata 587 try: 588 episode_name = episode_map[view_row[view_col_idx['pk_episode']]] 589 except: 590 episode_name = view_row[view_col_idx['pk_episode']] 591 try: 592 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]] 593 except: 594 issue_name = view_row[view_col_idx['pk_health_issue']] 595 596 if not emr_data.has_key(age): 597 emr_data[age] = [] 598 599 emr_data[age].append( 600 _('%s: encounter (%s)') % ( 601 view_row[view_col_idx['clin_when']], 602 view_row[view_col_idx['pk_encounter']] 603 ) 604 ) 605 emr_data[age].append(_('health issue: %s') % issue_name) 606 emr_data[age].append(_('episode : %s') % episode_name) 607 # format table specific data columns 608 # - ignore those, they are metadata, some 609 # are in clin.v_pat_items data already 610 cols2ignore = [ 611 'pk_audit', 'row_version', 'modified_when', 'modified_by', 612 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk' 613 ] 614 col_data = [] 615 for col_name in table_col_idx.keys(): 616 if col_name in cols2ignore: 617 continue 618 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]])) 619 emr_data[age].append("----------------------------------------------------") 620 emr_data[age].append("-- %s from table %s" % ( 621 view_row[view_col_idx['modified_string']], 622 src_table 623 )) 624 emr_data[age].append("-- written %s by %s" % ( 625 view_row[view_col_idx['modified_when']], 626 view_row[view_col_idx['modified_by']] 627 )) 628 emr_data[age].append("----------------------------------------------------") 629 curs.close() 630 return emr_data
631 #--------------------------------------------------------
632 - def get_patient_ID(self):
633 return self.pk_patient
634 #--------------------------------------------------------
635 - def get_statistics(self):
636 union_query = u'\n union all\n'.join ([ 637 u""" 638 SELECT (( 639 -- all relevant health issues + active episodes WITH health issue 640 SELECT COUNT(1) 641 FROM clin.v_problem_list 642 WHERE 643 pk_patient = %(pat)s 644 AND 645 pk_health_issue is not null 646 ) + ( 647 -- active episodes WITHOUT health issue 648 SELECT COUNT(1) 649 FROM clin.v_problem_list 650 WHERE 651 pk_patient = %(pat)s 652 AND 653 pk_health_issue is null 654 ))""", 655 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s', 656 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s', 657 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s', 658 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s', 659 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s', 660 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s', 661 # active and approved substances == medication 662 u""" 663 SELECT count(1) 664 from clin.v_pat_substance_intake 665 WHERE 666 pk_patient = %(pat)s 667 and is_currently_active in (null, true) 668 and intake_is_approved_of in (null, true)""" 669 ]) 670 671 rows, idx = gmPG2.run_ro_queries ( 672 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}], 673 get_col_idx = False 674 ) 675 676 stats = dict ( 677 problems = rows[0][0], 678 encounters = rows[1][0], 679 items = rows[2][0], 680 documents = rows[3][0], 681 results = rows[4][0], 682 stays = rows[5][0], 683 procedures = rows[6][0], 684 active_drugs = rows[7][0] 685 ) 686 687 return stats
688 #--------------------------------------------------------
689 - def format_statistics(self):
690 return _("""Medical problems: %(problems)s 691 Total encounters: %(encounters)s 692 Total EMR entries: %(items)s 693 Active medications: %(active_drugs)s 694 Documents: %(documents)s 695 Test results: %(results)s 696 Hospital stays: %(stays)s 697 Procedures: %(procedures)s 698 """ ) % self.get_statistics()
699 #--------------------------------------------------------
700 - def format_summary(self):
701 702 stats = self.get_statistics() 703 first = self.get_first_encounter() 704 last = self.get_last_encounter() 705 probs = self.get_problems() 706 707 txt = _('EMR Statistics\n\n') 708 if len(probs) > 0: 709 txt += _(' %s known problems. Clinically relevant thereof:\n') % stats['problems'] 710 else: 711 txt += _(' %s known problems\n') % stats['problems'] 712 for prob in probs: 713 if not prob['clinically_relevant']: 714 continue 715 txt += u' \u00BB%s\u00AB (%s)\n' % ( 716 prob['problem'], 717 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive')) 718 ) 719 txt += _(' %s encounters from %s to %s\n') % ( 720 stats['encounters'], 721 first['started'].strftime('%x'), 722 last['started'].strftime('%x') 723 ) 724 txt += _(' %s active medications\n') % stats['active_drugs'] 725 txt += _(' %s documents\n') % stats['documents'] 726 txt += _(' %s test results\n') % stats['results'] 727 txt += _(' %s hospital stays\n') % stats['stays'] 728 txt += _(' %s performed procedures\n\n') % stats['procedures'] 729 730 txt += _('Allergies and Intolerances\n\n') 731 732 allg_state = self.allergy_state 733 txt += (u' ' + allg_state.state_string) 734 if allg_state['last_confirmed'] is not None: 735 txt += (_(' (last confirmed %s)') % allg_state['last_confirmed'].strftime('%x')) 736 txt += u'\n' 737 txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n') 738 for allg in self.get_allergies(): 739 txt += u' %s: %s\n' % ( 740 allg['descriptor'], 741 gmTools.coalesce(allg['reaction'], _('unknown reaction')) 742 ) 743 744 return txt
745 #-------------------------------------------------------- 746 # allergy API 747 #--------------------------------------------------------
748 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
749 """Retrieves patient allergy items. 750 751 remove_sensitivities 752 - retrieve real allergies only, without sensitivities 753 since 754 - initial date for allergy items 755 until 756 - final date for allergy items 757 encounters 758 - list of encounters whose allergies are to be retrieved 759 episodes 760 - list of episodes whose allergies are to be retrieved 761 issues 762 - list of health issues whose allergies are to be retrieved 763 """ 764 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor" 765 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True) 766 allergies = [] 767 for r in rows: 768 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'})) 769 770 # ok, let's constrain our list 771 filtered_allergies = [] 772 filtered_allergies.extend(allergies) 773 774 if ID_list is not None: 775 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies) 776 if len(filtered_allergies) == 0: 777 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient)) 778 # better fail here contrary to what we do elsewhere 779 return None 780 else: 781 return filtered_allergies 782 783 if remove_sensitivities: 784 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies) 785 if since is not None: 786 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies) 787 if until is not None: 788 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies) 789 if issues is not None: 790 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies) 791 if episodes is not None: 792 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies) 793 if encounters is not None: 794 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies) 795 796 return filtered_allergies
797 #--------------------------------------------------------
798 - def add_allergy(self, substance=None, allg_type=None, encounter_id=None, episode_id=None):
799 if encounter_id is None: 800 encounter_id = self.current_encounter['pk_encounter'] 801 802 if episode_id is None: 803 issue = self.add_health_issue(issue_name = _('allergies/intolerances')) 804 epi = self.add_episode(episode_name = substance, pk_health_issue = issue['pk_health_issue']) 805 episode_id = epi['pk_episode'] 806 807 new_allergy = gmAllergy.create_allergy ( 808 substance = substance, 809 allg_type = allg_type, 810 encounter_id = encounter_id, 811 episode_id = episode_id 812 ) 813 814 return new_allergy
815 #--------------------------------------------------------
816 - def delete_allergy(self, pk_allergy=None):
817 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s' 818 args = {'pk_allg': pk_allergy} 819 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
820 #--------------------------------------------------------
821 - def _set_allergy_state(self, state):
822 823 if state not in gmAllergy.allergy_states: 824 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states)) 825 826 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter']) 827 allg_state['has_allergy'] = state 828 allg_state.save_payload() 829 return True
830
831 - def _get_allergy_state(self):
832 return gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
833 834 allergy_state = property(_get_allergy_state, _set_allergy_state) 835 #-------------------------------------------------------- 836 # episodes API 837 #--------------------------------------------------------
838 - def get_episodes(self, id_list=None, issues=None, open_status=None):
839 """Fetches from backend patient episodes. 840 841 id_list - Episodes' PKs list 842 issues - Health issues' PKs list to filter episodes by 843 open_status - return all episodes, only open or closed one(s) 844 """ 845 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s" 846 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True) 847 tmp = [] 848 for r in rows: 849 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'})) 850 851 # now filter 852 if (id_list is None) and (issues is None) and (open_status is None): 853 return tmp 854 855 # ok, let's filter episode list 856 filtered_episodes = [] 857 filtered_episodes.extend(tmp) 858 if open_status is not None: 859 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes) 860 861 if issues is not None: 862 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes) 863 864 if id_list is not None: 865 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes) 866 867 return filtered_episodes
868 #------------------------------------------------------------------
869 - def get_episodes_by_encounter(self, pk_encounter=None):
870 cmd = u"""SELECT distinct pk_episode 871 from clin.v_pat_items 872 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s""" 873 args = { 874 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']), 875 'pat': self.pk_patient 876 } 877 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 878 if len(rows) == 0: 879 return [] 880 epis = [] 881 for row in rows: 882 epis.append(row[0]) 883 return self.get_episodes(id_list=epis)
884 #------------------------------------------------------------------
885 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
886 """Add episode 'episode_name' for a patient's health issue. 887 888 - silently returns if episode already exists 889 """ 890 episode = gmEMRStructItems.create_episode ( 891 pk_health_issue = pk_health_issue, 892 episode_name = episode_name, 893 is_open = is_open, 894 encounter = self.current_encounter['pk_encounter'] 895 ) 896 return episode
897 #--------------------------------------------------------
898 - def get_most_recent_episode(self, issue=None):
899 # try to find the episode with the most recently modified clinical item 900 901 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s') 902 903 cmd = u""" 904 SELECT pk 905 from clin.episode 906 WHERE pk = ( 907 SELECT distinct on(pk_episode) pk_episode 908 from clin.v_pat_items 909 WHERE 910 pk_patient = %%(pat)s 911 and 912 modified_when = ( 913 SELECT max(vpi.modified_when) 914 from clin.v_pat_items vpi 915 WHERE vpi.pk_patient = %%(pat)s 916 ) 917 %s 918 -- guard against several episodes created at the same moment of time 919 limit 1 920 )""" % issue_where 921 rows, idx = gmPG2.run_ro_queries(queries = [ 922 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}} 923 ]) 924 if len(rows) != 0: 925 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0]) 926 927 # no clinical items recorded, so try to find 928 # the youngest episode for this patient 929 cmd = u""" 930 SELECT vpe0.pk_episode 931 from 932 clin.v_pat_episodes vpe0 933 WHERE 934 vpe0.pk_patient = %%(pat)s 935 and 936 vpe0.episode_modified_when = ( 937 SELECT max(vpe1.episode_modified_when) 938 from clin.v_pat_episodes vpe1 939 WHERE vpe1.pk_episode = vpe0.pk_episode 940 ) 941 %s""" % issue_where 942 rows, idx = gmPG2.run_ro_queries(queries = [ 943 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}} 944 ]) 945 if len(rows) != 0: 946 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0]) 947 948 return None
949 #--------------------------------------------------------
950 - def episode2problem(self, episode=None):
951 return gmEMRStructItems.episode2problem(episode=episode)
952 #-------------------------------------------------------- 953 # problems API 954 #--------------------------------------------------------
955 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
956 """Retrieve a patient's problems. 957 958 "Problems" are the UNION of: 959 960 - issues which are .clinically_relevant 961 - episodes which are .is_open 962 963 Therefore, both an issue and the open episode 964 thereof can each be listed as a problem. 965 966 include_closed_episodes/include_irrelevant_issues will 967 include those -- which departs from the definition of 968 the problem list being "active" items only ... 969 970 episodes - episodes' PKs to filter problems by 971 issues - health issues' PKs to filter problems by 972 """ 973 # FIXME: this could use a good measure of streamlining, probably 974 975 args = {'pat': self.pk_patient} 976 977 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s""" 978 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 979 980 # Instantiate problem items 981 problems = [] 982 for row in rows: 983 pk_args = { 984 u'pk_patient': self.pk_patient, 985 u'pk_health_issue': row['pk_health_issue'], 986 u'pk_episode': row['pk_episode'] 987 } 988 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False)) 989 990 # include non-problems ? 991 other_rows = [] 992 if include_closed_episodes: 993 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'""" 994 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 995 other_rows.extend(rows) 996 997 if include_irrelevant_issues: 998 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'""" 999 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1000 other_rows.extend(rows) 1001 1002 if len(other_rows) > 0: 1003 for row in other_rows: 1004 pk_args = { 1005 u'pk_patient': self.pk_patient, 1006 u'pk_health_issue': row['pk_health_issue'], 1007 u'pk_episode': row['pk_episode'] 1008 } 1009 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True)) 1010 1011 # filter ? 1012 if (episodes is None) and (issues is None): 1013 return problems 1014 1015 # filter 1016 if issues is not None: 1017 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems) 1018 if episodes is not None: 1019 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems) 1020 1021 return problems
1022 #--------------------------------------------------------
1023 - def problem2episode(self, problem=None):
1024 """ 1025 Retrieve the cEpisode instance equivalent to the given problem. 1026 The problem's type attribute must be 'episode' 1027 1028 @param problem: The problem to retrieve its related episode for 1029 @type problem: A gmEMRStructItems.cProblem instance 1030 """ 1031 if isinstance(problem, gmEMRStructItems.cProblem) and (problem['type'] == 'episode'): 1032 return self.get_episodes(id_list=[problem['pk_episode']])[0] 1033 1034 if isinstance(problem, gmEMRStructItems.cEpisode): 1035 return problem 1036 1037 raise TypeError('cannot convert [%s] to episode' % problem)
1038 #--------------------------------------------------------
1039 - def problem2issue(self, problem=None):
1040 """ 1041 Retrieve the cIssue instance equivalent to the given problem. 1042 The problem's type attribute must be 'issue'. 1043 1044 @param problem: The problem to retrieve the corresponding issue for 1045 @type problem: A gmEMRStructItems.cProblem instance 1046 """ 1047 if isinstance(problem, gmEMRStructItems.cProblem) and (problem['type'] == 'issue'): 1048 return self.get_health_issues(id_list=[problem['pk_health_issue']])[0] 1049 1050 if isinstance(problem, gmEMRStructItems.cHealthIssue): 1051 return problem 1052 1053 raise TypeError('cannot convert [%s] to health issue' % problem)
1054 #--------------------------------------------------------
1055 - def reclass_problem(self, problem):
1056 """Transform given problem into either episode or health issue instance. 1057 """ 1058 if not isinstance(problem, gmEMRStructItems.cProblem): 1059 _log.debug(str(problem)) 1060 raise TypeError, 'cannot reclass [%s] instance to problem' % type(problem) 1061 if problem['type'] == 'episode': 1062 return self.get_episodes(id_list=[problem['pk_episode']])[0] 1063 if problem['type'] == 'issue': 1064 return self.get_health_issues(id_list=[problem['pk_health_issue']])[0] 1065 return None
1066 #-------------------------------------------------------- 1067 # health issues API 1068 #--------------------------------------------------------
1069 - def get_health_issues(self, id_list = None):
1070 1071 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s" 1072 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True) 1073 issues = [] 1074 for row in rows: 1075 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'} 1076 issues.append(gmEMRStructItems.cHealthIssue(row=r)) 1077 1078 if id_list is None: 1079 return issues 1080 1081 if len(id_list) == 0: 1082 raise ValueError('id_list to filter by is empty, most likely a programming error') 1083 1084 filtered_issues = [] 1085 for issue in issues: 1086 if issue['pk_health_issue'] in id_list: 1087 filtered_issues.append(issue) 1088 1089 return filtered_issues
1090 #------------------------------------------------------------------
1091 - def add_health_issue(self, issue_name=None):
1092 """Adds patient health issue.""" 1093 return gmEMRStructItems.create_health_issue ( 1094 description = issue_name, 1095 encounter = self.current_encounter['pk_encounter'], 1096 patient = self.pk_patient 1097 )
1098 #--------------------------------------------------------
1099 - def health_issue2problem(self, issue=None):
1100 return gmEMRStructItems.health_issue2problem(issue = issue)
1101 #-------------------------------------------------------- 1102 # API: substance intake 1103 #--------------------------------------------------------
1104 - def get_current_substance_intake(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1105 1106 where_parts = [u'pk_patient = %(pat)s'] 1107 1108 if not include_inactive: 1109 where_parts.append(u'is_currently_active in (true, null)') 1110 1111 if not include_unapproved: 1112 where_parts.append(u'intake_is_approved_of in (true, null)') 1113 1114 if order_by is None: 1115 order_by = u'' 1116 else: 1117 order_by = u'order by %s' % order_by 1118 1119 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % ( 1120 u'\nand '.join(where_parts), 1121 order_by 1122 ) 1123 1124 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True) 1125 1126 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ] 1127 1128 if episodes is not None: 1129 meds = filter(lambda s: s['pk_episode'] in episodes, meds) 1130 1131 if issues is not None: 1132 meds = filter(lambda s: s['pk_health_issue'] in issues, meds) 1133 1134 return meds
1135 #--------------------------------------------------------
1136 - def add_substance_intake(self, substance=None, atc=None, episode=None, preparation=None):
1137 return gmMedication.create_substance_intake ( 1138 substance = substance, 1139 atc = atc, 1140 encounter = self.current_encounter['pk_encounter'], 1141 episode = episode, 1142 preparation = preparation 1143 )
1144 #-------------------------------------------------------- 1145 # vaccinations API 1146 #--------------------------------------------------------
1147 - def get_scheduled_vaccination_regimes(self, ID=None, indications=None):
1148 """Retrieves vaccination regimes the patient is on. 1149 1150 optional: 1151 * ID - PK of the vaccination regime 1152 * indications - indications we want to retrieve vaccination 1153 regimes for, must be primary language, not l10n_indication 1154 """ 1155 # FIXME: use course, not regime 1156 try: 1157 self.__db_cache['vaccinations']['scheduled regimes'] 1158 except KeyError: 1159 # retrieve vaccination regimes definitions 1160 self.__db_cache['vaccinations']['scheduled regimes'] = [] 1161 cmd = """SELECT distinct on(pk_course) pk_course 1162 FROM clin.v_vaccs_scheduled4pat 1163 WHERE pk_patient=%s""" 1164 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1165 if rows is None: 1166 _log.error('cannot retrieve scheduled vaccination courses') 1167 del self.__db_cache['vaccinations']['scheduled regimes'] 1168 return None 1169 # Instantiate vaccination items and keep cache 1170 for row in rows: 1171 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0])) 1172 1173 # ok, let's constrain our list 1174 filtered_regimes = [] 1175 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes']) 1176 if ID is not None: 1177 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes) 1178 if len(filtered_regimes) == 0: 1179 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient)) 1180 return [] 1181 else: 1182 return filtered_regimes[0] 1183 if indications is not None: 1184 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes) 1185 1186 return filtered_regimes
1187 #--------------------------------------------------------
1188 - def get_vaccinated_indications(self):
1189 """Retrieves patient vaccinated indications list. 1190 1191 Note that this does NOT rely on the patient being on 1192 some schedule or other but rather works with what the 1193 patient has ACTUALLY been vaccinated against. This is 1194 deliberate ! 1195 """ 1196 # most likely, vaccinations will be fetched close 1197 # by so it makes sense to count on the cache being 1198 # filled (or fill it for nearby use) 1199 vaccinations = self.get_vaccinations() 1200 if vaccinations is None: 1201 _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient) 1202 return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]]) 1203 if len(vaccinations) == 0: 1204 return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]]) 1205 v_indications = [] 1206 for vacc in vaccinations: 1207 tmp = [vacc['indication'], vacc['l10n_indication']] 1208 # remove duplicates 1209 if tmp in v_indications: 1210 continue 1211 v_indications.append(tmp) 1212 return (True, v_indications)
1213 #--------------------------------------------------------
1214 - def get_vaccinations(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1215 """Retrieves list of vaccinations the patient has received. 1216 1217 optional: 1218 * ID - PK of a vaccination 1219 * indications - indications we want to retrieve vaccination 1220 items for, must be primary language, not l10n_indication 1221 * since - initial date for allergy items 1222 * until - final date for allergy items 1223 * encounters - list of encounters whose allergies are to be retrieved 1224 * episodes - list of episodes whose allergies are to be retrieved 1225 * issues - list of health issues whose allergies are to be retrieved 1226 """ 1227 try: 1228 self.__db_cache['vaccinations']['vaccinated'] 1229 except KeyError: 1230 self.__db_cache['vaccinations']['vaccinated'] = [] 1231 # Important fetch ordering by indication, date to know if a vaccination is booster 1232 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication 1233 WHERE pk_patient=%s 1234 order by indication, date""" 1235 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient) 1236 if rows is None: 1237 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient) 1238 del self.__db_cache['vaccinations']['vaccinated'] 1239 return None 1240 # Instantiate vaccination items 1241 vaccs_by_ind = {} 1242 for row in rows: 1243 vacc_row = { 1244 'pk_field': 'pk_vaccination', 1245 'idx': idx, 1246 'data': row 1247 } 1248 vacc = gmVaccination.cVaccination(row=vacc_row) 1249 self.__db_cache['vaccinations']['vaccinated'].append(vacc) 1250 # keep them, ordered by indication 1251 try: 1252 vaccs_by_ind[vacc['indication']].append(vacc) 1253 except KeyError: 1254 vaccs_by_ind[vacc['indication']] = [vacc] 1255 1256 # calculate sequence number and is_booster 1257 for ind in vaccs_by_ind.keys(): 1258 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind]) 1259 for vacc in vaccs_by_ind[ind]: 1260 # due to the "order by indication, date" the vaccinations are in the 1261 # right temporal order inside the indication-keyed dicts 1262 seq_no = vaccs_by_ind[ind].index(vacc) + 1 1263 vacc['seq_no'] = seq_no 1264 # if no active schedule for indication we cannot 1265 # check for booster status (eg. seq_no > max_shot) 1266 if (vacc_regimes is None) or (len(vacc_regimes) == 0): 1267 continue 1268 if seq_no > vacc_regimes[0]['shots']: 1269 vacc['is_booster'] = True 1270 del vaccs_by_ind 1271 1272 # ok, let's constrain our list 1273 filtered_shots = [] 1274 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated']) 1275 if ID is not None: 1276 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots) 1277 if len(filtered_shots) == 0: 1278 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient)) 1279 return None 1280 else: 1281 return filtered_shots[0] 1282 if since is not None: 1283 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots) 1284 if until is not None: 1285 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots) 1286 if issues is not None: 1287 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots) 1288 if episodes is not None: 1289 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots) 1290 if encounters is not None: 1291 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots) 1292 if indications is not None: 1293 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots) 1294 return filtered_shots
1295 #--------------------------------------------------------
1296 - def get_scheduled_vaccinations(self, indications=None):
1297 """Retrieves vaccinations scheduled for a regime a patient is on. 1298 1299 The regime is referenced by its indication (not l10n) 1300 1301 * indications - List of indications (not l10n) of regimes we want scheduled 1302 vaccinations to be fetched for 1303 """ 1304 try: 1305 self.__db_cache['vaccinations']['scheduled'] 1306 except KeyError: 1307 self.__db_cache['vaccinations']['scheduled'] = [] 1308 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s""" 1309 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient) 1310 if rows is None: 1311 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient) 1312 del self.__db_cache['vaccinations']['scheduled'] 1313 return None 1314 # Instantiate vaccination items 1315 for row in rows: 1316 vacc_row = { 1317 'pk_field': 'pk_vacc_def', 1318 'idx': idx, 1319 'data': row 1320 } 1321 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row)) 1322 1323 # ok, let's constrain our list 1324 if indications is None: 1325 return self.__db_cache['vaccinations']['scheduled'] 1326 filtered_shots = [] 1327 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled']) 1328 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots) 1329 return filtered_shots
1330 #--------------------------------------------------------
1331 - def get_missing_vaccinations(self, indications=None):
1332 try: 1333 self.__db_cache['vaccinations']['missing'] 1334 except KeyError: 1335 self.__db_cache['vaccinations']['missing'] = {} 1336 # 1) non-booster 1337 self.__db_cache['vaccinations']['missing']['due'] = [] 1338 # get list of (indication, seq_no) tuples 1339 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s" 1340 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1341 if rows is None: 1342 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient) 1343 return None 1344 pk_args = {'pat_id': self.pk_patient} 1345 if rows is not None: 1346 for row in rows: 1347 pk_args['indication'] = row[0] 1348 pk_args['seq_no'] = row[1] 1349 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args)) 1350 1351 # 2) boosters 1352 self.__db_cache['vaccinations']['missing']['boosters'] = [] 1353 # get list of indications 1354 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s" 1355 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient) 1356 if rows is None: 1357 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient) 1358 return None 1359 pk_args = {'pat_id': self.pk_patient} 1360 if rows is not None: 1361 for row in rows: 1362 pk_args['indication'] = row[0] 1363 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args)) 1364 1365 # if any filters ... 1366 if indications is None: 1367 return self.__db_cache['vaccinations']['missing'] 1368 if len(indications) == 0: 1369 return self.__db_cache['vaccinations']['missing'] 1370 # ... apply them 1371 filtered_shots = { 1372 'due': [], 1373 'boosters': [] 1374 } 1375 for due_shot in self.__db_cache['vaccinations']['missing']['due']: 1376 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']: 1377 filtered_shots['due'].append(due_shot) 1378 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']: 1379 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']: 1380 filtered_shots['boosters'].append(due_shot) 1381 return filtered_shots
1382 #--------------------------------------------------------
1383 - def add_vaccination(self, vaccine=None, episode=None):
1384 """Creates a new vaccination entry in backend.""" 1385 return gmVaccination.create_vaccination ( 1386 patient_id = self.pk_patient, 1387 episode_id = episode['pk_episode'], 1388 encounter_id = self.current_encounter['pk_encounter'], 1389 staff_id = _me['pk_staff'], 1390 vaccine = vaccine 1391 )
1392 #------------------------------------------------------------------ 1393 # API: encounters 1394 #------------------------------------------------------------------
1395 - def _get_current_encounter(self):
1396 return self.__encounter
1397
1398 - def _set_current_encounter(self, encounter):
1399 1400 # first ever setting ? 1401 if self.__encounter is None: 1402 _log.debug('first setting of active encounter in this clinical record instance') 1403 else: 1404 _log.debug('switching of active encounter') 1405 # fail if the currently active encounter has unsaved changes 1406 if self.__encounter.is_modified(): 1407 _log.debug('unsaved changes in active encounter, cannot switch to another one') 1408 raise ValueError('unsaved changes in active encounter, cannot switch to another one') 1409 1410 # set the currently active encounter and announce that change 1411 self.__encounter = encounter 1412 gmDispatcher.send(u'current_encounter_switched') 1413 1414 # this will trigger another signal "encounter_mod_db" 1415 self.__encounter.set_active(staff_id = _me['pk_staff']) 1416 1417 return True
1418 1419 current_encounter = property(_get_current_encounter, _set_current_encounter) 1420 active_encounter = property(_get_current_encounter, _set_current_encounter) 1421 #------------------------------------------------------------------
1423 # 1) "very recent" encounter recorded ? 1424 if self.__activate_very_recent_encounter(): 1425 return True 1426 # 2) "fairly recent" encounter recorded ? 1427 if self.__activate_fairly_recent_encounter(): 1428 return True 1429 self.start_new_encounter() 1430 return True
1431 #------------------------------------------------------------------
1433 """Try to attach to a "very recent" encounter if there is one. 1434 1435 returns: 1436 False: no "very recent" encounter, create new one 1437 True: success 1438 """ 1439 cfg_db = gmCfg.cCfgSQL() 1440 min_ttl = cfg_db.get2 ( 1441 option = u'encounter.minimum_ttl', 1442 workplace = _here.active_workplace, 1443 bias = u'user', 1444 default = u'1 hour 30 minutes' 1445 ) 1446 cmd = u""" 1447 SELECT pk_encounter 1448 from clin.v_most_recent_encounters 1449 WHERE 1450 pk_patient = %s 1451 and 1452 last_affirmed > (now() - %s::interval)""" 1453 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}]) 1454 # none found 1455 if len(enc_rows) == 0: 1456 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl) 1457 return False 1458 # attach to existing 1459 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0]) 1460 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0]) 1461 return True
1462 #------------------------------------------------------------------
1464 """Try to attach to a "fairly recent" encounter if there is one. 1465 1466 returns: 1467 False: no "fairly recent" encounter, create new one 1468 True: success 1469 """ 1470 cfg_db = gmCfg.cCfgSQL() 1471 min_ttl = cfg_db.get2 ( 1472 option = u'encounter.minimum_ttl', 1473 workplace = _here.active_workplace, 1474 bias = u'user', 1475 default = u'1 hour 30 minutes' 1476 ) 1477 max_ttl = cfg_db.get2 ( 1478 option = u'encounter.maximum_ttl', 1479 workplace = _here.active_workplace, 1480 bias = u'user', 1481 default = u'6 hours' 1482 ) 1483 cmd = u""" 1484 SELECT pk_encounter 1485 from clin.v_most_recent_encounters 1486 WHERE 1487 pk_patient=%s 1488 and 1489 last_affirmed between (now() - %s::interval) and (now() - %s::interval)""" 1490 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}]) 1491 # none found 1492 if len(enc_rows) == 0: 1493 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl)) 1494 return False 1495 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0]) 1496 # ask user whether to attach or not 1497 cmd = u""" 1498 SELECT title, firstnames, lastnames, gender, dob 1499 from dem.v_basic_person WHERE pk_identity=%s""" 1500 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}]) 1501 pat = pats[0] 1502 pat_str = u'%s %s %s (%s), %s [#%s]' % ( 1503 gmTools.coalesce(pat[0], u'')[:5], 1504 pat[1][:15], 1505 pat[2][:15], 1506 pat[3], 1507 pat[4].strftime('%x'), 1508 self.pk_patient 1509 ) 1510 enc = gmI18N.get_encoding() 1511 msg = _( 1512 '%s\n' 1513 '\n' 1514 "This patient's chart was worked on only recently:\n" 1515 '\n' 1516 ' %s %s - %s (%s)\n' 1517 '\n' 1518 ' Request: %s\n' 1519 ' Outcome: %s\n' 1520 '\n' 1521 'Do you want to continue that consultation\n' 1522 'or do you want to start a new one ?\n' 1523 ) % ( 1524 pat_str, 1525 encounter['started'].strftime('%x').decode(enc), 1526 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'), 1527 encounter['l10n_type'], 1528 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')), 1529 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')), 1530 ) 1531 attach = False 1532 try: 1533 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter) 1534 except: 1535 _log.exception('cannot ask user for guidance, not attaching to existing encounter') 1536 return False 1537 if not attach: 1538 return False 1539 1540 # attach to existing 1541 self.current_encounter = encounter 1542 1543 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0]) 1544 return True
1545 #------------------------------------------------------------------
1546 - def start_new_encounter(self):
1547 cfg_db = gmCfg.cCfgSQL() 1548 # FIXME: look for MRU/MCU encounter type config here 1549 enc_type = cfg_db.get2 ( 1550 option = u'encounter.default_type', 1551 workplace = _here.active_workplace, 1552 bias = u'user', 1553 default = u'in surgery' 1554 ) 1555 self.current_encounter = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type) 1556 _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
1557 #------------------------------------------------------------------
1558 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None):
1559 """Retrieves patient's encounters. 1560 1561 id_list - PKs of encounters to fetch 1562 since - initial date for encounter items, DateTime instance 1563 until - final date for encounter items, DateTime instance 1564 episodes - PKs of the episodes the encounters belong to (many-to-many relation) 1565 issues - PKs of the health issues the encounters belong to (many-to-many relation) 1566 1567 NOTE: if you specify *both* issues and episodes 1568 you will get the *aggregate* of all encounters even 1569 if the episodes all belong to the health issues listed. 1570 IOW, the issues broaden the episode list rather than 1571 the episode list narrowing the episodes-from-issues 1572 list. 1573 Rationale: If it was the other way round it would be 1574 redundant to specify the list of issues at all. 1575 """ 1576 # fetch all encounters for patient 1577 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started" 1578 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True) 1579 encounters = [] 1580 for r in rows: 1581 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'})) 1582 1583 # we've got the encounters, start filtering 1584 filtered_encounters = [] 1585 filtered_encounters.extend(encounters) 1586 if id_list is not None: 1587 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters) 1588 if since is not None: 1589 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters) 1590 if until is not None: 1591 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters) 1592 1593 if (issues is not None) and (len(issues) > 0): 1594 1595 issues = tuple(issues) 1596 1597 # Syan attests that an explicit union of child tables is way faster 1598 # as there seem to be problems with parent table expansion and use 1599 # of child table indexes, so if get_encounter() runs very slow on 1600 # your machine use the lines below 1601 1602 # rows = gmPG.run_ro_query('historica', cClinicalRecord._clin_root_item_children_union_query, None, (tuple(issues),)) 1603 # if rows is None: 1604 # _log.error('cannot load encounters for issues [%s] (patient [%s])' % (str(issues), self.pk_patient)) 1605 # else: 1606 # enc_ids = map(lambda x:x[0], rows) 1607 # filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters) 1608 1609 # this problem seems fixed for us as of PostgreSQL 8.2 :-) 1610 1611 # however, this seems like the proper approach: 1612 # - find episodes corresponding to the health issues in question 1613 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s" 1614 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}]) 1615 epi_ids = map(lambda x:x[0], rows) 1616 if episodes is None: 1617 episodes = [] 1618 episodes.extend(epi_ids) 1619 1620 if (episodes is not None) and (len(episodes) > 0): 1621 1622 episodes = tuple(episodes) 1623 1624 # if the episodes to filter by belong to the patient in question so will 1625 # the encounters found with them - hence we don't need a WHERE on the patient ... 1626 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s" 1627 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}]) 1628 enc_ids = map(lambda x:x[0], rows) 1629 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters) 1630 1631 return filtered_encounters
1632 #--------------------------------------------------------
1633 - def get_first_encounter(self, issue_id=None, episode_id=None):
1634 """Retrieves first encounter for a particular issue and/or episode 1635 1636 issue_id - First encounter associated health issue 1637 episode - First encounter associated episode 1638 """ 1639 # FIXME: use direct query 1640 1641 if issue_id is None: 1642 issues = None 1643 else: 1644 issues = [issue_id] 1645 1646 if episode_id is None: 1647 episodes = None 1648 else: 1649 episodes = [episode_id] 1650 1651 encounters = self.get_encounters(issues=issues, episodes=episodes) 1652 if len(encounters) == 0: 1653 return None 1654 1655 # FIXME: this does not scale particularly well, I assume 1656 encounters.sort(lambda x,y: cmp(x['started'], y['started'])) 1657 return encounters[0]
1658 #--------------------------------------------------------
1659 - def get_last_encounter(self, issue_id=None, episode_id=None):
1660 """Retrieves last encounter for a concrete issue and/or episode 1661 1662 issue_id - Last encounter associated health issue 1663 episode_id - Last encounter associated episode 1664 """ 1665 # FIXME: use direct query 1666 1667 if issue_id is None: 1668 issues = None 1669 else: 1670 issues = [issue_id] 1671 1672 if episode_id is None: 1673 episodes = None 1674 else: 1675 episodes = [episode_id] 1676 1677 encounters = self.get_encounters(issues=issues, episodes=episodes) 1678 if len(encounters) == 0: 1679 return None 1680 1681 # FIXME: this does not scale particularly well, I assume 1682 encounters.sort(lambda x,y: cmp(x['started'], y['started'])) 1683 return encounters[-1]
1684 #------------------------------------------------------------------
1685 - def get_last_but_one_encounter(self, issue_id=None, episode_id=None):
1686 1687 args = {'pat': self.pk_patient} 1688 1689 if (issue_id is None) and (episode_id is None): 1690 1691 cmd = u""" 1692 SELECT * FROM clin.v_pat_encounters 1693 WHERE pk_patient = %(pat)s 1694 order by started desc 1695 limit 2 1696 """ 1697 else: 1698 where_parts = [] 1699 1700 if issue_id is not None: 1701 where_parts.append(u'pk_health_issue = %(issue)s') 1702 args['issue'] = issue_id 1703 1704 if episode_id is not None: 1705 where_parts.append(u'pk_episode = %(epi)s') 1706 args['epi'] = episode_id 1707 1708 cmd = u""" 1709 SELECT * 1710 from clin.v_pat_encounters 1711 WHERE 1712 pk_patient = %%(pat)s 1713 and 1714 pk_encounter in ( 1715 SELECT distinct pk_encounter 1716 from clin.v_pat_narrative 1717 WHERE 1718 %s 1719 ) 1720 order by started desc 1721 limit 2 1722 """ % u' and '.join(where_parts) 1723 1724 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1725 1726 if len(rows) == 0: 1727 return None 1728 1729 # just one encounter within the above limits 1730 if len(rows) == 1: 1731 # is it the current encounter ? 1732 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']: 1733 # yes 1734 return None 1735 # no 1736 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'}) 1737 1738 # more than one encounter 1739 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']: 1740 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'}) 1741 1742 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1743 #------------------------------------------------------------------
1744 - def remove_empty_encounters(self):
1745 cfg_db = gmCfg.cCfgSQL() 1746 ttl = cfg_db.get2 ( 1747 option = u'encounter.ttl_if_empty', 1748 workplace = _here.active_workplace, 1749 bias = u'user', 1750 default = u'1 week' 1751 ) 1752 1753 # FIXME: this should be done async 1754 cmd = u""" 1755 delete FROM clin.encounter 1756 WHERE 1757 clin.encounter.fk_patient = %(pat)s 1758 and 1759 age(clin.encounter.last_affirmed) > %(ttl)s::interval 1760 and 1761 not exists (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk) 1762 and 1763 not exists (SELECT 1 FROM blobs.doc_med WHERE fk_encounter = clin.encounter.pk) 1764 and 1765 not exists (SELECT 1 FROM clin.episode WHERE fk_encounter = clin.encounter.pk) 1766 and 1767 not exists (SELECT 1 FROM clin.health_issue WHERE fk_encounter = clin.encounter.pk) 1768 and 1769 not exists (SELECT 1 FROM clin.operation WHERE fk_encounter = clin.encounter.pk) 1770 and 1771 not exists (SELECT 1 FROM clin.allergy_state WHERE fk_encounter = clin.encounter.pk) 1772 """ 1773 try: 1774 rows, idx = gmPG2.run_rw_queries(queries = [{ 1775 'cmd': cmd, 1776 'args': {'pat': self.pk_patient, 'ttl': ttl} 1777 }]) 1778 except: 1779 _log.exception('error deleting empty encounters') 1780 1781 return True
1782 #------------------------------------------------------------------ 1783 # measurements API 1784 #------------------------------------------------------------------ 1785 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
1786 - def get_test_types_for_results(self):
1787 """Retrieve data about test types for which this patient has results.""" 1788 1789 cmd = u""" 1790 SELECT * FROM ( 1791 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name 1792 FROM clin.v_test_results 1793 WHERE pk_patient = %(pat)s 1794 ) AS foo 1795 ORDER BY clin_when desc, unified_name 1796 """ 1797 args = {'pat': self.pk_patient} 1798 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1799 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
1800 #------------------------------------------------------------------
1801 - def get_test_types_details(self):
1802 """Retrieve details on tests grouped under unified names for this patient's results.""" 1803 cmd = u""" 1804 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in ( 1805 SELECT distinct on (unified_name, unified_abbrev) pk_test_type 1806 from clin.v_test_results 1807 WHERE pk_patient = %(pat)s 1808 ) 1809 order by unified_name""" 1810 args = {'pat': self.pk_patient} 1811 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1812 return rows, idx
1813 #------------------------------------------------------------------
1814 - def get_dates_for_results(self):
1815 """Get the dates for which we have results.""" 1816 cmd = u""" 1817 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen 1818 from clin.v_test_results 1819 WHERE pk_patient = %(pat)s 1820 order by cwhen desc""" 1821 args = {'pat': self.pk_patient} 1822 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1823 return rows
1824 #------------------------------------------------------------------
1825 - def get_test_results_by_date(self, encounter=None, episodes=None):
1826 1827 cmd = u""" 1828 SELECT *, xmin_test_result FROM clin.v_test_results 1829 WHERE pk_patient = %(pat)s 1830 order by clin_when desc, pk_episode, unified_name""" 1831 args = {'pat': self.pk_patient} 1832 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1833 1834 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ] 1835 1836 if episodes is not None: 1837 tests = [ t for t in tests if t['pk_episode'] in episodes ] 1838 1839 if encounter is not None: 1840 tests = [ t for t in tests if t['pk_encounter'] == encounter ] 1841 1842 return tests
1843 #------------------------------------------------------------------
1844 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1845 1846 try: 1847 epi = int(episode) 1848 except: 1849 epi = episode['pk_episode'] 1850 1851 try: 1852 type = int(type) 1853 except: 1854 type = type['pk_test_type'] 1855 1856 if intended_reviewer is None: 1857 from Gnumed.business import gmPerson 1858 intended_reviewer = _me['pk_staff'] 1859 1860 tr = gmPathLab.create_test_result ( 1861 encounter = self.current_encounter['pk_encounter'], 1862 episode = epi, 1863 type = type, 1864 intended_reviewer = intended_reviewer, 1865 val_num = val_num, 1866 val_alpha = val_alpha, 1867 unit = unit 1868 ) 1869 1870 return tr
1871 #------------------------------------------------------------------ 1872 #------------------------------------------------------------------
1873 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1874 """Retrieves lab result clinical items. 1875 1876 limit - maximum number of results to retrieve 1877 since - initial date 1878 until - final date 1879 encounters - list of encounters 1880 episodes - list of episodes 1881 issues - list of health issues 1882 """ 1883 try: 1884 return self.__db_cache['lab results'] 1885 except KeyError: 1886 pass 1887 self.__db_cache['lab results'] = [] 1888 if limit is None: 1889 lim = '' 1890 else: 1891 # only use limit if all other constraints are None 1892 if since is None and until is None and encounters is None and episodes is None and issues is None: 1893 lim = "limit %s" % limit 1894 else: 1895 lim = '' 1896 1897 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim 1898 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient) 1899 if rows is None: 1900 return False 1901 for row in rows: 1902 lab_row = { 1903 'pk_field': 'pk_result', 1904 'idx': idx, 1905 'data': row 1906 } 1907 lab_result = gmPathLab.cLabResult(row=lab_row) 1908 self.__db_cache['lab results'].append(lab_result) 1909 1910 # ok, let's constrain our list 1911 filtered_lab_results = [] 1912 filtered_lab_results.extend(self.__db_cache['lab results']) 1913 if since is not None: 1914 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results) 1915 if until is not None: 1916 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results) 1917 if issues is not None: 1918 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results) 1919 if episodes is not None: 1920 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results) 1921 if encounters is not None: 1922 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results) 1923 return filtered_lab_results
1924 #------------------------------------------------------------------
1925 - def get_lab_request(self, pk=None, req_id=None, lab=None):
1926 # FIXME: verify that it is our patient ? ... 1927 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab) 1928 return req
1929 #------------------------------------------------------------------
1930 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
1931 if encounter_id is None: 1932 encounter_id = self.current_encounter['pk_encounter'] 1933 status, data = gmPathLab.create_lab_request( 1934 lab=lab, 1935 req_id=req_id, 1936 pat_id=self.pk_patient, 1937 encounter_id=encounter_id, 1938 episode_id=episode_id 1939 ) 1940 if not status: 1941 _log.error(str(data)) 1942 return None 1943 return data
1944 #============================================================ 1945 # convenience functions 1946 #------------------------------------------------------------
1947 -def set_func_ask_user(a_func = None):
1948 if a_func is not None: 1949 global _func_ask_user 1950 _func_ask_user = a_func
1951 #============================================================ 1952 # main 1953 #------------------------------------------------------------ 1954 if __name__ == "__main__": 1955 1956 from Gnumed.pycommon import gmLog2 1957 1958 #-----------------------------------------
1959 - def test_allergy_state():
1960 emr = cClinicalRecord(aPKey=1) 1961 state = emr.allergy_state 1962 print "allergy state is:", state 1963 1964 print "setting state to 0" 1965 emr.allergy_state = 0 1966 1967 print "setting state to None" 1968 emr.allergy_state = None 1969 1970 print "setting state to 'abc'" 1971 emr.allergy_state = 'abc'
1972 #-----------------------------------------
1973 - def test_get_test_names():
1974 emr = cClinicalRecord(aPKey=12) 1975 rows = emr.get_test_types_for_results() 1976 print "test result names:" 1977 for row in rows: 1978 print row
1979 #-----------------------------------------
1980 - def test_get_dates_for_results():
1981 emr = cClinicalRecord(aPKey=12) 1982 rows = emr.get_dates_for_results() 1983 print "test result dates:" 1984 for row in rows: 1985 print row
1986 #-----------------------------------------
1987 - def test_get_measurements():
1988 emr = cClinicalRecord(aPKey=12) 1989 rows, idx = emr.get_measurements_by_date() 1990 print "test results:" 1991 for row in rows: 1992 print row
1993 #-----------------------------------------
1994 - def test_get_test_results_by_date():
1995 emr = cClinicalRecord(aPKey=12) 1996 tests = emr.get_test_results_by_date() 1997 print "test results:" 1998 for test in tests: 1999 print test
2000 #-----------------------------------------
2001 - def test_get_test_types_details():
2002 emr = cClinicalRecord(aPKey=12) 2003 rows, idx = emr.get_test_types_details() 2004 print "test type details:" 2005 for row in rows: 2006 print row
2007 #-----------------------------------------
2008 - def test_get_statistics():
2009 emr = cClinicalRecord(aPKey=12) 2010 for key, item in emr.get_statistics().iteritems(): 2011 print key, ":", item
2012 #-----------------------------------------
2013 - def test_get_problems():
2014 emr = cClinicalRecord(aPKey=12) 2015 2016 probs = emr.get_problems() 2017 print "normal probs (%s):" % len(probs) 2018 for p in probs: 2019 print u'%s (%s)' % (p['problem'], p['type']) 2020 2021 probs = emr.get_problems(include_closed_episodes=True) 2022 print "probs + closed episodes (%s):" % len(probs) 2023 for p in probs: 2024 print u'%s (%s)' % (p['problem'], p['type']) 2025 2026 probs = emr.get_problems(include_irrelevant_issues=True) 2027 print "probs + issues (%s):" % len(probs) 2028 for p in probs: 2029 print u'%s (%s)' % (p['problem'], p['type']) 2030 2031 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True) 2032 print "probs + issues + epis (%s):" % len(probs) 2033 for p in probs: 2034 print u'%s (%s)' % (p['problem'], p['type'])
2035 #-----------------------------------------
2036 - def test_add_test_result():
2037 emr = cClinicalRecord(aPKey=12) 2038 tr = emr.add_test_result ( 2039 episode = 1, 2040 intended_reviewer = 1, 2041 type = 1, 2042 val_num = 75, 2043 val_alpha = u'somewhat obese', 2044 unit = u'kg' 2045 ) 2046 print tr
2047 #-----------------------------------------
2048 - def test_get_most_recent_episode():
2049 emr = cClinicalRecord(aPKey=12) 2050 print emr.get_most_recent_episode(issue = 2)
2051 #-----------------------------------------
2052 - def test_get_almost_recent_encounter():
2053 emr = cClinicalRecord(aPKey=12) 2054 print emr.get_last_encounter(issue_id=2) 2055 print emr.get_last_but_one_encounter(issue_id=2)
2056 #-----------------------------------------
2057 - def test_get_meds():
2058 emr = cClinicalRecord(aPKey=12) 2059 for med in emr.get_current_substance_intake(): 2060 print med
2061 #----------------------------------------- 2062 if (len(sys.argv) > 0) and (sys.argv[1] == 'test'): 2063 #test_allergy_state() 2064 test_get_test_names() 2065 #test_get_dates_for_results() 2066 #test_get_measurements() 2067 #test_get_test_results_by_date() 2068 #test_get_test_types_details() 2069 #test_get_statistics() 2070 #test_get_problems() 2071 #test_add_test_result() 2072 #test_get_most_recent_episode() 2073 #test_get_almost_recent_encounter() 2074 #test_get_meds() 2075 2076 # emr = cClinicalRecord(aPKey = 12) 2077 2078 # # Vacc regimes 2079 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus']) 2080 # print '\nVaccination regimes: ' 2081 # for a_regime in vacc_regimes: 2082 # pass 2083 # #print a_regime 2084 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10) 2085 # #print vacc_regime 2086 2087 # # vaccination regimes and vaccinations for regimes 2088 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus']) 2089 # print 'Vaccinations for the regime:' 2090 # for a_scheduled_vacc in scheduled_vaccs: 2091 # pass 2092 # #print ' %s' %(a_scheduled_vacc) 2093 2094 # # vaccination next shot and booster 2095 # vaccinations = emr.get_vaccinations() 2096 # for a_vacc in vaccinations: 2097 # 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']) 2098 2099 # # first and last encounters 2100 # first_encounter = emr.get_first_encounter(issue_id = 1) 2101 # print '\nFirst encounter: ' + str(first_encounter) 2102 # last_encounter = emr.get_last_encounter(episode_id = 1) 2103 # print '\nLast encounter: ' + str(last_encounter) 2104 # print '' 2105 2106 # # lab results 2107 # lab = emr.get_lab_results() 2108 # lab_file = open('lab-data.txt', 'wb') 2109 # for lab_result in lab: 2110 # lab_file.write(str(lab_result)) 2111 # lab_file.write('\n') 2112 # lab_file.close() 2113 2114 #dump = record.get_missing_vaccinations() 2115 #f = open('vaccs.lst', 'wb') 2116 #if dump is not None: 2117 # print "=== due ===" 2118 # f.write("=== due ===\n") 2119 # for row in dump['due']: 2120 # print row 2121 # f.write(repr(row)) 2122 # f.write('\n') 2123 # print "=== overdue ===" 2124 # f.write("=== overdue ===\n") 2125 # for row in dump['overdue']: 2126 # print row 2127 # f.write(repr(row)) 2128 # f.write('\n') 2129 #f.close() 2130 #============================================================ 2131 # $Log: gmClinicalRecord.py,v $ 2132 # Revision 1.308 2009/12/03 17:44:18 ncq 2133 # - rewrite get-test-types-for-results 2134 # 2135 # Revision 1.307 2009/12/01 21:47:52 ncq 2136 # - improved naming 2137 # 2138 # Revision 1.306 2009/11/28 18:21:51 ncq 2139 # - normalize turning things into problems 2140 # 2141 # Revision 1.305 2009/11/28 18:14:36 ncq 2142 # - lotsa SQL writ cleanup as suggested by David Merz 2143 # - fix statistics 2144 # - get-problems enhancement 2145 # - create-patient-consumed-substance -> create-substance-intake 2146 # - more tests 2147 # 2148 # Revision 1.304 2009/11/24 19:55:03 ncq 2149 # - cleanup 2150 # - much enhanced get-problems() but not done yet 2151 # 2152 # Revision 1.303 2009/11/15 01:04:08 ncq 2153 # - add missing self 2154 # 2155 # Revision 1.302 2009/11/14 22:46:31 ncq 2156 # - better naming 2157 # 2158 # Revision 1.301 2009/11/13 20:47:16 ncq 2159 # - add-performed-procedure 2160 # 2161 # Revision 1.300 2009/11/06 15:00:50 ncq 2162 # - add substances to stats 2163 # - enhance get-current-substance-intake 2164 # - add-consumed-substance 2165 # 2166 # Revision 1.299 2009/09/23 14:28:13 ncq 2167 # - support procedures 2168 # - create-health-issue can now take advantage of patient pk again 2169 # 2170 # Revision 1.298 2009/09/17 21:51:26 ncq 2171 # - add performed procedures support 2172 # 2173 # Revision 1.297 2009/09/15 15:26:06 ncq 2174 # - rework handling of setting active encounter and listening 2175 # to encounter changes in the DB 2176 # - no more get-active-encounter() 2177 # 2178 # Revision 1.296 2009/09/01 22:13:24 ncq 2179 # - cleanup 2180 # 2181 # Revision 1.295 2009/07/02 20:56:49 ncq 2182 # - cleanup 2183 # 2184 # Revision 1.294 2009/07/01 17:14:40 ncq 2185 # - cleanup 2186 # 2187 # Revision 1.293 2009/06/29 14:58:29 ncq 2188 # - fix emr stats getter which returned stats over *all* patients :-) 2189 # 2190 # Revision 1.292 2009/06/22 09:20:43 ncq 2191 # - problem statistics: 2192 # don't count open episode under active issue 2193 # again as per list discussion 2194 # 2195 # Revision 1.291 2009/06/04 14:29:18 ncq 2196 # - re-import lost adjustments 2197 # 2198 # Revision 1.291 2009/05/28 10:44:52 ncq 2199 # - adjust to test table changes 2200 # 2201 # Revision 1.290 2009/05/26 09:16:33 ncq 2202 # - comment on cursors 2203 # 2204 # Revision 1.289 2009/05/12 12:05:04 ncq 2205 # - start medication handling + test 2206 # 2207 # Revision 1.288 2009/04/16 12:47:00 ncq 2208 # - fix logic when getting last-but-one encounter 2209 # 2210 # Revision 1.287 2009/04/13 11:00:08 ncq 2211 # - proper property current_encounter/active_encounter and self-use it 2212 # - properly detect current encounter modification 2213 # - broadcast current encounter switching 2214 # 2215 # Revision 1.286 2009/04/03 11:06:07 ncq 2216 # - filter stays by issue 2217 # - include stays in summary and statistics 2218 # 2219 # Revision 1.285 2009/04/03 10:38:59 ncq 2220 # - allow filtering by episode when getting hospital stays 2221 # 2222 # Revision 1.284 2009/04/03 09:24:42 ncq 2223 # - start hospital stay API 2224 # 2225 # Revision 1.283 2009/02/20 15:41:42 ncq 2226 # - fix remove_empty_encounters 2227 # 2228 # Revision 1.282 2009/01/02 11:34:35 ncq 2229 # - cleanup 2230 # - pk_patient earlier in __init__ 2231 # - listen to db encounter mods, check for current encounter mod, 2232 # send send specific signal 2233 # 2234 # Revision 1.281 2008/12/27 15:49:42 ncq 2235 # - add_notes 2236 # 2237 # Revision 1.280 2008/12/17 21:52:11 ncq 2238 # - filter narrative by provider 2239 # 2240 # Revision 1.279 2008/12/12 16:34:13 ncq 2241 # - HIPAA: log "access to the EMR" 2242 # 2243 # Revision 1.278 2008/12/09 23:19:15 ncq 2244 # - adjust to blobs.doc_med changes 2245 # 2246 # Revision 1.277 2008/11/24 11:06:24 ncq 2247 # - no more patient_id in create_episode 2248 # 2249 # Revision 1.276 2008/11/20 18:39:40 ncq 2250 # - get_last_but_one_encounter 2251 # 2252 # Revision 1.275 2008/11/03 10:27:41 ncq 2253 # - no more patient_id in create_health_issue 2254 # 2255 # Revision 1.274 2008/10/26 01:21:22 ncq 2256 # - improve EMR search result 2257 # 2258 # Revision 1.273 2008/10/22 12:04:21 ncq 2259 # - use %x in strftime 2260 # 2261 # Revision 1.272 2008/10/12 15:12:52 ncq 2262 # - adapt to reworked allergy support and adjust test 2263 # - statistics now says encounters, not visits 2264 # - improved wording for known problems/clinically relevant 2265 # 2266 # Revision 1.271 2008/09/02 18:58:26 ncq 2267 # - fk_patient dropped from clin.health_issue 2268 # 2269 # Revision 1.270 2008/08/15 15:55:41 ncq 2270 # - comment on row_version 2271 # 2272 # Revision 1.269 2008/07/12 15:19:16 ncq 2273 # - improve summary formatting 2274 # - fix get_most_recent_episode plus test 2275 # 2276 # Revision 1.268 2008/07/07 11:33:15 ncq 2277 # - improve "continue encounter ?" message 2278 # 2279 # Revision 1.267 2008/06/24 16:53:24 ncq 2280 # - enhance get_test_results_by_date to filter by episode/encounter 2281 # 2282 # Revision 1.266 2008/06/16 15:01:01 ncq 2283 # - test suite cleanup 2284 # - add_test_result 2285 # 2286 # Revision 1.265 2008/05/19 16:22:55 ncq 2287 # - format_statistics/summary() 2288 # 2289 # Revision 1.264 2008/05/19 15:43:17 ncq 2290 # - get_summary -> _statistics 2291 # - get all statistics in one database roundtrip 2292 # - fix test suite 2293 # 2294 # Revision 1.263 2008/04/22 21:12:08 ncq 2295 # - get_test_results_by_date and test 2296 # 2297 # Revision 1.262 2008/04/11 23:07:08 ncq 2298 # - fix remove_empty_encounters() 2299 # 2300 # Revision 1.261 2008/03/29 16:04:31 ncq 2301 # - retrieve test results ordered by day-truncated date 2302 # 2303 # Revision 1.260 2008/03/20 15:28:17 ncq 2304 # - get_test_types_details() w/ test 2305 # 2306 # Revision 1.259 2008/03/17 14:53:57 ncq 2307 # - improve deletion of empty encounters 2308 # - get_test_types_for_results() 2309 # - get_dates_for_results() 2310 # - get_measurements_by_date() 2311 # - improve tests 2312 # 2313 # Revision 1.258 2008/03/05 22:24:31 ncq 2314 # - support fk_encounter in issue and episode creation 2315 # 2316 # Revision 1.257 2008/02/25 16:58:03 ncq 2317 # - use new logging 2318 # 2319 # Revision 1.256 2008/01/30 13:34:49 ncq 2320 # - switch to std lib logging 2321 # 2322 # Revision 1.255 2008/01/16 19:43:28 ncq 2323 # - use backend configured default encounter type in create_encounter() 2324 # - configurable empty encounter removal age 2325 # 2326 # Revision 1.254 2007/12/26 18:31:53 ncq 2327 # - remove reference to old PG library 2328 # 2329 # Revision 1.253 2007/12/11 12:59:11 ncq 2330 # - cleanup and explicit signal handling 2331 # 2332 # Revision 1.252 2007/10/25 12:26:05 ncq 2333 # - cleanup 2334 # 2335 # Revision 1.251 2007/10/25 12:15:39 ncq 2336 # - we don't send allergy_updated() signal anymore, the backend does it for us 2337 # 2338 # Revision 1.250 2007/10/08 13:22:42 ncq 2339 # - avoid circular import 2340 # 2341 # Revision 1.249 2007/10/08 13:17:55 ncq 2342 # - missing gmPerson import 2343 # 2344 # Revision 1.248 2007/10/07 12:22:51 ncq 2345 # - workplace property now on gmSurgery.gmCurrentPractice() borg 2346 # 2347 # Revision 1.247 2007/09/24 22:07:32 ncq 2348 # - be careful about deleting empty encounters - make sure they are at least 1 week old 2349 # 2350 # Revision 1.246 2007/09/07 10:55:40 ncq 2351 # - order by in get_clin_narrative() 2352 # 2353 # Revision 1.245 2007/06/28 12:30:05 ncq 2354 # - uncache get_clin_narrative access 2355 # 2356 # Revision 1.244 2007/06/19 12:40:40 ncq 2357 # - cleanup 2358 # 2359 # Revision 1.243 2007/04/25 21:59:15 ncq 2360 # - improve message on very-recent-encounter 2361 # 2362 # Revision 1.242 2007/04/06 23:12:58 ncq 2363 # - move remove_empty_encounters() from cleanup() to init() 2364 # 2365 # Revision 1.241 2007/04/02 18:32:51 ncq 2366 # - start_new_encounter() 2367 # 2368 # Revision 1.240 2007/04/01 15:25:25 ncq 2369 # - safely get encoding 2370 # 2371 # Revision 1.239 2007/03/31 21:18:13 ncq 2372 # - fix get_episodes_by_encounter() 2373 # 2374 # Revision 1.238 2007/03/26 16:49:26 ncq 2375 # - settle on health issue/episode naming for newly added allergies 2376 # 2377 # Revision 1.237 2007/03/23 15:01:14 ncq 2378 # - cleanup get_allergies() API 2379 # 2380 # Revision 1.236 2007/03/21 08:12:14 ncq 2381 # - allergic_state property 2382 # - send allergy_modified() signal 2383 # - make cClinicalRecord a new-style class 2384 # 2385 # Revision 1.235 2007/03/18 13:01:16 ncq 2386 # - re-add lost 1.235 2387 # - add ensure_has_allergic_state() 2388 # - remove allergies cache 2389 # - add delete_allergy() 2390 # 2391 # Revision 1.235 2007/03/12 12:23:54 ncq 2392 # - create_allergy() now throws exceptions so deal with that 2393 # 2394 # Revision 1.234 2007/03/02 15:29:33 ncq 2395 # - need to decode() strftime() output to u'' 2396 # 2397 # Revision 1.233 2007/02/19 14:06:56 ncq 2398 # - add_health_issue() should return True if health_issue already exists 2399 # 2400 # Revision 1.232 2007/02/17 14:08:52 ncq 2401 # - gmPerson.gmCurrentProvider.workplace now a property 2402 # 2403 # Revision 1.231 2007/02/09 14:58:43 ncq 2404 # - honor <pk_encounter> in get_episodes_by_encounter 2405 # instead of always assuming the current encounter 2406 # 2407 # Revision 1.230 2007/01/31 23:30:33 ncq 2408 # - fix __activate_fairly_recent_encounter() 2409 # 2410 # Revision 1.229 2007/01/29 11:58:53 ncq 2411 # - cleanup 2412 # - let _ask_user_func fail so programmers knows early 2413 # 2414 # Revision 1.228 2007/01/09 18:01:12 ncq 2415 # - error policy is now exceptions 2416 # 2417 # Revision 1.227 2007/01/09 12:55:29 ncq 2418 # - create_episode() now always takes patient fk 2419 # 2420 # Revision 1.226 2006/12/25 22:48:09 ncq 2421 # - comment on PG 8.2 fixing child table index scans for us 2422 # 2423 # Revision 1.225 2006/12/13 13:41:33 ncq 2424 # - add remove_empty_encounters() and call from cleanup() 2425 # 2426 # Revision 1.224 2006/12/13 00:30:43 ncq 2427 # - fix get_health_issues() id_list sanity check insanity 2428 # 2429 # Revision 1.223 2006/11/28 20:39:30 ncq 2430 # - improve problem2*() 2431 # - de-cache get_health_issues() 2432 # 2433 # Revision 1.222 2006/11/26 15:43:41 ncq 2434 # - keys in get_summary() shouldn't be _() 2435 # 2436 # Revision 1.221 2006/11/24 14:15:20 ncq 2437 # - u'' one query 2438 # 2439 # Revision 1.220 2006/11/20 18:22:39 ncq 2440 # - invalidate problem cache when health issues are updated, too 2441 # - do not use cache for now when getting problems/episodes 2442 # 2443 # Revision 1.219 2006/11/19 10:50:35 ncq 2444 # - fix get_episodes_by_encounter() 2445 # 2446 # Revision 1.218 2006/11/14 16:55:05 ncq 2447 # - make sure issues/episodes are tuple()s in get_encounters() 2448 # 2449 # Revision 1.217 2006/11/05 17:53:15 ncq 2450 # - one more get_col_idx 2451 # 2452 # Revision 1.216 2006/11/05 17:01:50 ncq 2453 # - fix some queries to produce proper rows 2454 # - var name fixes in get_encounters() 2455 # 2456 # Revision 1.215 2006/11/05 16:28:24 ncq 2457 # - fix a few double uses of variable row 2458 # 2459 # Revision 1.214 2006/11/05 16:20:49 ncq 2460 # - remove 2 printk()s 2461 # 2462 # Revision 1.213 2006/11/05 15:59:16 ncq 2463 # - make encounter ttl configurable 2464 # - audit clinical data retrieval and never appear to succeed if something fails 2465 # - this will show up some more exceptions which were thought harmless before and therefor masked out 2466 # - u'' some queries 2467 # - clarify get_encounters() 2468 # - remove cruft 2469 # - more gmPG -> gmPG2 2470 # 2471 # Revision 1.212 2006/10/28 15:01:21 ncq 2472 # - speed up allergy, encounter fetching 2473 # - unicode() queries 2474 # 2475 # Revision 1.211 2006/10/25 07:46:44 ncq 2476 # - Format() -> strftime() since datetime.datetime does not have .Format() 2477 # 2478 # Revision 1.210 2006/10/25 07:17:40 ncq 2479 # - no more gmPG 2480 # - no more cClinItem 2481 # 2482 # Revision 1.209 2006/10/24 13:14:07 ncq 2483 # - must import gmPG2, too, now 2484 # 2485 # Revision 1.208 2006/10/23 13:06:19 ncq 2486 # - don't import path lab/vaccs business objects, they are not converted yet 2487 # - use gmPG2 (not finished yet) 2488 # - comment out backend signal handling for now 2489 # - drop services support 2490 # 2491 # Revision 1.207 2006/07/19 20:25:00 ncq 2492 # - gmPyCompat.py is history 2493 # 2494 # Revision 1.206 2006/06/26 12:25:30 ncq 2495 # - cleanup 2496 # 2497 # Revision 1.205 2006/06/07 22:01:57 ncq 2498 # - cVaccinationRegime -> cVaccinationCourse 2499 # 2500 # Revision 1.204 2006/05/28 15:25:18 ncq 2501 # - ever better docs in get_encounters() just short of a proper fix 2502 # 2503 # Revision 1.203 2006/05/25 22:10:43 ncq 2504 # - improve comment in get_encounters() 2505 # 2506 # Revision 1.202 2006/05/14 21:44:22 ncq 2507 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof 2508 # - remove use of gmWhoAmI.py 2509 # 2510 # Revision 1.201 2006/05/12 13:54:26 ncq 2511 # - lazy import gmPerson 2512 # 2513 # Revision 1.200 2006/05/12 12:02:25 ncq 2514 # - use gmCurrentProvider() 2515 # 2516 # Revision 1.199 2006/05/06 18:53:56 ncq 2517 # - select age(...) <> ...; -> select ... <> now() - ...; as per Syan 2518 # 2519 # Revision 1.198 2006/05/04 18:01:39 ncq 2520 # - "properly" include Syan's hack to speed up get_encounters() 2521 # - not active but has comment on how and when to activate it 2522 # - programmatically finds clin_root_item child tables :-) 2523 # - vaccination regime -> course adjustments 2524 # - try yet another approach in get_encounters() which really 2525 # should speed things up, too, without resorting to brute-force 2526 # child table resolution just yet 2527 # 2528 # Revision 1.197 2006/04/23 16:49:03 ncq 2529 # - properly access encounters by health issue 2530 # 2531 # Revision 1.196 2006/04/23 16:46:28 ncq 2532 # - do not select age field from clin.v_pat_items since it doesn't exist anymore 2533 # - add get_summary() 2534 # - try faster get_encounters() 2535 # 2536 # Revision 1.195 2006/02/27 22:38:36 ncq 2537 # - spell out rfe/aoe as per Richard's request 2538 # 2539 # Revision 1.194 2006/01/07 13:00:19 ncq 2540 # - add some schema qualifiers 2541 # 2542 # Revision 1.193 2005/12/26 12:03:10 sjtan 2543 # 2544 # more schema matching. some delegation . 2545 # 2546 # Revision 1.192 2005/12/25 13:24:30 sjtan 2547 # 2548 # schema changes in names . 2549 # 2550 # Revision 1.191 2005/12/10 22:55:17 ncq 2551 # - fully log newly created encounters 2552 # 2553 # Revision 1.190 2005/12/06 14:24:14 ncq 2554 # - clin.clin_health_issue/episode -> clin.health_issue/episode 2555 # 2556 # Revision 1.189 2005/11/27 12:44:57 ncq 2557 # - clinical tables are in schema "clin" now 2558 # 2559 # Revision 1.188 2005/11/18 15:16:15 ncq 2560 # - add simple (non-context aware) search function 2561 # 2562 # Revision 1.187 2005/10/19 09:16:29 ncq 2563 # - cleanup, return to well-defined state re narrative 2564 # cache rebuild, to be fixed later 2565 # 2566 # Revision 1.186 2005/10/15 18:19:23 ncq 2567 # - cleanup 2568 # - improved logging when initiating active encounter 2569 # 2570 # Revision 1.185 2005/10/11 21:03:13 ncq 2571 # - a bit of cleanup re Syan's changes 2572 # 2573 # Revision 1.184 2005/10/08 12:33:08 sjtan 2574 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch. 2575 # 2576 # Revision 1.183 2005/10/04 19:31:45 sjtan 2577 # allow for flagged invalidation of cache and cache reloading. 2578 # 2579 # Revision 1.182 2005/09/22 15:45:11 ncq 2580 # - clin_encounter.fk_provider removed 2581 # 2582 # Revision 1.181 2005/09/12 15:05:44 ncq 2583 # - enhance get_episodes() to allow selection by is_open 2584 # 2585 # Revision 1.180 2005/09/11 17:21:55 ncq 2586 # - get_episodes_by_encounter() 2587 # - support is_open when adding episodes 2588 # 2589 # Revision 1.179 2005/09/09 13:49:25 ncq 2590 # - add instance casts from/to episode/issue/problem 2591 # 2592 # Revision 1.178 2005/09/05 16:26:36 ncq 2593 # - add reclass_problem() 2594 # - improve problem2(episode | issue) 2595 # 2596 # Revision 1.177 2005/08/14 15:34:32 ncq 2597 # - TODO item 2598 # 2599 # Revision 1.176 2005/08/06 16:03:31 ncq 2600 # - guard against concurrent cache flushing 2601 # 2602 # Revision 1.175 2005/05/08 21:40:27 ncq 2603 # - robustify get_first/last_encounter() as there may really be None 2604 # 2605 # Revision 1.174 2005/04/27 12:24:23 sjtan 2606 # 2607 # id_patient should be pk_patient ? changed a while ago. effect: enables emr_dump window to display. 2608 # 2609 # Revision 1.173 2005/04/24 14:41:04 ncq 2610 # - cleanup, fail in add_health_issue if issue exists 2611 # 2612 # Revision 1.172 2005/04/11 17:54:19 ncq 2613 # - cleanup 2614 # 2615 # Revision 1.171 2005/04/03 20:04:56 ncq 2616 # - remove __load_active_episode() sillyness and related stuff 2617 # 2618 # Revision 1.170 2005/04/03 09:26:48 ncq 2619 # - cleanup 2620 # 2621 # Revision 1.169 2005/03/30 22:07:35 ncq 2622 # - guard against several "latest episodes" 2623 # - maybe do away with the *explicit* "latest episode" stuff ? 2624 # 2625 # Revision 1.168 2005/03/23 18:30:40 ncq 2626 # - v_patient_items -> v_pat_items 2627 # - add problem2issue() 2628 # 2629 # Revision 1.167 2005/03/20 16:47:26 ncq 2630 # - cleanup 2631 # 2632 # Revision 1.166 2005/03/17 21:15:29 cfmoro 2633 # Added problem2episode cast method 2634 # 2635 # Revision 1.165 2005/03/14 18:16:52 cfmoro 2636 # Create episode according to only_standalone_epi_has_patient backend constraint 2637 # 2638 # Revision 1.164 2005/03/14 14:27:21 ncq 2639 # - id_patient -> pk_patient 2640 # - rewrite add_episode() work with simplified episode naming 2641 # 2642 # Revision 1.163 2005/03/10 19:49:34 cfmoro 2643 # Added episodes and issues constraints to get_problem 2644 # 2645 # Revision 1.162 2005/03/01 20:48:46 ncq 2646 # - rearrange __init__ such that encounter is set up before episode 2647 # - fix handling in __load_last_active_episode() 2648 # 2649 # Revision 1.161 2005/02/23 19:38:40 ncq 2650 # - listen to episode changes in DB, too 2651 # 2652 # Revision 1.160 2005/02/20 10:31:44 sjtan 2653 # 2654 # lazy initialize preconditions for create_episode. 2655 # 2656 # Revision 1.159 2005/02/13 15:44:52 ncq 2657 # - v_basic_person.i_pk -> pk_identity 2658 # 2659 # Revision 1.158 2005/02/12 13:52:45 ncq 2660 # - identity.id -> pk 2661 # - v_basic_person.i_id -> i_pk 2662 # - self.id_patient -> self.pk_patient 2663 # 2664 # Revision 1.157 2005/02/02 19:55:26 cfmoro 2665 # Delete problems cache on episodes modified callback method 2666 # 2667 # Revision 1.156 2005/01/31 20:25:07 ncq 2668 # - listen on episode changes, too 2669 # 2670 # Revision 1.155 2005/01/29 19:20:49 cfmoro 2671 # Commented out episode cache update after episode creation, should we use gmSignals? 2672 # 2673 # Revision 1.154 2005/01/29 19:14:00 cfmoro 2674 # Added newly created episode to episode cache 2675 # 2676 # Revision 1.153 2005/01/15 19:55:55 cfmoro 2677 # Added problem support to emr 2678 # 2679 # Revision 1.152 2004/12/18 15:57:57 ncq 2680 # - Syan found a logging bug, which is now fixed 2681 # - eventually fix bug in use of create_encounter() that 2682 # prevented gmSoapImporter from working properly 2683 # 2684 # Revision 1.151 2004/12/15 10:28:11 ncq 2685 # - fix create_episode() aka add_episode() 2686 # 2687 # Revision 1.150 2004/10/27 12:09:28 ncq 2688 # - properly set booster/seq_no in the face of the patient 2689 # not being on any vaccination schedule 2690 # 2691 # Revision 1.149 2004/10/26 12:51:24 ncq 2692 # - Carlos: bulk load lab results 2693 # 2694 # Revision 1.148 2004/10/20 21:50:29 ncq 2695 # - return [] on no vacc regimes found 2696 # - in get_vaccinations() handle case where patient is not on any schedule 2697 # 2698 # Revision 1.147 2004/10/20 12:28:25 ncq 2699 # - revert back to Carlos' bulk loading code 2700 # - keep some bits of what Syan added 2701 # - he likes to force overwriting other peoples' commits 2702 # - if that continues his CVS rights are at stake (to be discussed 2703 # on list when appropriate) 2704 # 2705 # Revision 1.144 2004/10/18 11:33:48 ncq 2706 # - more bulk loading 2707 # 2708 # Revision 1.143 2004/10/12 18:39:12 ncq 2709 # - first cut at using Carlos' bulk fetcher in get_vaccinations(), 2710 # seems to work fine so far ... please test and report lossage ... 2711 # 2712 # Revision 1.142 2004/10/12 11:14:51 ncq 2713 # - improve get_scheduled_vaccination_regimes/get_vaccinations, mostly by Carlos 2714 # 2715 # Revision 1.141 2004/10/11 19:50:15 ncq 2716 # - improve get_allergies() 2717 # 2718 # Revision 1.140 2004/09/28 12:19:15 ncq 2719 # - any vaccination related data now cached under 'vaccinations' so 2720 # all of it is flushed when any change to vaccinations occurs 2721 # - rewrite get_scheduled_vaccination_regimes() (Carlos) 2722 # - in get_vaccinations() compute seq_no and is_booster status 2723 # 2724 # Revision 1.139 2004/09/19 15:07:01 ncq 2725 # - we don't use a default health issue anymore 2726 # - remove duplicate existence checks 2727 # - cleanup, reformat/fix episode queries 2728 # 2729 # Revision 1.138 2004/09/13 19:07:00 ncq 2730 # - get_scheduled_vaccination_regimes() returns list of lists 2731 # (indication, l10n_indication, nr_of_shots) - this allows to 2732 # easily build table of given/missing vaccinations 2733 # 2734 # Revision 1.137 2004/09/06 18:54:31 ncq 2735 # - some cleanup 2736 # - in get_first/last_encounter we do need to check issue/episode for None so 2737 # we won't be applying the "one-item -> two-duplicate-items for IN query" trick 2738 # to "None" which would yield the wrong results 2739 # 2740 # Revision 1.136 2004/08/31 19:19:43 ncq 2741 # - Carlos added constraints to get_encounters() 2742 # - he also added get_first/last_encounter() 2743 # 2744 # Revision 1.135 2004/08/23 09:07:58 ncq 2745 # - removed unneeded get_vaccinated_regimes() - was faulty anyways 2746 # 2747 # Revision 1.134 2004/08/11 09:44:15 ncq 2748 # - gracefully continue loading clin_narrative items if one fails 2749 # - map soap_cats filter to lowercase in get_clin_narrative() 2750 # 2751 # Revision 1.133 2004/08/11 09:01:27 ncq 2752 # - Carlos-contributed get_clin_narrative() with usual filtering 2753 # and soap_cat filtering based on v_pat_narrative 2754 # 2755 # Revision 1.132 2004/07/17 21:08:51 ncq 2756 # - gmPG.run_query() now has a verbosity parameter, so use it 2757 # 2758 # Revision 1.131 2004/07/06 00:11:11 ncq 2759 # - make add_clin_narrative use gmClinNarrative.create_clin_narrative() 2760 # 2761 # Revision 1.130 2004/07/05 22:30:01 ncq 2762 # - improve get_text_dump() 2763 # 2764 # Revision 1.129 2004/07/05 22:23:38 ncq 2765 # - log some timings to find time sinks 2766 # - get_clinical_record now takes between 4 and 11 seconds 2767 # - record active episode at clinical record *cleanup* instead of startup ! 2768 # 2769 # Revision 1.128 2004/07/02 00:20:54 ncq 2770 # - v_patient_items.id_item -> pk_item 2771 # 2772 # Revision 1.127 2004/06/30 20:33:40 ncq 2773 # - add_clinical_note() -> add_clin_narrative() 2774 # 2775 # Revision 1.126 2004/06/30 15:31:22 shilbert 2776 # - fk/pk issue fixed 2777 # 2778 # Revision 1.125 2004/06/28 16:05:42 ncq 2779 # - fix spurious 'id' for episode -> pk_episode 2780 # 2781 # Revision 1.124 2004/06/28 12:18:41 ncq 2782 # - more id_* -> fk_* 2783 # 2784 # Revision 1.123 2004/06/26 23:45:50 ncq 2785 # - cleanup, id_* -> fk/pk_* 2786 # 2787 # Revision 1.122 2004/06/26 07:33:55 ncq 2788 # - id_episode -> fk/pk_episode 2789 # 2790 # Revision 1.121 2004/06/20 18:39:30 ncq 2791 # - get_encounters() added by Carlos 2792 # 2793 # Revision 1.120 2004/06/17 21:30:53 ncq 2794 # - time range constraints in get()ters, by Carlos 2795 # 2796 # Revision 1.119 2004/06/15 19:08:15 ncq 2797 # - self._backend -> self._conn_pool 2798 # - remove instance level self._ro_conn_clin 2799 # - cleanup 2800 # 2801 # Revision 1.118 2004/06/14 06:36:51 ncq 2802 # - fix = -> == in filter(lambda ...) 2803 # 2804 # Revision 1.117 2004/06/13 08:03:07 ncq 2805 # - cleanup, better separate vaccination code from general EMR code 2806 # 2807 # Revision 1.116 2004/06/13 07:55:00 ncq 2808 # - create_allergy moved to gmAllergy 2809 # - get_indications moved to gmVaccinations 2810 # - many get_*()ers constrained by issue/episode/encounter 2811 # - code by Carlos 2812 # 2813 # Revision 1.115 2004/06/09 14:33:31 ncq 2814 # - cleanup 2815 # - rewrite _db_callback_allg_modified() 2816 # 2817 # Revision 1.114 2004/06/08 00:43:26 ncq 2818 # - add staff_id to add_allergy, unearthed by unittest 2819 # 2820 # Revision 1.113 2004/06/02 22:18:14 ncq 2821 # - fix my broken streamlining 2822 # 2823 # Revision 1.112 2004/06/02 22:11:47 ncq 2824 # - streamline Syan's check for failing create_episode() in __load_last_active_episode() 2825 # 2826 # Revision 1.111 2004/06/02 13:10:18 sjtan 2827 # 2828 # error handling , in case unsuccessful get_episodes. 2829 # 2830 # Revision 1.110 2004/06/01 23:51:33 ncq 2831 # - id_episode/pk_encounter 2832 # 2833 # Revision 1.109 2004/06/01 08:21:56 ncq 2834 # - default limit to all on get_lab_results() 2835 # 2836 # Revision 1.108 2004/06/01 08:20:14 ncq 2837 # - limit in get_lab_results 2838 # 2839 # Revision 1.107 2004/05/30 20:51:34 ncq 2840 # - verify provider in __init__, too 2841 # 2842 # Revision 1.106 2004/05/30 19:54:57 ncq 2843 # - comment out attach_to_encounter(), actually, all relevant 2844 # methods should have encounter_id, episode_id kwds that 2845 # default to self.__*['id'] 2846 # - add_allergy() 2847 # 2848 # Revision 1.105 2004/05/28 15:46:28 ncq 2849 # - get_active_episode() 2850 # 2851 # Revision 1.104 2004/05/28 15:11:32 ncq 2852 # - get_active_encounter() 2853 # 2854 # Revision 1.103 2004/05/27 13:40:21 ihaywood 2855 # more work on referrals, still not there yet 2856 # 2857 # Revision 1.102 2004/05/24 21:13:33 ncq 2858 # - return better from add_lab_request() 2859 # 2860 # Revision 1.101 2004/05/24 20:52:18 ncq 2861 # - add_lab_request() 2862 # 2863 # Revision 1.100 2004/05/23 12:28:58 ncq 2864 # - fix missing : and episode reference before assignment 2865 # 2866 # Revision 1.99 2004/05/22 12:42:53 ncq 2867 # - add create_episode() 2868 # - cleanup add_episode() 2869 # 2870 # Revision 1.98 2004/05/22 11:46:15 ncq 2871 # - some cleanup re allergy signal handling 2872 # - get_health_issue_names doesn't exist anymore, so use get_health_issues 2873 # 2874 # Revision 1.97 2004/05/18 22:35:09 ncq 2875 # - readd set_active_episode() 2876 # 2877 # Revision 1.96 2004/05/18 20:33:40 ncq 2878 # - fix call to create_encounter() in __initiate_active_encounter() 2879 # 2880 # Revision 1.95 2004/05/17 19:01:40 ncq 2881 # - convert encounter API to use encounter class 2882 # 2883 # Revision 1.94 2004/05/16 15:47:27 ncq 2884 # - switch to use of episode class 2885 # 2886 # Revision 1.93 2004/05/16 14:34:45 ncq 2887 # - cleanup, small fix in patient xdb checking 2888 # - switch health issue handling to clin item class 2889 # 2890 # Revision 1.92 2004/05/14 13:16:34 ncq 2891 # - cleanup, remove dead code 2892 # 2893 # Revision 1.91 2004/05/12 14:33:42 ncq 2894 # - get_due_vaccinations -> get_missing_vaccinations + rewrite 2895 # thereof for value object use 2896 # - __activate_fairly_recent_encounter now fails right away if it 2897 # cannot talk to the user anyways 2898 # 2899 # Revision 1.90 2004/05/08 17:41:34 ncq 2900 # - due vaccs views are better now, so simplify get_due_vaccinations() 2901 # 2902 # Revision 1.89 2004/05/02 19:27:30 ncq 2903 # - simplify get_due_vaccinations 2904 # 2905 # Revision 1.88 2004/04/24 12:59:17 ncq 2906 # - all shiny and new, vastly improved vaccinations 2907 # handling via clinical item objects 2908 # - mainly thanks to Carlos Moro 2909 # 2910 # Revision 1.87 2004/04/20 12:56:58 ncq 2911 # - remove outdated get_due_vaccs(), use get_due_vaccinations() now 2912 # 2913 # Revision 1.86 2004/04/20 00:17:55 ncq 2914 # - allergies API revamped, kudos to Carlos 2915 # 2916 # Revision 1.85 2004/04/17 11:54:16 ncq 2917 # - v_patient_episodes -> v_pat_episodes 2918 # 2919 # Revision 1.84 2004/04/15 09:46:56 ncq 2920 # - cleanup, get_lab_data -> get_lab_results 2921 # 2922 # Revision 1.83 2004/04/14 21:06:10 ncq 2923 # - return cLabResult from get_lab_data() 2924 # 2925 # Revision 1.82 2004/03/27 22:18:43 ncq 2926 # - 7.4 doesn't allow aggregates in subselects which refer to outer-query 2927 # columns only, therefor use explicit inner query from list 2928 # 2929 # Revision 1.81 2004/03/25 11:00:19 ncq 2930 # test get_lab_data() 2931 # 2932 # Revision 1.80 2004/03/23 17:32:59 ncq 2933 # - support "unified" test code/name on get_lab_data() 2934 # 2935 # Revision 1.79 2004/03/23 15:04:59 ncq 2936 # - merge Carlos' constraints into get_text_dump 2937 # - add gmPatient.export_data() 2938 # 2939 # Revision 1.78 2004/03/23 02:29:24 ncq 2940 # - cleanup import/add pyCompat 2941 # - get_lab_data() 2942 # - unit test 2943 # 2944 # Revision 1.77 2004/03/20 19:41:59 ncq 2945 # - gmClin* cClin* 2946 # 2947 # Revision 1.76 2004/03/19 11:55:38 ncq 2948 # - in allergy.reaction -> allergy.narrative 2949 # 2950 # Revision 1.75 2004/03/04 19:35:01 ncq 2951 # - AU has rules on encounter timeout, so use them 2952 # 2953 # Revision 1.74 2004/02/25 09:46:19 ncq 2954 # - import from pycommon now, not python-common 2955 # 2956 # Revision 1.73 2004/02/18 15:25:20 ncq 2957 # - rewrote encounter support 2958 # - __init__() now initiates encounter 2959 # - _encounter_soft/hard_ttl now global mx.DateTime.TimeDelta 2960 # - added set_encounter_ttl() 2961 # - added set_func_ask_user() for UI callback on "fairly recent" 2962 # encounter detection 2963 # 2964 # Revision 1.72 2004/02/17 04:04:34 ihaywood 2965 # fixed patient creation refeential integrity error 2966 # 2967 # Revision 1.71 2004/02/14 00:37:10 ihaywood 2968 # Bugfixes 2969 # - weeks = days / 7 2970 # - create_new_patient to maintain xlnk_identity in historica 2971 # 2972 # Revision 1.70 2004/02/12 23:39:33 ihaywood 2973 # fixed parse errors on vaccine queries (I'm using postgres 7.3.3) 2974 # 2975 # Revision 1.69 2004/02/02 23:02:40 ncq 2976 # - it's personalia, not demographica 2977 # 2978 # Revision 1.68 2004/02/02 16:19:03 ncq 2979 # - rewrite get_due_vaccinations() taking advantage of indication-based tables 2980 # 2981 # Revision 1.67 2004/01/26 22:08:52 ncq 2982 # - gracefully handle failure to retrive vacc_ind 2983 # 2984 # Revision 1.66 2004/01/26 21:48:48 ncq 2985 # - v_patient_vacc4ind -> v_pat_vacc4ind 2986 # 2987 # Revision 1.65 2004/01/24 17:07:46 ncq 2988 # - fix insertion into xlnk_identity 2989 # 2990 # Revision 1.64 2004/01/21 16:52:02 ncq 2991 # - eventually do the right thing in get_vaccinations() 2992 # 2993 # Revision 1.63 2004/01/21 15:53:05 ncq 2994 # - use deepcopy when copying dict as to leave original intact in get_vaccinations() 2995 # 2996 # Revision 1.62 2004/01/19 13:41:15 ncq 2997 # - fix typos in SQL 2998 # 2999 # Revision 1.61 2004/01/19 13:30:46 ncq 3000 # - substantially smarten up __load_last_active_episode() after cleaning it up 3001 # 3002 # Revision 1.60 2004/01/18 21:41:49 ncq 3003 # - get_vaccinated_indications() 3004 # - make get_vaccinations() work against v_patient_vacc4ind 3005 # - don't store vacc_def/link on saving vaccination 3006 # - update_vaccination() 3007 # 3008 # Revision 1.59 2004/01/15 15:05:13 ncq 3009 # - verify patient id in xlnk_identity in _pkey_exists() 3010 # - make set_active_episode() logic more consistent - don't create default episode ... 3011 # - also, failing to record most_recently_used episode should prevent us 3012 # from still keeping things up 3013 # 3014 # Revision 1.58 2004/01/12 16:20:14 ncq 3015 # - set_active_episode() does not read rows from run_commit() 3016 # - need to check for argument aVacc keys *before* adding 3017 # corresponding snippets to where/cols clause else we would end 3018 # up with orphaned query parts 3019 # - also, aVacc will always have all keys, it's just that they may 3020 # be empty (because editarea.GetValue() will always return something) 3021 # - fix set_active_encounter: don't ask for column index 3022 # 3023 # Revision 1.57 2004/01/06 23:44:40 ncq 3024 # - __default__ -> xxxDEFAULTxxx 3025 # 3026 # Revision 1.56 2004/01/06 09:56:41 ncq 3027 # - default encounter name __default__ is nonsense, of course, 3028 # use mxDateTime.today().Format() instead 3029 # - consolidate API: 3030 # - load_most_recent_episode() -> load_last_active_episode() 3031 # - _get_* -> get_* 3032 # - sort methods 3033 # - convert more gmPG.run_query() 3034 # - handle health issue on episode change as they are tighthly coupled 3035 # 3036 # Revision 1.55 2003/12/29 16:13:51 uid66147 3037 # - listen to vaccination changes in DB 3038 # - allow filtering by ID in get_vaccinations() 3039 # - order get_due_vacc() by time_left/amount_overdue 3040 # - add add_vaccination() 3041 # - deal with provider in encounter handling 3042 # 3043 # Revision 1.54 2003/12/02 01:58:28 ncq 3044 # - make get_due_vaccinations return the right thing on empty lists 3045 # 3046 # Revision 1.53 2003/12/01 01:01:05 ncq 3047 # - add get_vaccinated_regimes() 3048 # - allow regime_list filter in get_vaccinations() 3049 # - handle empty lists better in get_due_vaccinations() 3050 # 3051 # Revision 1.52 2003/11/30 01:05:30 ncq 3052 # - improve get_vaccinations 3053 # - added get_vacc_regimes 3054 # 3055 # Revision 1.51 2003/11/28 10:06:18 ncq 3056 # - remove dead code 3057 # 3058 # Revision 1.50 2003/11/28 08:08:05 ncq 3059 # - improve get_due_vaccinations() 3060 # 3061 # Revision 1.49 2003/11/19 23:27:44 sjtan 3062 # 3063 # make _print() a dummy function , so that code reaching gmLog through this function works; 3064 # 3065 # Revision 1.48 2003/11/18 14:16:41 ncq 3066 # - cleanup 3067 # - intentionally comment out some methods and remove some code that 3068 # isn't fit for the main trunk such that it breaks and gets fixed 3069 # eventually 3070 # 3071 # Revision 1.47 2003/11/17 11:34:22 sjtan 3072 # 3073 # no ref to yaml 3074 # 3075 # Revision 1.46 2003/11/17 11:32:46 sjtan 3076 # 3077 # print ... -> _log.Log(gmLog.lInfo ...) 3078 # 3079 # Revision 1.45 2003/11/17 10:56:33 sjtan 3080 # 3081 # synced and commiting. 3082 # 3083 # 3084 # 3085 # uses gmDispatcher to send new currentPatient objects to toplevel gmGP_ widgets. Proprosal to use 3086 # yaml serializer to store editarea data in narrative text field of clin_root_item until 3087 # clin_root_item schema stabilizes. 3088 # 3089 # Revision 1.44 2003/11/16 19:30:26 ncq 3090 # - use clin_when in clin_root_item 3091 # - pretty _print(EMR text dump) 3092 # 3093 # Revision 1.43 2003/11/11 20:28:59 ncq 3094 # - get_allergy_names(), reimplemented 3095 # 3096 # Revision 1.42 2003/11/11 18:20:58 ncq 3097 # - fix get_text_dump() to actually do what it suggests 3098 # 3099 # Revision 1.41 2003/11/09 22:51:29 ncq 3100 # - don't close cursor prematurely in get_text_dump() 3101 # 3102 # Revision 1.40 2003/11/09 16:24:03 ncq 3103 # - typo fix 3104 # 3105 # Revision 1.39 2003/11/09 03:29:11 ncq 3106 # - API cleanup, __set/getitem__ deprecated 3107 # 3108 # Revision 1.38 2003/10/31 23:18:48 ncq 3109 # - improve encounter business 3110 # 3111 # Revision 1.37 2003/10/26 11:27:10 ihaywood 3112 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics. 3113 # 3114 # manual edit areas modelled after r.terry's specs. 3115 # 3116 # Revision 1.36 2003/10/19 12:12:36 ncq 3117 # - remove obsolete code 3118 # - filter out sensitivities on get_allergy_names 3119 # - start get_vacc_status 3120 # 3121 # Revision 1.35 2003/09/30 19:11:58 ncq 3122 # - remove dead code 3123 # 3124 # Revision 1.34 2003/07/19 20:17:23 ncq 3125 # - code cleanup 3126 # - add cleanup() 3127 # - use signals better 3128 # - fix get_text_dump() 3129 # 3130 # Revision 1.33 2003/07/09 16:20:18 ncq 3131 # - remove dead code 3132 # - def_conn_ro -> ro_conn_clin 3133 # - check for patient existence in personalia, not historica 3134 # - listen to health issue changes, too 3135 # - add _get_health_issues 3136 # 3137 # Revision 1.32 2003/07/07 08:34:31 ihaywood 3138 # bugfixes on gmdrugs.sql for postgres 7.3 3139 # 3140 # Revision 1.31 2003/07/05 13:44:12 ncq 3141 # - modify -> modified 3142 # 3143 # Revision 1.30 2003/07/03 15:20:55 ncq 3144 # - lots od cleanup, some nice formatting for text dump of EMR 3145 # 3146 # Revision 1.29 2003/06/27 22:54:29 ncq 3147 # - improved _get_text_dump() 3148 # - added _get_episode/health_issue_names() 3149 # - remove old workaround code 3150 # - sort test output by age, oldest on top 3151 # 3152 # Revision 1.28 2003/06/27 16:03:50 ncq 3153 # - no need for ; in DB-API queries 3154 # - implement EMR text dump 3155 # 3156 # Revision 1.27 2003/06/26 21:24:49 ncq 3157 # - cleanup re quoting + ";" and (cmd, arg) style 3158 # 3159 # Revision 1.26 2003/06/26 06:05:38 ncq 3160 # - always add ; at end of sql queries but have space after %s 3161 # 3162 # Revision 1.25 2003/06/26 02:29:20 ihaywood 3163 # Bugfix for searching for pre-existing health issue records 3164 # 3165 # Revision 1.24 2003/06/24 12:55:08 ncq 3166 # - eventually make create_clinical_note() functional 3167 # 3168 # Revision 1.23 2003/06/23 22:28:22 ncq 3169 # - finish encounter handling for now, somewhat tested 3170 # - use gmPG.run_query changes where appropriate 3171 # - insert_clin_note() finished but untested 3172 # - cleanup 3173 # 3174 # Revision 1.22 2003/06/22 16:17:40 ncq 3175 # - start dealing with encounter initialization 3176 # - add create_clinical_note() 3177 # - cleanup 3178 # 3179 # Revision 1.21 2003/06/19 15:22:57 ncq 3180 # - fix spelling error in SQL in episode creation 3181 # 3182 # Revision 1.20 2003/06/03 14:05:05 ncq 3183 # - start listening threads last in __init__ so we won't hang 3184 # if anything else fails in the constructor 3185 # 3186 # Revision 1.19 2003/06/03 13:17:32 ncq 3187 # - finish default clinical episode/health issue handling, simple tests work 3188 # - clinical encounter handling still insufficient 3189 # - add some more comments to Syan's code 3190 # 3191 # Revision 1.18 2003/06/02 20:58:32 ncq 3192 # - nearly finished with default episode/health issue stuff 3193 # 3194 # Revision 1.17 2003/06/01 16:25:51 ncq 3195 # - preliminary code for episode handling 3196 # 3197 # Revision 1.16 2003/06/01 15:00:31 sjtan 3198 # 3199 # works with definite, maybe note definate. 3200 # 3201 # Revision 1.15 2003/06/01 14:45:31 sjtan 3202 # 3203 # definite and definate databases catered for, temporarily. 3204 # 3205 # Revision 1.14 2003/06/01 14:34:47 sjtan 3206 # 3207 # hopefully complies with temporary model; not using setData now ( but that did work). 3208 # Please leave a working and tested substitute (i.e. select a patient , allergy list 3209 # will change; check allergy panel allows update of allergy list), if still 3210 # not satisfied. I need a working model-view connection ; trying to get at least 3211 # a basically database updating version going . 3212 # 3213 # Revision 1.13 2003/06/01 14:15:48 ncq 3214 # - more comments 3215 # 3216 # Revision 1.12 2003/06/01 14:11:52 ncq 3217 # - added some comments 3218 # 3219 # Revision 1.11 2003/06/01 14:07:42 ncq 3220 # - "select into" is an update command, too 3221 # 3222 # Revision 1.10 2003/06/01 13:53:55 ncq 3223 # - typo fixes, cleanup, spelling definate -> definite 3224 # - fix my obsolote handling of patient allergies tables 3225 # - remove obsolete clin_transaction stuff 3226 # 3227 # Revision 1.9 2003/06/01 13:20:32 sjtan 3228 # 3229 # logging to data stream for debugging. Adding DEBUG tags when work out how to use vi 3230 # with regular expression groups (maybe never). 3231 # 3232 # Revision 1.8 2003/06/01 12:55:58 sjtan 3233 # 3234 # sql commit may cause PortalClose, whilst connection.commit() doesnt? 3235 # 3236 # Revision 1.7 2003/06/01 01:47:32 sjtan 3237 # 3238 # starting allergy connections. 3239 # 3240 # Revision 1.6 2003/05/17 17:23:43 ncq 3241 # - a little more testing in main() 3242 # 3243 # Revision 1.5 2003/05/05 00:06:32 ncq 3244 # - make allergies work again after EMR rework 3245 # 3246 # Revision 1.4 2003/05/03 14:11:22 ncq 3247 # - make allergy change signalling work properly 3248 # 3249 # Revision 1.3 2003/05/03 00:41:14 ncq 3250 # - fetchall() returns list, not dict, so handle accordingly in "allergy names" 3251 # 3252 # Revision 1.2 2003/05/01 14:59:24 ncq 3253 # - listen on allergy add/delete in backend, invalidate cache and notify frontend 3254 # - "allergies", "allergy names" getters 3255 # 3256 # Revision 1.1 2003/04/29 12:33:20 ncq 3257 # - first draft 3258 # 3259