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 v2 or later 
   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 latest = open_episode.latest_access_date 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 with_summary=True, 282 with_codes=True, 283 with_episodes=True, 284 with_encounters=True, 285 with_medications=True, 286 with_hospital_stays=True, 287 with_procedures=True, 288 with_family_history=True, 289 with_documents=True, 290 with_tests=True, 291 with_vaccinations=True 292 ):
293 294 if patient.ID != self._payload[self._idx['pk_patient']]: 295 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % ( 296 patient.ID, 297 self._payload[self._idx['pk_health_issue']], 298 self._payload[self._idx['pk_patient']] 299 ) 300 raise ValueError(msg) 301 302 lines = [] 303 304 lines.append(_('Health Issue %s%s%s%s [#%s]') % ( 305 u'\u00BB', 306 self._payload[self._idx['description']], 307 u'\u00AB', 308 gmTools.coalesce ( 309 initial = self.laterality_description, 310 instead = u'', 311 template_initial = u' (%s)', 312 none_equivalents = [None, u'', u'?'] 313 ), 314 self._payload[self._idx['pk_health_issue']] 315 )) 316 317 if self._payload[self._idx['is_confidential']]: 318 lines.append('') 319 lines.append(_(' ***** CONFIDENTIAL *****')) 320 lines.append('') 321 322 if self._payload[self._idx['is_cause_of_death']]: 323 lines.append('') 324 lines.append(_(' contributed to death of patient')) 325 lines.append('') 326 327 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 328 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % ( 329 enc['l10n_type'], 330 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 331 enc['last_affirmed_original_tz'].strftime('%H:%M'), 332 self._payload[self._idx['pk_encounter']] 333 )) 334 335 if self._payload[self._idx['age_noted']] is not None: 336 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable()) 337 338 lines.append(u' ' + _('Status') + u': %s, %s%s' % ( 339 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')), 340 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')), 341 gmTools.coalesce ( 342 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 343 instead = u'', 344 template_initial = u', %s', 345 none_equivalents = [None, u''] 346 ) 347 )) 348 349 if with_summary: 350 if self._payload[self._idx['summary']] is not None: 351 lines.append(u'') 352 lines.append(gmTools.wrap ( 353 text = self._payload[self._idx['summary']], 354 width = 60, 355 initial_indent = u' ', 356 subsequent_indent = u' ' 357 )) 358 359 # codes ? 360 if with_codes: 361 codes = self.generic_codes 362 if len(codes) > 0: 363 lines.append(u'') 364 for c in codes: 365 lines.append(u' %s: %s (%s - %s)' % ( 366 c['code'], 367 c['term'], 368 c['name_short'], 369 c['version'] 370 )) 371 del codes 372 373 lines.append(u'') 374 375 emr = patient.get_emr() 376 377 # episodes 378 if with_episodes: 379 epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]]) 380 if epis is None: 381 lines.append(_('Error retrieving episodes for this health issue.')) 382 elif len(epis) == 0: 383 lines.append(_('There are no episodes for this health issue.')) 384 else: 385 lines.append ( 386 _('Episodes: %s (most recent: %s%s%s)') % ( 387 len(epis), 388 gmTools.u_left_double_angle_quote, 389 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'], 390 gmTools.u_right_double_angle_quote 391 ) 392 ) 393 for epi in epis: 394 lines.append(u' \u00BB%s\u00AB (%s)' % ( 395 epi['description'], 396 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed')) 397 )) 398 lines.append('') 399 400 # encounters 401 if with_encounters: 402 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 403 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 404 405 if first_encounter is None or last_encounter is None: 406 lines.append(_('No encounters found for this health issue.')) 407 else: 408 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]]) 409 lines.append(_('Encounters: %s (%s - %s):') % ( 410 len(encs), 411 first_encounter['started_original_tz'].strftime('%m/%Y'), 412 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y') 413 )) 414 lines.append(_(' Most recent: %s - %s') % ( 415 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 416 last_encounter['last_affirmed_original_tz'].strftime('%H:%M') 417 )) 418 419 # medications 420 if with_medications: 421 meds = emr.get_current_substance_intake ( 422 issues = [ self._payload[self._idx['pk_health_issue']] ], 423 order_by = u'is_currently_active, started, substance' 424 ) 425 if len(meds) > 0: 426 lines.append(u'') 427 lines.append(_('Active medications: %s') % len(meds)) 428 for m in meds: 429 lines.append(m.format(left_margin = (left_margin + 1))) 430 del meds 431 432 # hospitalizations 433 if with_hospital_stays: 434 stays = emr.get_hospital_stays ( 435 issues = [ self._payload[self._idx['pk_health_issue']] ] 436 ) 437 if len(stays) > 0: 438 lines.append(u'') 439 lines.append(_('Hospitalizations: %s') % len(stays)) 440 for s in stays: 441 lines.append(s.format(left_margin = (left_margin + 1))) 442 del stays 443 444 # procedures 445 if with_procedures: 446 procs = emr.get_performed_procedures ( 447 issues = [ self._payload[self._idx['pk_health_issue']] ] 448 ) 449 if len(procs) > 0: 450 lines.append(u'') 451 lines.append(_('Procedures performed: %s') % len(procs)) 452 for p in procs: 453 lines.append(p.format(left_margin = (left_margin + 1))) 454 del procs 455 456 # family history 457 if with_family_history: 458 fhx = emr.get_family_history(issues = [ self._payload[self._idx['pk_health_issue']] ]) 459 if len(fhx) > 0: 460 lines.append(u'') 461 lines.append(_('Family History: %s') % len(fhx)) 462 for f in fhx: 463 lines.append(f.format ( 464 left_margin = (left_margin + 1), 465 include_episode = True, 466 include_comment = True, 467 include_codes = False 468 )) 469 del fhx 470 471 epis = self.get_episodes() 472 if len(epis) > 0: 473 epi_pks = [ e['pk_episode'] for e in epis ] 474 475 # documents 476 if with_documents: 477 doc_folder = patient.get_document_folder() 478 docs = doc_folder.get_documents(episodes = epi_pks) 479 if len(docs) > 0: 480 lines.append(u'') 481 lines.append(_('Documents: %s') % len(docs)) 482 del docs 483 484 # test results 485 if with_tests: 486 tests = emr.get_test_results_by_date(episodes = epi_pks) 487 if len(tests) > 0: 488 lines.append(u'') 489 lines.append(_('Measurements and Results: %s') % len(tests)) 490 del tests 491 492 # vaccinations 493 if with_vaccinations: 494 vaccs = emr.get_vaccinations(episodes = epi_pks, order_by = u'date_given, vaccine') 495 if len(vaccs) > 0: 496 lines.append(u'') 497 lines.append(_('Vaccinations:')) 498 for vacc in vaccs: 499 lines.extend(vacc.format(with_reaction = True)) 500 del vaccs 501 502 del epis 503 504 left_margin = u' ' * left_margin 505 eol_w_margin = u'\n%s' % left_margin 506 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n') 507 return left_margin + eol_w_margin.join(lines) + u'\n'
508 #-------------------------------------------------------- 509 # properties 510 #-------------------------------------------------------- 511 episodes = property(get_episodes, lambda x:x) 512 #-------------------------------------------------------- 513 open_episode = property(get_open_episode, lambda x:x) 514 #-------------------------------------------------------- 515 has_open_episode = property(has_open_episode, lambda x:x) 516 #--------------------------------------------------------
517 - def _get_first_episode(self):
518 cmd = u"""SELECT pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue = %(issue)s ORDER BY started_first limit 1""" 519 args = {'issue': self.pk_obj} 520 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 521 if len(rows) == 0: 522 return None 523 return cEpisode(aPK_obj = rows[0][0])
524 525 first_episode = property(_get_first_episode, lambda x:x) 526 #--------------------------------------------------------
527 - def _get_latest_episode(self):
528 cmd = u"""SELECT 529 coalesce ( 530 (SELECT pk FROM clin.episode WHERE fk_health_issue = %(issue)s AND is_open IS TRUE), 531 (SELECT pk_episode AS pk FROM clin.v_pat_episodes WHERE pk_health_issue = %(issue)s ORDER BY last_affirmed DESC limit 1) 532 )""" 533 args = {'issue': self.pk_obj} 534 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 535 if len(rows) == 0: 536 return None 537 if rows[0][0] is None: 538 return None 539 return cEpisode(aPK_obj = rows[0][0])
540 541 latest_episode = property(_get_latest_episode, lambda x:x) 542 #-------------------------------------------------------- 543 # Steffi suggested we divide into safe and assumed start dates
544 - def _get_safe_start_date(self):
545 """This returns the date when we can assume to safely 546 KNOW the health issue existed (because 547 the provider said so).""" 548 args = { 549 'enc': self._payload[self._idx['pk_encounter']], 550 'pk': self._payload[self._idx['pk_health_issue']] 551 } 552 cmd = u""" 553 SELECT COALESCE ( 554 -- this one must override all: 555 -- .age_noted if not null and DOB is known 556 (CASE 557 WHEN c_hi.age_noted IS NULL 558 THEN NULL::timestamp with time zone 559 WHEN 560 (SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = ( 561 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s 562 )) IS NULL 563 THEN NULL::timestamp with time zone 564 ELSE 565 c_hi.age_noted + ( 566 SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = ( 567 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s 568 ) 569 ) 570 END), 571 572 -- start of encounter in which created, earliest = explicitely set 573 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 574 c_hi.fk_encounter 575 --SELECT fk_encounter FROM clin.health_issue WHERE clin.health_issue.pk = %(pk)s 576 )) 577 ) 578 FROM clin.health_issue c_hi 579 WHERE c_hi.pk = %(pk)s""" 580 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 581 return rows[0][0]
582 583 safe_start_date = property(_get_safe_start_date, lambda x:x) 584 #--------------------------------------------------------
585 - def _get_possible_start_date(self):
586 args = {'pk': self._payload[self._idx['pk_health_issue']]} 587 cmd = u""" 588 SELECT MIN(earliest) FROM ( 589 -- last modification, earliest = when created in/changed to the current state 590 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s) 591 592 UNION ALL 593 -- last modification of encounter in which created, earliest = initial creation of that encounter 594 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 595 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s 596 )) 597 598 UNION ALL 599 -- earliest explicit .clin_when of clinical items linked to this health_issue 600 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s) 601 602 UNION ALL 603 -- earliest modification time of clinical items linked to this health issue 604 -- this CAN be used since if an item is linked to a health issue it can be 605 -- assumed the health issue (should have) existed at the time of creation 606 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s) 607 608 UNION ALL 609 -- earliest start of encounters of clinical items linked to this episode 610 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN ( 611 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s 612 )) 613 614 -- here we should be looking at 615 -- .best_guess_start_date of all episodes linked to this encounter 616 617 ) AS candidates""" 618 619 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 620 return rows[0][0]
621 622 possible_start_date = property(_get_possible_start_date) 623 #--------------------------------------------------------
624 - def _get_end_date(self):
625 if self._payload[self._idx['is_active']]: 626 return gmDateTime.pydt_now_here() 627 if self.has_open_episode: 628 return gmDateTime.pydt_now_here() 629 return self.latest_access_date
630 631 end_date = property(_get_end_date) 632 #--------------------------------------------------------
633 - def _get_latest_access_date(self):
634 args = { 635 'enc': self._payload[self._idx['pk_encounter']], 636 'pk': self._payload[self._idx['pk_health_issue']] 637 } 638 cmd = u""" 639 SELECT 640 MAX(latest) 641 FROM ( 642 -- last modification, latest = when last changed to the current state 643 -- DO NOT USE: database upgrades may change this field 644 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s) 645 646 --UNION ALL 647 -- last modification of encounter in which created, latest = initial creation of that encounter 648 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer 649 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 650 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 651 -- ) 652 --) 653 654 --UNION ALL 655 -- end of encounter in which created, latest = explicitely set 656 -- DO NOT USE: we can retrospectively create issues which 657 -- DO NOT USE: are long since finished 658 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 659 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 660 -- ) 661 --) 662 663 UNION ALL 664 -- latest end of encounters of clinical items linked to this issue 665 (SELECT 666 MAX(last_affirmed) AS latest 667 FROM clin.encounter 668 WHERE pk IN ( 669 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s 670 ) 671 ) 672 673 UNION ALL 674 -- latest explicit .clin_when of clinical items linked to this issue 675 (SELECT 676 MAX(clin_when) AS latest 677 FROM clin.v_pat_items 678 WHERE pk_health_issue = %(pk)s 679 ) 680 681 -- latest modification time of clinical items linked to this issue 682 -- this CAN be used since if an item is linked to an issue it can be 683 -- assumed the issue (should have) existed at the time of modification 684 -- DO NOT USE, because typo fixes should not extend the issue 685 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 686 687 ) AS candidates""" 688 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 689 return rows[0][0]
690 691 latest_access_date = property(_get_latest_access_date) 692 #--------------------------------------------------------
694 try: 695 return laterality2str[self._payload[self._idx['laterality']]] 696 except KeyError: 697 return u'<???>'
698 699 laterality_description = property(_get_laterality_description, lambda x:x) 700 #--------------------------------------------------------
702 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
703 704 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 705 #--------------------------------------------------------
706 - def _get_generic_codes(self):
707 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 708 return [] 709 710 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 711 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 712 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 713 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
714
715 - def _set_generic_codes(self, pk_codes):
716 queries = [] 717 # remove all codes 718 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 719 queries.append ({ 720 'cmd': u'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s', 721 'args': { 722 'issue': self._payload[self._idx['pk_health_issue']], 723 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 724 } 725 }) 726 # add new codes 727 for pk_code in pk_codes: 728 queries.append ({ 729 'cmd': u'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)', 730 'args': { 731 'issue': self._payload[self._idx['pk_health_issue']], 732 'pk_code': pk_code 733 } 734 }) 735 if len(queries) == 0: 736 return 737 # run it all in one transaction 738 rows, idx = gmPG2.run_rw_queries(queries = queries) 739 return
740 741 generic_codes = property(_get_generic_codes, _set_generic_codes)
742 #============================================================
743 -def create_health_issue(description=None, encounter=None, patient=None):
744 """Creates a new health issue for a given patient. 745 746 description - health issue name 747 """ 748 try: 749 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 750 return h_issue 751 except gmExceptions.NoSuchBusinessObjectError: 752 pass 753 754 queries = [] 755 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 756 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 757 758 cmd = u"select currval('clin.health_issue_pk_seq')" 759 queries.append({'cmd': cmd}) 760 761 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 762 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 763 764 return h_issue
765 #-----------------------------------------------------------
766 -def delete_health_issue(health_issue=None):
767 if isinstance(health_issue, cHealthIssue): 768 pk = health_issue['pk_health_issue'] 769 else: 770 pk = int(health_issue) 771 772 try: 773 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}]) 774 except gmPG2.dbapi.IntegrityError: 775 # should be parsing pgcode/and or error message 776 _log.exception('cannot delete health issue') 777 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
778 #------------------------------------------------------------ 779 # use as dummy for unassociated episodes
780 -def get_dummy_health_issue():
781 issue = { 782 'pk_health_issue': None, 783 'description': _('Unattributed episodes'), 784 'age_noted': None, 785 'laterality': u'na', 786 'is_active': True, 787 'clinically_relevant': True, 788 'is_confidential': None, 789 'is_cause_of_death': False, 790 'is_dummy': True, 791 'grouping': None 792 } 793 return issue
794 #-----------------------------------------------------------
795 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
796 return cProblem ( 797 aPK_obj = { 798 'pk_patient': health_issue['pk_patient'], 799 'pk_health_issue': health_issue['pk_health_issue'], 800 'pk_episode': None 801 }, 802 try_potential_problems = allow_irrelevant 803 )
804 #============================================================ 805 # episodes API 806 #============================================================
807 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
808 """Represents one clinical episode. 809 """ 810 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s" 811 _cmds_store_payload = [ 812 u"""update clin.episode set 813 fk_health_issue = %(pk_health_issue)s, 814 is_open = %(episode_open)s::boolean, 815 description = %(description)s, 816 summary = gm.nullify_empty_string(%(summary)s), 817 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 818 where 819 pk = %(pk_episode)s and 820 xmin = %(xmin_episode)s""", 821 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 822 ] 823 _updatable_fields = [ 824 'pk_health_issue', 825 'episode_open', 826 'description', 827 'summary', 828 'diagnostic_certainty_classification' 829 ] 830 #--------------------------------------------------------
831 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
832 pk = aPK_obj 833 if pk is None and row is None: 834 835 where_parts = [u'description = %(desc)s'] 836 837 if id_patient is not None: 838 where_parts.append(u'pk_patient = %(pat)s') 839 840 if health_issue is not None: 841 where_parts.append(u'pk_health_issue = %(issue)s') 842 843 if encounter is not None: 844 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)') 845 846 args = { 847 'pat': id_patient, 848 'issue': health_issue, 849 'enc': encounter, 850 'desc': name 851 } 852 853 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts) 854 855 rows, idx = gmPG2.run_ro_queries( 856 queries = [{'cmd': cmd, 'args': args}], 857 get_col_idx=True 858 ) 859 860 if len(rows) == 0: 861 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter) 862 863 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 864 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 865 866 else: 867 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
868 #-------------------------------------------------------- 869 # external API 870 #--------------------------------------------------------
871 - def get_access_range(self):
872 """Get earliest and latest access to this episode. 873 874 Returns a tuple(earliest, latest). 875 """ 876 return (self.best_guess_start_date, self.latest_access_date)
877 #--------------------------------------------------------
878 - def get_patient(self):
879 return self._payload[self._idx['pk_patient']]
880 #--------------------------------------------------------
881 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
882 return gmClinNarrative.get_narrative ( 883 soap_cats = soap_cats, 884 encounters = encounters, 885 episodes = [self.pk_obj], 886 order_by = order_by 887 )
888 #--------------------------------------------------------
889 - def rename(self, description=None):
890 """Method for episode editing, that is, episode renaming. 891 892 @param description 893 - the new descriptive name for the encounter 894 @type description 895 - a string instance 896 """ 897 # sanity check 898 if description.strip() == '': 899 _log.error('<description> must be a non-empty string instance') 900 return False 901 # update the episode description 902 old_description = self._payload[self._idx['description']] 903 self._payload[self._idx['description']] = description.strip() 904 self._is_modified = True 905 successful, data = self.save_payload() 906 if not successful: 907 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 908 self._payload[self._idx['description']] = old_description 909 return False 910 return True
911 #--------------------------------------------------------
912 - def add_code(self, pk_code=None):
913 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 914 915 if pk_code in self._payload[self._idx['pk_generic_codes']]: 916 return 917 918 cmd = u""" 919 INSERT INTO clin.lnk_code2episode 920 (fk_item, fk_generic_code) 921 SELECT 922 %(item)s, 923 %(code)s 924 WHERE NOT EXISTS ( 925 SELECT 1 FROM clin.lnk_code2episode 926 WHERE 927 fk_item = %(item)s 928 AND 929 fk_generic_code = %(code)s 930 )""" 931 args = { 932 'item': self._payload[self._idx['pk_episode']], 933 'code': pk_code 934 } 935 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 936 return
937 #--------------------------------------------------------
938 - def remove_code(self, pk_code=None):
939 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 940 cmd = u"DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 941 args = { 942 'item': self._payload[self._idx['pk_episode']], 943 'code': pk_code 944 } 945 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 946 return True
947 #--------------------------------------------------------
948 - def format_as_journal(self, left_margin=0, date_format='%a, %b %d %Y'):
949 rows = gmClinNarrative.get_as_journal ( 950 episodes = (self.pk_obj,), 951 order_by = u'pk_encounter, clin_when, scr, src_table' 952 #order_by = u'pk_encounter, scr, clin_when, src_table' 953 ) 954 955 if len(rows) == 0: 956 return u'' 957 958 lines = [] 959 960 lines.append(_('Clinical data generated during encounters within this episode:')) 961 962 left_margin = u' ' * left_margin 963 964 prev_enc = None 965 for row in rows: 966 if row['pk_encounter'] != prev_enc: 967 lines.append(u'') 968 prev_enc = row['pk_encounter'] 969 970 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding()) 971 top_row = u'%s%s %s (%s) %s' % ( 972 gmTools.u_box_top_left_arc, 973 gmTools.u_box_horiz_single, 974 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']], 975 when, 976 gmTools.u_box_horiz_single * 5 977 ) 978 soap = gmTools.wrap ( 979 text = row['narrative'], 980 width = 60, 981 initial_indent = u' ', 982 subsequent_indent = u' ' + left_margin 983 ) 984 row_ver = u'' 985 if row['row_version'] > 0: 986 row_ver = u'v%s: ' % row['row_version'] 987 bottom_row = u'%s%s %s, %s%s %s' % ( 988 u' ' * 40, 989 gmTools.u_box_horiz_light_heavy, 990 row['modified_by'], 991 row_ver, 992 row['date_modified'], 993 gmTools.u_box_horiz_heavy_light 994 ) 995 996 lines.append(top_row) 997 lines.append(soap) 998 lines.append(bottom_row) 999 1000 eol_w_margin = u'\n%s' % left_margin 1001 return left_margin + eol_w_margin.join(lines) + u'\n'
1002 #--------------------------------------------------------
1003 - def format(self, left_margin=0, patient=None, 1004 with_summary=True, 1005 with_codes=True, 1006 with_encounters=True, 1007 with_documents=True, 1008 with_hospital_stays=True, 1009 with_procedures=True, 1010 with_family_history=True, 1011 with_tests=True, 1012 with_vaccinations=True, 1013 with_health_issue=False 1014 ):
1015 1016 if patient.ID != self._payload[self._idx['pk_patient']]: 1017 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 1018 patient.ID, 1019 self._payload[self._idx['pk_episode']], 1020 self._payload[self._idx['pk_patient']] 1021 ) 1022 raise ValueError(msg) 1023 1024 lines = [] 1025 1026 # episode details 1027 lines.append (_('Episode %s%s%s [#%s]') % ( 1028 gmTools.u_left_double_angle_quote, 1029 self._payload[self._idx['description']], 1030 gmTools.u_right_double_angle_quote, 1031 self._payload[self._idx['pk_episode']] 1032 )) 1033 1034 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 1035 lines.append (u' ' + _('Created during encounter: %s (%s - %s) [#%s]') % ( 1036 enc['l10n_type'], 1037 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1038 enc['last_affirmed_original_tz'].strftime('%H:%M'), 1039 self._payload[self._idx['pk_encounter']] 1040 )) 1041 1042 emr = patient.get_emr() 1043 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 1044 first_encounter = None 1045 last_encounter = None 1046 if (encs is not None) and (len(encs) > 0): 1047 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 1048 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 1049 if self._payload[self._idx['episode_open']]: 1050 end = gmDateTime.pydt_now_here() 1051 end_str = gmTools.u_ellipsis 1052 else: 1053 end = last_encounter['last_affirmed'] 1054 end_str = last_encounter['last_affirmed'].strftime('%m/%Y') 1055 age = gmDateTime.format_interval_medically(end - first_encounter['started']) 1056 lines.append(_(' Duration: %s (%s - %s)') % ( 1057 age, 1058 first_encounter['started'].strftime('%m/%Y'), 1059 end_str 1060 )) 1061 1062 lines.append(u' ' + _('Status') + u': %s%s' % ( 1063 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 1064 gmTools.coalesce ( 1065 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 1066 instead = u'', 1067 template_initial = u', %s', 1068 none_equivalents = [None, u''] 1069 ) 1070 )) 1071 1072 if with_health_issue: 1073 lines.append(u' ' + _('Health issue') + u': %s' % gmTools.coalesce ( 1074 self._payload[self._idx['health_issue']], 1075 _('none associated') 1076 )) 1077 1078 if with_summary: 1079 if self._payload[self._idx['summary']] is not None: 1080 lines.append(u'') 1081 lines.append(gmTools.wrap ( 1082 text = self._payload[self._idx['summary']], 1083 width = 60, 1084 initial_indent = u' ', 1085 subsequent_indent = u' ' 1086 ) 1087 ) 1088 1089 # codes 1090 if with_codes: 1091 codes = self.generic_codes 1092 if len(codes) > 0: 1093 lines.append(u'') 1094 for c in codes: 1095 lines.append(u' %s: %s (%s - %s)' % ( 1096 c['code'], 1097 c['term'], 1098 c['name_short'], 1099 c['version'] 1100 )) 1101 del codes 1102 1103 lines.append(u'') 1104 1105 # encounters 1106 if with_encounters: 1107 if encs is None: 1108 lines.append(_('Error retrieving encounters for this episode.')) 1109 elif len(encs) == 0: 1110 #lines.append(_('There are no encounters for this episode.')) 1111 pass 1112 else: 1113 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 1114 1115 if len(encs) < 4: 1116 line = _('%s encounter(s) (%s - %s):') 1117 else: 1118 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):') 1119 lines.append(line % ( 1120 len(encs), 1121 first_encounter['started'].strftime('%m/%Y'), 1122 last_encounter['last_affirmed'].strftime('%m/%Y') 1123 )) 1124 1125 lines.append(u' %s - %s (%s):%s' % ( 1126 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1127 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 1128 first_encounter['l10n_type'], 1129 gmTools.coalesce ( 1130 first_encounter['assessment_of_encounter'], 1131 gmTools.coalesce ( 1132 first_encounter['reason_for_encounter'], 1133 u'', 1134 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 1135 ), 1136 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 1137 ) 1138 )) 1139 1140 if len(encs) > 4: 1141 lines.append(_(' ... %s skipped ...') % (len(encs) - 4)) 1142 1143 for enc in encs[1:][-3:]: 1144 lines.append(u' %s - %s (%s):%s' % ( 1145 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1146 enc['last_affirmed_original_tz'].strftime('%H:%M'), 1147 enc['l10n_type'], 1148 gmTools.coalesce ( 1149 enc['assessment_of_encounter'], 1150 gmTools.coalesce ( 1151 enc['reason_for_encounter'], 1152 u'', 1153 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 1154 ), 1155 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 1156 ) 1157 )) 1158 del encs 1159 1160 # spell out last encounter 1161 if last_encounter is not None: 1162 lines.append('') 1163 lines.append(_('Progress notes in most recent encounter:')) 1164 lines.extend(last_encounter.format_soap ( 1165 episodes = [ self._payload[self._idx['pk_episode']] ], 1166 left_margin = left_margin, 1167 soap_cats = 'soapu', 1168 emr = emr 1169 )) 1170 1171 # documents 1172 if with_documents: 1173 doc_folder = patient.get_document_folder() 1174 docs = doc_folder.get_documents ( 1175 episodes = [ self._payload[self._idx['pk_episode']] ] 1176 ) 1177 if len(docs) > 0: 1178 lines.append('') 1179 lines.append(_('Documents: %s') % len(docs)) 1180 for d in docs: 1181 lines.append(u' %s %s:%s%s' % ( 1182 d['clin_when'].strftime('%Y-%m-%d'), 1183 d['l10n_type'], 1184 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1185 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1186 )) 1187 del docs 1188 1189 # hospitalizations 1190 if with_hospital_stays: 1191 stays = emr.get_hospital_stays(episodes = [ self._payload[self._idx['pk_episode']] ]) 1192 if len(stays) > 0: 1193 lines.append('') 1194 lines.append(_('Hospitalizations: %s') % len(stays)) 1195 for s in stays: 1196 lines.append(s.format(left_margin = (left_margin + 1))) 1197 del stays 1198 1199 # procedures 1200 if with_procedures: 1201 procs = emr.get_performed_procedures(episodes = [ self._payload[self._idx['pk_episode']] ]) 1202 if len(procs) > 0: 1203 lines.append(u'') 1204 lines.append(_('Procedures performed: %s') % len(procs)) 1205 for p in procs: 1206 lines.append(p.format ( 1207 left_margin = (left_margin + 1), 1208 include_episode = False, 1209 include_codes = True 1210 )) 1211 del procs 1212 1213 # family history 1214 if with_family_history: 1215 fhx = emr.get_family_history(episodes = [ self._payload[self._idx['pk_episode']] ]) 1216 if len(fhx) > 0: 1217 lines.append(u'') 1218 lines.append(_('Family History: %s') % len(fhx)) 1219 for f in fhx: 1220 lines.append(f.format ( 1221 left_margin = (left_margin + 1), 1222 include_episode = False, 1223 include_comment = True, 1224 include_codes = True 1225 )) 1226 del fhx 1227 1228 # test results 1229 if with_tests: 1230 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 1231 if len(tests) > 0: 1232 lines.append('') 1233 lines.append(_('Measurements and Results:')) 1234 for t in tests: 1235 lines.extend(t.format ( 1236 with_review = False, 1237 with_comments = False, 1238 date_format = '%Y-%m-%d' 1239 )) 1240 del tests 1241 1242 # vaccinations 1243 if with_vaccinations: 1244 vaccs = emr.get_vaccinations ( 1245 episodes = [ self._payload[self._idx['pk_episode']] ], 1246 order_by = u'date_given DESC, vaccine' 1247 ) 1248 if len(vaccs) > 0: 1249 lines.append(u'') 1250 lines.append(_('Vaccinations:')) 1251 for vacc in vaccs: 1252 lines.extend(vacc.format ( 1253 with_indications = True, 1254 with_comment = True, 1255 with_reaction = True, 1256 date_format = '%Y-%m-%d' 1257 )) 1258 del vaccs 1259 1260 left_margin = u' ' * left_margin 1261 eol_w_margin = u'\n%s' % left_margin 1262 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n') 1263 return left_margin + eol_w_margin.join(lines) + u'\n'
1264 #-------------------------------------------------------- 1265 # properties 1266 #--------------------------------------------------------
1267 - def _get_best_guess_start_date(self):
1268 cmd = u""" 1269 SELECT 1270 MIN(earliest) 1271 FROM ( 1272 -- last modification, earliest = when created in/changed to the current state 1273 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s) 1274 1275 UNION ALL 1276 1277 -- last modification of encounter in which created, earliest = initial creation of that encounter 1278 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1279 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1280 ) 1281 ) 1282 UNION ALL 1283 1284 -- start of encounter in which created, earliest = explicitely set 1285 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1286 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1287 ) 1288 ) 1289 UNION ALL 1290 1291 -- earliest start of encounters of clinical items linked to this episode 1292 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN ( 1293 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1294 ) 1295 ) 1296 UNION ALL 1297 1298 -- earliest explicit .clin_when of clinical items linked to this episode 1299 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1300 1301 UNION ALL 1302 1303 -- earliest modification time of clinical items linked to this episode 1304 -- this CAN be used since if an item is linked to an episode it can be 1305 -- assumed the episode (should have) existed at the time of creation 1306 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1307 1308 -- not sure about this one: 1309 -- .pk -> clin.clin_root_item.fk_encounter.modified_when 1310 1311 ) AS candidates""" 1312 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1313 return rows[0][0]
1314 1315 best_guess_start_date = property(_get_best_guess_start_date) 1316 #--------------------------------------------------------
1317 - def _get_latest_access_date(self):
1318 cmd = u""" 1319 SELECT 1320 MAX(latest) 1321 FROM ( 1322 -- last modification, latest = when last changed to the current state 1323 (SELECT c_epi.modified_when AS latest, 'clin.episode.modified_when'::text AS candidate FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s) 1324 1325 UNION ALL 1326 1327 -- last modification of encounter in which created, latest = initial creation of that encounter 1328 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer 1329 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1330 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1331 -- ) 1332 --) 1333 1334 -- end of encounter in which created, latest = explicitely set 1335 -- DO NOT USE: we can retrospectively create episodes which 1336 -- DO NOT USE: are long since finished 1337 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1338 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1339 -- ) 1340 --) 1341 1342 -- latest end of encounters of clinical items linked to this episode 1343 (SELECT 1344 MAX(last_affirmed) AS latest, 1345 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate 1346 FROM clin.encounter 1347 WHERE pk IN ( 1348 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1349 ) 1350 ) 1351 UNION ALL 1352 1353 -- latest explicit .clin_when of clinical items linked to this episode 1354 (SELECT 1355 MAX(clin_when) AS latest, 1356 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate 1357 FROM clin.clin_root_item 1358 WHERE fk_episode = %(pk)s 1359 ) 1360 1361 -- latest modification time of clinical items linked to this episode 1362 -- this CAN be used since if an item is linked to an episode it can be 1363 -- assumed the episode (should have) existed at the time of creation 1364 -- DO NOT USE, because typo fixes should not extend the episode 1365 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1366 1367 -- not sure about this one: 1368 -- .pk -> clin.clin_root_item.fk_encounter.modified_when 1369 1370 ) AS candidates""" 1371 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1372 #_log.debug('last episode access: %s (%s)', rows[0][0], rows[0][1]) 1373 return rows[0][0]
1374 1375 latest_access_date = property(_get_latest_access_date) 1376 #--------------------------------------------------------
1378 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1379 1380 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 1381 #--------------------------------------------------------
1382 - def _get_generic_codes(self):
1383 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 1384 return [] 1385 1386 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1387 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 1388 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1389 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1390
1391 - def _set_generic_codes(self, pk_codes):
1392 queries = [] 1393 # remove all codes 1394 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 1395 queries.append ({ 1396 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s', 1397 'args': { 1398 'epi': self._payload[self._idx['pk_episode']], 1399 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 1400 } 1401 }) 1402 # add new codes 1403 for pk_code in pk_codes: 1404 queries.append ({ 1405 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)', 1406 'args': { 1407 'epi': self._payload[self._idx['pk_episode']], 1408 'pk_code': pk_code 1409 } 1410 }) 1411 if len(queries) == 0: 1412 return 1413 # run it all in one transaction 1414 rows, idx = gmPG2.run_rw_queries(queries = queries) 1415 return
1416 1417 generic_codes = property(_get_generic_codes, _set_generic_codes) 1418 #--------------------------------------------------------
1419 - def _get_has_narrative(self):
1420 cmd = u"""SELECT EXISTS ( 1421 SELECT 1 FROM clin.clin_narrative 1422 WHERE 1423 fk_episode = %(epi)s 1424 AND 1425 fk_encounter IN ( 1426 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1427 ) 1428 )""" 1429 args = { 1430 u'pat': self._payload[self._idx['pk_patient']], 1431 u'epi': self._payload[self._idx['pk_episode']] 1432 } 1433 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1434 return rows[0][0]
1435 1436 has_narrative = property(_get_has_narrative, lambda x:x)
1437 #============================================================
1438 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
1439 """Creates a new episode for a given patient's health issue. 1440 1441 pk_health_issue - given health issue PK 1442 episode_name - name of episode 1443 """ 1444 if not allow_dupes: 1445 try: 1446 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 1447 if episode['episode_open'] != is_open: 1448 episode['episode_open'] = is_open 1449 episode.save_payload() 1450 return episode 1451 except gmExceptions.ConstructorError: 1452 pass 1453 1454 queries = [] 1455 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 1456 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 1457 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 1458 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 1459 1460 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 1461 return episode
1462 #-----------------------------------------------------------
1463 -def delete_episode(episode=None):
1464 if isinstance(episode, cEpisode): 1465 pk = episode['pk_episode'] 1466 else: 1467 pk = int(episode) 1468 1469 cmd = u'DELETE FROM clin.episode WHERE pk = %(pk)s' 1470 1471 try: 1472 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}]) 1473 except gmPG2.dbapi.IntegrityError: 1474 # should be parsing pgcode/and or error message 1475 _log.exception('cannot delete episode, it is in use') 1476 return False 1477 1478 return True
1479 #-----------------------------------------------------------
1480 -def episode2problem(episode=None, allow_closed=False):
1481 return cProblem ( 1482 aPK_obj = { 1483 'pk_patient': episode['pk_patient'], 1484 'pk_episode': episode['pk_episode'], 1485 'pk_health_issue': episode['pk_health_issue'] 1486 }, 1487 try_potential_problems = allow_closed 1488 )
1489 #============================================================ 1490 # encounter API 1491 #============================================================
1492 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1493 """Represents one encounter.""" 1494 1495 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 1496 _cmds_store_payload = [ 1497 u"""UPDATE clin.encounter SET 1498 started = %(started)s, 1499 last_affirmed = %(last_affirmed)s, 1500 fk_location = %(pk_location)s, 1501 fk_type = %(pk_type)s, 1502 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1503 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1504 WHERE 1505 pk = %(pk_encounter)s AND 1506 xmin = %(xmin_encounter)s 1507 """, 1508 # need to return all fields so we can survive in-place upgrades 1509 u"""select * from clin.v_pat_encounters where pk_encounter = %(pk_encounter)s""" 1510 ] 1511 _updatable_fields = [ 1512 'started', 1513 'last_affirmed', 1514 'pk_location', 1515 'pk_type', 1516 'reason_for_encounter', 1517 'assessment_of_encounter' 1518 ] 1519 #--------------------------------------------------------
1520 - def set_active(self):
1521 """Set the encounter as the active one. 1522 1523 "Setting active" means making sure the encounter 1524 row has the youngest "last_affirmed" timestamp of 1525 all encounter rows for this patient. 1526 """ 1527 self['last_affirmed'] = gmDateTime.pydt_now_here() 1528 self.save()
1529 #--------------------------------------------------------
1530 - def transfer_clinical_data(self, source_episode=None, target_episode=None):
1531 """ 1532 Moves every element currently linked to the current encounter 1533 and the source_episode onto target_episode. 1534 1535 @param source_episode The episode the elements are currently linked to. 1536 @type target_episode A cEpisode intance. 1537 @param target_episode The episode the elements will be relinked to. 1538 @type target_episode A cEpisode intance. 1539 """ 1540 if source_episode['pk_episode'] == target_episode['pk_episode']: 1541 return True 1542 1543 queries = [] 1544 cmd = u""" 1545 UPDATE clin.clin_root_item 1546 SET fk_episode = %(trg)s 1547 WHERE 1548 fk_encounter = %(enc)s AND 1549 fk_episode = %(src)s 1550 """ 1551 rows, idx = gmPG2.run_rw_queries(queries = [{ 1552 'cmd': cmd, 1553 'args': { 1554 'trg': target_episode['pk_episode'], 1555 'enc': self.pk_obj, 1556 'src': source_episode['pk_episode'] 1557 } 1558 }]) 1559 self.refetch_payload() 1560 return True
1561 #--------------------------------------------------------
1562 - def same_payload(self, another_object=None):
1563 1564 relevant_fields = [ 1565 'pk_location', 1566 'pk_type', 1567 'pk_patient', 1568 'reason_for_encounter', 1569 'assessment_of_encounter' 1570 ] 1571 for field in relevant_fields: 1572 if self._payload[self._idx[field]] != another_object[field]: 1573 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1574 return False 1575 1576 relevant_fields = [ 1577 'started', 1578 'last_affirmed', 1579 ] 1580 for field in relevant_fields: 1581 if self._payload[self._idx[field]] is None: 1582 if another_object[field] is None: 1583 continue 1584 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1585 return False 1586 1587 if another_object[field] is None: 1588 return False 1589 1590 # compares at minute granularity 1591 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'): 1592 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1593 return False 1594 1595 # compare codes 1596 # 1) RFE 1597 if another_object['pk_generic_codes_rfe'] is None: 1598 if self._payload[self._idx['pk_generic_codes_rfe']] is not None: 1599 return False 1600 if another_object['pk_generic_codes_rfe'] is not None: 1601 if self._payload[self._idx['pk_generic_codes_rfe']] is None: 1602 return False 1603 if ( 1604 (another_object['pk_generic_codes_rfe'] is None) 1605 and 1606 (self._payload[self._idx['pk_generic_codes_rfe']] is None) 1607 ) is False: 1608 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]): 1609 return False 1610 # 2) AOE 1611 if another_object['pk_generic_codes_aoe'] is None: 1612 if self._payload[self._idx['pk_generic_codes_aoe']] is not None: 1613 return False 1614 if another_object['pk_generic_codes_aoe'] is not None: 1615 if self._payload[self._idx['pk_generic_codes_aoe']] is None: 1616 return False 1617 if ( 1618 (another_object['pk_generic_codes_aoe'] is None) 1619 and 1620 (self._payload[self._idx['pk_generic_codes_aoe']] is None) 1621 ) is False: 1622 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]): 1623 return False 1624 1625 return True
1626 #--------------------------------------------------------
1627 - def has_clinical_data(self):
1628 cmd = u""" 1629 select exists ( 1630 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 1631 union all 1632 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1633 )""" 1634 args = { 1635 'pat': self._payload[self._idx['pk_patient']], 1636 'enc': self.pk_obj 1637 } 1638 rows, idx = gmPG2.run_ro_queries ( 1639 queries = [{ 1640 'cmd': cmd, 1641 'args': args 1642 }] 1643 ) 1644 return rows[0][0]
1645 #--------------------------------------------------------
1646 - def has_narrative(self):
1647 cmd = u""" 1648 select exists ( 1649 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 1650 )""" 1651 args = { 1652 'pat': self._payload[self._idx['pk_patient']], 1653 'enc': self.pk_obj 1654 } 1655 rows, idx = gmPG2.run_ro_queries ( 1656 queries = [{ 1657 'cmd': cmd, 1658 'args': args 1659 }] 1660 ) 1661 return rows[0][0]
1662 #--------------------------------------------------------
1663 - def has_soap_narrative(self, soap_cats=None):
1664 """soap_cats: <space> = admin category""" 1665 1666 if soap_cats is None: 1667 soap_cats = u'soap ' 1668 else: 1669 soap_cats = soap_cats.lower() 1670 1671 cats = [] 1672 for cat in soap_cats: 1673 if cat in u'soapu': 1674 cats.append(cat) 1675 continue 1676 if cat == u' ': 1677 cats.append(None) 1678 1679 cmd = u""" 1680 SELECT EXISTS ( 1681 SELECT 1 FROM clin.clin_narrative 1682 WHERE 1683 fk_encounter = %(enc)s 1684 AND 1685 soap_cat IN %(cats)s 1686 LIMIT 1 1687 ) 1688 """ 1689 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 1690 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 1691 return rows[0][0]
1692 #--------------------------------------------------------
1693 - def has_documents(self):
1694 cmd = u""" 1695 select exists ( 1696 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1697 )""" 1698 args = { 1699 'pat': self._payload[self._idx['pk_patient']], 1700 'enc': self.pk_obj 1701 } 1702 rows, idx = gmPG2.run_ro_queries ( 1703 queries = [{ 1704 'cmd': cmd, 1705 'args': args 1706 }] 1707 ) 1708 return rows[0][0]
1709 #--------------------------------------------------------
1710 - def get_latest_soap(self, soap_cat=None, episode=None):
1711 1712 if soap_cat is not None: 1713 soap_cat = soap_cat.lower() 1714 1715 if episode is None: 1716 epi_part = u'fk_episode is null' 1717 else: 1718 epi_part = u'fk_episode = %(epi)s' 1719 1720 cmd = u""" 1721 select narrative 1722 from clin.clin_narrative 1723 where 1724 fk_encounter = %%(enc)s 1725 and 1726 soap_cat = %%(cat)s 1727 and 1728 %s 1729 order by clin_when desc 1730 limit 1 1731 """ % epi_part 1732 1733 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 1734 1735 rows, idx = gmPG2.run_ro_queries ( 1736 queries = [{ 1737 'cmd': cmd, 1738 'args': args 1739 }] 1740 ) 1741 if len(rows) == 0: 1742 return None 1743 1744 return rows[0][0]
1745 #--------------------------------------------------------
1746 - def get_episodes(self, exclude=None):
1747 cmd = u""" 1748 SELECT * FROM clin.v_pat_episodes 1749 WHERE 1750 pk_episode IN ( 1751 1752 SELECT DISTINCT fk_episode 1753 FROM clin.clin_root_item 1754 WHERE fk_encounter = %%(enc)s 1755 1756 UNION 1757 1758 SELECT DISTINCT fk_episode 1759 FROM blobs.doc_med 1760 WHERE fk_encounter = %%(enc)s 1761 ) 1762 %s""" 1763 args = {'enc': self.pk_obj} 1764 if exclude is not None: 1765 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s' 1766 args['excluded'] = tuple(exclude) 1767 else: 1768 cmd = cmd % u'' 1769 1770 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1771 1772 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1773 #--------------------------------------------------------
1774 - def add_code(self, pk_code=None, field=None):
1775 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1776 if field == u'rfe': 1777 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1778 elif field == u'aoe': 1779 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1780 else: 1781 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1782 args = { 1783 'item': self._payload[self._idx['pk_encounter']], 1784 'code': pk_code 1785 } 1786 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1787 return True
1788 #--------------------------------------------------------
1789 - def remove_code(self, pk_code=None, field=None):
1790 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1791 if field == u'rfe': 1792 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1793 elif field == u'aoe': 1794 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1795 else: 1796 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1797 args = { 1798 'item': self._payload[self._idx['pk_encounter']], 1799 'code': pk_code 1800 } 1801 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1802 return True
1803 #--------------------------------------------------------
1804 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):
1805 1806 lines = [] 1807 for soap_cat in soap_cats: 1808 soap_cat_narratives = emr.get_clin_narrative ( 1809 episodes = episodes, 1810 issues = issues, 1811 encounters = [self._payload[self._idx['pk_encounter']]], 1812 soap_cats = [soap_cat] 1813 ) 1814 if soap_cat_narratives is None: 1815 continue 1816 if len(soap_cat_narratives) == 0: 1817 continue 1818 1819 lines.append(u'%s%s %s %s' % ( 1820 gmTools.u_box_top_left_arc, 1821 gmTools.u_box_horiz_single, 1822 gmClinNarrative.soap_cat2l10n_str[soap_cat], 1823 gmTools.u_box_horiz_single * 5 1824 )) 1825 for soap_entry in soap_cat_narratives: 1826 txt = gmTools.wrap ( 1827 text = soap_entry['narrative'], 1828 width = 75, 1829 initial_indent = u'', 1830 subsequent_indent = (u' ' * left_margin) 1831 ) 1832 lines.append(txt) 1833 when = gmDateTime.pydt_strftime ( 1834 soap_entry['date'], 1835 format = '%Y-%m-%d %H:%M', 1836 accuracy = gmDateTime.acc_minutes 1837 ) 1838 txt = u'%s%s %.8s, %s %s' % ( 1839 u' ' * 40, 1840 gmTools.u_box_horiz_light_heavy, 1841 soap_entry['provider'], 1842 when, 1843 gmTools.u_box_horiz_heavy_light 1844 ) 1845 lines.append(txt) 1846 lines.append('') 1847 1848 return lines
1849 #--------------------------------------------------------
1850 - def format_latex(self, date_format=None, soap_cats=None, soap_order=None):
1851 1852 nothing2format = ( 1853 (self._payload[self._idx['reason_for_encounter']] is None) 1854 and 1855 (self._payload[self._idx['assessment_of_encounter']] is None) 1856 and 1857 (self.has_soap_narrative(soap_cats = u'soapu') is False) 1858 ) 1859 if nothing2format: 1860 return u'' 1861 1862 if date_format is None: 1863 date_format = '%A, %B %d %Y' 1864 1865 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 1866 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 1867 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()), 1868 self._payload[self._idx['started']].strftime('%H:%M'), 1869 self._payload[self._idx['last_affirmed']].strftime('%H:%M') 1870 ) 1871 tex += u'\\hline \\tabularnewline \n' 1872 1873 for epi in self.get_episodes(): 1874 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 1875 if len(soaps) == 0: 1876 continue 1877 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1878 gmTools.tex_escape_string(_('Problem')), 1879 gmTools.tex_escape_string(epi['description']), 1880 gmTools.coalesce ( 1881 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 1882 instead = u'', 1883 template_initial = u' {\\footnotesize [%s]}', 1884 none_equivalents = [None, u''] 1885 ) 1886 ) 1887 if epi['pk_health_issue'] is not None: 1888 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1889 gmTools.tex_escape_string(_('Health issue')), 1890 gmTools.tex_escape_string(epi['health_issue']), 1891 gmTools.coalesce ( 1892 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 1893 instead = u'', 1894 template_initial = u' {\\footnotesize [%s]}', 1895 none_equivalents = [None, u''] 1896 ) 1897 ) 1898 for soap in soaps: 1899 tex += u'{\\small %s} & {\\small %s} \\tabularnewline \n' % ( 1900 gmClinNarrative.soap_cat2l10n[soap['soap_cat']], 1901 gmTools.tex_escape_string(soap['narrative'].strip(u'\n')) 1902 ) 1903 tex += u' & \\tabularnewline \n' 1904 1905 if self._payload[self._idx['reason_for_encounter']] is not None: 1906 tex += u'%s & %s \\tabularnewline \n' % ( 1907 gmTools.tex_escape_string(_('RFE')), 1908 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 1909 ) 1910 if self._payload[self._idx['assessment_of_encounter']] is not None: 1911 tex += u'%s & %s \\tabularnewline \n' % ( 1912 gmTools.tex_escape_string(_('AOE')), 1913 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 1914 ) 1915 1916 tex += u'\\hline \\tabularnewline \n' 1917 tex += u' & \\tabularnewline \n' 1918 1919 return tex
1920 #--------------------------------------------------------
1921 - 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, with_family_history=True):
1922 """Format an encounter. 1923 1924 with_co_encountlet_hints: 1925 - whether to include which *other* episodes were discussed during this encounter 1926 - (only makes sense if episodes != None) 1927 """ 1928 lines = [] 1929 1930 if fancy_header: 1931 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1932 u' ' * left_margin, 1933 self._payload[self._idx['l10n_type']], 1934 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1935 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1936 self._payload[self._idx['source_time_zone']], 1937 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 1938 self._payload[self._idx['pk_encounter']] 1939 )) 1940 1941 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1942 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1943 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1944 gmDateTime.current_local_iso_numeric_timezone_string, 1945 gmTools.bool2subst ( 1946 gmDateTime.dst_currently_in_effect, 1947 gmDateTime.py_dst_timezone_name, 1948 gmDateTime.py_timezone_name 1949 ), 1950 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1951 )) 1952 1953 if self._payload[self._idx['reason_for_encounter']] is not None: 1954 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1955 codes = self.generic_codes_rfe 1956 for c in codes: 1957 lines.append(u' %s: %s (%s - %s)' % ( 1958 c['code'], 1959 c['term'], 1960 c['name_short'], 1961 c['version'] 1962 )) 1963 if len(codes) > 0: 1964 lines.append(u'') 1965 1966 if self._payload[self._idx['assessment_of_encounter']] is not None: 1967 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1968 codes = self.generic_codes_aoe 1969 for c in codes: 1970 lines.append(u' %s: %s (%s - %s)' % ( 1971 c['code'], 1972 c['term'], 1973 c['name_short'], 1974 c['version'] 1975 )) 1976 if len(codes) > 0: 1977 lines.append(u'') 1978 del codes 1979 1980 else: 1981 now = gmDateTime.pydt_now_here() 1982 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'): 1983 start = u'%s %s' % ( 1984 _('today'), 1985 self._payload[self._idx['started_original_tz']].strftime('%H:%M') 1986 ) 1987 else: 1988 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M') 1989 lines.append(u'%s%s: %s - %s%s' % ( 1990 u' ' * left_margin, 1991 self._payload[self._idx['l10n_type']], 1992 start, 1993 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1994 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1995 )) 1996 if with_rfe_aoe: 1997 if self._payload[self._idx['reason_for_encounter']] is not None: 1998 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1999 codes = self.generic_codes_rfe 2000 for c in codes: 2001 lines.append(u' %s: %s (%s - %s)' % ( 2002 c['code'], 2003 c['term'], 2004 c['name_short'], 2005 c['version'] 2006 )) 2007 if len(codes) > 0: 2008 lines.append(u'') 2009 if self._payload[self._idx['assessment_of_encounter']] is not None: 2010 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2011 codes = self.generic_codes_aoe 2012 if len(codes) > 0: 2013 lines.append(u'') 2014 for c in codes: 2015 lines.append(u' %s: %s (%s - %s)' % ( 2016 c['code'], 2017 c['term'], 2018 c['name_short'], 2019 c['version'] 2020 )) 2021 if len(codes) > 0: 2022 lines.append(u'') 2023 del codes 2024 2025 if with_soap: 2026 lines.append(u'') 2027 2028 if patient.ID != self._payload[self._idx['pk_patient']]: 2029 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2030 patient.ID, 2031 self._payload[self._idx['pk_encounter']], 2032 self._payload[self._idx['pk_patient']] 2033 ) 2034 raise ValueError(msg) 2035 2036 emr = patient.get_emr() 2037 2038 lines.extend(self.format_soap ( 2039 episodes = episodes, 2040 left_margin = left_margin, 2041 soap_cats = 'soapu', 2042 emr = emr, 2043 issues = issues 2044 )) 2045 2046 # # family history 2047 # if with_family_history: 2048 # if episodes is not None: 2049 # fhx = emr.get_family_history(episodes = episodes) 2050 # if len(fhx) > 0: 2051 # lines.append(u'') 2052 # lines.append(_('Family History: %s') % len(fhx)) 2053 # for f in fhx: 2054 # lines.append(f.format ( 2055 # left_margin = (left_margin + 1), 2056 # include_episode = False, 2057 # include_comment = True 2058 # )) 2059 # del fhx 2060 2061 # test results 2062 if with_tests: 2063 emr = patient.get_emr() 2064 tests = emr.get_test_results_by_date ( 2065 episodes = episodes, 2066 encounter = self._payload[self._idx['pk_encounter']] 2067 ) 2068 if len(tests) > 0: 2069 lines.append('') 2070 lines.append(_('Measurements and Results:')) 2071 2072 for t in tests: 2073 lines.extend(t.format()) 2074 2075 del tests 2076 2077 # vaccinations 2078 if with_vaccinations: 2079 emr = patient.get_emr() 2080 vaccs = emr.get_vaccinations ( 2081 episodes = episodes, 2082 encounters = [ self._payload[self._idx['pk_encounter']] ], 2083 order_by = u'date_given DESC, vaccine' 2084 ) 2085 2086 if len(vaccs) > 0: 2087 lines.append(u'') 2088 lines.append(_('Vaccinations:')) 2089 2090 for vacc in vaccs: 2091 lines.extend(vacc.format ( 2092 with_indications = True, 2093 with_comment = True, 2094 with_reaction = True, 2095 date_format = '%Y-%m-%d' 2096 )) 2097 del vaccs 2098 2099 # documents 2100 if with_docs: 2101 doc_folder = patient.get_document_folder() 2102 docs = doc_folder.get_documents ( 2103 episodes = episodes, 2104 encounter = self._payload[self._idx['pk_encounter']] 2105 ) 2106 2107 if len(docs) > 0: 2108 lines.append(u'') 2109 lines.append(_('Documents:')) 2110 2111 for d in docs: 2112 lines.append(u' %s %s:%s%s' % ( 2113 d['clin_when'].strftime('%Y-%m-%d'), 2114 d['l10n_type'], 2115 gmTools.coalesce(d['comment'], u'', u' "%s"'), 2116 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 2117 )) 2118 2119 del docs 2120 2121 # co-encountlets 2122 if with_co_encountlet_hints: 2123 if episodes is not None: 2124 other_epis = self.get_episodes(exclude = episodes) 2125 if len(other_epis) > 0: 2126 lines.append(u'') 2127 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 2128 for epi in other_epis: 2129 lines.append(u' %s%s%s%s' % ( 2130 gmTools.u_left_double_angle_quote, 2131 epi['description'], 2132 gmTools.u_right_double_angle_quote, 2133 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2134 )) 2135 2136 eol_w_margin = u'\n%s' % (u' ' * left_margin) 2137 return u'%s\n' % eol_w_margin.join(lines)
2138 #-------------------------------------------------------- 2139 # properties 2140 #--------------------------------------------------------
2141 - def _get_generic_codes_rfe(self):
2142 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 2143 return [] 2144 2145 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2146 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 2147 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2148 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2149
2150 - def _set_generic_codes_rfe(self, pk_codes):
2151 queries = [] 2152 # remove all codes 2153 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 2154 queries.append ({ 2155 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2156 'args': { 2157 'enc': self._payload[self._idx['pk_encounter']], 2158 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 2159 } 2160 }) 2161 # add new codes 2162 for pk_code in pk_codes: 2163 queries.append ({ 2164 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2165 'args': { 2166 'enc': self._payload[self._idx['pk_encounter']], 2167 'pk_code': pk_code 2168 } 2169 }) 2170 if len(queries) == 0: 2171 return 2172 # run it all in one transaction 2173 rows, idx = gmPG2.run_rw_queries(queries = queries) 2174 self.refetch_payload() 2175 return
2176 2177 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 2178 #--------------------------------------------------------
2179 - def _get_generic_codes_aoe(self):
2180 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 2181 return [] 2182 2183 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2184 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 2185 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2186 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2187
2188 - def _set_generic_codes_aoe(self, pk_codes):
2189 queries = [] 2190 # remove all codes 2191 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 2192 queries.append ({ 2193 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2194 'args': { 2195 'enc': self._payload[self._idx['pk_encounter']], 2196 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 2197 } 2198 }) 2199 # add new codes 2200 for pk_code in pk_codes: 2201 queries.append ({ 2202 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2203 'args': { 2204 'enc': self._payload[self._idx['pk_encounter']], 2205 'pk_code': pk_code 2206 } 2207 }) 2208 if len(queries) == 0: 2209 return 2210 # run it all in one transaction 2211 rows, idx = gmPG2.run_rw_queries(queries = queries) 2212 self.refetch_payload() 2213 return
2214 2215 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2216 #-----------------------------------------------------------
2217 -def create_encounter(fk_patient=None, fk_location=-1, enc_type=None):
2218 """Creates a new encounter for a patient. 2219 2220 fk_patient - patient PK 2221 fk_location - encounter location 2222 enc_type - type of encounter 2223 2224 FIXME: we don't deal with location yet 2225 """ 2226 if enc_type is None: 2227 enc_type = u'in surgery' 2228 # insert new encounter 2229 queries = [] 2230 try: 2231 enc_type = int(enc_type) 2232 cmd = u""" 2233 INSERT INTO clin.encounter ( 2234 fk_patient, fk_location, fk_type 2235 ) VALUES ( 2236 %(pat)s, 2237 -1, 2238 %(typ)s 2239 ) RETURNING pk""" 2240 except ValueError: 2241 enc_type = enc_type 2242 cmd = u""" 2243 insert into clin.encounter ( 2244 fk_patient, fk_location, fk_type 2245 ) values ( 2246 %(pat)s, 2247 -1, 2248 coalesce ( 2249 (select pk from clin.encounter_type where description = %(typ)s), 2250 -- pick the first available 2251 (select pk from clin.encounter_type limit 1) 2252 ) 2253 ) RETURNING pk""" 2254 args = {'pat': fk_patient, 'typ': enc_type} 2255 queries.append({'cmd': cmd, 'args': args}) 2256 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 2257 encounter = cEncounter(aPK_obj = rows[0]['pk']) 2258 2259 return encounter
2260 #-----------------------------------------------------------
2261 -def update_encounter_type(description=None, l10n_description=None):
2262 2263 rows, idx = gmPG2.run_rw_queries( 2264 queries = [{ 2265 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 2266 'args': {'desc': description, 'l10n_desc': l10n_description} 2267 }], 2268 return_data = True 2269 ) 2270 2271 success = rows[0][0] 2272 if not success: 2273 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 2274 2275 return {'description': description, 'l10n_description': l10n_description}
2276 #-----------------------------------------------------------
2277 -def create_encounter_type(description=None, l10n_description=None):
2278 """This will attempt to create a NEW encounter type.""" 2279 2280 # need a system name, so derive one if necessary 2281 if description is None: 2282 description = l10n_description 2283 2284 args = { 2285 'desc': description, 2286 'l10n_desc': l10n_description 2287 } 2288 2289 _log.debug('creating encounter type: %s, %s', description, l10n_description) 2290 2291 # does it exist already ? 2292 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 2293 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2294 2295 # yes 2296 if len(rows) > 0: 2297 # both system and l10n name are the same so all is well 2298 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 2299 _log.info('encounter type [%s] already exists with the proper translation') 2300 return {'description': description, 'l10n_description': l10n_description} 2301 2302 # or maybe there just wasn't a translation to 2303 # the current language for this type yet ? 2304 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 2305 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2306 2307 # there was, so fail 2308 if rows[0][0]: 2309 _log.error('encounter type [%s] already exists but with another translation') 2310 return None 2311 2312 # else set it 2313 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 2314 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2315 return {'description': description, 'l10n_description': l10n_description} 2316 2317 # no 2318 queries = [ 2319 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 2320 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 2321 ] 2322 rows, idx = gmPG2.run_rw_queries(queries = queries) 2323 2324 return {'description': description, 'l10n_description': l10n_description}
2325 #-----------------------------------------------------------
2326 -def get_encounter_types():
2327 cmd = u""" 2328 SELECT 2329 _(description) AS l10n_description, 2330 description 2331 FROM 2332 clin.encounter_type 2333 ORDER BY 2334 l10n_description 2335 """ 2336 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 2337 return rows
2338 #-----------------------------------------------------------
2339 -def get_encounter_type(description=None):
2340 cmd = u"SELECT * from clin.encounter_type where description = %s" 2341 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 2342 return rows
2343 #-----------------------------------------------------------
2344 -def delete_encounter_type(description=None):
2345 cmd = u"delete from clin.encounter_type where description = %(desc)s" 2346 args = {'desc': description} 2347 try: 2348 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2349 except gmPG2.dbapi.IntegrityError, e: 2350 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 2351 return False 2352 raise 2353 2354 return True
2355 #============================================================
2356 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
2357 """Represents one problem. 2358 2359 problems are the aggregation of 2360 .clinically_relevant=True issues and 2361 .is_open=True episodes 2362 """ 2363 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 2364 _cmds_store_payload = [u"select 1"] 2365 _updatable_fields = [] 2366 2367 #--------------------------------------------------------
2368 - def __init__(self, aPK_obj=None, try_potential_problems=False):
2369 """Initialize. 2370 2371 aPK_obj must contain the keys 2372 pk_patient 2373 pk_episode 2374 pk_health_issue 2375 """ 2376 if aPK_obj is None: 2377 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 2378 2379 # As problems are rows from a view of different emr struct items, 2380 # the PK can't be a single field and, as some of the values of the 2381 # composed PK may be None, they must be queried using 'is null', 2382 # so we must programmatically construct the SQL query 2383 where_parts = [] 2384 pk = {} 2385 for col_name in aPK_obj.keys(): 2386 val = aPK_obj[col_name] 2387 if val is None: 2388 where_parts.append('%s IS NULL' % col_name) 2389 else: 2390 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 2391 pk[col_name] = val 2392 2393 # try to instantiate from true problem view 2394 cProblem._cmd_fetch_payload = u""" 2395 SELECT *, False as is_potential_problem 2396 FROM clin.v_problem_list 2397 WHERE %s""" % u' AND '.join(where_parts) 2398 2399 try: 2400 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 2401 return 2402 except gmExceptions.ConstructorError: 2403 _log.exception('actual problem not found, trying "potential" problems') 2404 if try_potential_problems is False: 2405 raise 2406 2407 # try to instantiate from potential-problems view 2408 cProblem._cmd_fetch_payload = u""" 2409 SELECT *, True as is_potential_problem 2410 FROM clin.v_potential_problem_list 2411 WHERE %s""" % u' AND '.join(where_parts) 2412 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2413 #--------------------------------------------------------
2414 - def get_as_episode(self):
2415 """ 2416 Retrieve the cEpisode instance equivalent to this problem. 2417 The problem's type attribute must be 'episode' 2418 """ 2419 if self._payload[self._idx['type']] != 'episode': 2420 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2421 return None 2422 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
2423 #--------------------------------------------------------
2424 - def get_as_health_issue(self):
2425 """ 2426 Retrieve the cHealthIssue instance equivalent to this problem. 2427 The problem's type attribute must be 'issue' 2428 """ 2429 if self._payload[self._idx['type']] != 'issue': 2430 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2431 return None 2432 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
2433 #--------------------------------------------------------
2434 - def get_visual_progress_notes(self, encounter_id=None):
2435 2436 if self._payload[self._idx['type']] == u'issue': 2437 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 2438 #xxxxxxxxxxxxx 2439 2440 emr = patient.get_emr() 2441 2442 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 2443 return doc_folder.get_visual_progress_notes ( 2444 health_issue = self._payload[self._idx['pk_health_issue']], 2445 episode = self._payload[self._idx['pk_episode']] 2446 )
2447 #-------------------------------------------------------- 2448 # properties 2449 #-------------------------------------------------------- 2450 # doubles as 'diagnostic_certainty_description' getter:
2452 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
2453 2454 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 2455 #--------------------------------------------------------
2456 - def _get_generic_codes(self):
2457 if self._payload[self._idx['type']] == u'issue': 2458 cmd = u""" 2459 SELECT * FROM clin.v_linked_codes WHERE 2460 item_table = 'clin.lnk_code2h_issue'::regclass 2461 AND 2462 pk_item = %(item)s 2463 """ 2464 args = {'item': self._payload[self._idx['pk_health_issue']]} 2465 else: 2466 cmd = u""" 2467 SELECT * FROM clin.v_linked_codes WHERE 2468 item_table = 'clin.lnk_code2episode'::regclass 2469 AND 2470 pk_item = %(item)s 2471 """ 2472 args = {'item': self._payload[self._idx['pk_episode']]} 2473 2474 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2475 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2476 2477 generic_codes = property(_get_generic_codes, lambda x:x)
2478 #-----------------------------------------------------------
2479 -def problem2episode(problem=None):
2480 """Retrieve the cEpisode instance equivalent to the given problem. 2481 2482 The problem's type attribute must be 'episode' 2483 2484 @param problem: The problem to retrieve its related episode for 2485 @type problem: A gmEMRStructItems.cProblem instance 2486 """ 2487 if isinstance(problem, cEpisode): 2488 return problem 2489 2490 exc = TypeError('cannot convert [%s] to episode' % problem) 2491 2492 if not isinstance(problem, cProblem): 2493 raise exc 2494 2495 if problem['type'] != 'episode': 2496 raise exc 2497 2498 return cEpisode(aPK_obj = problem['pk_episode'])
2499 #-----------------------------------------------------------
2500 -def problem2issue(problem=None):
2501 """Retrieve the cIssue instance equivalent to the given problem. 2502 2503 The problem's type attribute must be 'issue'. 2504 2505 @param problem: The problem to retrieve the corresponding issue for 2506 @type problem: A gmEMRStructItems.cProblem instance 2507 """ 2508 if isinstance(problem, cHealthIssue): 2509 return problem 2510 2511 exc = TypeError('cannot convert [%s] to health issue' % problem) 2512 2513 if not isinstance(problem, cProblem): 2514 raise exc 2515 2516 if problem['type'] != 'issue': 2517 raise exc 2518 2519 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2520 #-----------------------------------------------------------
2521 -def reclass_problem(self, problem=None):
2522 """Transform given problem into either episode or health issue instance. 2523 """ 2524 if isinstance(problem, (cEpisode, cHealthIssue)): 2525 return problem 2526 2527 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 2528 2529 if not isinstance(problem, cProblem): 2530 _log.debug(u'%s' % problem) 2531 raise exc 2532 2533 if problem['type'] == 'episode': 2534 return cEpisode(aPK_obj = problem['pk_episode']) 2535 2536 if problem['type'] == 'issue': 2537 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 2538 2539 raise exc
2540 #============================================================
2541 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
2542 2543 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 2544 _cmds_store_payload = [ 2545 u"""update clin.hospital_stay set 2546 clin_when = %(admission)s, 2547 discharge = %(discharge)s, 2548 narrative = gm.nullify_empty_string(%(hospital)s), 2549 fk_episode = %(pk_episode)s, 2550 fk_encounter = %(pk_encounter)s 2551 where 2552 pk = %(pk_hospital_stay)s and 2553 xmin = %(xmin_hospital_stay)s""", 2554 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 2555 ] 2556 _updatable_fields = [ 2557 'admission', 2558 'discharge', 2559 'hospital', 2560 'pk_episode', 2561 'pk_encounter' 2562 ] 2563 #-------------------------------------------------------
2564 - def format(self, left_margin=0, include_procedures=False, include_docs=False):
2565 2566 if self._payload[self._idx['discharge']] is not None: 2567 discharge = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2568 else: 2569 discharge = u'' 2570 2571 line = u'%s%s%s%s: %s%s%s' % ( 2572 u' ' * left_margin, 2573 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2574 discharge, 2575 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 2576 gmTools.u_left_double_angle_quote, 2577 self._payload[self._idx['episode']], 2578 gmTools.u_right_double_angle_quote 2579 ) 2580 2581 return line
2582 #-----------------------------------------------------------
2583 -def get_latest_patient_hospital_stay(patient=None):
2584 queries = [{ 2585 # this assumes non-overarching stays 2586 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1', 2587 'args': {'pat': patient} 2588 }] 2589 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2590 if len(rows) == 0: 2591 return None 2592 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
2593 #-----------------------------------------------------------
2594 -def get_patient_hospital_stays(patient=None, ongoing_only=False):
2595 args = {'pat': patient} 2596 if ongoing_only: 2597 cmd = u""" 2598 SELECT * 2599 FROM clin.v_pat_hospital_stays 2600 WHERE 2601 pk_patient = %(pat)s 2602 AND 2603 discharge is NULL 2604 ORDER BY admission""" 2605 else: 2606 cmd = u""" 2607 SELECT * 2608 FROM clin.v_pat_hospital_stays 2609 WHERE pk_patient = %(pat)s 2610 ORDER BY admission""" 2611 2612 queries = [{'cmd': cmd, 'args': args}] 2613 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2614 2615 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
2616 #-----------------------------------------------------------
2617 -def create_hospital_stay(encounter=None, episode=None):
2618 2619 queries = [{ 2620 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk', 2621 'args': {'enc': encounter, 'epi': episode} 2622 }] 2623 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2624 2625 return cHospitalStay(aPK_obj = rows[0][0])
2626 #-----------------------------------------------------------
2627 -def delete_hospital_stay(stay=None):
2628 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 2629 args = {'pk': stay} 2630 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2631 return True
2632 #============================================================
2633 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
2634 2635 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 2636 _cmds_store_payload = [ 2637 u"""UPDATE clin.procedure SET 2638 soap_cat = 'p', 2639 clin_when = %(clin_when)s, 2640 clin_end = %(clin_end)s, 2641 is_ongoing = %(is_ongoing)s, 2642 clin_where = NULLIF ( 2643 COALESCE ( 2644 %(pk_hospital_stay)s::TEXT, 2645 gm.nullify_empty_string(%(clin_where)s) 2646 ), 2647 %(pk_hospital_stay)s::TEXT 2648 ), 2649 narrative = gm.nullify_empty_string(%(performed_procedure)s), 2650 fk_hospital_stay = %(pk_hospital_stay)s, 2651 fk_episode = %(pk_episode)s, 2652 fk_encounter = %(pk_encounter)s 2653 WHERE 2654 pk = %(pk_procedure)s AND 2655 xmin = %(xmin_procedure)s 2656 RETURNING xmin as xmin_procedure""" 2657 ] 2658 _updatable_fields = [ 2659 'clin_when', 2660 'clin_end', 2661 'is_ongoing', 2662 'clin_where', 2663 'performed_procedure', 2664 'pk_hospital_stay', 2665 'pk_episode', 2666 'pk_encounter' 2667 ] 2668 #-------------------------------------------------------
2669 - def __setitem__(self, attribute, value):
2670 2671 if (attribute == 'pk_hospital_stay') and (value is not None): 2672 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 2673 2674 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 2675 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 2676 2677 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
2678 #-------------------------------------------------------
2679 - def format(self, left_margin=0, include_episode=True, include_codes=False):
2680 2681 if self._payload[self._idx['is_ongoing']]: 2682 end = _(' (ongoing)') 2683 else: 2684 end = self._payload[self._idx['clin_end']] 2685 if end is None: 2686 end = u'' 2687 else: 2688 end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2689 2690 line = u'%s%s%s (%s): %s' % ( 2691 (u' ' * left_margin), 2692 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2693 end, 2694 self._payload[self._idx['clin_where']], 2695 self._payload[self._idx['performed_procedure']] 2696 ) 2697 if include_episode: 2698 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 2699 2700 if include_codes: 2701 codes = self.generic_codes 2702 if len(codes) > 0: 2703 line += u'\n' 2704 for c in codes: 2705 line += u'%s %s: %s (%s - %s)\n' % ( 2706 (u' ' * left_margin), 2707 c['code'], 2708 c['term'], 2709 c['name_short'], 2710 c['version'] 2711 ) 2712 del codes 2713 2714 return line
2715 #--------------------------------------------------------
2716 - def add_code(self, pk_code=None):
2717 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2718 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 2719 args = { 2720 'issue': self._payload[self._idx['pk_procedure']], 2721 'code': pk_code 2722 } 2723 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2724 return True
2725 #--------------------------------------------------------
2726 - def remove_code(self, pk_code=None):
2727 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2728 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 2729 args = { 2730 'issue': self._payload[self._idx['pk_procedure']], 2731 'code': pk_code 2732 } 2733 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2734 return True
2735 #-------------------------------------------------------- 2736 # properties 2737 #--------------------------------------------------------
2738 - def _get_generic_codes(self):
2739 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 2740 return [] 2741 2742 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2743 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 2744 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2745 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2746
2747 - def _set_generic_codes(self, pk_codes):
2748 queries = [] 2749 # remove all codes 2750 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 2751 queries.append ({ 2752 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 2753 'args': { 2754 'proc': self._payload[self._idx['pk_procedure']], 2755 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 2756 } 2757 }) 2758 # add new codes 2759 for pk_code in pk_codes: 2760 queries.append ({ 2761 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 2762 'args': { 2763 'proc': self._payload[self._idx['pk_procedure']], 2764 'pk_code': pk_code 2765 } 2766 }) 2767 if len(queries) == 0: 2768 return 2769 # run it all in one transaction 2770 rows, idx = gmPG2.run_rw_queries(queries = queries) 2771 return
2772 2773 generic_codes = property(_get_generic_codes, _set_generic_codes)
2774 #-----------------------------------------------------------
2775 -def get_performed_procedures(patient=None):
2776 2777 queries = [ 2778 { 2779 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 2780 'args': {'pat': patient} 2781 } 2782 ] 2783 2784 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2785 2786 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
2787 #-----------------------------------------------------------
2788 -def get_latest_performed_procedure(patient=None):
2789 queries = [ 2790 { 2791 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when DESC LIMIT 1', 2792 'args': {'pat': patient} 2793 } 2794 ] 2795 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2796 if len(rows) == 0: 2797 return None 2798 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})
2799 #-----------------------------------------------------------
2800 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
2801 2802 queries = [{ 2803 'cmd': u""" 2804 INSERT INTO clin.procedure ( 2805 fk_encounter, 2806 fk_episode, 2807 soap_cat, 2808 clin_where, 2809 fk_hospital_stay, 2810 narrative 2811 ) VALUES ( 2812 %(enc)s, 2813 %(epi)s, 2814 'p', 2815 gm.nullify_empty_string(%(loc)s), 2816 %(stay)s, 2817 gm.nullify_empty_string(%(proc)s) 2818 ) 2819 RETURNING pk""", 2820 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 2821 }] 2822 2823 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2824 2825 return cPerformedProcedure(aPK_obj = rows[0][0])
2826 #-----------------------------------------------------------
2827 -def delete_performed_procedure(procedure=None):
2828 cmd = u'delete from clin.procedure where pk = %(pk)s' 2829 args = {'pk': procedure} 2830 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2831 return True
2832 #============================================================ 2833 # main - unit testing 2834 #------------------------------------------------------------ 2835 if __name__ == '__main__': 2836 2837 if len(sys.argv) < 2: 2838 sys.exit() 2839 2840 if sys.argv[1] != 'test': 2841 sys.exit() 2842 2843 #-------------------------------------------------------- 2844 # define tests 2845 #--------------------------------------------------------
2846 - def test_problem():
2847 print "\nProblem test" 2848 print "------------" 2849 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 2850 print prob 2851 fields = prob.get_fields() 2852 for field in fields: 2853 print field, ':', prob[field] 2854 print '\nupdatable:', prob.get_updatable_fields() 2855 epi = prob.get_as_episode() 2856 print '\nas episode:' 2857 if epi is not None: 2858 for field in epi.get_fields(): 2859 print ' .%s : %s' % (field, epi[field])
2860 #--------------------------------------------------------
2861 - def test_health_issue():
2862 print "\nhealth issue test" 2863 print "-----------------" 2864 h_issue = cHealthIssue(aPK_obj=2) 2865 print h_issue 2866 print h_issue.latest_access_date 2867 print h_issue.end_date
2868 # fields = h_issue.get_fields() 2869 # for field in fields: 2870 # print field, ':', h_issue[field] 2871 # print "has open episode:", h_issue.has_open_episode() 2872 # print "open episode:", h_issue.get_open_episode() 2873 # print "updateable:", h_issue.get_updatable_fields() 2874 # h_issue.close_expired_episode(ttl=7300) 2875 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 2876 # print h_issue 2877 # print h_issue.format_as_journal() 2878 #--------------------------------------------------------
2879 - def test_episode():
2880 print "\nepisode test" 2881 print "------------" 2882 episode = cEpisode(aPK_obj=1) 2883 print episode 2884 fields = episode.get_fields() 2885 for field in fields: 2886 print field, ':', episode[field] 2887 print "updatable:", episode.get_updatable_fields() 2888 raw_input('ENTER to continue') 2889 2890 old_description = episode['description'] 2891 old_enc = cEncounter(aPK_obj = 1) 2892 2893 desc = '1-%s' % episode['description'] 2894 print "==> renaming to", desc 2895 successful = episode.rename ( 2896 description = desc 2897 ) 2898 if not successful: 2899 print "error" 2900 else: 2901 print "success" 2902 for field in fields: 2903 print field, ':', episode[field] 2904 2905 print "episode range:", episode.get_access_range() 2906 2907 raw_input('ENTER to continue')
2908 2909 #--------------------------------------------------------
2910 - def test_encounter():
2911 print "\nencounter test" 2912 print "--------------" 2913 encounter = cEncounter(aPK_obj=1) 2914 print encounter 2915 fields = encounter.get_fields() 2916 for field in fields: 2917 print field, ':', encounter[field] 2918 print "updatable:", encounter.get_updatable_fields()
2919 #--------------------------------------------------------
2920 - def test_encounter2latex():
2921 encounter = cEncounter(aPK_obj=1) 2922 print encounter 2923 print "" 2924 print encounter.format_latex()
2925 #--------------------------------------------------------
2926 - def test_performed_procedure():
2927 procs = get_performed_procedures(patient = 12) 2928 for proc in procs: 2929 print proc.format(left_margin=2)
2930 #--------------------------------------------------------
2931 - def test_hospital_stay():
2932 stay = create_hospital_stay(encounter = 1, episode = 2) 2933 stay['hospital'] = u'Starfleet Galaxy General Hospital' 2934 stay.save_payload() 2935 print stay 2936 for s in get_patient_hospital_stays(12): 2937 print s 2938 delete_hospital_stay(stay['pk_hospital_stay']) 2939 stay = create_hospital_stay(encounter = 1, episode = 4)
2940 #--------------------------------------------------------
2941 - def test_diagnostic_certainty_classification_map():
2942 tests = [None, 'A', 'B', 'C', 'D', 'E'] 2943 2944 for t in tests: 2945 print type(t), t 2946 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
2947 #--------------------------------------------------------
2948 - def test_episode_codes():
2949 epi = cEpisode(aPK_obj = 2) 2950 print epi 2951 print epi.generic_codes
2952 #-------------------------------------------------------- 2953 # run them 2954 #test_episode() 2955 #test_problem() 2956 #test_encounter() 2957 test_health_issue() 2958 #test_hospital_stay() 2959 #test_performed_procedure() 2960 #test_diagnostic_certainty_classification_map() 2961 #test_encounter2latex() 2962 #test_episode_codes() 2963 #============================================================ 2964