Home | Trees | Indices | Help |
|
---|
|
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 3638 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') % classification54 #============================================================ 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 #============================================================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 #--------------------------------------------------------742 #============================================================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 #--------------------------------------------------------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 True156 #--------------------------------------------------------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 #--------------------------------------------------------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 exception177 #--------------------------------------------------------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 False185 #--------------------------------------------------------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 #--------------------------------------------------------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 #--------------------------------------------------------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 #--------------------------------------------------------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 True216 #--------------------------------------------------------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 True226 #--------------------------------------------------------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' %s:' % _('Synopsis')) 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 #--------------------------------------------------------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 #--------------------------------------------------------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 dates545 """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 #--------------------------------------------------------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 #--------------------------------------------------------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_date630 631 end_date = property(_get_end_date) 632 #--------------------------------------------------------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 #--------------------------------------------------------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 ]714716 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 return740 741 generic_codes = property(_get_generic_codes, _set_generic_codes)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_issue765 #-----------------------------------------------------------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 episodes781 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 issue794 #-----------------------------------------------------------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 #============================================================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 #--------------------------------------------------------1437 #============================================================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 #--------------------------------------------------------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 #-------------------------------------------------------- 880 #--------------------------------------------------------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 #--------------------------------------------------------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 True911 #--------------------------------------------------------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 return937 #--------------------------------------------------------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 True947 #--------------------------------------------------------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' %s:' % _('Synopsis')) 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 health issue.')) 1109 elif len(encs) == 0: 1110 #lines.append(_('There are no encounters for this issue.')) 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 #--------------------------------------------------------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 #--------------------------------------------------------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 #--------------------------------------------------------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 ]13901392 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 return1416 1417 generic_codes = property(_get_generic_codes, _set_generic_codes) 1418 #--------------------------------------------------------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)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 episode1462 #-----------------------------------------------------------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 True1479 #-----------------------------------------------------------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 #============================================================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 #--------------------------------------------------------2346 #-----------------------------------------------------------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 #--------------------------------------------------------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 True1561 #--------------------------------------------------------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 True1626 #--------------------------------------------------------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 #--------------------------------------------------------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 #--------------------------------------------------------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 #--------------------------------------------------------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 #--------------------------------------------------------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 #--------------------------------------------------------1747 cmd = u""" 1748 SELECT * FROM clin.v_pat_episodes 1749 WHERE pk_episode IN ( 1750 SELECT DISTINCT fk_episode 1751 FROM clin.clin_root_item 1752 WHERE fk_encounter = %%(enc)s 1753 1754 UNION 1755 1756 SELECT DISTINCT fk_episode 1757 FROM blobs.doc_med 1758 WHERE fk_encounter = %%(enc)s 1759 ) %s""" 1760 args = {'enc': self.pk_obj} 1761 if exclude is not None: 1762 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s' 1763 args['excluded'] = tuple(exclude) 1764 else: 1765 cmd = cmd % u'' 1766 1767 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1768 1769 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]1770 1771 episodes = property(get_episodes, lambda x:x) 1772 #--------------------------------------------------------1774 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1775 if field == u'rfe': 1776 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1777 elif field == u'aoe': 1778 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1779 else: 1780 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1781 args = { 1782 'item': self._payload[self._idx['pk_encounter']], 1783 'code': pk_code 1784 } 1785 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1786 return True1787 #--------------------------------------------------------1789 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1790 if field == u'rfe': 1791 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1792 elif field == u'aoe': 1793 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1794 else: 1795 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1796 args = { 1797 'item': self._payload[self._idx['pk_encounter']], 1798 'code': pk_code 1799 } 1800 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1801 return True1802 #--------------------------------------------------------1803 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):1804 1805 lines = [] 1806 for soap_cat in soap_cats: 1807 soap_cat_narratives = emr.get_clin_narrative ( 1808 episodes = episodes, 1809 issues = issues, 1810 encounters = [self._payload[self._idx['pk_encounter']]], 1811 soap_cats = [soap_cat] 1812 ) 1813 if soap_cat_narratives is None: 1814 continue 1815 if len(soap_cat_narratives) == 0: 1816 continue 1817 1818 lines.append(u'%s%s %s %s' % ( 1819 gmTools.u_box_top_left_arc, 1820 gmTools.u_box_horiz_single, 1821 gmClinNarrative.soap_cat2l10n_str[soap_cat], 1822 gmTools.u_box_horiz_single * 5 1823 )) 1824 for soap_entry in soap_cat_narratives: 1825 txt = gmTools.wrap ( 1826 text = soap_entry['narrative'], 1827 width = 75, 1828 initial_indent = u'', 1829 subsequent_indent = (u' ' * left_margin) 1830 ) 1831 lines.append(txt) 1832 when = gmDateTime.pydt_strftime ( 1833 soap_entry['date'], 1834 format = '%Y-%m-%d %H:%M', 1835 accuracy = gmDateTime.acc_minutes 1836 ) 1837 txt = u'%s%s %.8s, %s %s' % ( 1838 u' ' * 40, 1839 gmTools.u_box_horiz_light_heavy, 1840 soap_entry['provider'], 1841 when, 1842 gmTools.u_box_horiz_heavy_light 1843 ) 1844 lines.append(txt) 1845 lines.append('') 1846 1847 return lines1848 #--------------------------------------------------------1850 1851 nothing2format = ( 1852 (self._payload[self._idx['reason_for_encounter']] is None) 1853 and 1854 (self._payload[self._idx['assessment_of_encounter']] is None) 1855 and 1856 (self.has_soap_narrative(soap_cats = u'soapu') is False) 1857 ) 1858 if nothing2format: 1859 return u'' 1860 1861 if date_format is None: 1862 date_format = '%A, %B %d %Y' 1863 1864 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 1865 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 1866 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()), 1867 self._payload[self._idx['started']].strftime('%H:%M'), 1868 self._payload[self._idx['last_affirmed']].strftime('%H:%M') 1869 ) 1870 tex += u'\\hline \\tabularnewline \n' 1871 1872 for epi in self.get_episodes(): 1873 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 1874 if len(soaps) == 0: 1875 continue 1876 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1877 gmTools.tex_escape_string(_('Problem')), 1878 gmTools.tex_escape_string(epi['description']), 1879 gmTools.coalesce ( 1880 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 1881 instead = u'', 1882 template_initial = u' {\\footnotesize [%s]}', 1883 none_equivalents = [None, u''] 1884 ) 1885 ) 1886 if epi['pk_health_issue'] is not None: 1887 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1888 gmTools.tex_escape_string(_('Health issue')), 1889 gmTools.tex_escape_string(epi['health_issue']), 1890 gmTools.coalesce ( 1891 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 1892 instead = u'', 1893 template_initial = u' {\\footnotesize [%s]}', 1894 none_equivalents = [None, u''] 1895 ) 1896 ) 1897 for soap in soaps: 1898 tex += u'{\\small %s} & %s \\tabularnewline \n' % ( 1899 gmClinNarrative.soap_cat2l10n[soap['soap_cat']], 1900 gmTools.tex_escape_string(soap['narrative'].strip(u'\n')) 1901 ) 1902 tex += u' & \\tabularnewline \n' 1903 1904 if self._payload[self._idx['reason_for_encounter']] is not None: 1905 tex += u'%s & %s \\tabularnewline \n' % ( 1906 gmTools.tex_escape_string(_('RFE')), 1907 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 1908 ) 1909 if self._payload[self._idx['assessment_of_encounter']] is not None: 1910 tex += u'%s & %s \\tabularnewline \n' % ( 1911 gmTools.tex_escape_string(_('AOE')), 1912 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 1913 ) 1914 1915 tex += u'\\hline \\tabularnewline \n' 1916 tex += u' & \\tabularnewline \n' 1917 1918 return tex1919 #--------------------------------------------------------1921 lines = [] 1922 1923 if fancy_header: 1924 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1925 u' ' * left_margin, 1926 self._payload[self._idx['l10n_type']], 1927 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1928 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1929 self._payload[self._idx['source_time_zone']], 1930 gmTools.coalesce ( 1931 self._payload[self._idx['assessment_of_encounter']], 1932 u'', 1933 u' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 1934 ), 1935 self._payload[self._idx['pk_encounter']] 1936 )) 1937 1938 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1939 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1940 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1941 gmDateTime.current_local_iso_numeric_timezone_string, 1942 gmTools.bool2subst ( 1943 gmDateTime.dst_currently_in_effect, 1944 gmDateTime.py_dst_timezone_name, 1945 gmDateTime.py_timezone_name 1946 ), 1947 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1948 )) 1949 1950 if self._payload[self._idx['reason_for_encounter']] is not None: 1951 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1952 codes = self.generic_codes_rfe 1953 for c in codes: 1954 lines.append(u' %s: %s (%s - %s)' % ( 1955 c['code'], 1956 c['term'], 1957 c['name_short'], 1958 c['version'] 1959 )) 1960 if len(codes) > 0: 1961 lines.append(u'') 1962 1963 if self._payload[self._idx['assessment_of_encounter']] is not None: 1964 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1965 codes = self.generic_codes_aoe 1966 for c in codes: 1967 lines.append(u' %s: %s (%s - %s)' % ( 1968 c['code'], 1969 c['term'], 1970 c['name_short'], 1971 c['version'] 1972 )) 1973 if len(codes) > 0: 1974 lines.append(u'') 1975 del codes 1976 return lines 1977 1978 now = gmDateTime.pydt_now_here() 1979 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'): 1980 start = u'%s %s' % ( 1981 _('today'), 1982 self._payload[self._idx['started_original_tz']].strftime('%H:%M') 1983 ) 1984 else: 1985 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M') 1986 lines.append(u'%s%s: %s - %s%s' % ( 1987 u' ' * left_margin, 1988 self._payload[self._idx['l10n_type']], 1989 start, 1990 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1991 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1992 )) 1993 if with_rfe_aoe: 1994 if self._payload[self._idx['reason_for_encounter']] is not None: 1995 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1996 codes = self.generic_codes_rfe 1997 for c in codes: 1998 lines.append(u' %s: %s (%s - %s)' % ( 1999 c['code'], 2000 c['term'], 2001 c['name_short'], 2002 c['version'] 2003 )) 2004 if len(codes) > 0: 2005 lines.append(u'') 2006 if self._payload[self._idx['assessment_of_encounter']] is not None: 2007 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2008 codes = self.generic_codes_aoe 2009 if len(codes) > 0: 2010 lines.append(u'') 2011 for c in codes: 2012 lines.append(u' %s: %s (%s - %s)' % ( 2013 c['code'], 2014 c['term'], 2015 c['name_short'], 2016 c['version'] 2017 )) 2018 if len(codes) > 0: 2019 lines.append(u'') 2020 del codes 2021 2022 return lines2023 #--------------------------------------------------------2024 - def format_by_episode(self, episodes=None, issues=None, left_margin=0, patient=None, with_soap=False, with_tests=True, with_docs=True, with_vaccinations=True, with_family_history=True):2025 2026 lines = [] 2027 emr = patient.emr 2028 if episodes is None: 2029 episodes = [ e['pk_episode'] for e in self.episodes ] 2030 2031 for pk in episodes: 2032 epi = cEpisode(aPK_obj = pk) 2033 lines.append(_('\nEpisode %s%s%s%s:') % ( 2034 gmTools.u_left_double_angle_quote, 2035 epi['description'], 2036 gmTools.u_right_double_angle_quote, 2037 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2038 )) 2039 2040 # soap 2041 if with_soap: 2042 if patient.ID != self._payload[self._idx['pk_patient']]: 2043 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2044 patient.ID, 2045 self._payload[self._idx['pk_encounter']], 2046 self._payload[self._idx['pk_patient']] 2047 ) 2048 raise ValueError(msg) 2049 2050 lines.extend(self.format_soap ( 2051 episodes = [pk], 2052 left_margin = left_margin, 2053 soap_cats = 'soapu', 2054 emr = emr, 2055 issues = issues 2056 )) 2057 2058 # test results 2059 if with_tests: 2060 tests = emr.get_test_results_by_date ( 2061 episodes = [pk], 2062 encounter = self._payload[self._idx['pk_encounter']] 2063 ) 2064 if len(tests) > 0: 2065 lines.append('') 2066 lines.append(_('Measurements and Results:')) 2067 2068 for t in tests: 2069 lines.extend(t.format()) 2070 2071 del tests 2072 2073 # vaccinations 2074 if with_vaccinations: 2075 vaccs = emr.get_vaccinations ( 2076 episodes = [pk], 2077 encounters = [ self._payload[self._idx['pk_encounter']] ], 2078 order_by = u'date_given DESC, vaccine' 2079 ) 2080 if len(vaccs) > 0: 2081 lines.append(u'') 2082 lines.append(_('Vaccinations:')) 2083 for vacc in vaccs: 2084 lines.extend(vacc.format ( 2085 with_indications = True, 2086 with_comment = True, 2087 with_reaction = True, 2088 date_format = '%Y-%m-%d' 2089 )) 2090 del vaccs 2091 2092 # family history 2093 if with_family_history: 2094 fhx = emr.get_family_history(episodes = [pk]) 2095 if len(fhx) > 0: 2096 lines.append(u'') 2097 lines.append(_('Family History: %s') % len(fhx)) 2098 for f in fhx: 2099 lines.append(f.format ( 2100 left_margin = (left_margin + 1), 2101 include_episode = False, 2102 include_comment = True 2103 )) 2104 del fhx 2105 2106 # documents 2107 if with_docs: 2108 doc_folder = patient.get_document_folder() 2109 docs = doc_folder.get_documents ( 2110 episodes = [pk], 2111 encounter = self._payload[self._idx['pk_encounter']] 2112 ) 2113 if len(docs) > 0: 2114 lines.append(u'') 2115 lines.append(_('Documents:')) 2116 for d in docs: 2117 lines.append(u' %s %s:%s%s' % ( 2118 d['clin_when'].strftime('%Y-%m-%d'), 2119 d['l10n_type'], 2120 gmTools.coalesce(d['comment'], u'', u' "%s"'), 2121 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 2122 )) 2123 2124 del docs 2125 2126 return lines2127 #--------------------------------------------------------2128 - 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, by_episode=False):2129 """Format an encounter. 2130 2131 with_co_encountlet_hints: 2132 - whether to include which *other* episodes were discussed during this encounter 2133 - (only makes sense if episodes != None) 2134 """ 2135 lines = self.format_header ( 2136 fancy_header = fancy_header, 2137 left_margin = left_margin, 2138 with_rfe_aoe = with_rfe_aoe 2139 ) 2140 2141 if by_episode: 2142 lines.extend(self.format_by_episode ( 2143 episodes = episodes, 2144 issues = issues, 2145 left_margin = left_margin, 2146 patient = patient, 2147 with_soap = with_soap, 2148 with_tests = with_tests, 2149 with_docs = with_docs, 2150 with_vaccinations = with_vaccinations, 2151 with_family_history = with_family_history 2152 )) 2153 2154 else: 2155 if with_soap: 2156 lines.append(u'') 2157 2158 if patient.ID != self._payload[self._idx['pk_patient']]: 2159 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2160 patient.ID, 2161 self._payload[self._idx['pk_encounter']], 2162 self._payload[self._idx['pk_patient']] 2163 ) 2164 raise ValueError(msg) 2165 2166 emr = patient.get_emr() 2167 2168 lines.extend(self.format_soap ( 2169 episodes = episodes, 2170 left_margin = left_margin, 2171 soap_cats = 'soapu', 2172 emr = emr, 2173 issues = issues 2174 )) 2175 2176 # # family history 2177 # if with_family_history: 2178 # if episodes is not None: 2179 # fhx = emr.get_family_history(episodes = episodes) 2180 # if len(fhx) > 0: 2181 # lines.append(u'') 2182 # lines.append(_('Family History: %s') % len(fhx)) 2183 # for f in fhx: 2184 # lines.append(f.format ( 2185 # left_margin = (left_margin + 1), 2186 # include_episode = False, 2187 # include_comment = True 2188 # )) 2189 # del fhx 2190 2191 # test results 2192 if with_tests: 2193 emr = patient.get_emr() 2194 tests = emr.get_test_results_by_date ( 2195 episodes = episodes, 2196 encounter = self._payload[self._idx['pk_encounter']] 2197 ) 2198 if len(tests) > 0: 2199 lines.append('') 2200 lines.append(_('Measurements and Results:')) 2201 2202 for t in tests: 2203 lines.extend(t.format()) 2204 2205 del tests 2206 2207 # vaccinations 2208 if with_vaccinations: 2209 emr = patient.get_emr() 2210 vaccs = emr.get_vaccinations ( 2211 episodes = episodes, 2212 encounters = [ self._payload[self._idx['pk_encounter']] ], 2213 order_by = u'date_given DESC, vaccine' 2214 ) 2215 2216 if len(vaccs) > 0: 2217 lines.append(u'') 2218 lines.append(_('Vaccinations:')) 2219 2220 for vacc in vaccs: 2221 lines.extend(vacc.format ( 2222 with_indications = True, 2223 with_comment = True, 2224 with_reaction = True, 2225 date_format = '%Y-%m-%d' 2226 )) 2227 del vaccs 2228 2229 # documents 2230 if with_docs: 2231 doc_folder = patient.get_document_folder() 2232 docs = doc_folder.get_documents ( 2233 episodes = episodes, 2234 encounter = self._payload[self._idx['pk_encounter']] 2235 ) 2236 2237 if len(docs) > 0: 2238 lines.append(u'') 2239 lines.append(_('Documents:')) 2240 2241 for d in docs: 2242 lines.append(u' %s %s:%s%s' % ( 2243 d['clin_when'].strftime('%Y-%m-%d'), 2244 d['l10n_type'], 2245 gmTools.coalesce(d['comment'], u'', u' "%s"'), 2246 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 2247 )) 2248 2249 del docs 2250 2251 # co-encountlets 2252 if with_co_encountlet_hints: 2253 if episodes is not None: 2254 other_epis = self.get_episodes(exclude = episodes) 2255 if len(other_epis) > 0: 2256 lines.append(u'') 2257 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 2258 for epi in other_epis: 2259 lines.append(u' %s%s%s%s' % ( 2260 gmTools.u_left_double_angle_quote, 2261 epi['description'], 2262 gmTools.u_right_double_angle_quote, 2263 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2264 )) 2265 2266 eol_w_margin = u'\n%s' % (u' ' * left_margin) 2267 return u'%s\n' % eol_w_margin.join(lines)2268 #-------------------------------------------------------- 2269 # properties 2270 #--------------------------------------------------------2272 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 2273 return [] 2274 2275 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2276 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 2277 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2278 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]22792281 queries = [] 2282 # remove all codes 2283 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 2284 queries.append ({ 2285 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2286 'args': { 2287 'enc': self._payload[self._idx['pk_encounter']], 2288 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 2289 } 2290 }) 2291 # add new codes 2292 for pk_code in pk_codes: 2293 queries.append ({ 2294 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2295 'args': { 2296 'enc': self._payload[self._idx['pk_encounter']], 2297 'pk_code': pk_code 2298 } 2299 }) 2300 if len(queries) == 0: 2301 return 2302 # run it all in one transaction 2303 rows, idx = gmPG2.run_rw_queries(queries = queries) 2304 self.refetch_payload() 2305 return2306 2307 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 2308 #--------------------------------------------------------2310 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 2311 return [] 2312 2313 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2314 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 2315 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2316 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]23172319 queries = [] 2320 # remove all codes 2321 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 2322 queries.append ({ 2323 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2324 'args': { 2325 'enc': self._payload[self._idx['pk_encounter']], 2326 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 2327 } 2328 }) 2329 # add new codes 2330 for pk_code in pk_codes: 2331 queries.append ({ 2332 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2333 'args': { 2334 'enc': self._payload[self._idx['pk_encounter']], 2335 'pk_code': pk_code 2336 } 2337 }) 2338 if len(queries) == 0: 2339 return 2340 # run it all in one transaction 2341 rows, idx = gmPG2.run_rw_queries(queries = queries) 2342 self.refetch_payload() 2343 return2344 2345 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)2348 """Creates a new encounter for a patient. 2349 2350 fk_patient - patient PK 2351 fk_location - encounter location 2352 enc_type - type of encounter 2353 2354 FIXME: we don't deal with location yet 2355 """ 2356 if enc_type is None: 2357 enc_type = u'in surgery' 2358 # insert new encounter 2359 queries = [] 2360 try: 2361 enc_type = int(enc_type) 2362 cmd = u""" 2363 INSERT INTO clin.encounter ( 2364 fk_patient, fk_location, fk_type 2365 ) VALUES ( 2366 %(pat)s, 2367 -1, 2368 %(typ)s 2369 ) RETURNING pk""" 2370 except ValueError: 2371 enc_type = enc_type 2372 cmd = u""" 2373 insert into clin.encounter ( 2374 fk_patient, fk_location, fk_type 2375 ) values ( 2376 %(pat)s, 2377 -1, 2378 coalesce ( 2379 (select pk from clin.encounter_type where description = %(typ)s), 2380 -- pick the first available 2381 (select pk from clin.encounter_type limit 1) 2382 ) 2383 ) RETURNING pk""" 2384 args = {'pat': fk_patient, 'typ': enc_type} 2385 queries.append({'cmd': cmd, 'args': args}) 2386 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 2387 encounter = cEncounter(aPK_obj = rows[0]['pk']) 2388 2389 return encounter2390 #-----------------------------------------------------------2392 2393 rows, idx = gmPG2.run_rw_queries( 2394 queries = [{ 2395 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 2396 'args': {'desc': description, 'l10n_desc': l10n_description} 2397 }], 2398 return_data = True 2399 ) 2400 2401 success = rows[0][0] 2402 if not success: 2403 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 2404 2405 return {'description': description, 'l10n_description': l10n_description}2406 #-----------------------------------------------------------2408 """This will attempt to create a NEW encounter type.""" 2409 2410 # need a system name, so derive one if necessary 2411 if description is None: 2412 description = l10n_description 2413 2414 args = { 2415 'desc': description, 2416 'l10n_desc': l10n_description 2417 } 2418 2419 _log.debug('creating encounter type: %s, %s', description, l10n_description) 2420 2421 # does it exist already ? 2422 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 2423 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2424 2425 # yes 2426 if len(rows) > 0: 2427 # both system and l10n name are the same so all is well 2428 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 2429 _log.info('encounter type [%s] already exists with the proper translation') 2430 return {'description': description, 'l10n_description': l10n_description} 2431 2432 # or maybe there just wasn't a translation to 2433 # the current language for this type yet ? 2434 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 2435 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2436 2437 # there was, so fail 2438 if rows[0][0]: 2439 _log.error('encounter type [%s] already exists but with another translation') 2440 return None 2441 2442 # else set it 2443 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 2444 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2445 return {'description': description, 'l10n_description': l10n_description} 2446 2447 # no 2448 queries = [ 2449 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 2450 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 2451 ] 2452 rows, idx = gmPG2.run_rw_queries(queries = queries) 2453 2454 return {'description': description, 'l10n_description': l10n_description}2455 #-----------------------------------------------------------2457 cmd = u""" 2458 SELECT 2459 _(description) AS l10n_description, 2460 description 2461 FROM 2462 clin.encounter_type 2463 ORDER BY 2464 l10n_description 2465 """ 2466 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 2467 return rows2468 #-----------------------------------------------------------2470 cmd = u"SELECT * from clin.encounter_type where description = %s" 2471 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 2472 return rows2473 #-----------------------------------------------------------2475 cmd = u"delete from clin.encounter_type where description = %(desc)s" 2476 args = {'desc': description} 2477 try: 2478 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2479 except gmPG2.dbapi.IntegrityError, e: 2480 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 2481 return False 2482 raise 2483 2484 return True2485 #============================================================2487 """Represents one problem. 2488 2489 problems are the aggregation of 2490 .clinically_relevant=True issues and 2491 .is_open=True episodes 2492 """ 2493 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 2494 _cmds_store_payload = [u"select 1"] 2495 _updatable_fields = [] 2496 2497 #--------------------------------------------------------2608 #-----------------------------------------------------------2499 """Initialize. 2500 2501 aPK_obj must contain the keys 2502 pk_patient 2503 pk_episode 2504 pk_health_issue 2505 """ 2506 if aPK_obj is None: 2507 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 2508 2509 # As problems are rows from a view of different emr struct items, 2510 # the PK can't be a single field and, as some of the values of the 2511 # composed PK may be None, they must be queried using 'is null', 2512 # so we must programmatically construct the SQL query 2513 where_parts = [] 2514 pk = {} 2515 for col_name in aPK_obj.keys(): 2516 val = aPK_obj[col_name] 2517 if val is None: 2518 where_parts.append('%s IS NULL' % col_name) 2519 else: 2520 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 2521 pk[col_name] = val 2522 2523 # try to instantiate from true problem view 2524 cProblem._cmd_fetch_payload = u""" 2525 SELECT *, False as is_potential_problem 2526 FROM clin.v_problem_list 2527 WHERE %s""" % u' AND '.join(where_parts) 2528 2529 try: 2530 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 2531 return 2532 except gmExceptions.ConstructorError: 2533 _log.exception('actual problem not found, trying "potential" problems') 2534 if try_potential_problems is False: 2535 raise 2536 2537 # try to instantiate from potential-problems view 2538 cProblem._cmd_fetch_payload = u""" 2539 SELECT *, True as is_potential_problem 2540 FROM clin.v_potential_problem_list 2541 WHERE %s""" % u' AND '.join(where_parts) 2542 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)2543 #--------------------------------------------------------2545 """ 2546 Retrieve the cEpisode instance equivalent to this problem. 2547 The problem's type attribute must be 'episode' 2548 """ 2549 if self._payload[self._idx['type']] != 'episode': 2550 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2551 return None 2552 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])2553 #--------------------------------------------------------2555 """ 2556 Retrieve the cHealthIssue instance equivalent to this problem. 2557 The problem's type attribute must be 'issue' 2558 """ 2559 if self._payload[self._idx['type']] != 'issue': 2560 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2561 return None 2562 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])2563 #--------------------------------------------------------2565 2566 if self._payload[self._idx['type']] == u'issue': 2567 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 2568 #xxxxxxxxxxxxx 2569 2570 emr = patient.get_emr() 2571 2572 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 2573 return doc_folder.get_visual_progress_notes ( 2574 health_issue = self._payload[self._idx['pk_health_issue']], 2575 episode = self._payload[self._idx['pk_episode']] 2576 )2577 #-------------------------------------------------------- 2578 # properties 2579 #-------------------------------------------------------- 2580 # doubles as 'diagnostic_certainty_description' getter:2582 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])2583 2584 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 2585 #--------------------------------------------------------2587 if self._payload[self._idx['type']] == u'issue': 2588 cmd = u""" 2589 SELECT * FROM clin.v_linked_codes WHERE 2590 item_table = 'clin.lnk_code2h_issue'::regclass 2591 AND 2592 pk_item = %(item)s 2593 """ 2594 args = {'item': self._payload[self._idx['pk_health_issue']]} 2595 else: 2596 cmd = u""" 2597 SELECT * FROM clin.v_linked_codes WHERE 2598 item_table = 'clin.lnk_code2episode'::regclass 2599 AND 2600 pk_item = %(item)s 2601 """ 2602 args = {'item': self._payload[self._idx['pk_episode']]} 2603 2604 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2605 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]2606 2607 generic_codes = property(_get_generic_codes, lambda x:x)2610 """Retrieve the cEpisode instance equivalent to the given problem. 2611 2612 The problem's type attribute must be 'episode' 2613 2614 @param problem: The problem to retrieve its related episode for 2615 @type problem: A gmEMRStructItems.cProblem instance 2616 """ 2617 if isinstance(problem, cEpisode): 2618 return problem 2619 2620 exc = TypeError('cannot convert [%s] to episode' % problem) 2621 2622 if not isinstance(problem, cProblem): 2623 raise exc 2624 2625 if problem['type'] != 'episode': 2626 raise exc 2627 2628 return cEpisode(aPK_obj = problem['pk_episode'])2629 #-----------------------------------------------------------2631 """Retrieve the cIssue instance equivalent to the given problem. 2632 2633 The problem's type attribute must be 'issue'. 2634 2635 @param problem: The problem to retrieve the corresponding issue for 2636 @type problem: A gmEMRStructItems.cProblem instance 2637 """ 2638 if isinstance(problem, cHealthIssue): 2639 return problem 2640 2641 exc = TypeError('cannot convert [%s] to health issue' % problem) 2642 2643 if not isinstance(problem, cProblem): 2644 raise exc 2645 2646 if problem['type'] != 'issue': 2647 raise exc 2648 2649 return cHealthIssue(aPK_obj = problem['pk_health_issue'])2650 #-----------------------------------------------------------2652 """Transform given problem into either episode or health issue instance. 2653 """ 2654 if isinstance(problem, (cEpisode, cHealthIssue)): 2655 return problem 2656 2657 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 2658 2659 if not isinstance(problem, cProblem): 2660 _log.debug(u'%s' % problem) 2661 raise exc 2662 2663 if problem['type'] == 'episode': 2664 return cEpisode(aPK_obj = problem['pk_episode']) 2665 2666 if problem['type'] == 'issue': 2667 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 2668 2669 raise exc2670 #============================================================2672 2673 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 2674 _cmds_store_payload = [ 2675 u"""update clin.hospital_stay set 2676 clin_when = %(admission)s, 2677 discharge = %(discharge)s, 2678 narrative = gm.nullify_empty_string(%(hospital)s), 2679 fk_episode = %(pk_episode)s, 2680 fk_encounter = %(pk_encounter)s 2681 where 2682 pk = %(pk_hospital_stay)s and 2683 xmin = %(xmin_hospital_stay)s""", 2684 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 2685 ] 2686 _updatable_fields = [ 2687 'admission', 2688 'discharge', 2689 'hospital', 2690 'pk_episode', 2691 'pk_encounter' 2692 ] 2693 #-------------------------------------------------------2712 #-----------------------------------------------------------2695 2696 if self._payload[self._idx['discharge']] is not None: 2697 discharge = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2698 else: 2699 discharge = u'' 2700 2701 line = u'%s%s%s%s: %s%s%s' % ( 2702 u' ' * left_margin, 2703 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2704 discharge, 2705 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 2706 gmTools.u_left_double_angle_quote, 2707 self._payload[self._idx['episode']], 2708 gmTools.u_right_double_angle_quote 2709 ) 2710 2711 return line2714 queries = [{ 2715 # this assumes non-overarching stays 2716 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1', 2717 'args': {'pat': patient} 2718 }] 2719 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2720 if len(rows) == 0: 2721 return None 2722 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})2723 #-----------------------------------------------------------2725 args = {'pat': patient} 2726 if ongoing_only: 2727 cmd = u""" 2728 SELECT * 2729 FROM clin.v_pat_hospital_stays 2730 WHERE 2731 pk_patient = %(pat)s 2732 AND 2733 discharge is NULL 2734 ORDER BY admission""" 2735 else: 2736 cmd = u""" 2737 SELECT * 2738 FROM clin.v_pat_hospital_stays 2739 WHERE pk_patient = %(pat)s 2740 ORDER BY admission""" 2741 2742 queries = [{'cmd': cmd, 'args': args}] 2743 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2744 2745 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]2746 #-----------------------------------------------------------2748 2749 queries = [{ 2750 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk', 2751 'args': {'enc': encounter, 'epi': episode} 2752 }] 2753 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2754 2755 return cHospitalStay(aPK_obj = rows[0][0])2756 #-----------------------------------------------------------2758 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 2759 args = {'pk': stay} 2760 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2761 return True2762 #============================================================2764 2765 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 2766 _cmds_store_payload = [ 2767 u"""UPDATE clin.procedure SET 2768 soap_cat = 'p', 2769 clin_when = %(clin_when)s, 2770 clin_end = %(clin_end)s, 2771 is_ongoing = %(is_ongoing)s, 2772 clin_where = NULLIF ( 2773 COALESCE ( 2774 %(pk_hospital_stay)s::TEXT, 2775 gm.nullify_empty_string(%(clin_where)s) 2776 ), 2777 %(pk_hospital_stay)s::TEXT 2778 ), 2779 narrative = gm.nullify_empty_string(%(performed_procedure)s), 2780 fk_hospital_stay = %(pk_hospital_stay)s, 2781 fk_episode = %(pk_episode)s, 2782 fk_encounter = %(pk_encounter)s 2783 WHERE 2784 pk = %(pk_procedure)s AND 2785 xmin = %(xmin_procedure)s 2786 RETURNING xmin as xmin_procedure""" 2787 ] 2788 _updatable_fields = [ 2789 'clin_when', 2790 'clin_end', 2791 'is_ongoing', 2792 'clin_where', 2793 'performed_procedure', 2794 'pk_hospital_stay', 2795 'pk_episode', 2796 'pk_encounter' 2797 ] 2798 #-------------------------------------------------------2904 #-----------------------------------------------------------2800 2801 if (attribute == 'pk_hospital_stay') and (value is not None): 2802 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 2803 2804 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 2805 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 2806 2807 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)2808 #-------------------------------------------------------2810 2811 if self._payload[self._idx['is_ongoing']]: 2812 end = _(' (ongoing)') 2813 else: 2814 end = self._payload[self._idx['clin_end']] 2815 if end is None: 2816 end = u'' 2817 else: 2818 end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2819 2820 line = u'%s%s%s (%s): %s' % ( 2821 (u' ' * left_margin), 2822 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2823 end, 2824 self._payload[self._idx['clin_where']], 2825 self._payload[self._idx['performed_procedure']] 2826 ) 2827 if include_episode: 2828 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 2829 2830 if include_codes: 2831 codes = self.generic_codes 2832 if len(codes) > 0: 2833 line += u'\n' 2834 for c in codes: 2835 line += u'%s %s: %s (%s - %s)\n' % ( 2836 (u' ' * left_margin), 2837 c['code'], 2838 c['term'], 2839 c['name_short'], 2840 c['version'] 2841 ) 2842 del codes 2843 2844 return line2845 #--------------------------------------------------------2847 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2848 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 2849 args = { 2850 'issue': self._payload[self._idx['pk_procedure']], 2851 'code': pk_code 2852 } 2853 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2854 return True2855 #--------------------------------------------------------2857 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2858 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 2859 args = { 2860 'issue': self._payload[self._idx['pk_procedure']], 2861 'code': pk_code 2862 } 2863 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2864 return True2865 #-------------------------------------------------------- 2866 # properties 2867 #--------------------------------------------------------2869 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 2870 return [] 2871 2872 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2873 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 2874 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2875 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]28762878 queries = [] 2879 # remove all codes 2880 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 2881 queries.append ({ 2882 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 2883 'args': { 2884 'proc': self._payload[self._idx['pk_procedure']], 2885 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 2886 } 2887 }) 2888 # add new codes 2889 for pk_code in pk_codes: 2890 queries.append ({ 2891 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 2892 'args': { 2893 'proc': self._payload[self._idx['pk_procedure']], 2894 'pk_code': pk_code 2895 } 2896 }) 2897 if len(queries) == 0: 2898 return 2899 # run it all in one transaction 2900 rows, idx = gmPG2.run_rw_queries(queries = queries) 2901 return2902 2903 generic_codes = property(_get_generic_codes, _set_generic_codes)2906 2907 queries = [ 2908 { 2909 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 2910 'args': {'pat': patient} 2911 } 2912 ] 2913 2914 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2915 2916 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]2917 #-----------------------------------------------------------2919 queries = [ 2920 { 2921 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when DESC LIMIT 1', 2922 'args': {'pat': patient} 2923 } 2924 ] 2925 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2926 if len(rows) == 0: 2927 return None 2928 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})2929 #-----------------------------------------------------------2930 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):2931 2932 queries = [{ 2933 'cmd': u""" 2934 INSERT INTO clin.procedure ( 2935 fk_encounter, 2936 fk_episode, 2937 soap_cat, 2938 clin_where, 2939 fk_hospital_stay, 2940 narrative 2941 ) VALUES ( 2942 %(enc)s, 2943 %(epi)s, 2944 'p', 2945 gm.nullify_empty_string(%(loc)s), 2946 %(stay)s, 2947 gm.nullify_empty_string(%(proc)s) 2948 ) 2949 RETURNING pk""", 2950 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 2951 }] 2952 2953 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2954 2955 return cPerformedProcedure(aPK_obj = rows[0][0])2956 #-----------------------------------------------------------2958 cmd = u'delete from clin.procedure where pk = %(pk)s' 2959 args = {'pk': procedure} 2960 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2961 return True2962 #============================================================ 2963 # main - unit testing 2964 #------------------------------------------------------------ 2965 if __name__ == '__main__': 2966 2967 if len(sys.argv) < 2: 2968 sys.exit() 2969 2970 if sys.argv[1] != 'test': 2971 sys.exit() 2972 2973 #-------------------------------------------------------- 2974 # define tests 2975 #--------------------------------------------------------2977 print "\nProblem test" 2978 print "------------" 2979 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 2980 print prob 2981 fields = prob.get_fields() 2982 for field in fields: 2983 print field, ':', prob[field] 2984 print '\nupdatable:', prob.get_updatable_fields() 2985 epi = prob.get_as_episode() 2986 print '\nas episode:' 2987 if epi is not None: 2988 for field in epi.get_fields(): 2989 print ' .%s : %s' % (field, epi[field])2990 #--------------------------------------------------------2992 print "\nhealth issue test" 2993 print "-----------------" 2994 h_issue = cHealthIssue(aPK_obj=2) 2995 print h_issue 2996 print h_issue.latest_access_date 2997 print h_issue.end_date2998 # fields = h_issue.get_fields() 2999 # for field in fields: 3000 # print field, ':', h_issue[field] 3001 # print "has open episode:", h_issue.has_open_episode() 3002 # print "open episode:", h_issue.get_open_episode() 3003 # print "updateable:", h_issue.get_updatable_fields() 3004 # h_issue.close_expired_episode(ttl=7300) 3005 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 3006 # print h_issue 3007 # print h_issue.format_as_journal() 3008 #--------------------------------------------------------3010 print "\nepisode test" 3011 print "------------" 3012 episode = cEpisode(aPK_obj=1) 3013 print episode 3014 fields = episode.get_fields() 3015 for field in fields: 3016 print field, ':', episode[field] 3017 print "updatable:", episode.get_updatable_fields() 3018 raw_input('ENTER to continue') 3019 3020 old_description = episode['description'] 3021 old_enc = cEncounter(aPK_obj = 1) 3022 3023 desc = '1-%s' % episode['description'] 3024 print "==> renaming to", desc 3025 successful = episode.rename ( 3026 description = desc 3027 ) 3028 if not successful: 3029 print "error" 3030 else: 3031 print "success" 3032 for field in fields: 3033 print field, ':', episode[field] 3034 3035 print "episode range:", episode.get_access_range() 3036 3037 raw_input('ENTER to continue')3038 3039 #--------------------------------------------------------3041 print "\nencounter test" 3042 print "--------------" 3043 encounter = cEncounter(aPK_obj=1) 3044 print encounter 3045 fields = encounter.get_fields() 3046 for field in fields: 3047 print field, ':', encounter[field] 3048 print "updatable:", encounter.get_updatable_fields()3049 #--------------------------------------------------------3051 encounter = cEncounter(aPK_obj=1) 3052 print encounter 3053 print "" 3054 print encounter.format_latex()3055 #--------------------------------------------------------3057 procs = get_performed_procedures(patient = 12) 3058 for proc in procs: 3059 print proc.format(left_margin=2)3060 #--------------------------------------------------------3062 stay = create_hospital_stay(encounter = 1, episode = 2) 3063 stay['hospital'] = u'Starfleet Galaxy General Hospital' 3064 stay.save_payload() 3065 print stay 3066 for s in get_patient_hospital_stays(12): 3067 print s 3068 delete_hospital_stay(stay['pk_hospital_stay']) 3069 stay = create_hospital_stay(encounter = 1, episode = 4)3070 #--------------------------------------------------------3072 tests = [None, 'A', 'B', 'C', 'D', 'E'] 3073 3074 for t in tests: 3075 print type(t), t 3076 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)3077 #-------------------------------------------------------- 3082 #-------------------------------------------------------- 3083 # run them 3084 #test_episode() 3085 #test_problem() 3086 #test_encounter() 3087 test_health_issue() 3088 #test_hospital_stay() 3089 #test_performed_procedure() 3090 #test_diagnostic_certainty_classification_map() 3091 #test_encounter2latex() 3092 #test_episode_codes() 3093 #============================================================ 3094
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Fri Feb 1 03:56:36 2013 | http://epydoc.sourceforge.net |