Home | Trees | Indices | Help |
|
---|
|
1 # -*- coding: utf8 -*- 2 """GNUmed health related business object. 3 4 license: GPL v2 or later 5 """ 6 #============================================================ 7 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>" 8 9 import types, sys, string, datetime, logging, time 10 11 12 if __name__ == '__main__': 13 sys.path.insert(0, '../../') 14 from Gnumed.pycommon import gmPG2 15 from Gnumed.pycommon import gmI18N 16 from Gnumed.pycommon import gmTools 17 from Gnumed.pycommon import gmDateTime 18 from Gnumed.pycommon import gmBusinessDBObject 19 from Gnumed.pycommon import gmNull 20 from Gnumed.pycommon import gmExceptions 21 22 from Gnumed.business import gmClinNarrative 23 from Gnumed.business import gmCoding 24 from Gnumed.business import gmPraxis 25 from Gnumed.business import gmOrganization 26 27 28 _log = logging.getLogger('gm.emr') 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_intakes ( 422 issues = [ self._payload[self._idx['pk_health_issue']] ], 423 order_by = u'is_currently_active DESC, started, substance' 424 ) 425 if len(meds) > 0: 426 lines.append(u'') 427 lines.append(_('Medications and Substances')) 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_org_unit)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_org_unit', 1518 'pk_type', 1519 'reason_for_encounter', 1520 'assessment_of_encounter' 1521 ] 1522 #--------------------------------------------------------2373 2374 #-----------------------------------------------------------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_org_unit', 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]: here="%s", other="%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 seconds granularity 1594 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'): 1595 _log.debug('mismatch on [%s]: here="%s", other="%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 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1927 u' ' * left_margin, 1928 self._payload[self._idx['l10n_type']], 1929 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1930 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1931 self._payload[self._idx['source_time_zone']], 1932 gmTools.coalesce ( 1933 self._payload[self._idx['assessment_of_encounter']], 1934 u'', 1935 u' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 1936 ), 1937 self._payload[self._idx['pk_encounter']] 1938 )) 1939 1940 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1941 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1942 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1943 gmDateTime.current_local_iso_numeric_timezone_string, 1944 gmTools.bool2subst ( 1945 gmDateTime.dst_currently_in_effect, 1946 gmDateTime.py_dst_timezone_name, 1947 gmDateTime.py_timezone_name 1948 ), 1949 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1950 )) 1951 1952 if self._payload[self._idx['praxis_branch']] is not None: 1953 lines.append(_('Location: %s (%s)') % (self._payload[self._idx['praxis_branch']], self._payload[self._idx['praxis']])) 1954 1955 if self._payload[self._idx['reason_for_encounter']] is not None: 1956 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1957 codes = self.generic_codes_rfe 1958 for c in codes: 1959 lines.append(u' %s: %s (%s - %s)' % ( 1960 c['code'], 1961 c['term'], 1962 c['name_short'], 1963 c['version'] 1964 )) 1965 if len(codes) > 0: 1966 lines.append(u'') 1967 1968 if self._payload[self._idx['assessment_of_encounter']] is not None: 1969 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1970 codes = self.generic_codes_aoe 1971 for c in codes: 1972 lines.append(u' %s: %s (%s - %s)' % ( 1973 c['code'], 1974 c['term'], 1975 c['name_short'], 1976 c['version'] 1977 )) 1978 if len(codes) > 0: 1979 lines.append(u'') 1980 del codes 1981 return lines1982 1983 #--------------------------------------------------------1985 lines = [] 1986 1987 if fancy_header: 1988 return self.__format_header_fancy(left_margin = left_margin) 1989 1990 now = gmDateTime.pydt_now_here() 1991 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'): 1992 start = u'%s %s' % ( 1993 _('today'), 1994 self._payload[self._idx['started_original_tz']].strftime('%H:%M') 1995 ) 1996 else: 1997 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M') 1998 lines.append(u'%s%s: %s - %s%s%s' % ( 1999 u' ' * left_margin, 2000 self._payload[self._idx['l10n_type']], 2001 start, 2002 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 2003 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 2004 gmTools.coalesce(self._payload[self._idx['praxis_branch']], u'', u' @%s') 2005 )) 2006 if with_rfe_aoe: 2007 if self._payload[self._idx['reason_for_encounter']] is not None: 2008 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 2009 codes = self.generic_codes_rfe 2010 for c in codes: 2011 lines.append(u' %s: %s (%s - %s)' % ( 2012 c['code'], 2013 c['term'], 2014 c['name_short'], 2015 c['version'] 2016 )) 2017 if len(codes) > 0: 2018 lines.append(u'') 2019 if self._payload[self._idx['assessment_of_encounter']] is not None: 2020 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2021 codes = self.generic_codes_aoe 2022 if len(codes) > 0: 2023 lines.append(u'') 2024 for c in codes: 2025 lines.append(u' %s: %s (%s - %s)' % ( 2026 c['code'], 2027 c['term'], 2028 c['name_short'], 2029 c['version'] 2030 )) 2031 if len(codes) > 0: 2032 lines.append(u'') 2033 del codes 2034 2035 return lines2036 #--------------------------------------------------------2037 - 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):2038 2039 lines = [] 2040 emr = patient.emr 2041 if episodes is None: 2042 episodes = [ e['pk_episode'] for e in self.episodes ] 2043 2044 for pk in episodes: 2045 epi = cEpisode(aPK_obj = pk) 2046 lines.append(_('\nEpisode %s%s%s%s:') % ( 2047 gmTools.u_left_double_angle_quote, 2048 epi['description'], 2049 gmTools.u_right_double_angle_quote, 2050 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2051 )) 2052 2053 # soap 2054 if with_soap: 2055 if patient.ID != self._payload[self._idx['pk_patient']]: 2056 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2057 patient.ID, 2058 self._payload[self._idx['pk_encounter']], 2059 self._payload[self._idx['pk_patient']] 2060 ) 2061 raise ValueError(msg) 2062 2063 lines.extend(self.format_soap ( 2064 episodes = [pk], 2065 left_margin = left_margin, 2066 soap_cats = 'soapu', 2067 emr = emr, 2068 issues = issues 2069 )) 2070 2071 # test results 2072 if with_tests: 2073 tests = emr.get_test_results_by_date ( 2074 episodes = [pk], 2075 encounter = self._payload[self._idx['pk_encounter']] 2076 ) 2077 if len(tests) > 0: 2078 lines.append('') 2079 lines.append(_('Measurements and Results:')) 2080 2081 for t in tests: 2082 lines.append(t.format()) 2083 2084 del tests 2085 2086 # vaccinations 2087 if with_vaccinations: 2088 vaccs = emr.get_vaccinations ( 2089 episodes = [pk], 2090 encounters = [ self._payload[self._idx['pk_encounter']] ], 2091 order_by = u'date_given DESC, vaccine' 2092 ) 2093 if len(vaccs) > 0: 2094 lines.append(u'') 2095 lines.append(_('Vaccinations:')) 2096 for vacc in vaccs: 2097 lines.extend(vacc.format ( 2098 with_indications = True, 2099 with_comment = True, 2100 with_reaction = True, 2101 date_format = '%Y-%m-%d' 2102 )) 2103 del vaccs 2104 2105 # family history 2106 if with_family_history: 2107 fhx = emr.get_family_history(episodes = [pk]) 2108 if len(fhx) > 0: 2109 lines.append(u'') 2110 lines.append(_('Family History: %s') % len(fhx)) 2111 for f in fhx: 2112 lines.append(f.format ( 2113 left_margin = (left_margin + 1), 2114 include_episode = False, 2115 include_comment = True 2116 )) 2117 del fhx 2118 2119 # documents 2120 if with_docs: 2121 doc_folder = patient.get_document_folder() 2122 docs = doc_folder.get_documents ( 2123 episodes = [pk], 2124 encounter = self._payload[self._idx['pk_encounter']] 2125 ) 2126 if len(docs) > 0: 2127 lines.append(u'') 2128 lines.append(_('Documents:')) 2129 for d in docs: 2130 lines.append(u' %s %s:%s%s' % ( 2131 d['clin_when'].strftime('%Y-%m-%d'), 2132 d['l10n_type'], 2133 gmTools.coalesce(d['comment'], u'', u' "%s"'), 2134 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 2135 )) 2136 2137 del docs 2138 2139 return lines2140 #--------------------------------------------------------2141 - 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):2142 """Format an encounter. 2143 2144 with_co_encountlet_hints: 2145 - whether to include which *other* episodes were discussed during this encounter 2146 - (only makes sense if episodes != None) 2147 """ 2148 lines = self.format_header ( 2149 fancy_header = fancy_header, 2150 left_margin = left_margin, 2151 with_rfe_aoe = with_rfe_aoe 2152 ) 2153 2154 if by_episode: 2155 lines.extend(self.format_by_episode ( 2156 episodes = episodes, 2157 issues = issues, 2158 left_margin = left_margin, 2159 patient = patient, 2160 with_soap = with_soap, 2161 with_tests = with_tests, 2162 with_docs = with_docs, 2163 with_vaccinations = with_vaccinations, 2164 with_family_history = with_family_history 2165 )) 2166 2167 else: 2168 if with_soap: 2169 lines.append(u'') 2170 2171 if patient.ID != self._payload[self._idx['pk_patient']]: 2172 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2173 patient.ID, 2174 self._payload[self._idx['pk_encounter']], 2175 self._payload[self._idx['pk_patient']] 2176 ) 2177 raise ValueError(msg) 2178 2179 emr = patient.get_emr() 2180 2181 lines.extend(self.format_soap ( 2182 episodes = episodes, 2183 left_margin = left_margin, 2184 soap_cats = 'soapu', 2185 emr = emr, 2186 issues = issues 2187 )) 2188 2189 # # family history 2190 # if with_family_history: 2191 # if episodes is not None: 2192 # fhx = emr.get_family_history(episodes = episodes) 2193 # if len(fhx) > 0: 2194 # lines.append(u'') 2195 # lines.append(_('Family History: %s') % len(fhx)) 2196 # for f in fhx: 2197 # lines.append(f.format ( 2198 # left_margin = (left_margin + 1), 2199 # include_episode = False, 2200 # include_comment = True 2201 # )) 2202 # del fhx 2203 2204 # test results 2205 if with_tests: 2206 emr = patient.get_emr() 2207 tests = emr.get_test_results_by_date ( 2208 episodes = episodes, 2209 encounter = self._payload[self._idx['pk_encounter']] 2210 ) 2211 if len(tests) > 0: 2212 lines.append('') 2213 lines.append(_('Measurements and Results:')) 2214 2215 for t in tests: 2216 lines.append(t.format()) 2217 2218 del tests 2219 2220 # vaccinations 2221 if with_vaccinations: 2222 emr = patient.get_emr() 2223 vaccs = emr.get_vaccinations ( 2224 episodes = episodes, 2225 encounters = [ self._payload[self._idx['pk_encounter']] ], 2226 order_by = u'date_given DESC, vaccine' 2227 ) 2228 2229 if len(vaccs) > 0: 2230 lines.append(u'') 2231 lines.append(_('Vaccinations:')) 2232 2233 for vacc in vaccs: 2234 lines.extend(vacc.format ( 2235 with_indications = True, 2236 with_comment = True, 2237 with_reaction = True, 2238 date_format = '%Y-%m-%d' 2239 )) 2240 del vaccs 2241 2242 # documents 2243 if with_docs: 2244 doc_folder = patient.get_document_folder() 2245 docs = doc_folder.get_documents ( 2246 episodes = episodes, 2247 encounter = self._payload[self._idx['pk_encounter']] 2248 ) 2249 2250 if len(docs) > 0: 2251 lines.append(u'') 2252 lines.append(_('Documents:')) 2253 2254 for d in docs: 2255 lines.append(u' %s %s:%s%s' % ( 2256 d['clin_when'].strftime('%Y-%m-%d'), 2257 d['l10n_type'], 2258 gmTools.coalesce(d['comment'], u'', u' "%s"'), 2259 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 2260 )) 2261 2262 del docs 2263 2264 # co-encountlets 2265 if with_co_encountlet_hints: 2266 if episodes is not None: 2267 other_epis = self.get_episodes(exclude = episodes) 2268 if len(other_epis) > 0: 2269 lines.append(u'') 2270 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 2271 for epi in other_epis: 2272 lines.append(u' %s%s%s%s' % ( 2273 gmTools.u_left_double_angle_quote, 2274 epi['description'], 2275 gmTools.u_right_double_angle_quote, 2276 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 2277 )) 2278 2279 eol_w_margin = u'\n%s' % (u' ' * left_margin) 2280 return u'%s\n' % eol_w_margin.join(lines)2281 #-------------------------------------------------------- 2282 # properties 2283 #--------------------------------------------------------2285 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 2286 return [] 2287 2288 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2289 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 2290 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2291 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]22922294 queries = [] 2295 # remove all codes 2296 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 2297 queries.append ({ 2298 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2299 'args': { 2300 'enc': self._payload[self._idx['pk_encounter']], 2301 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 2302 } 2303 }) 2304 # add new codes 2305 for pk_code in pk_codes: 2306 queries.append ({ 2307 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2308 'args': { 2309 'enc': self._payload[self._idx['pk_encounter']], 2310 'pk_code': pk_code 2311 } 2312 }) 2313 if len(queries) == 0: 2314 return 2315 # run it all in one transaction 2316 rows, idx = gmPG2.run_rw_queries(queries = queries) 2317 self.refetch_payload() 2318 return2319 2320 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 2321 #--------------------------------------------------------2323 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 2324 return [] 2325 2326 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2327 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 2328 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2329 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]23302332 queries = [] 2333 # remove all codes 2334 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 2335 queries.append ({ 2336 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2337 'args': { 2338 'enc': self._payload[self._idx['pk_encounter']], 2339 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 2340 } 2341 }) 2342 # add new codes 2343 for pk_code in pk_codes: 2344 queries.append ({ 2345 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2346 'args': { 2347 'enc': self._payload[self._idx['pk_encounter']], 2348 'pk_code': pk_code 2349 } 2350 }) 2351 if len(queries) == 0: 2352 return 2353 # run it all in one transaction 2354 rows, idx = gmPG2.run_rw_queries(queries = queries) 2355 self.refetch_payload() 2356 return2357 2358 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe) 2359 #--------------------------------------------------------2361 if self._payload[self._idx['pk_org_unit']] is None: 2362 return None 2363 return gmPraxis.get_praxis_branch_by_org_unit(pk_org_unit = self._payload[self._idx['pk_org_unit']])2364 2365 praxis_branch = property(_get_praxis_branch, lambda x:x) 2366 #--------------------------------------------------------2368 if self._payload[self._idx['pk_org_unit']] is None: 2369 return None 2370 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])2371 2372 org_unit = property(_get_org_unit, lambda x:x)2376 """Creates a new encounter for a patient. 2377 2378 fk_patient - patient PK 2379 enc_type - type of encounter 2380 """ 2381 if enc_type is None: 2382 enc_type = u'in surgery' 2383 # insert new encounter 2384 queries = [] 2385 try: 2386 enc_type = int(enc_type) 2387 cmd = u""" 2388 INSERT INTO clin.encounter (fk_patient, fk_type) 2389 VALUES (%(pat)s, %(typ)s) RETURNING pk""" 2390 except ValueError: 2391 enc_type = enc_type 2392 cmd = u""" 2393 INSERT INTO clin.encounter (fk_patient, fk_type) 2394 VALUES ( 2395 %(pat)s, 2396 coalesce ( 2397 (select pk from clin.encounter_type where description = %(typ)s), 2398 -- pick the first available 2399 (select pk from clin.encounter_type limit 1) 2400 ) 2401 ) RETURNING pk""" 2402 args = {'pat': fk_patient, 'typ': enc_type} 2403 queries.append({'cmd': cmd, 'args': args}) 2404 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 2405 encounter = cEncounter(aPK_obj = rows[0]['pk']) 2406 2407 return encounter2408 2409 #----------------------------------------------------------- 2410 # encounter types handling 2411 #-----------------------------------------------------------2413 2414 rows, idx = gmPG2.run_rw_queries( 2415 queries = [{ 2416 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 2417 'args': {'desc': description, 'l10n_desc': l10n_description} 2418 }], 2419 return_data = True 2420 ) 2421 2422 success = rows[0][0] 2423 if not success: 2424 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 2425 2426 return {'description': description, 'l10n_description': l10n_description}2427 #-----------------------------------------------------------2429 """This will attempt to create a NEW encounter type.""" 2430 2431 # need a system name, so derive one if necessary 2432 if description is None: 2433 description = l10n_description 2434 2435 args = { 2436 'desc': description, 2437 'l10n_desc': l10n_description 2438 } 2439 2440 _log.debug('creating encounter type: %s, %s', description, l10n_description) 2441 2442 # does it exist already ? 2443 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 2444 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2445 2446 # yes 2447 if len(rows) > 0: 2448 # both system and l10n name are the same so all is well 2449 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 2450 _log.info('encounter type [%s] already exists with the proper translation') 2451 return {'description': description, 'l10n_description': l10n_description} 2452 2453 # or maybe there just wasn't a translation to 2454 # the current language for this type yet ? 2455 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 2456 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 2457 2458 # there was, so fail 2459 if rows[0][0]: 2460 _log.error('encounter type [%s] already exists but with another translation') 2461 return None 2462 2463 # else set it 2464 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 2465 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2466 return {'description': description, 'l10n_description': l10n_description} 2467 2468 # no 2469 queries = [ 2470 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 2471 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 2472 ] 2473 rows, idx = gmPG2.run_rw_queries(queries = queries) 2474 2475 return {'description': description, 'l10n_description': l10n_description}2476 2477 #-----------------------------------------------------------2479 cmd = u""" 2480 SELECT 2481 COUNT(1) AS type_count, 2482 fk_type 2483 FROM clin.encounter 2484 GROUP BY fk_type 2485 ORDER BY type_count DESC 2486 LIMIT 1 2487 """ 2488 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 2489 if len(rows) == 0: 2490 return None 2491 return rows[0]['fk_type']2492 2493 #-----------------------------------------------------------2495 cmd = u""" 2496 SELECT 2497 _(description) AS l10n_description, 2498 description 2499 FROM 2500 clin.encounter_type 2501 ORDER BY 2502 l10n_description 2503 """ 2504 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 2505 return rows2506 2507 #-----------------------------------------------------------2509 cmd = u"SELECT * from clin.encounter_type where description = %s" 2510 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 2511 return rows2512 2513 #-----------------------------------------------------------2515 cmd = u"delete from clin.encounter_type where description = %(desc)s" 2516 args = {'desc': description} 2517 try: 2518 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2519 except gmPG2.dbapi.IntegrityError, e: 2520 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 2521 return False 2522 raise 2523 2524 return True2525 #============================================================2527 """Represents one problem. 2528 2529 problems are the aggregation of 2530 .clinically_relevant=True issues and 2531 .is_open=True episodes 2532 """ 2533 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 2534 _cmds_store_payload = [u"select 1"] 2535 _updatable_fields = [] 2536 2537 #--------------------------------------------------------2648 #-----------------------------------------------------------2539 """Initialize. 2540 2541 aPK_obj must contain the keys 2542 pk_patient 2543 pk_episode 2544 pk_health_issue 2545 """ 2546 if aPK_obj is None: 2547 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 2548 2549 # As problems are rows from a view of different emr struct items, 2550 # the PK can't be a single field and, as some of the values of the 2551 # composed PK may be None, they must be queried using 'is null', 2552 # so we must programmatically construct the SQL query 2553 where_parts = [] 2554 pk = {} 2555 for col_name in aPK_obj.keys(): 2556 val = aPK_obj[col_name] 2557 if val is None: 2558 where_parts.append('%s IS NULL' % col_name) 2559 else: 2560 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 2561 pk[col_name] = val 2562 2563 # try to instantiate from true problem view 2564 cProblem._cmd_fetch_payload = u""" 2565 SELECT *, False as is_potential_problem 2566 FROM clin.v_problem_list 2567 WHERE %s""" % u' AND '.join(where_parts) 2568 2569 try: 2570 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 2571 return 2572 except gmExceptions.ConstructorError: 2573 _log.exception('actual problem not found, trying "potential" problems') 2574 if try_potential_problems is False: 2575 raise 2576 2577 # try to instantiate from potential-problems view 2578 cProblem._cmd_fetch_payload = u""" 2579 SELECT *, True as is_potential_problem 2580 FROM clin.v_potential_problem_list 2581 WHERE %s""" % u' AND '.join(where_parts) 2582 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)2583 #--------------------------------------------------------2585 """ 2586 Retrieve the cEpisode instance equivalent to this problem. 2587 The problem's type attribute must be 'episode' 2588 """ 2589 if self._payload[self._idx['type']] != 'episode': 2590 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2591 return None 2592 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])2593 #--------------------------------------------------------2595 """ 2596 Retrieve the cHealthIssue instance equivalent to this problem. 2597 The problem's type attribute must be 'issue' 2598 """ 2599 if self._payload[self._idx['type']] != 'issue': 2600 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2601 return None 2602 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])2603 #--------------------------------------------------------2605 2606 if self._payload[self._idx['type']] == u'issue': 2607 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 2608 #xxxxxxxxxxxxx 2609 2610 emr = patient.get_emr() 2611 2612 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 2613 return doc_folder.get_visual_progress_notes ( 2614 health_issue = self._payload[self._idx['pk_health_issue']], 2615 episode = self._payload[self._idx['pk_episode']] 2616 )2617 #-------------------------------------------------------- 2618 # properties 2619 #-------------------------------------------------------- 2620 # doubles as 'diagnostic_certainty_description' getter:2622 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])2623 2624 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 2625 #--------------------------------------------------------2627 if self._payload[self._idx['type']] == u'issue': 2628 cmd = u""" 2629 SELECT * FROM clin.v_linked_codes WHERE 2630 item_table = 'clin.lnk_code2h_issue'::regclass 2631 AND 2632 pk_item = %(item)s 2633 """ 2634 args = {'item': self._payload[self._idx['pk_health_issue']]} 2635 else: 2636 cmd = u""" 2637 SELECT * FROM clin.v_linked_codes WHERE 2638 item_table = 'clin.lnk_code2episode'::regclass 2639 AND 2640 pk_item = %(item)s 2641 """ 2642 args = {'item': self._payload[self._idx['pk_episode']]} 2643 2644 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2645 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]2646 2647 generic_codes = property(_get_generic_codes, lambda x:x)2650 """Retrieve the cEpisode instance equivalent to the given problem. 2651 2652 The problem's type attribute must be 'episode' 2653 2654 @param problem: The problem to retrieve its related episode for 2655 @type problem: A gmEMRStructItems.cProblem instance 2656 """ 2657 if isinstance(problem, cEpisode): 2658 return problem 2659 2660 exc = TypeError('cannot convert [%s] to episode' % problem) 2661 2662 if not isinstance(problem, cProblem): 2663 raise exc 2664 2665 if problem['type'] != 'episode': 2666 raise exc 2667 2668 return cEpisode(aPK_obj = problem['pk_episode'])2669 #-----------------------------------------------------------2671 """Retrieve the cIssue instance equivalent to the given problem. 2672 2673 The problem's type attribute must be 'issue'. 2674 2675 @param problem: The problem to retrieve the corresponding issue for 2676 @type problem: A gmEMRStructItems.cProblem instance 2677 """ 2678 if isinstance(problem, cHealthIssue): 2679 return problem 2680 2681 exc = TypeError('cannot convert [%s] to health issue' % problem) 2682 2683 if not isinstance(problem, cProblem): 2684 raise exc 2685 2686 if problem['type'] != 'issue': 2687 raise exc 2688 2689 return cHealthIssue(aPK_obj = problem['pk_health_issue'])2690 #-----------------------------------------------------------2692 """Transform given problem into either episode or health issue instance. 2693 """ 2694 if isinstance(problem, (cEpisode, cHealthIssue)): 2695 return problem 2696 2697 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 2698 2699 if not isinstance(problem, cProblem): 2700 _log.debug(u'%s' % problem) 2701 raise exc 2702 2703 if problem['type'] == 'episode': 2704 return cEpisode(aPK_obj = problem['pk_episode']) 2705 2706 if problem['type'] == 'issue': 2707 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 2708 2709 raise exc2710 2711 #============================================================ 2712 _SQL_get_hospital_stays = u"select * from clin.v_hospital_stays where %s" 27132715 2716 _cmd_fetch_payload = _SQL_get_hospital_stays % u"pk_hospital_stay = %s" 2717 _cmds_store_payload = [ 2718 u"""UPDATE clin.hospital_stay SET 2719 clin_when = %(admission)s, 2720 discharge = %(discharge)s, 2721 fk_org_unit = %(pk_org_unit)s, 2722 narrative = gm.nullify_empty_string(%(comment)s), 2723 fk_episode = %(pk_episode)s, 2724 fk_encounter = %(pk_encounter)s 2725 WHERE 2726 pk = %(pk_hospital_stay)s 2727 AND 2728 xmin = %(xmin_hospital_stay)s 2729 RETURNING 2730 xmin AS xmin_hospital_stay 2731 """ 2732 ] 2733 _updatable_fields = [ 2734 'admission', 2735 'discharge', 2736 'pk_org_unit', 2737 'pk_episode', 2738 'pk_encounter', 2739 'comment' 2740 ] 2741 #-------------------------------------------------------2760 2761 #-----------------------------------------------------------2743 2744 if self._payload[self._idx['discharge']] is not None: 2745 discharge = u' - %s' % gmDateTime.pydt_strftime(self._payload[self._idx['discharge']], '%Y %b %d') 2746 else: 2747 discharge = u'' 2748 2749 line = u'%s%s%s (%s@%s): %s%s%s' % ( 2750 u' ' * left_margin, 2751 gmDateTime.pydt_strftime(self._payload[self._idx['admission']], '%Y %b %d'), 2752 discharge, 2753 self._payload[self._idx['ward']], 2754 self._payload[self._idx['hospital']], 2755 gmTools.u_left_double_angle_quote, 2756 self._payload[self._idx['episode']], 2757 gmTools.u_right_double_angle_quote 2758 ) 2759 return line2763 cmd = _SQL_get_hospital_stays % u"pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1" 2764 queries = [{ 2765 # this assumes non-overarching stays 2766 #'cmd': u'SELECT * FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1', 2767 'cmd': cmd, 2768 'args': {'pat': patient} 2769 }] 2770 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2771 if len(rows) == 0: 2772 return None 2773 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})2774 2775 #-----------------------------------------------------------2777 args = {'pat': patient} 2778 if ongoing_only: 2779 cmd = _SQL_get_hospital_stays % u"pk_patient = %(pat)s AND discharge is NULL ORDER BY admission" 2780 else: 2781 cmd = _SQL_get_hospital_stays % u"pk_patient = %(pat)s ORDER BY admission" 2782 2783 queries = [{'cmd': cmd, 'args': args}] 2784 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2785 2786 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]2787 2788 #-----------------------------------------------------------2790 2791 queries = [{ 2792 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk', 2793 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit} 2794 }] 2795 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2796 2797 return cHospitalStay(aPK_obj = rows[0][0])2798 2799 #-----------------------------------------------------------2801 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 2802 args = {'pk': stay} 2803 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2804 return True2805 2806 #============================================================2808 2809 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 2810 _cmds_store_payload = [ 2811 u"""UPDATE clin.procedure SET 2812 soap_cat = 'p', 2813 clin_when = %(clin_when)s, 2814 clin_end = %(clin_end)s, 2815 is_ongoing = %(is_ongoing)s, 2816 clin_where = NULLIF ( 2817 COALESCE ( 2818 %(pk_hospital_stay)s::TEXT, 2819 gm.nullify_empty_string(%(clin_where)s) 2820 ), 2821 %(pk_hospital_stay)s::TEXT 2822 ), 2823 narrative = gm.nullify_empty_string(%(performed_procedure)s), 2824 fk_hospital_stay = %(pk_hospital_stay)s, 2825 fk_episode = %(pk_episode)s, 2826 fk_encounter = %(pk_encounter)s 2827 WHERE 2828 pk = %(pk_procedure)s AND 2829 xmin = %(xmin_procedure)s 2830 RETURNING xmin as xmin_procedure""" 2831 ] 2832 _updatable_fields = [ 2833 'clin_when', 2834 'clin_end', 2835 'is_ongoing', 2836 'clin_where', 2837 'performed_procedure', 2838 'pk_hospital_stay', 2839 'pk_episode', 2840 'pk_encounter' 2841 ] 2842 #-------------------------------------------------------2948 #-----------------------------------------------------------2844 2845 if (attribute == 'pk_hospital_stay') and (value is not None): 2846 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 2847 2848 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 2849 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 2850 2851 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)2852 #-------------------------------------------------------2854 2855 if self._payload[self._idx['is_ongoing']]: 2856 end = _(' (ongoing)') 2857 else: 2858 end = self._payload[self._idx['clin_end']] 2859 if end is None: 2860 end = u'' 2861 else: 2862 end = u' - %s' % gmDateTime.pydt_strftime(end, '%Y %b %d') 2863 2864 line = u'%s%s%s (%s): %s' % ( 2865 (u' ' * left_margin), 2866 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d'), 2867 end, 2868 self._payload[self._idx['clin_where']], 2869 self._payload[self._idx['performed_procedure']] 2870 ) 2871 if include_episode: 2872 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 2873 2874 if include_codes: 2875 codes = self.generic_codes 2876 if len(codes) > 0: 2877 line += u'\n' 2878 for c in codes: 2879 line += u'%s %s: %s (%s - %s)\n' % ( 2880 (u' ' * left_margin), 2881 c['code'], 2882 c['term'], 2883 c['name_short'], 2884 c['version'] 2885 ) 2886 del codes 2887 2888 return line2889 #--------------------------------------------------------2891 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2892 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 2893 args = { 2894 'issue': self._payload[self._idx['pk_procedure']], 2895 'code': pk_code 2896 } 2897 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2898 return True2899 #--------------------------------------------------------2901 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2902 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 2903 args = { 2904 'issue': self._payload[self._idx['pk_procedure']], 2905 'code': pk_code 2906 } 2907 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2908 return True2909 #-------------------------------------------------------- 2910 # properties 2911 #--------------------------------------------------------2913 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 2914 return [] 2915 2916 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2917 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 2918 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2919 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]29202922 queries = [] 2923 # remove all codes 2924 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 2925 queries.append ({ 2926 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 2927 'args': { 2928 'proc': self._payload[self._idx['pk_procedure']], 2929 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 2930 } 2931 }) 2932 # add new codes 2933 for pk_code in pk_codes: 2934 queries.append ({ 2935 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 2936 'args': { 2937 'proc': self._payload[self._idx['pk_procedure']], 2938 'pk_code': pk_code 2939 } 2940 }) 2941 if len(queries) == 0: 2942 return 2943 # run it all in one transaction 2944 rows, idx = gmPG2.run_rw_queries(queries = queries) 2945 return2946 2947 generic_codes = property(_get_generic_codes, _set_generic_codes)2950 2951 queries = [ 2952 { 2953 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 2954 'args': {'pat': patient} 2955 } 2956 ] 2957 2958 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2959 2960 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]2961 #-----------------------------------------------------------2963 queries = [ 2964 { 2965 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when DESC LIMIT 1', 2966 'args': {'pat': patient} 2967 } 2968 ] 2969 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2970 if len(rows) == 0: 2971 return None 2972 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})2973 #-----------------------------------------------------------2974 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):2975 2976 queries = [{ 2977 'cmd': u""" 2978 INSERT INTO clin.procedure ( 2979 fk_encounter, 2980 fk_episode, 2981 soap_cat, 2982 clin_where, 2983 fk_hospital_stay, 2984 narrative 2985 ) VALUES ( 2986 %(enc)s, 2987 %(epi)s, 2988 'p', 2989 gm.nullify_empty_string(%(loc)s), 2990 %(stay)s, 2991 gm.nullify_empty_string(%(proc)s) 2992 ) 2993 RETURNING pk""", 2994 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 2995 }] 2996 2997 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2998 2999 return cPerformedProcedure(aPK_obj = rows[0][0])3000 #-----------------------------------------------------------3002 cmd = u'delete from clin.procedure where pk = %(pk)s' 3003 args = {'pk': procedure} 3004 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3005 return True3006 #============================================================ 3007 # main - unit testing 3008 #------------------------------------------------------------ 3009 if __name__ == '__main__': 3010 3011 if len(sys.argv) < 2: 3012 sys.exit() 3013 3014 if sys.argv[1] != 'test': 3015 sys.exit() 3016 3017 #-------------------------------------------------------- 3018 # define tests 3019 #--------------------------------------------------------3021 print "\nProblem test" 3022 print "------------" 3023 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 3024 print prob 3025 fields = prob.get_fields() 3026 for field in fields: 3027 print field, ':', prob[field] 3028 print '\nupdatable:', prob.get_updatable_fields() 3029 epi = prob.get_as_episode() 3030 print '\nas episode:' 3031 if epi is not None: 3032 for field in epi.get_fields(): 3033 print ' .%s : %s' % (field, epi[field])3034 #--------------------------------------------------------3036 print "\nhealth issue test" 3037 print "-----------------" 3038 h_issue = cHealthIssue(aPK_obj=2) 3039 print h_issue 3040 print h_issue.latest_access_date 3041 print h_issue.end_date3042 # fields = h_issue.get_fields() 3043 # for field in fields: 3044 # print field, ':', h_issue[field] 3045 # print "has open episode:", h_issue.has_open_episode() 3046 # print "open episode:", h_issue.get_open_episode() 3047 # print "updateable:", h_issue.get_updatable_fields() 3048 # h_issue.close_expired_episode(ttl=7300) 3049 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 3050 # print h_issue 3051 # print h_issue.format_as_journal() 3052 #--------------------------------------------------------3054 print "\nepisode test" 3055 print "------------" 3056 episode = cEpisode(aPK_obj=1) 3057 print episode 3058 fields = episode.get_fields() 3059 for field in fields: 3060 print field, ':', episode[field] 3061 print "updatable:", episode.get_updatable_fields() 3062 raw_input('ENTER to continue') 3063 3064 old_description = episode['description'] 3065 old_enc = cEncounter(aPK_obj = 1) 3066 3067 desc = '1-%s' % episode['description'] 3068 print "==> renaming to", desc 3069 successful = episode.rename ( 3070 description = desc 3071 ) 3072 if not successful: 3073 print "error" 3074 else: 3075 print "success" 3076 for field in fields: 3077 print field, ':', episode[field] 3078 3079 print "episode range:", episode.get_access_range() 3080 3081 raw_input('ENTER to continue')3082 3083 #--------------------------------------------------------3085 print "\nencounter test" 3086 print "--------------" 3087 encounter = cEncounter(aPK_obj=1) 3088 print encounter 3089 fields = encounter.get_fields() 3090 for field in fields: 3091 print field, ':', encounter[field] 3092 print "updatable:", encounter.get_updatable_fields()3093 #--------------------------------------------------------3095 encounter = cEncounter(aPK_obj=1) 3096 print encounter 3097 print "" 3098 print encounter.format_latex()3099 #--------------------------------------------------------3101 procs = get_performed_procedures(patient = 12) 3102 for proc in procs: 3103 print proc.format(left_margin=2)3104 #--------------------------------------------------------3106 stay = create_hospital_stay(encounter = 1, episode = 2, fk_org_unit = 1) 3107 # stay['hospital'] = u'Starfleet Galaxy General Hospital' 3108 # stay.save_payload() 3109 print stay 3110 for s in get_patient_hospital_stays(12): 3111 print s 3112 delete_hospital_stay(stay['pk_hospital_stay']) 3113 stay = create_hospital_stay(encounter = 1, episode = 4, fk_org_unit = 1)3114 #--------------------------------------------------------3116 tests = [None, 'A', 'B', 'C', 'D', 'E'] 3117 3118 for t in tests: 3119 print type(t), t 3120 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)3121 #-------------------------------------------------------- 3126 #-------------------------------------------------------- 3127 # run them 3128 #test_episode() 3129 #test_problem() 3130 #test_encounter() 3131 test_health_issue() 3132 #test_hospital_stay() 3133 #test_performed_procedure() 3134 #test_diagnostic_certainty_classification_map() 3135 #test_encounter2latex() 3136 #test_episode_codes() 3137 #============================================================ 3138
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sat Aug 3 03:56:28 2013 | http://epydoc.sourceforge.net |