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

Source Code for Module Gnumed.business.gmEMRStructItems

   1  # -*- coding: utf8 -*- 
   2  """GNUmed health related business object. 
   3   
   4  license: GPL 
   5  """ 
   6  #============================================================ 
   7  __version__ = "$Revision: 1.157 $" 
   8  __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>" 
   9   
  10  import types, sys, string, datetime, logging, time 
  11   
  12   
  13  if __name__ == '__main__': 
  14          sys.path.insert(0, '../../') 
  15  from Gnumed.pycommon import gmPG2 
  16  from Gnumed.pycommon import gmI18N 
  17  from Gnumed.pycommon import gmTools 
  18  from Gnumed.pycommon import gmDateTime 
  19  from Gnumed.pycommon import gmBusinessDBObject 
  20  from Gnumed.pycommon import gmNull 
  21  from Gnumed.pycommon import gmExceptions 
  22   
  23  from Gnumed.business import gmClinNarrative 
  24  from Gnumed.business import gmCoding 
  25   
  26   
  27  _log = logging.getLogger('gm.emr') 
  28  _log.info(__version__) 
  29   
  30  try: _ 
  31  except NameError: _ = lambda x:x 
  32  #============================================================ 
  33  # diagnostic certainty classification 
  34  #============================================================ 
  35  __diagnostic_certainty_classification_map = None 
  36   
