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