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 #--------------------------------------------------------1440 #============================================================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.append(t.format ( 1236 with_review = False, 1237 with_ranges = False, 1238 with_evaluation = False, 1239 with_episode = False, 1240 with_type_details = False, 1241 date_format = '%Y %b %d' 1242 )) 1243 del tests 1244 1245 # vaccinations 1246 if with_vaccinations: 1247 vaccs = emr.get_vaccinations ( 1248 episodes = [ self._payload[self._idx['pk_episode']] ], 1249 order_by = u'date_given DESC, vaccine' 1250 ) 1251 if len(vaccs) > 0: 1252 lines.append(u'') 1253 lines.append(_('Vaccinations:')) 1254 for vacc in vaccs: 1255 lines.extend(vacc.format ( 1256 with_indications = True, 1257 with_comment = True, 1258 with_reaction = True, 1259 date_format = '%Y-%m-%d' 1260 )) 1261 del vaccs 1262 1263 left_margin = u' ' * left_margin 1264 eol_w_margin = u'\n%s' % left_margin 1265 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n') 1266 return left_margin + eol_w_margin.join(lines) + u'\n'1267 #-------------------------------------------------------- 1268 # properties 1269 #--------------------------------------------------------1271 cmd = u""" 1272 SELECT 1273 MIN(earliest) 1274 FROM ( 1275 -- last modification, earliest = when created in/changed to the current state 1276 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s) 1277 1278 UNION ALL 1279 1280 -- last modification of encounter in which created, earliest = initial creation of that encounter 1281 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1282 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1283 ) 1284 ) 1285 UNION ALL 1286 1287 -- start of encounter in which created, earliest = explicitely set 1288 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1289 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1290 ) 1291 ) 1292 UNION ALL 1293 1294 -- earliest start of encounters of clinical items linked to this episode 1295 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN ( 1296 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1297 ) 1298 ) 1299 UNION ALL 1300 1301 -- earliest explicit .clin_when of clinical items linked to this episode 1302 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1303 1304 UNION ALL 1305 1306 -- earliest modification time of clinical items linked to this episode 1307 -- this CAN be used since if an item is linked to an episode it can be 1308 -- assumed the episode (should have) existed at the time of creation 1309 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1310 1311 -- not sure about this one: 1312 -- .pk -> clin.clin_root_item.fk_encounter.modified_when 1313 1314 ) AS candidates""" 1315 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1316 return rows[0][0]1317 1318 best_guess_start_date = property(_get_best_guess_start_date) 1319 #--------------------------------------------------------1321 cmd = u""" 1322 SELECT 1323 MAX(latest) 1324 FROM ( 1325 -- last modification, latest = when last changed to the current state 1326 (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) 1327 1328 UNION ALL 1329 1330 -- last modification of encounter in which created, latest = initial creation of that encounter 1331 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer 1332 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1333 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1334 -- ) 1335 --) 1336 1337 -- end of encounter in which created, latest = explicitely set 1338 -- DO NOT USE: we can retrospectively create episodes which 1339 -- DO NOT USE: are long since finished 1340 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1341 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1342 -- ) 1343 --) 1344 1345 -- latest end of encounters of clinical items linked to this episode 1346 (SELECT 1347 MAX(last_affirmed) AS latest, 1348 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate 1349 FROM clin.encounter 1350 WHERE pk IN ( 1351 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1352 ) 1353 ) 1354 UNION ALL 1355 1356 -- latest explicit .clin_when of clinical items linked to this episode 1357 (SELECT 1358 MAX(clin_when) AS latest, 1359 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate 1360 FROM clin.clin_root_item 1361 WHERE fk_episode = %(pk)s 1362 ) 1363 1364 -- latest modification time of clinical items linked to this episode 1365 -- this CAN be used since if an item is linked to an episode it can be 1366 -- assumed the episode (should have) existed at the time of creation 1367 -- DO NOT USE, because typo fixes should not extend the episode 1368 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1369 1370 -- not sure about this one: 1371 -- .pk -> clin.clin_root_item.fk_encounter.modified_when 1372 1373 ) AS candidates""" 1374 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1375 #_log.debug('last episode access: %s (%s)', rows[0][0], rows[0][1]) 1376 return rows[0][0]1377 1378 latest_access_date = property(_get_latest_access_date) 1379 #--------------------------------------------------------1381 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])1382 1383 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 1384 #--------------------------------------------------------1386 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 1387 return [] 1388 1389 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1390 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 1391 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1392 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]13931395 queries = [] 1396 # remove all codes 1397 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 1398 queries.append ({ 1399 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s', 1400 'args': { 1401 'epi': self._payload[self._idx['pk_episode']], 1402 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 1403 } 1404 }) 1405 # add new codes 1406 for pk_code in pk_codes: 1407 queries.append ({ 1408 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)', 1409 'args': { 1410 'epi': self._payload[self._idx['pk_episode']], 1411 'pk_code': pk_code 1412 } 1413 }) 1414 if len(queries) == 0: 1415 return 1416 # run it all in one transaction 1417 rows, idx = gmPG2.run_rw_queries(queries = queries) 1418 return1419 1420 generic_codes = property(_get_generic_codes, _set_generic_codes) 1421 #--------------------------------------------------------1423 cmd = u"""SELECT EXISTS ( 1424 SELECT 1 FROM clin.clin_narrative 1425 WHERE 1426 fk_episode = %(epi)s 1427 AND 1428 fk_encounter IN ( 1429 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1430 ) 1431 )""" 1432 args = { 1433 u'pat': self._payload[self._idx['pk_patient']], 1434 u'epi': self._payload[self._idx['pk_episode']] 1435 } 1436 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1437 return rows[0][0]1438 1439 has_narrative = property(_get_has_narrative, lambda x:x)1441 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):1442 """Creates a new episode for a given patient's health issue. 1443 1444 pk_health_issue - given health issue PK 1445 episode_name - name of episode 1446 """ 1447 if not allow_dupes: 1448 try: 1449 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 1450 if episode['episode_open'] != is_open: 1451 episode['episode_open'] = is_open 1452 episode.save_payload() 1453 return episode 1454 except gmExceptions.ConstructorError: 1455 pass 1456 1457 queries = [] 1458 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 1459 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 1460 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 1461 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 1462 1463 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 1464 return episode1465 #-----------------------------------------------------------1467 if isinstance(episode, cEpisode): 1468 pk = episode['pk_episode'] 1469 else: 1470 pk = int(episode) 1471 1472 cmd = u'DELETE FROM clin.episode WHERE pk = %(pk)s' 1473 1474 try: 1475 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}]) 1476 except gmPG2.dbapi.IntegrityError: 1477 # should be parsing pgcode/and or error message 1478 _log.exception('cannot delete episode, it is in use') 1479 return False 1480 1481 return True1482 #-----------------------------------------------------------1484 return cProblem ( 1485 aPK_obj = { 1486 'pk_patient': episode['pk_patient'], 1487 'pk_episode': episode['pk_episode'], 1488 'pk_health_issue': episode['pk_health_issue'] 1489 }, 1490 try_potential_problems = allow_closed 1491 )1492 #============================================================ 1493 # encounter API 1494 #============================================================1496 """Represents one encounter.""" 1497 1498 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 1499 _cmds_store_payload = [ 1500 u"""UPDATE clin.encounter SET 1501 started = %(started)s, 1502 last_affirmed = %(last_affirmed)s, 1503 fk_location = %(pk_location)s, 1504 fk_type = %(pk_type)s, 1505 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1506 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1507 WHERE 1508 pk = %(pk_encounter)s AND 1509 xmin = %(xmin_encounter)s 1510 """, 1511 # need to return all fields so we can survive in-place upgrades 1512 u"""select * from clin.v_pat_encounters where pk_encounter = %(pk_encounter)s""" 1513 ] 1514 _updatable_fields = [ 1515 'started', 1516 'last_affirmed', 1517 'pk_location', 1518 'pk_type', 1519 'reason_for_encounter', 1520 'assessment_of_encounter' 1521 ] 1522 #--------------------------------------------------------2349 #-----------------------------------------------------------1524 """Set the encounter as the active one. 1525 1526 "Setting active" means making sure the encounter 1527 row has the youngest "last_affirmed" timestamp of 1528 all encounter rows for this patient. 1529 """ 1530 self['last_affirmed'] = gmDateTime.pydt_now_here() 1531 self.save()1532 #--------------------------------------------------------1534 """ 1535 Moves every element currently linked to the current encounter 1536 and the source_episode onto target_episode. 1537 1538 @param source_episode The episode the elements are currently linked to. 1539 @type target_episode A cEpisode intance. 1540 @param target_episode The episode the elements will be relinked to. 1541 @type target_episode A cEpisode intance. 1542 """ 1543 if source_episode['pk_episode'] == target_episode['pk_episode']: 1544 return True 1545 1546 queries = [] 1547 cmd = u""" 1548 UPDATE clin.clin_root_item 1549 SET fk_episode = %(trg)s 1550 WHERE 1551 fk_encounter = %(enc)s AND 1552 fk_episode = %(src)s 1553 """ 1554 rows, idx = gmPG2.run_rw_queries(queries = [{ 1555 'cmd': cmd, 1556 'args': { 1557 'trg': target_episode['pk_episode'], 1558 'enc': self.pk_obj, 1559 'src': source_episode['pk_episode'] 1560 } 1561 }]) 1562 self.refetch_payload() 1563 return True1564 #--------------------------------------------------------1566 1567 relevant_fields = [ 1568 'pk_location', 1569 'pk_type', 1570 'pk_patient', 1571 'reason_for_encounter', 1572 'assessment_of_encounter' 1573 ] 1574 for field in relevant_fields: 1575 if self._payload[self._idx[field]] != another_object[field]: 1576 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1577 return False 1578 1579 relevant_fields = [ 1580 'started', 1581 'last_affirmed', 1582 ] 1583 for field in relevant_fields: 1584 if self._payload[self._idx[field]] is None: 1585 if another_object[field] is None: 1586 continue 1587 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1588 return False 1589 1590 if another_object[field] is None: 1591 return False 1592 1593 # compares at minute granularity 1594 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'): 1595 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1596 return False 1597 1598 # compare codes 1599 # 1) RFE 1600 if another_object['pk_generic_codes_rfe'] is None: 1601 if self._payload[self._idx['pk_generic_codes_rfe']] is not None: 1602 return False 1603 if another_object['pk_generic_codes_rfe'] is not None: 1604 if self._payload[self._idx['pk_generic_codes_rfe']] is None: 1605 return False 1606 if ( 1607 (another_object['pk_generic_codes_rfe'] is None) 1608 and 1609 (self._payload[self._idx['pk_generic_codes_rfe']] is None) 1610 ) is False: 1611 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]): 1612 return False 1613 # 2) AOE 1614 if another_object['pk_generic_codes_aoe'] is None: 1615 if self._payload[self._idx['pk_generic_codes_aoe']] is not None: 1616 return False 1617 if another_object['pk_generic_codes_aoe'] is not None: 1618 if self._payload[self._idx['pk_generic_codes_aoe']] is None: 1619 return False 1620 if ( 1621 (another_object['pk_generic_codes_aoe'] is None) 1622 and 1623 (self._payload[self._idx['pk_generic_codes_aoe']] is None) 1624 ) is False: 1625 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]): 1626 return False 1627 1628 return True1629 #--------------------------------------------------------1631 cmd = u""" 1632 select exists ( 1633 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 1634 union all 1635 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1636 )""" 1637 args = { 1638 'pat': self._payload[self._idx['pk_patient']], 1639 'enc': self.pk_obj 1640 } 1641 rows, idx = gmPG2.run_ro_queries ( 1642 queries = [{ 1643 'cmd': cmd, 1644 'args': args 1645 }] 1646 ) 1647 return rows[0][0]1648 #--------------------------------------------------------1650 cmd = u""" 1651 select exists ( 1652 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 1653 )""" 1654 args = { 1655 'pat': self._payload[self._idx['pk_patient']], 1656 'enc': self.pk_obj 1657 } 1658 rows, idx = gmPG2.run_ro_queries ( 1659 queries = [{ 1660 'cmd': cmd, 1661 'args': args 1662 }] 1663 ) 1664 return rows[0][0]1665 #--------------------------------------------------------1667 """soap_cats: <space> = admin category""" 1668 1669 if soap_cats is None: 1670 soap_cats = u'soap ' 1671 else: 1672 soap_cats = soap_cats.lower() 1673 1674 cats = [] 1675 for cat in soap_cats: 1676 if cat in u'soapu': 1677 cats.append(cat) 1678 continue 1679 if cat == u' ': 1680 cats.append(None) 1681 1682 cmd = u""" 1683 SELECT EXISTS ( 1684 SELECT 1 FROM clin.clin_narrative 1685 WHERE 1686 fk_encounter = %(enc)s 1687 AND 1688 soap_cat IN %(cats)s 1689 LIMIT 1 1690 ) 1691 """ 1692 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 1693 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 1694 return rows[0][0]1695 #--------------------------------------------------------1697 cmd = u""" 1698 select exists ( 1699 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1700 )""" 1701 args = { 1702 'pat': self._payload[self._idx['pk_patient']], 1703 'enc': self.pk_obj 1704 } 1705 rows, idx = gmPG2.run_ro_queries ( 1706 queries = [{ 1707 'cmd': cmd, 1708 'args': args 1709 }] 1710 ) 1711 return rows[0][0]1712 #--------------------------------------------------------1714 1715 if soap_cat is not None: 1716 soap_cat = soap_cat.lower() 1717 1718 if episode is None: 1719 epi_part = u'fk_episode is null' 1720 else: 1721 epi_part = u'fk_episode = %(epi)s' 1722 1723 cmd = u""" 1724 select narrative 1725 from clin.clin_narrative 1726 where 1727 fk_encounter = %%(enc)s 1728 and 1729 soap_cat = %%(cat)s 1730 and 1731 %s 1732 order by clin_when desc 1733 limit 1 1734 """ % epi_part 1735 1736 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 1737 1738 rows, idx = gmPG2.run_ro_queries ( 1739 queries = [{ 1740 'cmd': cmd, 1741 'args': args 1742 }] 1743 ) 1744 if len(rows) == 0: 1745 return None 1746 1747 return rows[0][0]1748 #--------------------------------------------------------1750 cmd = u""" 1751 SELECT * FROM clin.v_pat_episodes 1752 WHERE pk_episode IN ( 1753 SELECT DISTINCT fk_episode 1754 FROM clin.clin_root_item 1755 WHERE fk_encounter = %%(enc)s 1756 1757 UNION 1758 1759 SELECT DISTINCT fk_episode 1760 FROM blobs.doc_med 1761 WHERE fk_encounter = %%(enc)s 1762 ) %s""" 1763 args = {'enc': self.pk_obj} 1764 if exclude is not None: 1765 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s' 1766 args['excluded'] = tuple(exclude) 1767 else: 1768 cmd = cmd % u'' 1769 1770 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1771 1772 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]1773 1774 episodes = property(get_episodes, lambda x:x) 1775 #--------------------------------------------------------1777 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1778 if field == u'rfe': 1779 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1780 elif field == u'aoe': 1781 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1782 else: 1783 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1784 args = { 1785 'item': self._payload[self._idx['pk_encounter']], 1786 'code': pk_code 1787 } 1788 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1789 return True1790 #--------------------------------------------------------1792 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1793 if field == u'rfe': 1794 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1795 elif field == u'aoe': 1796 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1797 else: 1798 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1799 args = { 1800 'item': self._payload[self._idx['pk_encounter']], 1801 'code': pk_code 1802 } 1803 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1804 return True1805 #--------------------------------------------------------1806 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):1807 1808 lines = [] 1809 for soap_cat in soap_cats: 1810 soap_cat_narratives = emr.get_clin_narrative ( 1811 episodes = episodes, 1812 issues = issues, 1813 encounters = [self._payload[self._idx['pk_encounter']]], 1814 soap_cats = [soap_cat] 1815 ) 1816 if soap_cat_narratives is None: 1817 continue 1818 if len(soap_cat_narratives) == 0: 1819 continue 1820 1821 lines.append(u'%s%s %s %s' % ( 1822 gmTools.u_box_top_left_arc, 1823 gmTools.u_box_horiz_single, 1824 gmClinNarrative.soap_cat2l10n_str[soap_cat], 1825 gmTools.u_box_horiz_single * 5 1826 )) 1827 for soap_entry in soap_cat_narratives: 1828 txt = gmTools.wrap ( 1829 text = soap_entry['narrative'], 1830 width = 75, 1831 initial_indent = u'', 1832 subsequent_indent = (u' ' * left_margin) 1833 ) 1834 lines.append(txt) 1835 when = gmDateTime.pydt_strftime ( 1836 soap_entry['date'], 1837 format = '%Y-%m-%d %H:%M', 1838 accuracy = gmDateTime.acc_minutes 1839 ) 1840 txt = u'%s%s %.8s, %s %s' % ( 1841 u' ' * 40, 1842 gmTools.u_box_horiz_light_heavy, 1843 soap_entry['provider'], 1844 when, 1845 gmTools.u_box_horiz_heavy_light 1846 ) 1847 lines.append(txt) 1848 lines.append('') 1849 1850 return lines1851 #--------------------------------------------------------1853 1854 nothing2format = ( 1855 (self._payload[self._idx['reason_for_encounter']] is None) 1856 and 1857 (self._payload[self._idx['assessment_of_encounter']] is None) 1858 and 1859 (self.has_soap_narrative(soap_cats = u'soapu') is False) 1860 ) 1861 if nothing2format: 1862 return u'' 1863 1864 if date_format is None: 1865 date_format = '%A, %b %d %Y' 1866 1867 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 1868 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 1869 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()), 1870 self._payload[self._idx['started']].strftime('%H:%M'), 1871 self._payload[self._idx['last_affirmed']].strftime('%H:%M') 1872 ) 1873 tex += u'\\hline \\tabularnewline \n' 1874 1875 for epi in self.get_episodes(): 1876 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 1877 if len(soaps) == 0: 1878 continue 1879 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1880 gmTools.tex_escape_string(_('Problem')), 1881 gmTools.tex_escape_string(epi['description']), 1882 gmTools.coalesce ( 1883 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 1884 instead = u'', 1885 template_initial = u' {\\footnotesize [%s]}', 1886 none_equivalents = [None, u''] 1887 ) 1888 ) 1889 if epi['pk_health_issue'] is not None: 1890 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1891 gmTools.tex_escape_string(_('Health issue')), 1892 gmTools.tex_escape_string(epi['health_issue']), 1893 gmTools.coalesce ( 1894 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 1895 instead = u'', 1896 template_initial = u' {\\footnotesize [%s]}', 1897 none_equivalents = [None, u''] 1898 ) 1899 ) 1900 for soap in soaps: 1901 tex += u'{\\small %s} & %s \\tabularnewline \n' % ( 1902 gmClinNarrative.soap_cat2l10n[soap['soap_cat']], 1903 gmTools.tex_escape_string(soap['narrative'].strip(u'\n')) 1904 ) 1905 tex += u' & \\tabularnewline \n' 1906 1907 if self._payload[self._idx['reason_for_encounter']] is not None: 1908 tex += u'%s & %s \\tabularnewline \n' % ( 1909 gmTools.tex_escape_string(_('RFE')), 1910 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 1911 ) 1912 if self._payload[self._idx['assessment_of_encounter']] is not None: 1913 tex += u'%s & %s \\tabularnewline \n' % ( 1914 gmTools.tex_escape_string(_('AOE')), 1915 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 1916 ) 1917 1918 tex += u'\\hline \\tabularnewline \n' 1919 tex += u' & \\tabularnewline \n' 1920 1921 return tex1922 #--------------------------------------------------------1924 lines = [] 1925 1926 if fancy_header: 1927 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1928 u' ' * left_margin, 1929 self._payload[self._idx['l10n_type']], 1930 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1931 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1932 self._payload[self._idx['source_time_zone']], 1933 gmTools.coalesce ( 1934 self._payload[self._idx['assessment_of_encounter']], 1935 u'', 1936 u' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 1937 ), 1938 self._payload[self._idx['pk_encounter']] 1939 )) 1940 1941 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1942 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1943 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1944 gmDateTime.current_local_iso_numeric_timezone_string, 1945 gmTools.bool2subst ( 1946 gmDateTime.dst_currently_in_effect, 1947 gmDateTime.py_dst_timezone_name, 1948 gmDateTime.py_timezone_name 1949 ), 1950 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1951 )) 1952 1953 if self._payload[self._idx['reason_for_encounter']] is not None: 1954 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1955 codes = self.generic_codes_rfe 1956 for c in codes: 1957 lines.append(u' %s: %s (%s - %s)' % ( 1958 c['code'], 1959 c['term'], 1960 c['name_short'], 1961 c['version'] 1962 )) 1963 if len(codes) > 0: 1964 lines.append(u'') 1965 1966 if self._payload[self._idx['assessment_of_encounter']] is not None: 1967 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1968 codes = self.generic_codes_aoe 1969 for c in codes: 1970 lines.append(u' %s: %s (%s - %s)' % ( 1971 c['code'], 1972 c['term'], 1973 c['name_short'], 1974 c['version'] 1975 )) 1976 if len(codes) > 0: 1977 lines.append(u'') 1978 del codes 1979 return lines 1980 1981 now = gmDateTime.pydt_now_here() 1982 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'): 1983 start = u'%s %s' % ( 1984 _('today'), 1985 self._payload[self._idx['started_original_tz']].strftime('%H:%M') 1986 ) 1987 else: 1988 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M') 1989 lines.append(u'%s%s: %s - %s%s' % ( 1990 u' ' * left_margin, 1991 self._payload[self._idx['l10n_type']], 1992 start, 1993 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1994 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1995 )) 1996 if with_rfe_aoe: 1997 if self._payload[self._idx['reason_for_encounter']] is not None: 1998 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1999 codes = self.generic_codes_rfe 2000 for c in codes: 2001 lines.append(u' %s: %s (%s - %s)' % ( 2002 c['code'], 2003 c['term'], 2004 c['name_short'], 2005 c['version'] 2006 )) 2007 if len(codes) > 0: 2008 lines.append(u'') 2009 if self._payload[self._idx['assessment_of_encounter']] is not None: 2010 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2011 codes = self.generic_codes_aoe 2012 if len(codes) > 0: 2013 lines.append(u'') 2014 for c in codes: 2015 lines.append(u' %s: %s (%s - %s)' % ( 2016 c['code'], 2017 c['term'], 2018 c['name_short'], 2019 c['version'] 2020 )) 2021 if len(codes) > 0: 2022 lines.append(u'') 2023 del codes 2024 2025 return lines2026 #--------------------------------------------------------2027 - 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):2028 2029 lines = [] 2030 emr = patient.emr 2031 if episodes is None: 2032 episodes = [ e['pk_episode'] for e in self.episodes ] 2033 2034 for pk in episodes: 2035 epi = cEpisode(aPK_obj = pk) 2036 lines.append(_('\nEpisode %s%s%s%s:') % ( 2037 gmTools.u_left_double_angle_quote, 2038 epi['description'], 2039 gmTools.u_right_double_angle_quote, 2040 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2041 )) 2042 2043 # soap 2044 if with_soap: 2045 if patient.ID != self._payload[self._idx['pk_patient']]: 2046 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2047 patient.ID, 2048 self._payload[self._idx['pk_encounter']], 2049 self._payload[self._idx['pk_patient']] 2050 ) 2051 raise ValueError(msg) 2052 2053 lines.extend(self.format_soap ( 2054 episodes = [pk], 2055 left_margin = left_margin, 2056 soap_cats = 'soapu', 2057 emr = emr, 2058 issues = issues 2059 )) 2060 2061 # test results 2062 if with_tests: 2063 tests = emr.get_test_results_by_date ( 2064 episodes = [pk], 2065 encounter = self._payload[self._idx['pk_encounter']] 2066 ) 2067 if len(tests) > 0: 2068 lines.append('') 2069 lines.append(_('Measurements and Results:')) 2070 2071 for t in tests: 2072 lines.append(t.format()) 2073 2074 del tests 2075 2076 # vaccinations 2077 if with_vaccinations: 2078 vaccs = emr.get_vaccinations ( 2079 episodes = [pk], 2080 encounters = [ self._payload[self._idx['pk_encounter']] ], 2081 order_by = u'date_given DESC, vaccine' 2082 ) 2083 if len(vaccs) > 0: 2084 lines.append(u'') 2085 lines.append(_('Vaccinations:')) 2086 for vacc in vaccs: 2087 lines.extend(vacc.format ( 2088 with_indications = True, 2089 with_comment = True, 2090 with_reaction = True, 2091 date_format = '%Y-%m-%d' 2092 )) 2093 del vaccs 2094 2095 # family history 2096 if with_family_history: 2097 fhx = emr.get_family_history(episodes = [pk]) 2098 if len(fhx) > 0: 2099 lines.append(u'') 2100 lines.append(_('Family History: %s') % len(fhx)) 2101 for f in fhx: 2102 lines.append(f.format ( 2103 left_margin = (left_margin + 1), 2104 include_episode = False, 2105 include_comment = True 2106 )) 2107 del fhx 2108 2109 # documents 2110 if with_docs: 2111 doc_folder = patient.get_document_folder() 2112 docs = doc_folder.get_documents ( 2113 episodes = [pk], 2114 encounter = self._payload[self._idx['pk_encounter']] 2115 ) 2116 if len(docs) > 0: 2117 lines.append(u'') 2118 lines.append(_('Documents:')) 2119 for d in docs: 2120 lines.append(u' %s %s:%s%s' % ( 2121 d['clin_when'].strftime('%Y-%m-%d'), 2122 d['l10n_type'], 2123 gmTools.coalesce(d['comment'], u'', u' "%s"'), 2124 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 2125 )) 2126 2127 del docs 2128 2129 return lines2130 #--------------------------------------------------------2131 - 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):2132 """Format an encounter. 2133 2134 with_co_encountlet_hints: 2135 - whether to include which *other* episodes were discussed during this encounter 2136 - (only makes sense if episodes != None) 2137 """ 2138 lines = self.format_header ( 2139 fancy_header = fancy_header, 2140 left_margin = left_margin, 2141 with_rfe_aoe = with_rfe_aoe 2142 ) 2143 2144 if by_episode: 2145 lines.extend(self.format_by_episode ( 2146 episodes = episodes, 2147 issues = issues, 2148 left_margin = left_margin, 2149 patient = patient, 2150 with_soap = with_soap, 2151 with_tests = with_tests, 2152 with_docs = with_docs, 2153 with_vaccinations = with_vaccinations, 2154 with_family_history = with_family_history 2155 )) 2156 2157 else: 2158 if with_soap: 2159 lines.append(u'') 2160 2161 if patient.ID != self._payload[self._idx['pk_patient']]: 2162 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2163 patient.ID, 2164 self._payload[self._idx['pk_encounter']], 2165 self._payload[self._idx['pk_patient']] 2166 ) 2167 raise ValueError(msg) 2168 2169 emr = patient.get_emr() 2170 2171 lines.extend(self.format_soap ( 2172 episodes = episodes, 2173 left_margin = left_margin, 2174 soap_cats = 'soapu', 2175 emr = emr, 2176 issues = issues 2177 )) 2178 2179 # # family history 2180 # if with_family_history: 2181 # if episodes is not None: 2182 # fhx = emr.get_family_history(episodes = episodes) 2183 # if len(fhx) > 0: 2184 # lines.append(u'') 2185 # lines.append(_('Family History: %s') % len(fhx)) 2186 # for f in fhx: 2187 # lines.append(f.format ( 2188 # left_margin = (left_margin + 1), 2189 # include_episode = False, 2190 # include_comment = True 2191 # )) 2192 # del fhx 2193 2194 # test results 2195 if with_tests: 2196 emr = patient.get_emr() 2197 tests = emr.get_test_results_by_date ( 2198 episodes = episodes, 2199 encounter = self._payload[self._idx['pk_encounter']] 2200 ) 2201 if len(tests) > 0: 2202 lines.append('') 2203 lines.append(_('Measurements and Results:')) 2204 2205 for t in tests: 2206 lines.append(t.format()) 2207 2208 del tests 2209 2210 # vaccinations 2211 if with_vaccinations: 2212 emr = patient.get_emr() 2213 vaccs = emr.get_vaccinations ( 2214 episodes = episodes, 2215 encounters = [ self._payload[self._idx['pk_encounter']] ], 2216 order_by = u'date_given DESC, vaccine' 2217 ) 2218 2219 if len(vaccs) > 0: 2220 lines.append(u'') 2221 lines.append(_('Vaccinations:')) 2222 2223 for vacc in vaccs: 2224 lines.extend(vacc.format ( 2225 with_indications = True, 2226 with_comment = True, 2227 with_reaction = True, 2228 date_format = '%Y-%m-%d' 2229 )) 2230 del vaccs 2231 2232 # documents 2233 if with_docs: 2234 doc_folder = patient.get_document_folder() 2235 docs = doc_folder.get_documents ( 2236 episodes = episodes, 2237 encounter = self._payload[self._idx['pk_encounter']] 2238 ) 2239 2240 if len(docs) > 0: 2241 lines.append(u'') 2242 lines.append(_('Documents:')) 2243 2244 for d in docs: 2245 lines.append(u' %s %s:%s%s' % ( 2246 d['clin_when'].strftime('%Y-%m-%d'), 2247 d['l10n_type'], 2248 gmTools.coalesce(d['comment'], u'', u' "%s"'), 2249 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 2250 )) 2251 2252 del docs 2253 2254 # co-encountlets 2255 if with_co_encountlet_hints: 2256 if episodes is not None: 2257 other_epis = self.get_episodes(exclude = episodes) 2258 if len(other_epis) > 0: 2259 lines.append(u'') 2260 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 2261 for epi in other_epis: 2262 lines.append(u' %s%s%s%s' % ( 2263 gmTools.u_left_double_angle_quote, 2264 epi['description'], 2265 gmTools.u_right_double_angle_quote, 2266 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2267 )) 2268 2269 eol_w_margin = u'\n%s' % (u' ' * left_margin) 2270 return u'%s\n' % eol_w_margin.join(lines)2271 #-------------------------------------------------------- 2272 # properties 2273 #--------------------------------------------------------2275 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 2276 return [] 2277 2278 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2279 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 2280 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2281 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]22822284 queries = [] 2285 # remove all codes 2286 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 2287 queries.append ({ 2288 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2289 'args': { 2290 'enc': self._payload[self._idx['pk_encounter']], 2291 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 2292 } 2293 }) 2294 # add new codes 2295 for pk_code in pk_codes: 2296 queries.append ({ 2297 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2298 'args': { 2299 'enc': self._payload[self._idx['pk_encounter']], 2300 'pk_code': pk_code 2301 } 2302 }) 2303 if len(queries) == 0: 2304 return 2305 # run it all in one transaction 2306 rows, idx = gmPG2.run_rw_queries(queries = queries) 2307 self.refetch_payload() 2308 return2309 2310 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 2311 #--------------------------------------------------------2313 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 2314 return [] 2315 2316 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2317 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 2318 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2319 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]23202322 queries = [] 2323 # remove all codes 2324 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 2325 queries.append ({ 2326 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2327 'args': { 2328 'enc': self._payload[self._idx['pk_encounter']], 2329 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 2330 } 2331 }) 2332 # add new codes 2333 for pk_code in pk_codes: 2334 queries.append ({ 2335 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2336 'args': { 2337 'enc': self._payload[self._idx['pk_encounter']], 2338 'pk_code': pk_code 2339 } 2340 }) 2341 if len(queries) == 0: 2342 return 2343 # run it all in one transaction 2344 rows, idx = gmPG2.run_rw_queries(queries = queries) 2345 self.refetch_payload() 2346 return2347 2348 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)2351 """Creates a new encounter for a patient. 2352 2353 fk_patient - patient PK 2354 fk_location - encounter location 2355 enc_type - type of encounter 2356 2357 FIXME: we don't deal with location yet 2358 """ 2359 if enc_type is None: 2360 enc_type = u'in surgery' 2361 # insert new encounter 2362 queries = [] 2363 try: 2364 enc_type = int(enc_type) 2365 cmd = u""" 2366 INSERT INTO clin.encounter ( 2367 fk_patient, fk_location, fk_type 2368 ) VALUES ( 2369 %(pat)s, 2370 -1, 2371 %(typ)s 2372 ) RETURNING pk""" 2373 except ValueError: 2374 enc_type = enc_type 2375 cmd = u""" 2376 insert into clin.encounter ( 2377 fk_patient, fk_location, fk_type 2378 ) values ( 2379 %(pat)s, 2380 -1, 2381 coalesce ( 2382 (select pk from clin.encounter_type where description = %(typ)s), 2383 -- pick the first available 2384 (select pk from clin.encounter_type limit 1) 2385 ) 2386 ) RETURNING pk""" 2387 args = {'pat': fk_patient, 'typ': enc_type} 2388 queries.append({'cmd': cmd, 'args': args}) 2389 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 2390 encounter = cEncounter(aPK_obj = rows[0]['pk']) 2391 2392 return encounter2393 #-----------------------------------------------------------2395 2396 rows, idx = gmPG2.run_rw_queries( 2397 queries = [{ 2398 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 2399 'args': {'desc': description, 'l10n_desc': l10n_description} 2400 }], 2401 return_data = True 2402 ) 2403 2404 success = rows[0][0] 2405 if not success: 2406 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 2407 2408 return {'description': description, 'l10n_description': l10n_description}2409 #-----------------------------------------------------------2411 """This will attempt to create a NEW encounter type.""" 2412 2413 # need a system name, so derive one if necessary 2414 if description is None: 2415 description = l10n_description 2416 2417 args = { 2418 'desc': description, 2419 'l10n_desc': l10n_description 2420 } 2421 2422 _log.debug('creating encounter type: %s, %s', description, l10n_description) 2423 2424 # does it exist already ? 2425 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 2426 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2427 2428 # yes 2429 if len(rows) > 0: 2430 # both system and l10n name are the same so all is well 2431 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 2432 _log.info('encounter type [%s] already exists with the proper translation') 2433 return {'description': description, 'l10n_description': l10n_description} 2434 2435 # or maybe there just wasn't a translation to 2436 # the current language for this type yet ? 2437 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 2438 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2439 2440 # there was, so fail 2441 if rows[0][0]: 2442 _log.error('encounter type [%s] already exists but with another translation') 2443 return None 2444 2445 # else set it 2446 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 2447 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2448 return {'description': description, 'l10n_description': l10n_description} 2449 2450 # no 2451 queries = [ 2452 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 2453 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 2454 ] 2455 rows, idx = gmPG2.run_rw_queries(queries = queries) 2456 2457 return {'description': description, 'l10n_description': l10n_description}2458 #-----------------------------------------------------------2460 cmd = u""" 2461 SELECT 2462 _(description) AS l10n_description, 2463 description 2464 FROM 2465 clin.encounter_type 2466 ORDER BY 2467 l10n_description 2468 """ 2469 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 2470 return rows2471 #-----------------------------------------------------------2473 cmd = u"SELECT * from clin.encounter_type where description = %s" 2474 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 2475 return rows2476 #-----------------------------------------------------------2478 cmd = u"delete from clin.encounter_type where description = %(desc)s" 2479 args = {'desc': description} 2480 try: 2481 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2482 except gmPG2.dbapi.IntegrityError, e: 2483 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 2484 return False 2485 raise 2486 2487 return True2488 #============================================================2490 """Represents one problem. 2491 2492 problems are the aggregation of 2493 .clinically_relevant=True issues and 2494 .is_open=True episodes 2495 """ 2496 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 2497 _cmds_store_payload = [u"select 1"] 2498 _updatable_fields = [] 2499 2500 #--------------------------------------------------------2611 #-----------------------------------------------------------2502 """Initialize. 2503 2504 aPK_obj must contain the keys 2505 pk_patient 2506 pk_episode 2507 pk_health_issue 2508 """ 2509 if aPK_obj is None: 2510 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 2511 2512 # As problems are rows from a view of different emr struct items, 2513 # the PK can't be a single field and, as some of the values of the 2514 # composed PK may be None, they must be queried using 'is null', 2515 # so we must programmatically construct the SQL query 2516 where_parts = [] 2517 pk = {} 2518 for col_name in aPK_obj.keys(): 2519 val = aPK_obj[col_name] 2520 if val is None: 2521 where_parts.append('%s IS NULL' % col_name) 2522 else: 2523 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 2524 pk[col_name] = val 2525 2526 # try to instantiate from true problem view 2527 cProblem._cmd_fetch_payload = u""" 2528 SELECT *, False as is_potential_problem 2529 FROM clin.v_problem_list 2530 WHERE %s""" % u' AND '.join(where_parts) 2531 2532 try: 2533 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 2534 return 2535 except gmExceptions.ConstructorError: 2536 _log.exception('actual problem not found, trying "potential" problems') 2537 if try_potential_problems is False: 2538 raise 2539 2540 # try to instantiate from potential-problems view 2541 cProblem._cmd_fetch_payload = u""" 2542 SELECT *, True as is_potential_problem 2543 FROM clin.v_potential_problem_list 2544 WHERE %s""" % u' AND '.join(where_parts) 2545 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)2546 #--------------------------------------------------------2548 """ 2549 Retrieve the cEpisode instance equivalent to this problem. 2550 The problem's type attribute must be 'episode' 2551 """ 2552 if self._payload[self._idx['type']] != 'episode': 2553 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2554 return None 2555 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])2556 #--------------------------------------------------------2558 """ 2559 Retrieve the cHealthIssue instance equivalent to this problem. 2560 The problem's type attribute must be 'issue' 2561 """ 2562 if self._payload[self._idx['type']] != 'issue': 2563 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2564 return None 2565 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])2566 #--------------------------------------------------------2568 2569 if self._payload[self._idx['type']] == u'issue': 2570 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 2571 #xxxxxxxxxxxxx 2572 2573 emr = patient.get_emr() 2574 2575 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 2576 return doc_folder.get_visual_progress_notes ( 2577 health_issue = self._payload[self._idx['pk_health_issue']], 2578 episode = self._payload[self._idx['pk_episode']] 2579 )2580 #-------------------------------------------------------- 2581 # properties 2582 #-------------------------------------------------------- 2583 # doubles as 'diagnostic_certainty_description' getter:2585 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])2586 2587 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 2588 #--------------------------------------------------------2590 if self._payload[self._idx['type']] == u'issue': 2591 cmd = u""" 2592 SELECT * FROM clin.v_linked_codes WHERE 2593 item_table = 'clin.lnk_code2h_issue'::regclass 2594 AND 2595 pk_item = %(item)s 2596 """ 2597 args = {'item': self._payload[self._idx['pk_health_issue']]} 2598 else: 2599 cmd = u""" 2600 SELECT * FROM clin.v_linked_codes WHERE 2601 item_table = 'clin.lnk_code2episode'::regclass 2602 AND 2603 pk_item = %(item)s 2604 """ 2605 args = {'item': self._payload[self._idx['pk_episode']]} 2606 2607 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2608 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]2609 2610 generic_codes = property(_get_generic_codes, lambda x:x)2613 """Retrieve the cEpisode instance equivalent to the given problem. 2614 2615 The problem's type attribute must be 'episode' 2616 2617 @param problem: The problem to retrieve its related episode for 2618 @type problem: A gmEMRStructItems.cProblem instance 2619 """ 2620 if isinstance(problem, cEpisode): 2621 return problem 2622 2623 exc = TypeError('cannot convert [%s] to episode' % problem) 2624 2625 if not isinstance(problem, cProblem): 2626 raise exc 2627 2628 if problem['type'] != 'episode': 2629 raise exc 2630 2631 return cEpisode(aPK_obj = problem['pk_episode'])2632 #-----------------------------------------------------------2634 """Retrieve the cIssue instance equivalent to the given problem. 2635 2636 The problem's type attribute must be 'issue'. 2637 2638 @param problem: The problem to retrieve the corresponding issue for 2639 @type problem: A gmEMRStructItems.cProblem instance 2640 """ 2641 if isinstance(problem, cHealthIssue): 2642 return problem 2643 2644 exc = TypeError('cannot convert [%s] to health issue' % problem) 2645 2646 if not isinstance(problem, cProblem): 2647 raise exc 2648 2649 if problem['type'] != 'issue': 2650 raise exc 2651 2652 return cHealthIssue(aPK_obj = problem['pk_health_issue'])2653 #-----------------------------------------------------------2655 """Transform given problem into either episode or health issue instance. 2656 """ 2657 if isinstance(problem, (cEpisode, cHealthIssue)): 2658 return problem 2659 2660 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 2661 2662 if not isinstance(problem, cProblem): 2663 _log.debug(u'%s' % problem) 2664 raise exc 2665 2666 if problem['type'] == 'episode': 2667 return cEpisode(aPK_obj = problem['pk_episode']) 2668 2669 if problem['type'] == 'issue': 2670 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 2671 2672 raise exc2673 #============================================================2675 2676 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 2677 _cmds_store_payload = [ 2678 u"""update clin.hospital_stay set 2679 clin_when = %(admission)s, 2680 discharge = %(discharge)s, 2681 narrative = gm.nullify_empty_string(%(hospital)s), 2682 fk_episode = %(pk_episode)s, 2683 fk_encounter = %(pk_encounter)s 2684 where 2685 pk = %(pk_hospital_stay)s and 2686 xmin = %(xmin_hospital_stay)s""", 2687 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 2688 ] 2689 _updatable_fields = [ 2690 'admission', 2691 'discharge', 2692 'hospital', 2693 'pk_episode', 2694 'pk_encounter' 2695 ] 2696 #-------------------------------------------------------2715 #-----------------------------------------------------------2698 2699 if self._payload[self._idx['discharge']] is not None: 2700 discharge = u' - %s' % gmDateTime.pydt_strftime(self._payload[self._idx['discharge']], '%Y %b %d') 2701 else: 2702 discharge = u'' 2703 2704 line = u'%s%s%s%s: %s%s%s' % ( 2705 u' ' * left_margin, 2706 gmDateTime.pydt_strftime(self._payload[self._idx['admission']], '%Y %b %d'), 2707 discharge, 2708 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 2709 gmTools.u_left_double_angle_quote, 2710 self._payload[self._idx['episode']], 2711 gmTools.u_right_double_angle_quote 2712 ) 2713 2714 return line2717 queries = [{ 2718 # this assumes non-overarching stays 2719 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1', 2720 'args': {'pat': patient} 2721 }] 2722 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2723 if len(rows) == 0: 2724 return None 2725 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})2726 #-----------------------------------------------------------2728 args = {'pat': patient} 2729 if ongoing_only: 2730 cmd = u""" 2731 SELECT * 2732 FROM clin.v_pat_hospital_stays 2733 WHERE 2734 pk_patient = %(pat)s 2735 AND 2736 discharge is NULL 2737 ORDER BY admission""" 2738 else: 2739 cmd = u""" 2740 SELECT * 2741 FROM clin.v_pat_hospital_stays 2742 WHERE pk_patient = %(pat)s 2743 ORDER BY admission""" 2744 2745 queries = [{'cmd': cmd, 'args': args}] 2746 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2747 2748 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]2749 #-----------------------------------------------------------2751 2752 queries = [{ 2753 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk', 2754 'args': {'enc': encounter, 'epi': episode} 2755 }] 2756 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2757 2758 return cHospitalStay(aPK_obj = rows[0][0])2759 #-----------------------------------------------------------2761 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 2762 args = {'pk': stay} 2763 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2764 return True2765 #============================================================2767 2768 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 2769 _cmds_store_payload = [ 2770 u"""UPDATE clin.procedure SET 2771 soap_cat = 'p', 2772 clin_when = %(clin_when)s, 2773 clin_end = %(clin_end)s, 2774 is_ongoing = %(is_ongoing)s, 2775 clin_where = NULLIF ( 2776 COALESCE ( 2777 %(pk_hospital_stay)s::TEXT, 2778 gm.nullify_empty_string(%(clin_where)s) 2779 ), 2780 %(pk_hospital_stay)s::TEXT 2781 ), 2782 narrative = gm.nullify_empty_string(%(performed_procedure)s), 2783 fk_hospital_stay = %(pk_hospital_stay)s, 2784 fk_episode = %(pk_episode)s, 2785 fk_encounter = %(pk_encounter)s 2786 WHERE 2787 pk = %(pk_procedure)s AND 2788 xmin = %(xmin_procedure)s 2789 RETURNING xmin as xmin_procedure""" 2790 ] 2791 _updatable_fields = [ 2792 'clin_when', 2793 'clin_end', 2794 'is_ongoing', 2795 'clin_where', 2796 'performed_procedure', 2797 'pk_hospital_stay', 2798 'pk_episode', 2799 'pk_encounter' 2800 ] 2801 #-------------------------------------------------------2907 #-----------------------------------------------------------2803 2804 if (attribute == 'pk_hospital_stay') and (value is not None): 2805 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 2806 2807 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 2808 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 2809 2810 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)2811 #-------------------------------------------------------2813 2814 if self._payload[self._idx['is_ongoing']]: 2815 end = _(' (ongoing)') 2816 else: 2817 end = self._payload[self._idx['clin_end']] 2818 if end is None: 2819 end = u'' 2820 else: 2821 end = u' - %s' % gmDateTime.pydt_strftime(end, '%Y %b %d') 2822 2823 line = u'%s%s%s (%s): %s' % ( 2824 (u' ' * left_margin), 2825 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d'), 2826 end, 2827 self._payload[self._idx['clin_where']], 2828 self._payload[self._idx['performed_procedure']] 2829 ) 2830 if include_episode: 2831 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 2832 2833 if include_codes: 2834 codes = self.generic_codes 2835 if len(codes) > 0: 2836 line += u'\n' 2837 for c in codes: 2838 line += u'%s %s: %s (%s - %s)\n' % ( 2839 (u' ' * left_margin), 2840 c['code'], 2841 c['term'], 2842 c['name_short'], 2843 c['version'] 2844 ) 2845 del codes 2846 2847 return line2848 #--------------------------------------------------------2850 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2851 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 2852 args = { 2853 'issue': self._payload[self._idx['pk_procedure']], 2854 'code': pk_code 2855 } 2856 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2857 return True2858 #--------------------------------------------------------2860 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2861 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 2862 args = { 2863 'issue': self._payload[self._idx['pk_procedure']], 2864 'code': pk_code 2865 } 2866 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2867 return True2868 #-------------------------------------------------------- 2869 # properties 2870 #--------------------------------------------------------2872 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 2873 return [] 2874 2875 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2876 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 2877 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2878 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]28792881 queries = [] 2882 # remove all codes 2883 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 2884 queries.append ({ 2885 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 2886 'args': { 2887 'proc': self._payload[self._idx['pk_procedure']], 2888 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 2889 } 2890 }) 2891 # add new codes 2892 for pk_code in pk_codes: 2893 queries.append ({ 2894 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 2895 'args': { 2896 'proc': self._payload[self._idx['pk_procedure']], 2897 'pk_code': pk_code 2898 } 2899 }) 2900 if len(queries) == 0: 2901 return 2902 # run it all in one transaction 2903 rows, idx = gmPG2.run_rw_queries(queries = queries) 2904 return2905 2906 generic_codes = property(_get_generic_codes, _set_generic_codes)2909 2910 queries = [ 2911 { 2912 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 2913 'args': {'pat': patient} 2914 } 2915 ] 2916 2917 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2918 2919 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]2920 #-----------------------------------------------------------2922 queries = [ 2923 { 2924 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when DESC LIMIT 1', 2925 'args': {'pat': patient} 2926 } 2927 ] 2928 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2929 if len(rows) == 0: 2930 return None 2931 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})2932 #-----------------------------------------------------------2933 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):2934 2935 queries = [{ 2936 'cmd': u""" 2937 INSERT INTO clin.procedure ( 2938 fk_encounter, 2939 fk_episode, 2940 soap_cat, 2941 clin_where, 2942 fk_hospital_stay, 2943 narrative 2944 ) VALUES ( 2945 %(enc)s, 2946 %(epi)s, 2947 'p', 2948 gm.nullify_empty_string(%(loc)s), 2949 %(stay)s, 2950 gm.nullify_empty_string(%(proc)s) 2951 ) 2952 RETURNING pk""", 2953 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 2954 }] 2955 2956 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2957 2958 return cPerformedProcedure(aPK_obj = rows[0][0])2959 #-----------------------------------------------------------2961 cmd = u'delete from clin.procedure where pk = %(pk)s' 2962 args = {'pk': procedure} 2963 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2964 return True2965 #============================================================ 2966 # main - unit testing 2967 #------------------------------------------------------------ 2968 if __name__ == '__main__': 2969 2970 if len(sys.argv) < 2: 2971 sys.exit() 2972 2973 if sys.argv[1] != 'test': 2974 sys.exit() 2975 2976 #-------------------------------------------------------- 2977 # define tests 2978 #--------------------------------------------------------2980 print "\nProblem test" 2981 print "------------" 2982 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 2983 print prob 2984 fields = prob.get_fields() 2985 for field in fields: 2986 print field, ':', prob[field] 2987 print '\nupdatable:', prob.get_updatable_fields() 2988 epi = prob.get_as_episode() 2989 print '\nas episode:' 2990 if epi is not None: 2991 for field in epi.get_fields(): 2992 print ' .%s : %s' % (field, epi[field])2993 #--------------------------------------------------------2995 print "\nhealth issue test" 2996 print "-----------------" 2997 h_issue = cHealthIssue(aPK_obj=2) 2998 print h_issue 2999 print h_issue.latest_access_date 3000 print h_issue.end_date3001 # fields = h_issue.get_fields() 3002 # for field in fields: 3003 # print field, ':', h_issue[field] 3004 # print "has open episode:", h_issue.has_open_episode() 3005 # print "open episode:", h_issue.get_open_episode() 3006 # print "updateable:", h_issue.get_updatable_fields() 3007 # h_issue.close_expired_episode(ttl=7300) 3008 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 3009 # print h_issue 3010 # print h_issue.format_as_journal() 3011 #--------------------------------------------------------3013 print "\nepisode test" 3014 print "------------" 3015 episode = cEpisode(aPK_obj=1) 3016 print episode 3017 fields = episode.get_fields() 3018 for field in fields: 3019 print field, ':', episode[field] 3020 print "updatable:", episode.get_updatable_fields() 3021 raw_input('ENTER to continue') 3022 3023 old_description = episode['description'] 3024 old_enc = cEncounter(aPK_obj = 1) 3025 3026 desc = '1-%s' % episode['description'] 3027 print "==> renaming to", desc 3028 successful = episode.rename ( 3029 description = desc 3030 ) 3031 if not successful: 3032 print "error" 3033 else: 3034 print "success" 3035 for field in fields: 3036 print field, ':', episode[field] 3037 3038 print "episode range:", episode.get_access_range() 3039 3040 raw_input('ENTER to continue')3041 3042 #--------------------------------------------------------3044 print "\nencounter test" 3045 print "--------------" 3046 encounter = cEncounter(aPK_obj=1) 3047 print encounter 3048 fields = encounter.get_fields() 3049 for field in fields: 3050 print field, ':', encounter[field] 3051 print "updatable:", encounter.get_updatable_fields()3052 #--------------------------------------------------------3054 encounter = cEncounter(aPK_obj=1) 3055 print encounter 3056 print "" 3057 print encounter.format_latex()3058 #--------------------------------------------------------3060 procs = get_performed_procedures(patient = 12) 3061 for proc in procs: 3062 print proc.format(left_margin=2)3063 #--------------------------------------------------------3065 stay = create_hospital_stay(encounter = 1, episode = 2) 3066 stay['hospital'] = u'Starfleet Galaxy General Hospital' 3067 stay.save_payload() 3068 print stay 3069 for s in get_patient_hospital_stays(12): 3070 print s 3071 delete_hospital_stay(stay['pk_hospital_stay']) 3072 stay = create_hospital_stay(encounter = 1, episode = 4)3073 #--------------------------------------------------------3075 tests = [None, 'A', 'B', 'C', 'D', 'E'] 3076 3077 for t in tests: 3078 print type(t), t 3079 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)3080 #-------------------------------------------------------- 3085 #-------------------------------------------------------- 3086 # run them 3087 #test_episode() 3088 #test_problem() 3089 #test_encounter() 3090 test_health_issue() 3091 #test_hospital_stay() 3092 #test_performed_procedure() 3093 #test_diagnostic_certainty_classification_map() 3094 #test_encounter2latex() 3095 #test_episode_codes() 3096 #============================================================ 3097
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Mon Jun 10 03:57:00 2013 | http://epydoc.sourceforge.net |