37 -def diagnostic_certainty_classification2str(classification):
38 39 global __diagnostic_certainty_classification_map 40 41 if __diagnostic_certainty_classification_map is None: 42 __diagnostic_certainty_classification_map = { 43 None: u'', 44 u'A': _(u'A: Sign'), 45 u'B': _(u'B: Cluster of signs'), 46 u'C': _(u'C: Syndromic diagnosis'), 47 u'D': _(u'D: Scientific diagnosis') 48 } 49 50 try: 51 return __diagnostic_certainty_classification_map[classification] 52 except KeyError: 53 return _(u'<%s>: unknown diagnostic certainty classification') % classification
54 #============================================================ 55 # Health Issues API 56 #============================================================ 57 laterality2str = { 58 None: u'?', 59 u'na': u'', 60 u'sd': _('bilateral'), 61 u'ds': _('bilateral'), 62 u's': _('left'), 63 u'd': _('right') 64 } 65 66 #============================================================
67 -class cHealthIssue(gmBusinessDBObject.cBusinessDBObject):
68 """Represents one health issue.""" 69 70 _cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s" 71 _cmds_store_payload = [ 72 u"""update clin.health_issue set 73 description = %(description)s, 74 summary = gm.nullify_empty_string(%(summary)s), 75 age_noted = %(age_noted)s, 76 laterality = gm.nullify_empty_string(%(laterality)s), 77 grouping = gm.nullify_empty_string(%(grouping)s), 78 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s), 79 is_active = %(is_active)s, 80 clinically_relevant = %(clinically_relevant)s, 81 is_confidential = %(is_confidential)s, 82 is_cause_of_death = %(is_cause_of_death)s 83 where 84 pk = %(pk_health_issue)s and 85 xmin = %(xmin_health_issue)s""", 86 u"select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s" 87 ] 88 _updatable_fields = [ 89 'description', 90 'summary', 91 'grouping', 92 'age_noted', 93 'laterality', 94 'is_active', 95 'clinically_relevant', 96 'is_confidential', 97 'is_cause_of_death', 98 'diagnostic_certainty_classification' 99 ] 100 #--------------------------------------------------------
101 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
102 pk = aPK_obj 103 104 if (pk is not None) or (row is not None): 105 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row) 106 return 107 108 if patient is None: 109 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 110 where 111 description = %(desc)s 112 and 113 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)""" 114 else: 115 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 116 where 117 description = %(desc)s 118 and 119 pk_patient = %(pat)s""" 120 121 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}] 122 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 123 124 if len(rows) == 0: 125 raise gmExceptions.NoSuchBusinessObjectError, 'no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient) 126 127 pk = rows[0][0] 128 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'} 129 130 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
131 #-------------------------------------------------------- 132 # external API 133 #--------------------------------------------------------
134 - def rename(self, description=None):
135 """Method for issue renaming. 136 137 @param description 138 - the new descriptive name for the issue 139 @type description 140 - a string instance 141 """ 142 # sanity check 143 if not type(description) in [str, unicode] or description.strip() == '': 144 _log.error('<description> must be a non-empty string') 145 return False 146 # update the issue description 147 old_description = self._payload[self._idx['description']] 148 self._payload[self._idx['description']] = description.strip() 149 self._is_modified = True 150 successful, data = self.save_payload() 151 if not successful: 152 _log.error('cannot rename health issue [%s] with [%s]' % (self, description)) 153 self._payload[self._idx['description']] = old_description 154 return False 155 return True
156 #--------------------------------------------------------
157 - def get_episodes(self):
158 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s" 159 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True) 160 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
161 #--------------------------------------------------------
162 - def close_expired_episode(self, ttl=180):
163 """ttl in days""" 164 open_episode = self.get_open_episode() 165 if open_episode is None: 166 return True 167 earliest, latest = open_episode.get_access_range() 168 ttl = datetime.timedelta(ttl) 169 now = datetime.datetime.now(tz=latest.tzinfo) 170 if (latest + ttl) > now: 171 return False 172 open_episode['episode_open'] = False 173 success, data = open_episode.save_payload() 174 if success: 175 return True 176 return False # should be an exception
177 #--------------------------------------------------------
178 - def close_episode(self):
179 open_episode = self.get_open_episode() 180 open_episode['episode_open'] = False 181 success, data = open_episode.save_payload() 182 if success: 183 return True 184 return False
185 #--------------------------------------------------------
186 - def has_open_episode(self):
187 cmd = u"select exists (select 1 from clin.episode where fk_health_issue = %s and is_open is True)" 188 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 189 return rows[0][0]
190 #--------------------------------------------------------
191 - def get_open_episode(self):
192 cmd = u"select pk from clin.episode where fk_health_issue = %s and is_open is True" 193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 194 if len(rows) == 0: 195 return None 196 return cEpisode(aPK_obj=rows[0][0])
197 #--------------------------------------------------------
198 - def age_noted_human_readable(self):
199 if self._payload[self._idx['age_noted']] is None: 200 return u'<???>' 201 202 # since we've already got an interval we are bound to use it, 203 # further transformation will only introduce more errors, 204 # later we can improve this deeper inside 205 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
206 #--------------------------------------------------------
207 - def add_code(self, pk_code=None):
208 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 209 cmd = u"INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 210 args = { 211 'item': self._payload[self._idx['pk_health_issue']], 212 'code': pk_code 213 } 214 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 215 return True
216 #--------------------------------------------------------
217 - def remove_code(self, pk_code=None):
218 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 219 cmd = u"DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 220 args = { 221 'item': self._payload[self._idx['pk_health_issue']], 222 'code': pk_code 223 } 224 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 225 return True
226 #--------------------------------------------------------
227 - def format_as_journal(self, left_margin=0, date_format='%a, %b %d %Y'):
228 rows = gmClinNarrative.get_as_journal ( 229 issues = (self.pk_obj,), 230 order_by = u'pk_episode, pk_encounter, clin_when, scr, src_table' 231 ) 232 233 if len(rows) == 0: 234 return u'' 235 236 left_margin = u' ' * left_margin 237 238 lines = [] 239 lines.append(_('Clinical data generated during encounters under this health issue:')) 240 241 prev_epi = None 242 for row in rows: 243 if row['pk_episode'] != prev_epi: 244 lines.append(u'') 245 prev_epi = row['pk_episode'] 246 247 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding()) 248 top_row = u'%s%s %s (%s) %s' % ( 249 gmTools.u_box_top_left_arc, 250 gmTools.u_box_horiz_single, 251 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']], 252 when, 253 gmTools.u_box_horiz_single * 5 254 ) 255 soap = gmTools.wrap ( 256 text = row['narrative'], 257 width = 60, 258 initial_indent = u' ', 259 subsequent_indent = u' ' + left_margin 260 ) 261 row_ver = u'' 262 if row['row_version'] > 0: 263 row_ver = u'v%s: ' % row['row_version'] 264 bottom_row = u'%s%s %s, %s%s %s' % ( 265 u' ' * 40, 266 gmTools.u_box_horiz_light_heavy, 267 row['modified_by'], 268 row_ver, 269 row['date_modified'], 270 gmTools.u_box_horiz_heavy_light 271 ) 272 273 lines.append(top_row) 274 lines.append(soap) 275 lines.append(bottom_row) 276 277 eol_w_margin = u'\n%s' % left_margin 278 return left_margin + eol_w_margin.join(lines) + u'\n'
279 #--------------------------------------------------------
280 - def format(self, left_margin=0, patient=None):
281 282 if patient.ID != self._payload[self._idx['pk_patient']]: 283 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % ( 284 patient.ID, 285 self._payload[self._idx['pk_health_issue']], 286 self._payload[self._idx['pk_patient']] 287 ) 288 raise ValueError(msg) 289 290 lines = [] 291 292 lines.append(_('Health Issue %s%s%s%s [#%s]') % ( 293 u'\u00BB', 294 self._payload[self._idx['description']], 295 u'\u00AB', 296 gmTools.coalesce ( 297 initial = self.laterality_description, 298 instead = u'', 299 template_initial = u' (%s)', 300 none_equivalents = [None, u'', u'?'] 301 ), 302 self._payload[self._idx['pk_health_issue']] 303 )) 304 305 if self._payload[self._idx['is_confidential']]: 306 lines.append('') 307 lines.append(_(' ***** CONFIDENTIAL *****')) 308 lines.append('') 309 310 if self._payload[self._idx['is_cause_of_death']]: 311 lines.append('') 312 lines.append(_(' contributed to death of patient')) 313 lines.append('') 314 315 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 316 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % ( 317 enc['l10n_type'], 318 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 319 enc['last_affirmed_original_tz'].strftime('%H:%M'), 320 self._payload[self._idx['pk_encounter']] 321 )) 322 323 if self._payload[self._idx['age_noted']] is not None: 324 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable()) 325 326 lines.append(u' ' + _('Status') + u': %s, %s%s' % ( 327 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')), 328 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')), 329 gmTools.coalesce ( 330 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 331 instead = u'', 332 template_initial = u', %s', 333 none_equivalents = [None, u''] 334 ) 335 )) 336 337 if self._payload[self._idx['summary']] is not None: 338 lines.append(u'') 339 lines.append(gmTools.wrap ( 340 text = self._payload[self._idx['summary']], 341 width = 60, 342 initial_indent = u' ', 343 subsequent_indent = u' ' 344 )) 345 346 # codes 347 codes = self.generic_codes 348 if len(codes) > 0: 349 lines.append(u'') 350 for c in codes: 351 lines.append(u' %s: %s (%s - %s)' % ( 352 c['code'], 353 c['term'], 354 c['name_short'], 355 c['version'] 356 )) 357 del codes 358 359 lines.append(u'') 360 361 emr = patient.get_emr() 362 363 # episodes 364 epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]]) 365 if epis is None: 366 lines.append(_('Error retrieving episodes for this health issue.')) 367 elif len(epis) == 0: 368 lines.append(_('There are no episodes for this health issue.')) 369 else: 370 lines.append ( 371 _('Episodes: %s (most recent: %s%s%s)') % ( 372 len(epis), 373 gmTools.u_left_double_angle_quote, 374 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'], 375 gmTools.u_right_double_angle_quote 376 ) 377 ) 378 for epi in epis: 379 lines.append(u' \u00BB%s\u00AB (%s)' % ( 380 epi['description'], 381 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed')) 382 )) 383 384 lines.append('') 385 386 # encounters 387 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 388 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 389 390 if first_encounter is None or last_encounter is None: 391 lines.append(_('No encounters found for this health issue.')) 392 else: 393 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]]) 394 lines.append(_('Encounters: %s (%s - %s):') % ( 395 len(encs), 396 first_encounter['started_original_tz'].strftime('%m/%Y'), 397 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y') 398 )) 399 lines.append(_(' Most recent: %s - %s') % ( 400 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 401 last_encounter['last_affirmed_original_tz'].strftime('%H:%M') 402 )) 403 404 # medications 405 meds = emr.get_current_substance_intake ( 406 issues = [ self._payload[self._idx['pk_health_issue']] ], 407 order_by = u'is_currently_active, started, substance' 408 ) 409 410 if len(meds) > 0: 411 lines.append(u'') 412 lines.append(_('Active medications: %s') % len(meds)) 413 for m in meds: 414 lines.append(m.format(left_margin = (left_margin + 1))) 415 del meds 416 417 # hospital stays 418 stays = emr.get_hospital_stays ( 419 issues = [ self._payload[self._idx['pk_health_issue']] ] 420 ) 421 422 if len(stays) > 0: 423 lines.append(u'') 424 lines.append(_('Hospital stays: %s') % len(stays)) 425 for s in stays: 426 lines.append(s.format(left_margin = (left_margin + 1))) 427 del stays 428 429 # procedures 430 procs = emr.get_performed_procedures ( 431 issues = [ self._payload[self._idx['pk_health_issue']] ] 432 ) 433 434 if len(procs) > 0: 435 lines.append(u'') 436 lines.append(_('Procedures performed: %s') % len(procs)) 437 for p in procs: 438 lines.append(p.format(left_margin = (left_margin + 1))) 439 del procs 440 441 epis = self.get_episodes() 442 if len(epis) > 0: 443 epi_pks = [ e['pk_episode'] for e in epis ] 444 445 # documents 446 doc_folder = patient.get_document_folder() 447 docs = doc_folder.get_documents(episodes = epi_pks) 448 if len(docs) > 0: 449 lines.append(u'') 450 lines.append(_('Documents: %s') % len(docs)) 451 del docs 452 453 # test results 454 tests = emr.get_test_results_by_date(episodes = epi_pks) 455 if len(tests) > 0: 456 lines.append(u'') 457 lines.append(_('Measurements and Results: %s') % len(tests)) 458 del tests 459 460 # vaccinations 461 vaccs = emr.get_vaccinations(episodes = epi_pks) 462 if len(vaccs) > 0: 463 lines.append(u'') 464 lines.append(_('Vaccinations:')) 465 for vacc in vaccs: 466 lines.extend(vacc.format(with_reaction = True)) 467 del vaccs 468 469 del epis 470 471 left_margin = u' ' * left_margin 472 eol_w_margin = u'\n%s' % left_margin 473 return left_margin + eol_w_margin.join(lines) + u'\n'
474 #-------------------------------------------------------- 475 # properties 476 #-------------------------------------------------------- 477 episodes = property(get_episodes, lambda x:x) 478 #-------------------------------------------------------- 479 open_episode = property(get_open_episode, lambda x:x) 480 #--------------------------------------------------------
481 - def _get_latest_episode(self):
482 cmd = u"""SELECT 483 coalesce ( 484 (SELECT pk FROM clin.episode WHERE fk_health_issue = %(issue)s AND is_open IS TRUE), 485 (SELECT pk FROM clin.v_pat_episodes WHERE fk_health_issue = %(issue)s ORDER BY last_affirmed DESC limit 1) 486 )""" 487 args = {'issue': self.pk_obj} 488 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 489 if len(rows) == 0: 490 return None 491 return cEpisode(aPK_obj = rows[0][0])
492 493 latest_episode = property(_get_latest_episode, lambda x:x) 494 #--------------------------------------------------------
496 try: 497 return laterality2str[self._payload[self._idx['laterality']]] 498 except KeyError: 499 return u'<???>'
500 501 laterality_description = property(_get_laterality_description, lambda x:x) 502 #--------------------------------------------------------
504 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
505 506 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 507 #--------------------------------------------------------
508 - def _get_generic_codes(self):
509 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 510 return [] 511 512 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 513 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 514 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 515 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
516
517 - def _set_generic_codes(self, pk_codes):
518 queries = [] 519 # remove all codes 520 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 521 queries.append ({ 522 'cmd': u'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s', 523 'args': { 524 'issue': self._payload[self._idx['pk_health_issue']], 525 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 526 } 527 }) 528 # add new codes 529 for pk_code in pk_codes: 530 queries.append ({ 531 'cmd': u'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)', 532 'args': { 533 'issue': self._payload[self._idx['pk_health_issue']], 534 'pk_code': pk_code 535 } 536 }) 537 if len(queries) == 0: 538 return 539 # run it all in one transaction 540 rows, idx = gmPG2.run_rw_queries(queries = queries) 541 return
542 543 generic_codes = property(_get_generic_codes, _set_generic_codes)
544 #============================================================
545 -def create_health_issue(description=None, encounter=None, patient=None):
546 """Creates a new health issue for a given patient. 547 548 description - health issue name 549 """ 550 try: 551 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 552 return h_issue 553 except gmExceptions.NoSuchBusinessObjectError: 554 pass 555 556 queries = [] 557 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 558 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 559 560 cmd = u"select currval('clin.health_issue_pk_seq')" 561 queries.append({'cmd': cmd}) 562 563 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 564 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 565 566 return h_issue
567 #-----------------------------------------------------------
568 -def delete_health_issue(health_issue=None):
569 if isinstance(health_issue, cHealthIssue): 570 pk = health_issue['pk_health_issue'] 571 else: 572 pk = int(health_issue) 573 574 try: 575 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}]) 576 except gmPG2.dbapi.IntegrityError: 577 # should be parsing pgcode/and or error message 578 _log.exception('cannot delete health issue') 579 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
580 #------------------------------------------------------------ 581 # use as dummy for unassociated episodes
582 -def get_dummy_health_issue():
583 issue = { 584 'pk_health_issue': None, 585 'description': _('Unattributed episodes'), 586 'age_noted': None, 587 'laterality': u'na', 588 'is_active': True, 589 'clinically_relevant': True, 590 'is_confidential': None, 591 'is_cause_of_death': False, 592 'is_dummy': True, 593 'grouping': None 594 } 595 return issue
596 #-----------------------------------------------------------
597 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
598 return cProblem ( 599 aPK_obj = { 600 'pk_patient': health_issue['pk_patient'], 601 'pk_health_issue': health_issue['pk_health_issue'], 602 'pk_episode': None 603 }, 604 try_potential_problems = allow_irrelevant 605 )
606 #============================================================ 607 # episodes API 608 #============================================================
609 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
610 """Represents one clinical episode. 611 """ 612 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s" 613 _cmds_store_payload = [ 614 u"""update clin.episode set 615 fk_health_issue = %(pk_health_issue)s, 616 is_open = %(episode_open)s::boolean, 617 description = %(description)s, 618 summary = gm.nullify_empty_string(%(summary)s), 619 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 620 where 621 pk = %(pk_episode)s and 622 xmin = %(xmin_episode)s""", 623 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 624 ] 625 _updatable_fields = [ 626 'pk_health_issue', 627 'episode_open', 628 'description', 629 'summary', 630 'diagnostic_certainty_classification' 631 ] 632 #--------------------------------------------------------
633 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
634 pk = aPK_obj 635 if pk is None and row is None: 636 637 where_parts = [u'description = %(desc)s'] 638 639 if id_patient is not None: 640 where_parts.append(u'pk_patient = %(pat)s') 641 642 if health_issue is not None: 643 where_parts.append(u'pk_health_issue = %(issue)s') 644 645 if encounter is not None: 646 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)') 647 648 args = { 649 'pat': id_patient, 650 'issue': health_issue, 651 'enc': encounter, 652 'desc': name 653 } 654 655 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts) 656 657 rows, idx = gmPG2.run_ro_queries( 658 queries = [{'cmd': cmd, 'args': args}], 659 get_col_idx=True 660 ) 661 662 if len(rows) == 0: 663 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter) 664 665 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 666 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 667 668 else: 669 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
670 #-------------------------------------------------------- 671 # external API 672 #--------------------------------------------------------
673 - def get_access_range(self):
674 """Get earliest and latest access to this episode. 675 676 Returns a tuple(earliest, latest). 677 """ 678 cmd = u""" 679 select 680 min(earliest), 681 max(latest) 682 from ( 683 (select 684 (case when clin_when < modified_when 685 then clin_when 686 else modified_when 687 end) as earliest, 688 (case when clin_when > modified_when 689 then clin_when 690 else modified_when 691 end) as latest 692 from 693 clin.clin_root_item 694 where 695 fk_episode = %(pk)s 696 697 ) union all ( 698 699 select 700 modified_when as earliest, 701 modified_when as latest 702 from 703 clin.episode 704 where 705 pk = %(pk)s 706 ) 707 ) as ranges""" 708 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 709 if len(rows) == 0: 710 return (gmNull.cNull(warn=False), gmNull.cNull(warn=False)) 711 return (rows[0][0], rows[0][1])
712 #--------------------------------------------------------
713 - def get_patient(self):
714 return self._payload[self._idx['pk_patient']]
715 #--------------------------------------------------------
716 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
717 return gmClinNarrative.get_narrative ( 718 soap_cats = soap_cats, 719 encounters = encounters, 720 episodes = [self.pk_obj], 721 order_by = order_by 722 )
723 #--------------------------------------------------------
724 - def rename(self, description=None):
725 """Method for episode editing, that is, episode renaming. 726 727 @param description 728 - the new descriptive name for the encounter 729 @type description 730 - a string instance 731 """ 732 # sanity check 733 if description.strip() == '': 734 _log.error('<description> must be a non-empty string instance') 735 return False 736 # update the episode description 737 old_description = self._payload[self._idx['description']] 738 self._payload[self._idx['description']] = description.strip() 739 self._is_modified = True 740 successful, data = self.save_payload() 741 if not successful: 742 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 743 self._payload[self._idx['description']] = old_description 744 return False 745 return True
746 #--------------------------------------------------------
747 - def add_code(self, pk_code=None):
748 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 749 750 if pk_code in self._payload[self._idx['pk_generic_codes']]: 751 return 752 753 cmd = u""" 754 INSERT INTO clin.lnk_code2episode 755 (fk_item, fk_generic_code) 756 SELECT 757 %(item)s, 758 %(code)s 759 WHERE NOT EXISTS ( 760 SELECT 1 FROM clin.lnk_code2episode 761 WHERE 762 fk_item = %(item)s 763 AND 764 fk_generic_code = %(code)s 765 )""" 766 args = { 767 'item': self._payload[self._idx['pk_episode']], 768 'code': pk_code 769 } 770 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 771 return
772 #--------------------------------------------------------
773 - def remove_code(self, pk_code=None):
774 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 775 cmd = u"DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 776 args = { 777 'item': self._payload[self._idx['pk_episode']], 778 'code': pk_code 779 } 780 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 781 return True
782 #--------------------------------------------------------
783 - def format_as_journal(self, left_margin=0, date_format='%a, %b %d %Y'):
784 rows = gmClinNarrative.get_as_journal ( 785 episodes = (self.pk_obj,), 786 order_by = u'pk_encounter, clin_when, scr, src_table' 787 #order_by = u'pk_encounter, scr, clin_when, src_table' 788 ) 789 790 if len(rows) == 0: 791 return u'' 792 793 lines = [] 794 795 lines.append(_('Clinical data generated during encounters within this episode:')) 796 797 left_margin = u' ' * left_margin 798 799 prev_enc = None 800 for row in rows: 801 if row['pk_encounter'] != prev_enc: 802 lines.append(u'') 803 prev_enc = row['pk_encounter'] 804 805 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding()) 806 top_row = u'%s%s %s (%s) %s' % ( 807 gmTools.u_box_top_left_arc, 808 gmTools.u_box_horiz_single, 809 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']], 810 when, 811 gmTools.u_box_horiz_single * 5 812 ) 813 soap = gmTools.wrap ( 814 text = row['narrative'], 815 width = 60, 816 initial_indent = u' ', 817 subsequent_indent = u' ' + left_margin 818 ) 819 row_ver = u'' 820 if row['row_version'] > 0: 821 row_ver = u'v%s: ' % row['row_version'] 822 bottom_row = u'%s%s %s, %s%s %s' % ( 823 u' ' * 40, 824 gmTools.u_box_horiz_light_heavy, 825 row['modified_by'], 826 row_ver, 827 row['date_modified'], 828 gmTools.u_box_horiz_heavy_light 829 ) 830 831 lines.append(top_row) 832 lines.append(soap) 833 lines.append(bottom_row) 834 835 eol_w_margin = u'\n%s' % left_margin 836 return left_margin + eol_w_margin.join(lines) + u'\n'
837 #--------------------------------------------------------
838 - def format(self, left_margin=0, patient=None):
839 840 if patient.ID != self._payload[self._idx['pk_patient']]: 841 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 842 patient.ID, 843 self._payload[self._idx['pk_episode']], 844 self._payload[self._idx['pk_patient']] 845 ) 846 raise ValueError(msg) 847 848 lines = [] 849 850 # episode details 851 lines.append (_('Episode %s%s%s [#%s]') % ( 852 gmTools.u_left_double_angle_quote, 853 self._payload[self._idx['description']], 854 gmTools.u_right_double_angle_quote, 855 self._payload[self._idx['pk_episode']] 856 )) 857 858 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 859 lines.append (u' ' + _('Created during encounter: %s (%s - %s) [#%s]') % ( 860 enc['l10n_type'], 861 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 862 enc['last_affirmed_original_tz'].strftime('%H:%M'), 863 self._payload[self._idx['pk_encounter']] 864 )) 865 866 emr = patient.get_emr() 867 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 868 first_encounter = None 869 last_encounter = None 870 if (encs is not None) and (len(encs) > 0): 871 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 872 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 873 if self._payload[self._idx['episode_open']]: 874 end = gmDateTime.pydt_now_here() 875 end_str = gmTools.u_ellipsis 876 else: 877 end = last_encounter['last_affirmed'] 878 end_str = last_encounter['last_affirmed'].strftime('%m/%Y') 879 age = gmDateTime.format_interval_medically(end - first_encounter['started']) 880 lines.append(_(' Duration: %s (%s - %s)') % ( 881 age, 882 first_encounter['started'].strftime('%m/%Y'), 883 end_str 884 )) 885 886 lines.append(u' ' + _('Status') + u': %s%s' % ( 887 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 888 gmTools.coalesce ( 889 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 890 instead = u'', 891 template_initial = u', %s', 892 none_equivalents = [None, u''] 893 ) 894 )) 895 896 if self._payload[self._idx['summary']] is not None: 897 lines.append(u'') 898 lines.append(gmTools.wrap ( 899 text = self._payload[self._idx['summary']], 900 width = 60, 901 initial_indent = u' ', 902 subsequent_indent = u' ' 903 ) 904 ) 905 906 # codes 907 codes = self.generic_codes 908 if len(codes) > 0: 909 lines.append(u'') 910 for c in codes: 911 lines.append(u' %s: %s (%s - %s)' % ( 912 c['code'], 913 c['term'], 914 c['name_short'], 915 c['version'] 916 )) 917 del codes 918 919 lines.append(u'') 920 921 # encounters 922 if encs is None: 923 lines.append(_('Error retrieving encounters for this episode.')) 924 elif len(encs) == 0: 925 lines.append(_('There are no encounters for this episode.')) 926 else: 927 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 928 929 if len(encs) < 4: 930 line = _('%s encounter(s) (%s - %s):') 931 else: 932 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):') 933 lines.append(line % ( 934 len(encs), 935 first_encounter['started'].strftime('%m/%Y'), 936 last_encounter['last_affirmed'].strftime('%m/%Y') 937 )) 938 939 lines.append(u' %s - %s (%s):%s' % ( 940 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 941 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 942 first_encounter['l10n_type'], 943 gmTools.coalesce ( 944 first_encounter['assessment_of_encounter'], 945 gmTools.coalesce ( 946 first_encounter['reason_for_encounter'], 947 u'', 948 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 949 ), 950 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 951 ) 952 )) 953 954 if len(encs) > 4: 955 lines.append(_(' ... %s skipped ...') % (len(encs) - 4)) 956 957 for enc in encs[1:][-3:]: 958 lines.append(u' %s - %s (%s):%s' % ( 959 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 960 enc['last_affirmed_original_tz'].strftime('%H:%M'), 961 enc['l10n_type'], 962 gmTools.coalesce ( 963 enc['assessment_of_encounter'], 964 gmTools.coalesce ( 965 enc['reason_for_encounter'], 966 u'', 967 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 968 ), 969 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 970 ) 971 )) 972 del encs 973 974 # spell out last encounter 975 if last_encounter is not None: 976 lines.append('') 977 lines.append(_('Progress notes in most recent encounter:')) 978 lines.extend(last_encounter.format_soap ( 979 episodes = [ self._payload[self._idx['pk_episode']] ], 980 left_margin = left_margin, 981 soap_cats = 'soap', 982 emr = emr 983 )) 984 985 # documents 986 doc_folder = patient.get_document_folder() 987 docs = doc_folder.get_documents ( 988 episodes = [ self._payload[self._idx['pk_episode']] ] 989 ) 990 991 if len(docs) > 0: 992 lines.append('') 993 lines.append(_('Documents: %s') % len(docs)) 994 995 for d in docs: 996 lines.append(u' %s %s:%s%s' % ( 997 d['clin_when'].strftime('%Y-%m-%d'), 998 d['l10n_type'], 999 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1000 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1001 )) 1002 del docs 1003 1004 # hospital stays 1005 stays = emr.get_hospital_stays ( 1006 episodes = [ self._payload[self._idx['pk_episode']] ] 1007 ) 1008 1009 if len(stays) > 0: 1010 lines.append('') 1011 lines.append(_('Hospital stays: %s') % len(stays)) 1012 1013 for s in stays: 1014 lines.append(s.format(left_margin = (left_margin + 1))) 1015 del stays 1016 1017 # procedures 1018 procs = emr.get_performed_procedures ( 1019 episodes = [ self._payload[self._idx['pk_episode']] ] 1020 ) 1021 1022 if len(procs) > 0: 1023 lines.append(u'') 1024 lines.append(_('Procedures performed: %s') % len(procs)) 1025 for p in procs: 1026 lines.append(p.format(left_margin = (left_margin + 1), include_episode = False)) 1027 del procs 1028 1029 # test results 1030 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 1031 1032 if len(tests) > 0: 1033 lines.append('') 1034 lines.append(_('Measurements and Results:')) 1035 1036 for t in tests: 1037 lines.extend(t.format ( 1038 with_review = False, 1039 with_comments = False, 1040 date_format = '%Y-%m-%d' 1041 )) 1042 del tests 1043 1044 # vaccinations 1045 vaccs = emr.get_vaccinations(episodes = [ self._payload[self._idx['pk_episode']] ]) 1046 1047 if len(vaccs) > 0: 1048 lines.append(u'') 1049 lines.append(_('Vaccinations:')) 1050 1051 for vacc in vaccs: 1052 lines.extend(vacc.format ( 1053 with_indications = True, 1054 with_comment = True, 1055 with_reaction = True, 1056 date_format = '%Y-%m-%d' 1057 )) 1058 del vaccs 1059 1060 left_margin = u' ' * left_margin 1061 eol_w_margin = u'\n%s' % left_margin 1062 return left_margin + eol_w_margin.join(lines) + u'\n'
1063 #-------------------------------------------------------- 1064 # properties 1065 #--------------------------------------------------------
1067 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1068 1069 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 1070 #--------------------------------------------------------
1071 - def _get_generic_codes(self):
1072 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 1073 return [] 1074 1075 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1076 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 1077 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1078 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1079
1080 - def _set_generic_codes(self, pk_codes):
1081 queries = [] 1082 # remove all codes 1083 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 1084 queries.append ({ 1085 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s', 1086 'args': { 1087 'epi': self._payload[self._idx['pk_episode']], 1088 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 1089 } 1090 }) 1091 # add new codes 1092 for pk_code in pk_codes: 1093 queries.append ({ 1094 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)', 1095 'args': { 1096 'epi': self._payload[self._idx['pk_episode']], 1097 'pk_code': pk_code 1098 } 1099 }) 1100 if len(queries) == 0: 1101 return 1102 # run it all in one transaction 1103 rows, idx = gmPG2.run_rw_queries(queries = queries) 1104 return
1105 1106 generic_codes = property(_get_generic_codes, _set_generic_codes) 1107 #--------------------------------------------------------
1108 - def _get_has_narrative(self):
1109 cmd = u"""SELECT EXISTS ( 1110 SELECT 1 FROM clin.clin_narrative 1111 WHERE 1112 fk_episode = %(epi)s 1113 AND 1114 fk_encounter IN ( 1115 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1116 ) 1117 )""" 1118 args = { 1119 u'pat': self._payload[self._idx['pk_patient']], 1120 u'epi': self._payload[self._idx['pk_episode']] 1121 } 1122 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1123 return rows[0][0]
1124 1125 has_narrative = property(_get_has_narrative, lambda x:x)
1126 #============================================================
1127 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
1128 """Creates a new episode for a given patient's health issue. 1129 1130 pk_health_issue - given health issue PK 1131 episode_name - name of episode 1132 """ 1133 if not allow_dupes: 1134 try: 1135 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 1136 if episode['episode_open'] != is_open: 1137 episode['episode_open'] = is_open 1138 episode.save_payload() 1139 return episode 1140 except gmExceptions.ConstructorError: 1141 pass 1142 1143 queries = [] 1144 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 1145 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 1146 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 1147 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 1148 1149 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 1150 return episode
1151 #-----------------------------------------------------------
1152 -def delete_episode(episode=None):
1153 if isinstance(episode, cEpisode): 1154 pk = episode['pk_episode'] 1155 else: 1156 pk = int(episode) 1157 1158 try: 1159 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.episode where pk=%(pk)s', 'args': {'pk': pk}}]) 1160 except gmPG2.dbapi.IntegrityError: 1161 # should be parsing pgcode/and or error message 1162 _log.exception('cannot delete episode') 1163 raise gmExceptions.DatabaseObjectInUseError('cannot delete episode, it is in use')
1164 #-----------------------------------------------------------
1165 -def episode2problem(episode=None, allow_closed=False):
1166 return cProblem ( 1167 aPK_obj = { 1168 'pk_patient': episode['pk_patient'], 1169 'pk_episode': episode['pk_episode'], 1170 'pk_health_issue': episode['pk_health_issue'] 1171 }, 1172 try_potential_problems = allow_closed 1173 )
1174 #============================================================ 1175 # encounter API 1176 #============================================================
1177 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1178 """Represents one encounter.""" 1179 1180 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 1181 _cmds_store_payload = [ 1182 u"""update clin.encounter set 1183 started = %(started)s, 1184 last_affirmed = %(last_affirmed)s, 1185 fk_location = %(pk_location)s, 1186 fk_type = %(pk_type)s, 1187 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1188 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1189 where 1190 pk = %(pk_encounter)s and 1191 xmin = %(xmin_encounter)s""", 1192 u"""select xmin_encounter from clin.v_pat_encounters where pk_encounter=%(pk_encounter)s""" 1193 ] 1194 _updatable_fields = [ 1195 'started', 1196 'last_affirmed', 1197 'pk_location', 1198 'pk_type', 1199 'reason_for_encounter', 1200 'assessment_of_encounter' 1201 ] 1202 #--------------------------------------------------------
1203 - def set_active(self):
1204 """Set the enconter as the active one. 1205 1206 "Setting active" means making sure the encounter 1207 row has the youngest "last_affirmed" timestamp of 1208 all encounter rows for this patient. 1209 """ 1210 self['last_affirmed'] = gmDateTime.pydt_now_here() 1211 self.save()
1212 #--------------------------------------------------------
1213 - def transfer_clinical_data(self, source_episode=None, target_episode=None):
1214 """ 1215 Moves every element currently linked to the current encounter 1216 and the source_episode onto target_episode. 1217 1218 @param source_episode The episode the elements are currently linked to. 1219 @type target_episode A cEpisode intance. 1220 @param target_episode The episode the elements will be relinked to. 1221 @type target_episode A cEpisode intance. 1222 """ 1223 if source_episode['pk_episode'] == target_episode['pk_episode']: 1224 return True 1225 1226 queries = [] 1227 cmd = u""" 1228 UPDATE clin.clin_root_item 1229 SET fk_episode = %(trg)s 1230 WHERE 1231 fk_encounter = %(enc)s AND 1232 fk_episode = %(src)s 1233 """ 1234 rows, idx = gmPG2.run_rw_queries(queries = [{ 1235 'cmd': cmd, 1236 'args': { 1237 'trg': target_episode['pk_episode'], 1238 'enc': self.pk_obj, 1239 'src': source_episode['pk_episode'] 1240 } 1241 }]) 1242 self.refetch_payload() 1243 return True
1244 #--------------------------------------------------------
1245 - def same_payload(self, another_object=None):
1246 1247 relevant_fields = [ 1248 'pk_location', 1249 'pk_type', 1250 'pk_patient', 1251 'reason_for_encounter', 1252 'assessment_of_encounter' 1253 ] 1254 for field in relevant_fields: 1255 if self._payload[self._idx[field]] != another_object[field]: 1256 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1257 return False 1258 1259 relevant_fields = [ 1260 'started', 1261 'last_affirmed', 1262 ] 1263 for field in relevant_fields: 1264 if self._payload[self._idx[field]] is None: 1265 if another_object[field] is None: 1266 continue 1267 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1268 return False 1269 1270 if another_object[field] is None: 1271 return False 1272 1273 #if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S %Z') != another_object[field].strftime('%Y-%m-%d %H:%M:%S %Z'): 1274 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'): 1275 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1276 return False 1277 1278 return True
1279 #--------------------------------------------------------
1280 - def has_clinical_data(self):
1281 cmd = u""" 1282 select exists ( 1283 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 1284 union all 1285 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1286 )""" 1287 args = { 1288 'pat': self._payload[self._idx['pk_patient']], 1289 'enc': self.pk_obj 1290 } 1291 rows, idx = gmPG2.run_ro_queries ( 1292 queries = [{ 1293 'cmd': cmd, 1294 'args': args 1295 }] 1296 ) 1297 return rows[0][0]
1298 #--------------------------------------------------------
1299 - def has_narrative(self):
1300 cmd = u""" 1301 select exists ( 1302 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 1303 )""" 1304 args = { 1305 'pat': self._payload[self._idx['pk_patient']], 1306 'enc': self.pk_obj 1307 } 1308 rows, idx = gmPG2.run_ro_queries ( 1309 queries = [{ 1310 'cmd': cmd, 1311 'args': args 1312 }] 1313 ) 1314 return rows[0][0]
1315 #--------------------------------------------------------
1316 - def has_soap_narrative(self, soap_cats=None):
1317 """soap_cats: <space> = admin category""" 1318 1319 if soap_cats is None: 1320 soap_cats = u'soap ' 1321 else: 1322 soap_cats = soap_cats.lower() 1323 1324 cats = [] 1325 for cat in soap_cats: 1326 if cat in u'soap': 1327 cats.append(cat) 1328 continue 1329 if cat == u' ': 1330 cats.append(None) 1331 1332 cmd = u""" 1333 SELECT EXISTS ( 1334 SELECT 1 FROM clin.clin_narrative 1335 WHERE 1336 fk_encounter = %(enc)s 1337 AND 1338 soap_cat IN %(cats)s 1339 LIMIT 1 1340 ) 1341 """ 1342 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 1343 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 1344 return rows[0][0]
1345 #--------------------------------------------------------
1346 - def has_documents(self):
1347 cmd = u""" 1348 select exists ( 1349 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1350 )""" 1351 args = { 1352 'pat': self._payload[self._idx['pk_patient']], 1353 'enc': self.pk_obj 1354 } 1355 rows, idx = gmPG2.run_ro_queries ( 1356 queries = [{ 1357 'cmd': cmd, 1358 'args': args 1359 }] 1360 ) 1361 return rows[0][0]
1362 #--------------------------------------------------------
1363 - def get_latest_soap(self, soap_cat=None, episode=None):
1364 1365 if soap_cat is not None: 1366 soap_cat = soap_cat.lower() 1367 1368 if episode is None: 1369 epi_part = u'fk_episode is null' 1370 else: 1371 epi_part = u'fk_episode = %(epi)s' 1372 1373 cmd = u""" 1374 select narrative 1375 from clin.clin_narrative 1376 where 1377 fk_encounter = %%(enc)s 1378 and 1379 soap_cat = %%(cat)s 1380 and 1381 %s 1382 order by clin_when desc 1383 limit 1 1384 """ % epi_part 1385 1386 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 1387 1388 rows, idx = gmPG2.run_ro_queries ( 1389 queries = [{ 1390 'cmd': cmd, 1391 'args': args 1392 }] 1393 ) 1394 if len(rows) == 0: 1395 return None 1396 1397 return rows[0][0]
1398 #--------------------------------------------------------
1399 - def get_episodes(self, exclude=None):
1400 cmd = u""" 1401 SELECT * FROM clin.v_pat_episodes 1402 WHERE 1403 pk_episode IN ( 1404 1405 SELECT DISTINCT fk_episode 1406 FROM clin.clin_root_item 1407 WHERE fk_encounter = %%(enc)s 1408 1409 UNION 1410 1411 SELECT DISTINCT fk_episode 1412 FROM blobs.doc_med 1413 WHERE fk_encounter = %%(enc)s 1414 ) 1415 %s""" 1416 args = {'enc': self.pk_obj} 1417 if exclude is not None: 1418 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s' 1419 args['excluded'] = tuple(exclude) 1420 else: 1421 cmd = cmd % u'' 1422 1423 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1424 1425 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1426 #--------------------------------------------------------
1427 - def add_code(self, pk_code=None, field=None):
1428 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1429 if field == u'rfe': 1430 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1431 elif field == u'aoe': 1432 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1433 else: 1434 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1435 args = { 1436 'item': self._payload[self._idx['pk_encounter']], 1437 'code': pk_code 1438 } 1439 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1440 return True
1441 #--------------------------------------------------------
1442 - def remove_code(self, pk_code=None, field=None):
1443 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1444 if field == u'rfe': 1445 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1446 elif field == u'aoe': 1447 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1448 else: 1449 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1450 args = { 1451 'item': self._payload[self._idx['pk_encounter']], 1452 'code': pk_code 1453 } 1454 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1455 return True
1456 #--------------------------------------------------------
1457 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soap', emr=None, issues=None):
1458 1459 lines = [] 1460 for soap_cat in soap_cats: 1461 soap_cat_narratives = emr.get_clin_narrative ( 1462 episodes = episodes, 1463 issues = issues, 1464 encounters = [self._payload[self._idx['pk_encounter']]], 1465 soap_cats = [soap_cat] 1466 ) 1467 if soap_cat_narratives is None: 1468 continue 1469 if len(soap_cat_narratives) == 0: 1470 continue 1471 1472 lines.append(u'-- %s ----------' % gmClinNarrative.soap_cat2l10n_str[soap_cat]) 1473 for soap_entry in soap_cat_narratives: 1474 txt = gmTools.wrap ( 1475 text = u'%s\n (%.8s %s)' % ( 1476 soap_entry['narrative'], 1477 soap_entry['provider'], 1478 soap_entry['date'].strftime('%Y-%m-%d %H:%M') 1479 ), 1480 width = 75, 1481 initial_indent = u'', 1482 subsequent_indent = (u' ' * left_margin) 1483 ) 1484 lines.append(txt) 1485 lines.append('') 1486 1487 return lines
1488 #--------------------------------------------------------
1489 - def format_latex(self, date_format=None, soap_cats=None, soap_order=None):
1490 1491 nothing2format = ( 1492 (self._payload[self._idx['reason_for_encounter']] is None) 1493 and 1494 (self._payload[self._idx['assessment_of_encounter']] is None) 1495 and 1496 (self.has_soap_narrative(soap_cats = u'soap') is False) 1497 ) 1498 if nothing2format: 1499 return u'' 1500 1501 if date_format is None: 1502 date_format = '%A, %B %d %Y' 1503 1504 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 1505 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 1506 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()), 1507 self._payload[self._idx['started']].strftime('%H:%M'), 1508 self._payload[self._idx['last_affirmed']].strftime('%H:%M') 1509 ) 1510 tex += u'\\hline \\tabularnewline \n' 1511 1512 for epi in self.get_episodes(): 1513 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 1514 if len(soaps) == 0: 1515 continue 1516 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1517 gmTools.tex_escape_string(_('Problem')), 1518 gmTools.tex_escape_string(epi['description']), 1519 gmTools.coalesce ( 1520 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 1521 instead = u'', 1522 template_initial = u' {\\footnotesize [%s]}', 1523 none_equivalents = [None, u''] 1524 ) 1525 ) 1526 if epi['pk_health_issue'] is not None: 1527 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1528 gmTools.tex_escape_string(_('Health issue')), 1529 gmTools.tex_escape_string(epi['health_issue']), 1530 gmTools.coalesce ( 1531 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 1532 instead = u'', 1533 template_initial = u' {\\footnotesize [%s]}', 1534 none_equivalents = [None, u''] 1535 ) 1536 ) 1537 for soap in soaps: 1538 tex += u'{\\small %s} & {\\small %s} \\tabularnewline \n' % ( 1539 gmClinNarrative.soap_cat2l10n[soap['soap_cat']], 1540 gmTools.tex_escape_string(soap['narrative'].strip(u'\n')) 1541 ) 1542 tex += u' & \\tabularnewline \n' 1543 1544 if self._payload[self._idx['reason_for_encounter']] is not None: 1545 tex += u'%s & %s \\tabularnewline \n' % ( 1546 gmTools.tex_escape_string(_('RFE')), 1547 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 1548 ) 1549 if self._payload[self._idx['assessment_of_encounter']] is not None: 1550 tex += u'%s & %s \\tabularnewline \n' % ( 1551 gmTools.tex_escape_string(_('AOE')), 1552 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 1553 ) 1554 1555 tex += u'\\hline \\tabularnewline \n' 1556 tex += u' & \\tabularnewline \n' 1557 1558 return tex
1559 #--------------------------------------------------------
1560 - def format(self, episodes=None, with_soap=False, left_margin=0, patient=None, issues=None, with_docs=True, with_tests=True, fancy_header=True, with_vaccinations=True, with_co_encountlet_hints=False, with_rfe_aoe=False):
1561 """Format an encounter. 1562 1563 with_co_encountlet_hints: 1564 - whether to include which *other* episodes were discussed during this encounter 1565 - (only makes sense if episodes != None) 1566 """ 1567 lines = [] 1568 1569 if fancy_header: 1570 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1571 u' ' * left_margin, 1572 self._payload[self._idx['l10n_type']], 1573 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1574 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1575 self._payload[self._idx['source_time_zone']], 1576 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 1577 self._payload[self._idx['pk_encounter']] 1578 )) 1579 1580 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1581 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1582 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1583 gmDateTime.current_local_iso_numeric_timezone_string, 1584 gmTools.bool2subst ( 1585 gmDateTime.dst_currently_in_effect, 1586 gmDateTime.py_dst_timezone_name, 1587 gmDateTime.py_timezone_name 1588 ), 1589 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1590 )) 1591 1592 if self._payload[self._idx['reason_for_encounter']] is not None: 1593 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1594 1595 if self._payload[self._idx['assessment_of_encounter']] is not None: 1596 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1597 1598 else: 1599 lines.append(u'%s%s: %s - %s%s' % ( 1600 u' ' * left_margin, 1601 self._payload[self._idx['l10n_type']], 1602 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1603 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1604 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1605 )) 1606 if with_rfe_aoe: 1607 if self._payload[self._idx['reason_for_encounter']] is not None: 1608 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1609 if self._payload[self._idx['assessment_of_encounter']] is not None: 1610 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1611 1612 if with_soap: 1613 lines.append(u'') 1614 1615 if patient.ID != self._payload[self._idx['pk_patient']]: 1616 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 1617 patient.ID, 1618 self._payload[self._idx['pk_encounter']], 1619 self._payload[self._idx['pk_patient']] 1620 ) 1621 raise ValueError(msg) 1622 1623 emr = patient.get_emr() 1624 1625 lines.extend(self.format_soap ( 1626 episodes = episodes, 1627 left_margin = left_margin, 1628 soap_cats = 'soap', 1629 emr = emr, 1630 issues = issues 1631 )) 1632 1633 # test results 1634 if with_tests: 1635 tests = emr.get_test_results_by_date ( 1636 episodes = episodes, 1637 encounter = self._payload[self._idx['pk_encounter']] 1638 ) 1639 if len(tests) > 0: 1640 lines.append('') 1641 lines.append(_('Measurements and Results:')) 1642 1643 for t in tests: 1644 lines.extend(t.format()) 1645 1646 del tests 1647 1648 # vaccinations 1649 if with_vaccinations: 1650 vaccs = emr.get_vaccinations ( 1651 episodes = episodes, 1652 encounters = [ self._payload[self._idx['pk_encounter']] ] 1653 ) 1654 1655 if len(vaccs) > 0: 1656 lines.append(u'') 1657 lines.append(_('Vaccinations:')) 1658 1659 for vacc in vaccs: 1660 lines.extend(vacc.format ( 1661 with_indications = True, 1662 with_comment = True, 1663 with_reaction = True, 1664 date_format = '%Y-%m-%d' 1665 )) 1666 del vaccs 1667 1668 # documents 1669 if with_docs: 1670 doc_folder = patient.get_document_folder() 1671 docs = doc_folder.get_documents ( 1672 episodes = episodes, 1673 encounter = self._payload[self._idx['pk_encounter']] 1674 ) 1675 1676 if len(docs) > 0: 1677 lines.append(u'') 1678 lines.append(_('Documents:')) 1679 1680 for d in docs: 1681 lines.append(u' %s %s:%s%s' % ( 1682 d['clin_when'].strftime('%Y-%m-%d'), 1683 d['l10n_type'], 1684 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1685 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1686 )) 1687 1688 del docs 1689 1690 # co-encountlets 1691 if with_co_encountlet_hints: 1692 if episodes is not None: 1693 other_epis = self.get_episodes(exclude = episodes) 1694 if len(other_epis) > 0: 1695 lines.append(u'') 1696 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 1697 for epi in other_epis: 1698 lines.append(u' %s%s%s%s' % ( 1699 gmTools.u_left_double_angle_quote, 1700 epi['description'], 1701 gmTools.u_right_double_angle_quote, 1702 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 1703 )) 1704 1705 eol_w_margin = u'\n%s' % (u' ' * left_margin) 1706 return u'%s\n' % eol_w_margin.join(lines)
1707 #-------------------------------------------------------- 1708 # properties 1709 #--------------------------------------------------------
1710 - def _get_rfe_codes(self):
1711 cmd = u""" 1712 SELECT * FROM clin.v_linked_codes WHERE 1713 item_table = 'clin.lnk_code2rfe'::regclass 1714 AND 1715 pk_item = %(item)s 1716 """ 1717 args = {'item': self._payload[self._idx['pk_encounter']]} 1718 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1719 return rows
1720 1721 rfe_codes = property(_get_rfe_codes, lambda x:x) 1722 #--------------------------------------------------------
1723 - def _get_aoe_codes(self):
1724 cmd = u""" 1725 SELECT * FROM clin.v_linked_codes WHERE 1726 item_table = 'clin.lnk_code2aoe'::regclass 1727 AND 1728 pk_item = %(item)s 1729 """ 1730 args = {'item': self._payload[self._idx['pk_encounter']]} 1731 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1732 return rows
1733 1734 aoe_codes = property(_get_aoe_codes, lambda x:x)
1735 #-----------------------------------------------------------
1736 -def create_encounter(fk_patient=None, fk_location=-1, enc_type=None):
1737 """Creates a new encounter for a patient. 1738 1739 fk_patient - patient PK 1740 fk_location - encounter location 1741 enc_type - type of encounter 1742 1743 FIXME: we don't deal with location yet 1744 """ 1745 if enc_type is None: 1746 enc_type = u'in surgery' 1747 # insert new encounter 1748 queries = [] 1749 try: 1750 enc_type = int(enc_type) 1751 cmd = u""" 1752 INSERT INTO clin.encounter ( 1753 fk_patient, fk_location, fk_type 1754 ) VALUES ( 1755 %(pat)s, 1756 -1, 1757 %(typ)s 1758 ) RETURNING pk""" 1759 except ValueError: 1760 enc_type = enc_type 1761 cmd = u""" 1762 insert into clin.encounter ( 1763 fk_patient, fk_location, fk_type 1764 ) values ( 1765 %(pat)s, 1766 -1, 1767 coalesce((select pk from clin.encounter_type where description = %(typ)s), 0) 1768 ) RETURNING pk""" 1769 args = {'pat': fk_patient, 'typ': enc_type} 1770 queries.append({'cmd': cmd, 'args': args}) 1771 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 1772 encounter = cEncounter(aPK_obj = rows[0]['pk']) 1773 1774 return encounter
1775 #-----------------------------------------------------------
1776 -def update_encounter_type(description=None, l10n_description=None):
1777 1778 rows, idx = gmPG2.run_rw_queries( 1779 queries = [{ 1780 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 1781 'args': {'desc': description, 'l10n_desc': l10n_description} 1782 }], 1783 return_data = True 1784 ) 1785 1786 success = rows[0][0] 1787 if not success: 1788 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 1789 1790 return {'description': description, 'l10n_description': l10n_description}
1791 #-----------------------------------------------------------
1792 -def create_encounter_type(description=None, l10n_description=None):
1793 """This will attempt to create a NEW encounter type.""" 1794 1795 # need a system name, so derive one if necessary 1796 if description is None: 1797 description = l10n_description 1798 1799 args = { 1800 'desc': description, 1801 'l10n_desc': l10n_description 1802 } 1803 1804 _log.debug('creating encounter type: %s, %s', description, l10n_description) 1805 1806 # does it exist already ? 1807 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 1808 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1809 1810 # yes 1811 if len(rows) > 0: 1812 # both system and l10n name are the same so all is well 1813 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 1814 _log.info('encounter type [%s] already exists with the proper translation') 1815 return {'description': description, 'l10n_description': l10n_description} 1816 1817 # or maybe there just wasn't a translation to 1818 # the current language for this type yet ? 1819 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 1820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1821 1822 # there was, so fail 1823 if rows[0][0]: 1824 _log.error('encounter type [%s] already exists but with another translation') 1825 return None 1826 1827 # else set it 1828 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 1829 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1830 return {'description': description, 'l10n_description': l10n_description} 1831 1832 # no 1833 queries = [ 1834 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 1835 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 1836 ] 1837 rows, idx = gmPG2.run_rw_queries(queries = queries) 1838 1839 return {'description': description, 'l10n_description': l10n_description}
1840 #-----------------------------------------------------------
1841 -def get_encounter_types():
1842 cmd = u"select _(description) as l10n_description, description from clin.encounter_type" 1843 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 1844 return rows
1845 #-----------------------------------------------------------
1846 -def get_encounter_type(description=None):
1847 cmd = u"SELECT * from clin.encounter_type where description = %s" 1848 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 1849 return rows
1850 #-----------------------------------------------------------
1851 -def delete_encounter_type(description=None):
1852 cmd = u"delete from clin.encounter_type where description = %(desc)s" 1853 args = {'desc': description} 1854 try: 1855 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1856 except gmPG2.dbapi.IntegrityError, e: 1857 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 1858 return False 1859 raise 1860 1861 return True
1862 #============================================================
1863 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
1864 """Represents one problem. 1865 1866 problems are the aggregation of 1867 .clinically_relevant=True issues and 1868 .is_open=True episodes 1869 """ 1870 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 1871 _cmds_store_payload = [u"select 1"] 1872 _updatable_fields = [] 1873 1874 #--------------------------------------------------------
1875 - def __init__(self, aPK_obj=None, try_potential_problems=False):
1876 """Initialize. 1877 1878 aPK_obj must contain the keys 1879 pk_patient 1880 pk_episode 1881 pk_health_issue 1882 """ 1883 if aPK_obj is None: 1884 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 1885 1886 # As problems are rows from a view of different emr struct items, 1887 # the PK can't be a single field and, as some of the values of the 1888 # composed PK may be None, they must be queried using 'is null', 1889 # so we must programmatically construct the SQL query 1890 where_parts = [] 1891 pk = {} 1892 for col_name in aPK_obj.keys(): 1893 val = aPK_obj[col_name] 1894 if val is None: 1895 where_parts.append('%s IS NULL' % col_name) 1896 else: 1897 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 1898 pk[col_name] = val 1899 1900 # try to instantiate from true problem view 1901 cProblem._cmd_fetch_payload = u""" 1902 SELECT *, False as is_potential_problem 1903 FROM clin.v_problem_list 1904 WHERE %s""" % u' AND '.join(where_parts) 1905 1906 try: 1907 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 1908 return 1909 except gmExceptions.ConstructorError: 1910 _log.exception('actual problem not found, trying "potential" problems') 1911 if try_potential_problems is False: 1912 raise 1913 1914 # try to instantiate from potential-problems view 1915 cProblem._cmd_fetch_payload = u""" 1916 SELECT *, True as is_potential_problem 1917 FROM clin.v_potential_problem_list 1918 WHERE %s""" % u' AND '.join(where_parts) 1919 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1920 #--------------------------------------------------------
1921 - def get_as_episode(self):
1922 """ 1923 Retrieve the cEpisode instance equivalent to this problem. 1924 The problem's type attribute must be 'episode' 1925 """ 1926 if self._payload[self._idx['type']] != 'episode': 1927 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 1928 return None 1929 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
1930 #--------------------------------------------------------
1931 - def get_visual_progress_notes(self, encounter_id=None):
1932 1933 if self._payload[self._idx['type']] == u'issue': 1934 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 1935 #xxxxxxxxxxxxx 1936 1937 emr = patient.get_emr() 1938 1939 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 1940 return doc_folder.get_visual_progress_notes ( 1941 health_issue = self._payload[self._idx['pk_health_issue']], 1942 episode = self._payload[self._idx['pk_episode']] 1943 )
1944 #-------------------------------------------------------- 1945 # properties 1946 #-------------------------------------------------------- 1947 # doubles as 'diagnostic_certainty_description' getter:
1949 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1950 1951 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 1952 #--------------------------------------------------------
1953 - def _get_generic_codes(self):
1954 if self._payload[self._idx['type']] == u'issue': 1955 cmd = u""" 1956 SELECT * FROM clin.v_linked_codes WHERE 1957 item_table = 'clin.lnk_code2h_issue'::regclass 1958 AND 1959 pk_item = %(item)s 1960 """ 1961 args = {'item': self._payload[self._idx['pk_health_issue']]} 1962 else: 1963 cmd = u""" 1964 SELECT * FROM clin.v_linked_codes WHERE 1965 item_table = 'clin.lnk_code2episode'::regclass 1966 AND 1967 pk_item = %(item)s 1968 """ 1969 args = {'item': self._payload[self._idx['pk_episode']]} 1970 1971 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1972 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1973 1974 generic_codes = property(_get_generic_codes, lambda x:x)
1975 #-----------------------------------------------------------
1976 -def problem2episode(problem=None):
1977 """Retrieve the cEpisode instance equivalent to the given problem. 1978 1979 The problem's type attribute must be 'episode' 1980 1981 @param problem: The problem to retrieve its related episode for 1982 @type problem: A gmEMRStructItems.cProblem instance 1983 """ 1984 if isinstance(problem, cEpisode): 1985 return problem 1986 1987 exc = TypeError('cannot convert [%s] to episode' % problem) 1988 1989 if not isinstance(problem, cProblem): 1990 raise exc 1991 1992 if problem['type'] != 'episode': 1993 raise exc 1994 1995 return cEpisode(aPK_obj = problem['pk_episode'])
1996 #-----------------------------------------------------------
1997 -def problem2issue(problem=None):
1998 """Retrieve the cIssue instance equivalent to the given problem. 1999 2000 The problem's type attribute must be 'issue'. 2001 2002 @param problem: The problem to retrieve the corresponding issue for 2003 @type problem: A gmEMRStructItems.cProblem instance 2004 """ 2005 if isinstance(problem, cHealthIssue): 2006 return problem 2007 2008 exc = TypeError('cannot convert [%s] to health issue' % problem) 2009 2010 if not isinstance(problem, cProblem): 2011 raise exc 2012 2013 if problem['type'] != 'issue': 2014 raise exc 2015 2016 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2017 #-----------------------------------------------------------
2018 -def reclass_problem(self, problem=None):
2019 """Transform given problem into either episode or health issue instance. 2020 """ 2021 if isinstance(problem, (cEpisode, cHealthIssue)): 2022 return problem 2023 2024 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 2025 2026 if not isinstance(problem, cProblem): 2027 _log.debug(u'%s' % problem) 2028 raise exc 2029 2030 if problem['type'] == 'episode': 2031 return cEpisode(aPK_obj = problem['pk_episode']) 2032 2033 if problem['type'] == 'issue': 2034 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 2035 2036 raise exc
2037 #============================================================
2038 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
2039 2040 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 2041 _cmds_store_payload = [ 2042 u"""update clin.hospital_stay set 2043 clin_when = %(admission)s, 2044 discharge = %(discharge)s, 2045 narrative = gm.nullify_empty_string(%(hospital)s), 2046 fk_episode = %(pk_episode)s, 2047 fk_encounter = %(pk_encounter)s 2048 where 2049 pk = %(pk_hospital_stay)s and 2050 xmin = %(xmin_hospital_stay)s""", 2051 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 2052 ] 2053 _updatable_fields = [ 2054 'admission', 2055 'discharge', 2056 'hospital', 2057 'pk_episode', 2058 'pk_encounter' 2059 ] 2060 #-------------------------------------------------------
2061 - def format(self, left_margin=0, include_procedures=False, include_docs=False):
2062 2063 if self._payload[self._idx['discharge']] is not None: 2064 dis = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2065 else: 2066 dis = u'' 2067 2068 line = u'%s%s%s%s: %s%s%s' % ( 2069 u' ' * left_margin, 2070 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2071 dis, 2072 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 2073 gmTools.u_left_double_angle_quote, 2074 self._payload[self._idx['episode']], 2075 gmTools.u_right_double_angle_quote 2076 ) 2077 2078 return line
2079 #-----------------------------------------------------------
2080 -def get_patient_hospital_stays(patient=None):
2081 2082 queries = [{ 2083 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission', 2084 'args': {'pat': patient} 2085 }] 2086 2087 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2088 2089 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
2090 #-----------------------------------------------------------
2091 -def create_hospital_stay(encounter=None, episode=None):
2092 2093 queries = [{ 2094 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk', 2095 'args': {'enc': encounter, 'epi': episode} 2096 }] 2097 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2098 2099 return cHospitalStay(aPK_obj = rows[0][0])
2100 #-----------------------------------------------------------
2101 -def delete_hospital_stay(stay=None):
2102 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 2103 args = {'pk': stay} 2104 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2105 return True
2106 #============================================================
2107 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
2108 2109 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 2110 _cmds_store_payload = [ 2111 u"""UPDATE clin.procedure SET 2112 soap_cat = 'p', 2113 clin_when = %(clin_when)s, 2114 clin_end = %(clin_end)s, 2115 is_ongoing = %(is_ongoing)s, 2116 clin_where = NULLIF ( 2117 COALESCE ( 2118 %(pk_hospital_stay)s::TEXT, 2119 gm.nullify_empty_string(%(clin_where)s) 2120 ), 2121 %(pk_hospital_stay)s::TEXT 2122 ), 2123 narrative = gm.nullify_empty_string(%(performed_procedure)s), 2124 fk_hospital_stay = %(pk_hospital_stay)s, 2125 fk_episode = %(pk_episode)s, 2126 fk_encounter = %(pk_encounter)s 2127 WHERE 2128 pk = %(pk_procedure)s AND 2129 xmin = %(xmin_procedure)s 2130 RETURNING xmin as xmin_procedure""" 2131 ] 2132 _updatable_fields = [ 2133 'clin_when', 2134 'clin_end', 2135 'is_ongoing', 2136 'clin_where', 2137 'performed_procedure', 2138 'pk_hospital_stay', 2139 'pk_episode', 2140 'pk_encounter' 2141 ] 2142 #-------------------------------------------------------
2143 - def __setitem__(self, attribute, value):
2144 2145 if (attribute == 'pk_hospital_stay') and (value is not None): 2146 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 2147 2148 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 2149 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 2150 2151 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
2152 #-------------------------------------------------------
2153 - def format(self, left_margin=0, include_episode=True):
2154 2155 if self._payload[self._idx['is_ongoing']]: 2156 end = _(' (ongoing)') 2157 else: 2158 end = self._payload[self._idx['clin_end']] 2159 if end is None: 2160 end = u'' 2161 else: 2162 end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2163 2164 line = u'%s%s%s, %s: %s' % ( 2165 (u' ' * left_margin), 2166 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2167 end, 2168 self._payload[self._idx['clin_where']], 2169 self._payload[self._idx['performed_procedure']] 2170 ) 2171 if include_episode: 2172 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 2173 2174 return line
2175 #--------------------------------------------------------
2176 - def add_code(self, pk_code=None):
2177 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2178 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 2179 args = { 2180 'issue': self._payload[self._idx['pk_procedure']], 2181 'code': pk_code 2182 } 2183 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2184 return True
2185 #--------------------------------------------------------
2186 - def remove_code(self, pk_code=None):
2187 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2188 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 2189 args = { 2190 'issue': self._payload[self._idx['pk_procedure']], 2191 'code': pk_code 2192 } 2193 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2194 return True
2195 #-------------------------------------------------------- 2196 # properties 2197 #--------------------------------------------------------
2198 - def _get_codes(self):
2199 cmd = u""" 2200 SELECT * FROM clin.v_linked_codes WHERE 2201 item_table = 'clin.lnk_code2procedure'::regclass 2202 AND 2203 pk_item = %(issue)s 2204 """ 2205 args = {'issue': self._payload[self._idx['pk_procedure']]} 2206 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 2207 return rows
2208 2209 codes = property(_get_codes, lambda x:x)
2210 #-----------------------------------------------------------
2211 -def get_performed_procedures(patient=None):
2212 2213 queries = [ 2214 { 2215 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 2216 'args': {'pat': patient} 2217 } 2218 ] 2219 2220 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2221 2222 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
2223 #-----------------------------------------------------------
2224 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
2225 2226 queries = [{ 2227 'cmd': u""" 2228 INSERT INTO clin.procedure ( 2229 fk_encounter, 2230 fk_episode, 2231 soap_cat, 2232 clin_where, 2233 fk_hospital_stay, 2234 narrative 2235 ) VALUES ( 2236 %(enc)s, 2237 %(epi)s, 2238 'p', 2239 gm.nullify_empty_string(%(loc)s), 2240 %(stay)s, 2241 gm.nullify_empty_string(%(proc)s) 2242 ) 2243 RETURNING pk""", 2244 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 2245 }] 2246 2247 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2248 2249 return cPerformedProcedure(aPK_obj = rows[0][0])
2250 #-----------------------------------------------------------
2251 -def delete_performed_procedure(procedure=None):
2252 cmd = u'delete from clin.procedure where pk = %(pk)s' 2253 args = {'pk': procedure} 2254 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2255 return True
2256 #============================================================ 2257 # main - unit testing 2258 #------------------------------------------------------------ 2259 if __name__ == '__main__': 2260 2261 if len(sys.argv) < 2: 2262 sys.exit() 2263 2264 if sys.argv[1] != 'test': 2265 sys.exit() 2266 2267 #-------------------------------------------------------- 2268 # define tests 2269 #--------------------------------------------------------
2270 - def test_problem():
2271 print "\nProblem test" 2272 print "------------" 2273 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 2274 print prob 2275 fields = prob.get_fields() 2276 for field in fields: 2277 print field, ':', prob[field] 2278 print '\nupdatable:', prob.get_updatable_fields() 2279 epi = prob.get_as_episode() 2280 print '\nas episode:' 2281 if epi is not None: 2282 for field in epi.get_fields(): 2283 print ' .%s : %s' % (field, epi[field])
2284 #--------------------------------------------------------
2285 - def test_health_issue():
2286 print "\nhealth issue test" 2287 print "-----------------" 2288 h_issue = cHealthIssue(aPK_obj=2) 2289 print h_issue 2290 fields = h_issue.get_fields() 2291 for field in fields: 2292 print field, ':', h_issue[field] 2293 print "has open episode:", h_issue.has_open_episode() 2294 print "open episode:", h_issue.get_open_episode() 2295 print "updateable:", h_issue.get_updatable_fields() 2296 h_issue.close_expired_episode(ttl=7300) 2297 h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 2298 print h_issue 2299 print h_issue.format_as_journal()
2300 #--------------------------------------------------------
2301 - def test_episode():
2302 print "\nepisode test" 2303 print "------------" 2304 episode = cEpisode(aPK_obj=1) 2305 print episode 2306 fields = episode.get_fields() 2307 for field in fields: 2308 print field, ':', episode[field] 2309 print "updatable:", episode.get_updatable_fields() 2310 raw_input('ENTER to continue') 2311 2312 old_description = episode['description'] 2313 old_enc = cEncounter(aPK_obj = 1) 2314 2315 desc = '1-%s' % episode['description'] 2316 print "==> renaming to", desc 2317 successful = episode.rename ( 2318 description = desc 2319 ) 2320 if not successful: 2321 print "error" 2322 else: 2323 print "success" 2324 for field in fields: 2325 print field, ':', episode[field] 2326 2327 print "episode range:", episode.get_access_range() 2328 2329 raw_input('ENTER to continue')
2330 2331 #--------------------------------------------------------
2332 - def test_encounter():
2333 print "\nencounter test" 2334 print "--------------" 2335 encounter = cEncounter(aPK_obj=1) 2336 print encounter 2337 fields = encounter.get_fields() 2338 for field in fields: 2339 print field, ':', encounter[field] 2340 print "updatable:", encounter.get_updatable_fields()
2341 #--------------------------------------------------------
2342 - def test_encounter2latex():
2343 encounter = cEncounter(aPK_obj=1) 2344 print encounter 2345 print "" 2346 print encounter.format_latex()
2347 #--------------------------------------------------------
2348 - def test_performed_procedure():
2349 procs = get_performed_procedures(patient = 12) 2350 for proc in procs: 2351 print proc.format(left_margin=2)
2352 #--------------------------------------------------------
2353 - def test_hospital_stay():
2354 stay = create_hospital_stay(encounter = 1, episode = 2) 2355 stay['hospital'] = u'Starfleet Galaxy General Hospital' 2356 stay.save_payload() 2357 print stay 2358 for s in get_patient_hospital_stays(12): 2359 print s 2360 delete_hospital_stay(stay['pk_hospital_stay']) 2361 stay = create_hospital_stay(encounter = 1, episode = 4)
2362 #--------------------------------------------------------
2363 - def test_diagnostic_certainty_classification_map():
2364 tests = [None, 'A', 'B', 'C', 'D', 'E'] 2365 2366 for t in tests: 2367 print type(t), t 2368 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
2369 #--------------------------------------------------------
2370 - def test_episode_codes():
2371 epi = cEpisode(aPK_obj = 2) 2372 print epi 2373 print epi.generic_codes
2374 #-------------------------------------------------------- 2375 # run them 2376 #test_episode() 2377 #test_problem() 2378 #test_encounter() 2379 #test_health_issue() 2380 #test_hospital_stay() 2381 #test_performed_procedure() 2382 #test_diagnostic_certainty_classification_map() 2383 #test_encounter2latex() 2384 test_episode_codes() 2385 #============================================================ 2386