Home | Trees | Indices | Help |
|
---|
|
1 # -*- coding: utf8 -*- 2 """GNUmed health related business object. 3 4 license: GPL v2 or later 5 """ 6 #============================================================ 7 __version__ = "$Revision: 1.157 $" 8 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>" 9 10 import types, sys, string, datetime, logging, time 11 12 13 if __name__ == '__main__': 14 sys.path.insert(0, '../../') 15 from Gnumed.pycommon import gmPG2 16 from Gnumed.pycommon import gmI18N 17 from Gnumed.pycommon import gmTools 18 from Gnumed.pycommon import gmDateTime 19 from Gnumed.pycommon import gmBusinessDBObject 20 from Gnumed.pycommon import gmNull 21 from Gnumed.pycommon import gmExceptions 22 23 from Gnumed.business import gmClinNarrative 24 from Gnumed.business import gmCoding 25 26 27 _log = logging.getLogger('gm.emr') 28 _log.info(__version__) 29 30 try: _ 31 except NameError: _ = lambda x:x 32 #============================================================ 33 # diagnostic certainty classification 34 #============================================================ 35 __diagnostic_certainty_classification_map = None 3638 39 global __diagnostic_certainty_classification_map 40 41 if __diagnostic_certainty_classification_map is None: 42 __diagnostic_certainty_classification_map = { 43 None: u'', 44 u'A': _(u'A: Sign'), 45 u'B': _(u'B: Cluster of signs'), 46 u'C': _(u'C: Syndromic diagnosis'), 47 u'D': _(u'D: Scientific diagnosis') 48 } 49 50 try: 51 return __diagnostic_certainty_classification_map[classification] 52 except KeyError: 53 return _(u'<%s>: unknown diagnostic certainty classification') % classification54 #============================================================ 55 # Health Issues API 56 #============================================================ 57 laterality2str = { 58 None: u'?', 59 u'na': u'', 60 u'sd': _('bilateral'), 61 u'ds': _('bilateral'), 62 u's': _('left'), 63 u'd': _('right') 64 } 65 66 #============================================================68 """Represents one health issue.""" 69 70 _cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s" 71 _cmds_store_payload = [ 72 u"""update clin.health_issue set 73 description = %(description)s, 74 summary = gm.nullify_empty_string(%(summary)s), 75 age_noted = %(age_noted)s, 76 laterality = gm.nullify_empty_string(%(laterality)s), 77 grouping = gm.nullify_empty_string(%(grouping)s), 78 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s), 79 is_active = %(is_active)s, 80 clinically_relevant = %(clinically_relevant)s, 81 is_confidential = %(is_confidential)s, 82 is_cause_of_death = %(is_cause_of_death)s 83 where 84 pk = %(pk_health_issue)s and 85 xmin = %(xmin_health_issue)s""", 86 u"select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s" 87 ] 88 _updatable_fields = [ 89 'description', 90 'summary', 91 'grouping', 92 'age_noted', 93 'laterality', 94 'is_active', 95 'clinically_relevant', 96 'is_confidential', 97 'is_cause_of_death', 98 'diagnostic_certainty_classification' 99 ] 100 #--------------------------------------------------------556 #============================================================101 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):102 pk = aPK_obj 103 104 if (pk is not None) or (row is not None): 105 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row) 106 return 107 108 if patient is None: 109 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 110 where 111 description = %(desc)s 112 and 113 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)""" 114 else: 115 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 116 where 117 description = %(desc)s 118 and 119 pk_patient = %(pat)s""" 120 121 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}] 122 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 123 124 if len(rows) == 0: 125 raise gmExceptions.NoSuchBusinessObjectError, 'no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient) 126 127 pk = rows[0][0] 128 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'} 129 130 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)131 #-------------------------------------------------------- 132 # external API 133 #--------------------------------------------------------135 """Method for issue renaming. 136 137 @param description 138 - the new descriptive name for the issue 139 @type description 140 - a string instance 141 """ 142 # sanity check 143 if not type(description) in [str, unicode] or description.strip() == '': 144 _log.error('<description> must be a non-empty string') 145 return False 146 # update the issue description 147 old_description = self._payload[self._idx['description']] 148 self._payload[self._idx['description']] = description.strip() 149 self._is_modified = True 150 successful, data = self.save_payload() 151 if not successful: 152 _log.error('cannot rename health issue [%s] with [%s]' % (self, description)) 153 self._payload[self._idx['description']] = old_description 154 return False 155 return True156 #--------------------------------------------------------158 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s" 159 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True) 160 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]161 #--------------------------------------------------------163 """ttl in days""" 164 open_episode = self.get_open_episode() 165 if open_episode is None: 166 return True 167 earliest, latest = open_episode.get_access_range() 168 ttl = datetime.timedelta(ttl) 169 now = datetime.datetime.now(tz=latest.tzinfo) 170 if (latest + ttl) > now: 171 return False 172 open_episode['episode_open'] = False 173 success, data = open_episode.save_payload() 174 if success: 175 return True 176 return False # should be an exception177 #--------------------------------------------------------179 open_episode = self.get_open_episode() 180 open_episode['episode_open'] = False 181 success, data = open_episode.save_payload() 182 if success: 183 return True 184 return False185 #--------------------------------------------------------187 cmd = u"select exists (select 1 from clin.episode where fk_health_issue = %s and is_open is True)" 188 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 189 return rows[0][0]190 #--------------------------------------------------------192 cmd = u"select pk from clin.episode where fk_health_issue = %s and is_open is True" 193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 194 if len(rows) == 0: 195 return None 196 return cEpisode(aPK_obj=rows[0][0])197 #--------------------------------------------------------199 if self._payload[self._idx['age_noted']] is None: 200 return u'<???>' 201 202 # since we've already got an interval we are bound to use it, 203 # further transformation will only introduce more errors, 204 # later we can improve this deeper inside 205 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])206 #--------------------------------------------------------208 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 209 cmd = u"INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 210 args = { 211 'item': self._payload[self._idx['pk_health_issue']], 212 'code': pk_code 213 } 214 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 215 return True216 #--------------------------------------------------------218 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 219 cmd = u"DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 220 args = { 221 'item': self._payload[self._idx['pk_health_issue']], 222 'code': pk_code 223 } 224 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 225 return True226 #--------------------------------------------------------228 rows = gmClinNarrative.get_as_journal ( 229 issues = (self.pk_obj,), 230 order_by = u'pk_episode, pk_encounter, clin_when, scr, src_table' 231 ) 232 233 if len(rows) == 0: 234 return u'' 235 236 left_margin = u' ' * left_margin 237 238 lines = [] 239 lines.append(_('Clinical data generated during encounters under this health issue:')) 240 241 prev_epi = None 242 for row in rows: 243 if row['pk_episode'] != prev_epi: 244 lines.append(u'') 245 prev_epi = row['pk_episode'] 246 247 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding()) 248 top_row = u'%s%s %s (%s) %s' % ( 249 gmTools.u_box_top_left_arc, 250 gmTools.u_box_horiz_single, 251 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']], 252 when, 253 gmTools.u_box_horiz_single * 5 254 ) 255 soap = gmTools.wrap ( 256 text = row['narrative'], 257 width = 60, 258 initial_indent = u' ', 259 subsequent_indent = u' ' + left_margin 260 ) 261 row_ver = u'' 262 if row['row_version'] > 0: 263 row_ver = u'v%s: ' % row['row_version'] 264 bottom_row = u'%s%s %s, %s%s %s' % ( 265 u' ' * 40, 266 gmTools.u_box_horiz_light_heavy, 267 row['modified_by'], 268 row_ver, 269 row['date_modified'], 270 gmTools.u_box_horiz_heavy_light 271 ) 272 273 lines.append(top_row) 274 lines.append(soap) 275 lines.append(bottom_row) 276 277 eol_w_margin = u'\n%s' % left_margin 278 return left_margin + eol_w_margin.join(lines) + u'\n'279 #--------------------------------------------------------281 282 if patient.ID != self._payload[self._idx['pk_patient']]: 283 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % ( 284 patient.ID, 285 self._payload[self._idx['pk_health_issue']], 286 self._payload[self._idx['pk_patient']] 287 ) 288 raise ValueError(msg) 289 290 lines = [] 291 292 lines.append(_('Health Issue %s%s%s%s [#%s]') % ( 293 u'\u00BB', 294 self._payload[self._idx['description']], 295 u'\u00AB', 296 gmTools.coalesce ( 297 initial = self.laterality_description, 298 instead = u'', 299 template_initial = u' (%s)', 300 none_equivalents = [None, u'', u'?'] 301 ), 302 self._payload[self._idx['pk_health_issue']] 303 )) 304 305 if self._payload[self._idx['is_confidential']]: 306 lines.append('') 307 lines.append(_(' ***** CONFIDENTIAL *****')) 308 lines.append('') 309 310 if self._payload[self._idx['is_cause_of_death']]: 311 lines.append('') 312 lines.append(_(' contributed to death of patient')) 313 lines.append('') 314 315 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 316 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % ( 317 enc['l10n_type'], 318 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 319 enc['last_affirmed_original_tz'].strftime('%H:%M'), 320 self._payload[self._idx['pk_encounter']] 321 )) 322 323 if self._payload[self._idx['age_noted']] is not None: 324 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable()) 325 326 lines.append(u' ' + _('Status') + u': %s, %s%s' % ( 327 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')), 328 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')), 329 gmTools.coalesce ( 330 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 331 instead = u'', 332 template_initial = u', %s', 333 none_equivalents = [None, u''] 334 ) 335 )) 336 337 if self._payload[self._idx['summary']] is not None: 338 lines.append(u'') 339 lines.append(gmTools.wrap ( 340 text = self._payload[self._idx['summary']], 341 width = 60, 342 initial_indent = u' ', 343 subsequent_indent = u' ' 344 )) 345 346 # codes 347 codes = self.generic_codes 348 if len(codes) > 0: 349 lines.append(u'') 350 for c in codes: 351 lines.append(u' %s: %s (%s - %s)' % ( 352 c['code'], 353 c['term'], 354 c['name_short'], 355 c['version'] 356 )) 357 del codes 358 359 lines.append(u'') 360 361 emr = patient.get_emr() 362 363 # episodes 364 epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]]) 365 if epis is None: 366 lines.append(_('Error retrieving episodes for this health issue.')) 367 elif len(epis) == 0: 368 lines.append(_('There are no episodes for this health issue.')) 369 else: 370 lines.append ( 371 _('Episodes: %s (most recent: %s%s%s)') % ( 372 len(epis), 373 gmTools.u_left_double_angle_quote, 374 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'], 375 gmTools.u_right_double_angle_quote 376 ) 377 ) 378 for epi in epis: 379 lines.append(u' \u00BB%s\u00AB (%s)' % ( 380 epi['description'], 381 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed')) 382 )) 383 384 lines.append('') 385 386 # encounters 387 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 388 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 389 390 if first_encounter is None or last_encounter is None: 391 lines.append(_('No encounters found for this health issue.')) 392 else: 393 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]]) 394 lines.append(_('Encounters: %s (%s - %s):') % ( 395 len(encs), 396 first_encounter['started_original_tz'].strftime('%m/%Y'), 397 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y') 398 )) 399 lines.append(_(' Most recent: %s - %s') % ( 400 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 401 last_encounter['last_affirmed_original_tz'].strftime('%H:%M') 402 )) 403 404 # medications 405 meds = emr.get_current_substance_intake ( 406 issues = [ self._payload[self._idx['pk_health_issue']] ], 407 order_by = u'is_currently_active, started, substance' 408 ) 409 410 if len(meds) > 0: 411 lines.append(u'') 412 lines.append(_('Active medications: %s') % len(meds)) 413 for m in meds: 414 lines.append(m.format(left_margin = (left_margin + 1))) 415 del meds 416 417 # hospital stays 418 stays = emr.get_hospital_stays ( 419 issues = [ self._payload[self._idx['pk_health_issue']] ] 420 ) 421 if len(stays) > 0: 422 lines.append(u'') 423 lines.append(_('Hospital stays: %s') % len(stays)) 424 for s in stays: 425 lines.append(s.format(left_margin = (left_margin + 1))) 426 del stays 427 428 # procedures 429 procs = emr.get_performed_procedures ( 430 issues = [ self._payload[self._idx['pk_health_issue']] ] 431 ) 432 if len(procs) > 0: 433 lines.append(u'') 434 lines.append(_('Procedures performed: %s') % len(procs)) 435 for p in procs: 436 lines.append(p.format(left_margin = (left_margin + 1))) 437 del procs 438 439 # family history 440 fhx = emr.get_family_history(issues = [ self._payload[self._idx['pk_health_issue']] ]) 441 if len(fhx) > 0: 442 lines.append(u'') 443 lines.append(_('Family History: %s') % len(fhx)) 444 for f in fhx: 445 lines.append(f.format ( 446 left_margin = (left_margin + 1), 447 include_episode = True, 448 include_comment = True, 449 include_codes = False 450 )) 451 del fhx 452 453 epis = self.get_episodes() 454 if len(epis) > 0: 455 epi_pks = [ e['pk_episode'] for e in epis ] 456 457 # documents 458 doc_folder = patient.get_document_folder() 459 docs = doc_folder.get_documents(episodes = epi_pks) 460 if len(docs) > 0: 461 lines.append(u'') 462 lines.append(_('Documents: %s') % len(docs)) 463 del docs 464 465 # test results 466 tests = emr.get_test_results_by_date(episodes = epi_pks) 467 if len(tests) > 0: 468 lines.append(u'') 469 lines.append(_('Measurements and Results: %s') % len(tests)) 470 del tests 471 472 # vaccinations 473 vaccs = emr.get_vaccinations(episodes = epi_pks) 474 if len(vaccs) > 0: 475 lines.append(u'') 476 lines.append(_('Vaccinations:')) 477 for vacc in vaccs: 478 lines.extend(vacc.format(with_reaction = True)) 479 del vaccs 480 481 del epis 482 483 left_margin = u' ' * left_margin 484 eol_w_margin = u'\n%s' % left_margin 485 return left_margin + eol_w_margin.join(lines) + u'\n'486 #-------------------------------------------------------- 487 # properties 488 #-------------------------------------------------------- 489 episodes = property(get_episodes, lambda x:x) 490 #-------------------------------------------------------- 491 open_episode = property(get_open_episode, lambda x:x) 492 #--------------------------------------------------------494 cmd = u"""SELECT 495 coalesce ( 496 (SELECT pk FROM clin.episode WHERE fk_health_issue = %(issue)s AND is_open IS TRUE), 497 (SELECT pk FROM clin.v_pat_episodes WHERE fk_health_issue = %(issue)s ORDER BY last_affirmed DESC limit 1) 498 )""" 499 args = {'issue': self.pk_obj} 500 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 501 if len(rows) == 0: 502 return None 503 return cEpisode(aPK_obj = rows[0][0])504 505 latest_episode = property(_get_latest_episode, lambda x:x) 506 #--------------------------------------------------------508 try: 509 return laterality2str[self._payload[self._idx['laterality']]] 510 except KeyError: 511 return u'<???>'512 513 laterality_description = property(_get_laterality_description, lambda x:x) 514 #--------------------------------------------------------516 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])517 518 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 519 #--------------------------------------------------------521 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 522 return [] 523 524 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 525 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 526 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 527 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]528530 queries = [] 531 # remove all codes 532 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 533 queries.append ({ 534 'cmd': u'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s', 535 'args': { 536 'issue': self._payload[self._idx['pk_health_issue']], 537 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 538 } 539 }) 540 # add new codes 541 for pk_code in pk_codes: 542 queries.append ({ 543 'cmd': u'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)', 544 'args': { 545 'issue': self._payload[self._idx['pk_health_issue']], 546 'pk_code': pk_code 547 } 548 }) 549 if len(queries) == 0: 550 return 551 # run it all in one transaction 552 rows, idx = gmPG2.run_rw_queries(queries = queries) 553 return554 555 generic_codes = property(_get_generic_codes, _set_generic_codes)558 """Creates a new health issue for a given patient. 559 560 description - health issue name 561 """ 562 try: 563 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 564 return h_issue 565 except gmExceptions.NoSuchBusinessObjectError: 566 pass 567 568 queries = [] 569 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 570 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 571 572 cmd = u"select currval('clin.health_issue_pk_seq')" 573 queries.append({'cmd': cmd}) 574 575 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 576 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 577 578 return h_issue579 #-----------------------------------------------------------581 if isinstance(health_issue, cHealthIssue): 582 pk = health_issue['pk_health_issue'] 583 else: 584 pk = int(health_issue) 585 586 try: 587 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}]) 588 except gmPG2.dbapi.IntegrityError: 589 # should be parsing pgcode/and or error message 590 _log.exception('cannot delete health issue') 591 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')592 #------------------------------------------------------------ 593 # use as dummy for unassociated episodes595 issue = { 596 'pk_health_issue': None, 597 'description': _('Unattributed episodes'), 598 'age_noted': None, 599 'laterality': u'na', 600 'is_active': True, 601 'clinically_relevant': True, 602 'is_confidential': None, 603 'is_cause_of_death': False, 604 'is_dummy': True, 605 'grouping': None 606 } 607 return issue608 #-----------------------------------------------------------610 return cProblem ( 611 aPK_obj = { 612 'pk_patient': health_issue['pk_patient'], 613 'pk_health_issue': health_issue['pk_health_issue'], 614 'pk_episode': None 615 }, 616 try_potential_problems = allow_irrelevant 617 )618 #============================================================ 619 # episodes API 620 #============================================================622 """Represents one clinical episode. 623 """ 624 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s" 625 _cmds_store_payload = [ 626 u"""update clin.episode set 627 fk_health_issue = %(pk_health_issue)s, 628 is_open = %(episode_open)s::boolean, 629 description = %(description)s, 630 summary = gm.nullify_empty_string(%(summary)s), 631 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 632 where 633 pk = %(pk_episode)s and 634 xmin = %(xmin_episode)s""", 635 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 636 ] 637 _updatable_fields = [ 638 'pk_health_issue', 639 'episode_open', 640 'description', 641 'summary', 642 'diagnostic_certainty_classification' 643 ] 644 #--------------------------------------------------------1149 #============================================================645 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):646 pk = aPK_obj 647 if pk is None and row is None: 648 649 where_parts = [u'description = %(desc)s'] 650 651 if id_patient is not None: 652 where_parts.append(u'pk_patient = %(pat)s') 653 654 if health_issue is not None: 655 where_parts.append(u'pk_health_issue = %(issue)s') 656 657 if encounter is not None: 658 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)') 659 660 args = { 661 'pat': id_patient, 662 'issue': health_issue, 663 'enc': encounter, 664 'desc': name 665 } 666 667 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts) 668 669 rows, idx = gmPG2.run_ro_queries( 670 queries = [{'cmd': cmd, 'args': args}], 671 get_col_idx=True 672 ) 673 674 if len(rows) == 0: 675 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter) 676 677 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 678 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 679 680 else: 681 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)682 #-------------------------------------------------------- 683 # external API 684 #--------------------------------------------------------686 """Get earliest and latest access to this episode. 687 688 Returns a tuple(earliest, latest). 689 """ 690 cmd = u""" 691 select 692 min(earliest), 693 max(latest) 694 from ( 695 (select 696 (case when clin_when < modified_when 697 then clin_when 698 else modified_when 699 end) as earliest, 700 (case when clin_when > modified_when 701 then clin_when 702 else modified_when 703 end) as latest 704 from 705 clin.clin_root_item 706 where 707 fk_episode = %(pk)s 708 709 ) union all ( 710 711 select 712 modified_when as earliest, 713 modified_when as latest 714 from 715 clin.episode 716 where 717 pk = %(pk)s 718 ) 719 ) as ranges""" 720 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 721 if len(rows) == 0: 722 return (gmNull.cNull(warn=False), gmNull.cNull(warn=False)) 723 return (rows[0][0], rows[0][1])724 #-------------------------------------------------------- 727 #--------------------------------------------------------729 return gmClinNarrative.get_narrative ( 730 soap_cats = soap_cats, 731 encounters = encounters, 732 episodes = [self.pk_obj], 733 order_by = order_by 734 )735 #--------------------------------------------------------737 """Method for episode editing, that is, episode renaming. 738 739 @param description 740 - the new descriptive name for the encounter 741 @type description 742 - a string instance 743 """ 744 # sanity check 745 if description.strip() == '': 746 _log.error('<description> must be a non-empty string instance') 747 return False 748 # update the episode description 749 old_description = self._payload[self._idx['description']] 750 self._payload[self._idx['description']] = description.strip() 751 self._is_modified = True 752 successful, data = self.save_payload() 753 if not successful: 754 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 755 self._payload[self._idx['description']] = old_description 756 return False 757 return True758 #--------------------------------------------------------760 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 761 762 if pk_code in self._payload[self._idx['pk_generic_codes']]: 763 return 764 765 cmd = u""" 766 INSERT INTO clin.lnk_code2episode 767 (fk_item, fk_generic_code) 768 SELECT 769 %(item)s, 770 %(code)s 771 WHERE NOT EXISTS ( 772 SELECT 1 FROM clin.lnk_code2episode 773 WHERE 774 fk_item = %(item)s 775 AND 776 fk_generic_code = %(code)s 777 )""" 778 args = { 779 'item': self._payload[self._idx['pk_episode']], 780 'code': pk_code 781 } 782 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 783 return784 #--------------------------------------------------------786 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 787 cmd = u"DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 788 args = { 789 'item': self._payload[self._idx['pk_episode']], 790 'code': pk_code 791 } 792 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 793 return True794 #--------------------------------------------------------796 rows = gmClinNarrative.get_as_journal ( 797 episodes = (self.pk_obj,), 798 order_by = u'pk_encounter, clin_when, scr, src_table' 799 #order_by = u'pk_encounter, scr, clin_when, src_table' 800 ) 801 802 if len(rows) == 0: 803 return u'' 804 805 lines = [] 806 807 lines.append(_('Clinical data generated during encounters within this episode:')) 808 809 left_margin = u' ' * left_margin 810 811 prev_enc = None 812 for row in rows: 813 if row['pk_encounter'] != prev_enc: 814 lines.append(u'') 815 prev_enc = row['pk_encounter'] 816 817 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding()) 818 top_row = u'%s%s %s (%s) %s' % ( 819 gmTools.u_box_top_left_arc, 820 gmTools.u_box_horiz_single, 821 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']], 822 when, 823 gmTools.u_box_horiz_single * 5 824 ) 825 soap = gmTools.wrap ( 826 text = row['narrative'], 827 width = 60, 828 initial_indent = u' ', 829 subsequent_indent = u' ' + left_margin 830 ) 831 row_ver = u'' 832 if row['row_version'] > 0: 833 row_ver = u'v%s: ' % row['row_version'] 834 bottom_row = u'%s%s %s, %s%s %s' % ( 835 u' ' * 40, 836 gmTools.u_box_horiz_light_heavy, 837 row['modified_by'], 838 row_ver, 839 row['date_modified'], 840 gmTools.u_box_horiz_heavy_light 841 ) 842 843 lines.append(top_row) 844 lines.append(soap) 845 lines.append(bottom_row) 846 847 eol_w_margin = u'\n%s' % left_margin 848 return left_margin + eol_w_margin.join(lines) + u'\n'849 #--------------------------------------------------------851 852 if patient.ID != self._payload[self._idx['pk_patient']]: 853 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 854 patient.ID, 855 self._payload[self._idx['pk_episode']], 856 self._payload[self._idx['pk_patient']] 857 ) 858 raise ValueError(msg) 859 860 lines = [] 861 862 # episode details 863 lines.append (_('Episode %s%s%s [#%s]') % ( 864 gmTools.u_left_double_angle_quote, 865 self._payload[self._idx['description']], 866 gmTools.u_right_double_angle_quote, 867 self._payload[self._idx['pk_episode']] 868 )) 869 870 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 871 lines.append (u' ' + _('Created during encounter: %s (%s - %s) [#%s]') % ( 872 enc['l10n_type'], 873 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 874 enc['last_affirmed_original_tz'].strftime('%H:%M'), 875 self._payload[self._idx['pk_encounter']] 876 )) 877 878 emr = patient.get_emr() 879 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 880 first_encounter = None 881 last_encounter = None 882 if (encs is not None) and (len(encs) > 0): 883 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 884 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 885 if self._payload[self._idx['episode_open']]: 886 end = gmDateTime.pydt_now_here() 887 end_str = gmTools.u_ellipsis 888 else: 889 end = last_encounter['last_affirmed'] 890 end_str = last_encounter['last_affirmed'].strftime('%m/%Y') 891 age = gmDateTime.format_interval_medically(end - first_encounter['started']) 892 lines.append(_(' Duration: %s (%s - %s)') % ( 893 age, 894 first_encounter['started'].strftime('%m/%Y'), 895 end_str 896 )) 897 898 lines.append(u' ' + _('Status') + u': %s%s' % ( 899 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 900 gmTools.coalesce ( 901 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 902 instead = u'', 903 template_initial = u', %s', 904 none_equivalents = [None, u''] 905 ) 906 )) 907 908 if self._payload[self._idx['summary']] is not None: 909 lines.append(u'') 910 lines.append(gmTools.wrap ( 911 text = self._payload[self._idx['summary']], 912 width = 60, 913 initial_indent = u' ', 914 subsequent_indent = u' ' 915 ) 916 ) 917 918 # codes 919 codes = self.generic_codes 920 if len(codes) > 0: 921 lines.append(u'') 922 for c in codes: 923 lines.append(u' %s: %s (%s - %s)' % ( 924 c['code'], 925 c['term'], 926 c['name_short'], 927 c['version'] 928 )) 929 del codes 930 931 lines.append(u'') 932 933 # encounters 934 if encs is None: 935 lines.append(_('Error retrieving encounters for this episode.')) 936 elif len(encs) == 0: 937 lines.append(_('There are no encounters for this episode.')) 938 else: 939 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 940 941 if len(encs) < 4: 942 line = _('%s encounter(s) (%s - %s):') 943 else: 944 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):') 945 lines.append(line % ( 946 len(encs), 947 first_encounter['started'].strftime('%m/%Y'), 948 last_encounter['last_affirmed'].strftime('%m/%Y') 949 )) 950 951 lines.append(u' %s - %s (%s):%s' % ( 952 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 953 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 954 first_encounter['l10n_type'], 955 gmTools.coalesce ( 956 first_encounter['assessment_of_encounter'], 957 gmTools.coalesce ( 958 first_encounter['reason_for_encounter'], 959 u'', 960 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 961 ), 962 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 963 ) 964 )) 965 966 if len(encs) > 4: 967 lines.append(_(' ... %s skipped ...') % (len(encs) - 4)) 968 969 for enc in encs[1:][-3:]: 970 lines.append(u' %s - %s (%s):%s' % ( 971 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 972 enc['last_affirmed_original_tz'].strftime('%H:%M'), 973 enc['l10n_type'], 974 gmTools.coalesce ( 975 enc['assessment_of_encounter'], 976 gmTools.coalesce ( 977 enc['reason_for_encounter'], 978 u'', 979 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 980 ), 981 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 982 ) 983 )) 984 del encs 985 986 # spell out last encounter 987 if last_encounter is not None: 988 lines.append('') 989 lines.append(_('Progress notes in most recent encounter:')) 990 lines.extend(last_encounter.format_soap ( 991 episodes = [ self._payload[self._idx['pk_episode']] ], 992 left_margin = left_margin, 993 soap_cats = 'soap', 994 emr = emr 995 )) 996 997 # documents 998 doc_folder = patient.get_document_folder() 999 docs = doc_folder.get_documents ( 1000 episodes = [ self._payload[self._idx['pk_episode']] ] 1001 ) 1002 1003 if len(docs) > 0: 1004 lines.append('') 1005 lines.append(_('Documents: %s') % len(docs)) 1006 1007 for d in docs: 1008 lines.append(u' %s %s:%s%s' % ( 1009 d['clin_when'].strftime('%Y-%m-%d'), 1010 d['l10n_type'], 1011 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1012 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1013 )) 1014 del docs 1015 1016 # hospital stays 1017 stays = emr.get_hospital_stays(episodes = [ self._payload[self._idx['pk_episode']] ]) 1018 if len(stays) > 0: 1019 lines.append('') 1020 lines.append(_('Hospital stays: %s') % len(stays)) 1021 for s in stays: 1022 lines.append(s.format(left_margin = (left_margin + 1))) 1023 del stays 1024 1025 # procedures 1026 procs = emr.get_performed_procedures(episodes = [ self._payload[self._idx['pk_episode']] ]) 1027 if len(procs) > 0: 1028 lines.append(u'') 1029 lines.append(_('Procedures performed: %s') % len(procs)) 1030 for p in procs: 1031 lines.append(p.format ( 1032 left_margin = (left_margin + 1), 1033 include_episode = False, 1034 include_codes = True 1035 )) 1036 del procs 1037 1038 # family history 1039 fhx = emr.get_family_history(episodes = [ self._payload[self._idx['pk_episode']] ]) 1040 if len(fhx) > 0: 1041 lines.append(u'') 1042 lines.append(_('Family History: %s') % len(fhx)) 1043 for f in fhx: 1044 lines.append(f.format ( 1045 left_margin = (left_margin + 1), 1046 include_episode = False, 1047 include_comment = True, 1048 include_codes = True 1049 )) 1050 del fhx 1051 1052 # test results 1053 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 1054 1055 if len(tests) > 0: 1056 lines.append('') 1057 lines.append(_('Measurements and Results:')) 1058 1059 for t in tests: 1060 lines.extend(t.format ( 1061 with_review = False, 1062 with_comments = False, 1063 date_format = '%Y-%m-%d' 1064 )) 1065 del tests 1066 1067 # vaccinations 1068 vaccs = emr.get_vaccinations(episodes = [ self._payload[self._idx['pk_episode']] ]) 1069 1070 if len(vaccs) > 0: 1071 lines.append(u'') 1072 lines.append(_('Vaccinations:')) 1073 1074 for vacc in vaccs: 1075 lines.extend(vacc.format ( 1076 with_indications = True, 1077 with_comment = True, 1078 with_reaction = True, 1079 date_format = '%Y-%m-%d' 1080 )) 1081 del vaccs 1082 1083 left_margin = u' ' * left_margin 1084 eol_w_margin = u'\n%s' % left_margin 1085 return left_margin + eol_w_margin.join(lines) + u'\n'1086 #-------------------------------------------------------- 1087 # properties 1088 #--------------------------------------------------------1090 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])1091 1092 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 1093 #--------------------------------------------------------1095 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 1096 return [] 1097 1098 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1099 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 1100 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1101 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]11021104 queries = [] 1105 # remove all codes 1106 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 1107 queries.append ({ 1108 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s', 1109 'args': { 1110 'epi': self._payload[self._idx['pk_episode']], 1111 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 1112 } 1113 }) 1114 # add new codes 1115 for pk_code in pk_codes: 1116 queries.append ({ 1117 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)', 1118 'args': { 1119 'epi': self._payload[self._idx['pk_episode']], 1120 'pk_code': pk_code 1121 } 1122 }) 1123 if len(queries) == 0: 1124 return 1125 # run it all in one transaction 1126 rows, idx = gmPG2.run_rw_queries(queries = queries) 1127 return1128 1129 generic_codes = property(_get_generic_codes, _set_generic_codes) 1130 #--------------------------------------------------------1132 cmd = u"""SELECT EXISTS ( 1133 SELECT 1 FROM clin.clin_narrative 1134 WHERE 1135 fk_episode = %(epi)s 1136 AND 1137 fk_encounter IN ( 1138 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1139 ) 1140 )""" 1141 args = { 1142 u'pat': self._payload[self._idx['pk_patient']], 1143 u'epi': self._payload[self._idx['pk_episode']] 1144 } 1145 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1146 return rows[0][0]1147 1148 has_narrative = property(_get_has_narrative, lambda x:x)1150 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):1151 """Creates a new episode for a given patient's health issue. 1152 1153 pk_health_issue - given health issue PK 1154 episode_name - name of episode 1155 """ 1156 if not allow_dupes: 1157 try: 1158 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 1159 if episode['episode_open'] != is_open: 1160 episode['episode_open'] = is_open 1161 episode.save_payload() 1162 return episode 1163 except gmExceptions.ConstructorError: 1164 pass 1165 1166 queries = [] 1167 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 1168 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 1169 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 1170 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 1171 1172 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 1173 return episode1174 #-----------------------------------------------------------1176 if isinstance(episode, cEpisode): 1177 pk = episode['pk_episode'] 1178 else: 1179 pk = int(episode) 1180 1181 try: 1182 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.episode where pk=%(pk)s', 'args': {'pk': pk}}]) 1183 except gmPG2.dbapi.IntegrityError: 1184 # should be parsing pgcode/and or error message 1185 _log.exception('cannot delete episode') 1186 raise gmExceptions.DatabaseObjectInUseError('cannot delete episode, it is in use')1187 #-----------------------------------------------------------1189 return cProblem ( 1190 aPK_obj = { 1191 'pk_patient': episode['pk_patient'], 1192 'pk_episode': episode['pk_episode'], 1193 'pk_health_issue': episode['pk_health_issue'] 1194 }, 1195 try_potential_problems = allow_closed 1196 )1197 #============================================================ 1198 # encounter API 1199 #============================================================1201 """Represents one encounter.""" 1202 1203 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 1204 _cmds_store_payload = [ 1205 u"""update clin.encounter set 1206 started = %(started)s, 1207 last_affirmed = %(last_affirmed)s, 1208 fk_location = %(pk_location)s, 1209 fk_type = %(pk_type)s, 1210 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1211 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1212 where 1213 pk = %(pk_encounter)s and 1214 xmin = %(xmin_encounter)s""", 1215 u"""select xmin_encounter from clin.v_pat_encounters where pk_encounter=%(pk_encounter)s""" 1216 ] 1217 _updatable_fields = [ 1218 'started', 1219 'last_affirmed', 1220 'pk_location', 1221 'pk_type', 1222 'reason_for_encounter', 1223 'assessment_of_encounter' 1224 ] 1225 #--------------------------------------------------------1879 #-----------------------------------------------------------1227 """Set the encounter as the active one. 1228 1229 "Setting active" means making sure the encounter 1230 row has the youngest "last_affirmed" timestamp of 1231 all encounter rows for this patient. 1232 """ 1233 self['last_affirmed'] = gmDateTime.pydt_now_here() 1234 self.save()1235 #--------------------------------------------------------1237 """ 1238 Moves every element currently linked to the current encounter 1239 and the source_episode onto target_episode. 1240 1241 @param source_episode The episode the elements are currently linked to. 1242 @type target_episode A cEpisode intance. 1243 @param target_episode The episode the elements will be relinked to. 1244 @type target_episode A cEpisode intance. 1245 """ 1246 if source_episode['pk_episode'] == target_episode['pk_episode']: 1247 return True 1248 1249 queries = [] 1250 cmd = u""" 1251 UPDATE clin.clin_root_item 1252 SET fk_episode = %(trg)s 1253 WHERE 1254 fk_encounter = %(enc)s AND 1255 fk_episode = %(src)s 1256 """ 1257 rows, idx = gmPG2.run_rw_queries(queries = [{ 1258 'cmd': cmd, 1259 'args': { 1260 'trg': target_episode['pk_episode'], 1261 'enc': self.pk_obj, 1262 'src': source_episode['pk_episode'] 1263 } 1264 }]) 1265 self.refetch_payload() 1266 return True1267 #--------------------------------------------------------1269 1270 relevant_fields = [ 1271 'pk_location', 1272 'pk_type', 1273 'pk_patient', 1274 'reason_for_encounter', 1275 'assessment_of_encounter' 1276 ] 1277 for field in relevant_fields: 1278 if self._payload[self._idx[field]] != another_object[field]: 1279 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1280 return False 1281 1282 relevant_fields = [ 1283 'started', 1284 'last_affirmed', 1285 ] 1286 for field in relevant_fields: 1287 if self._payload[self._idx[field]] is None: 1288 if another_object[field] is None: 1289 continue 1290 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1291 return False 1292 1293 if another_object[field] is None: 1294 return False 1295 1296 #if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S %Z') != another_object[field].strftime('%Y-%m-%d %H:%M:%S %Z'): 1297 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'): 1298 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1299 return False 1300 1301 return True1302 #--------------------------------------------------------1304 cmd = u""" 1305 select exists ( 1306 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 1307 union all 1308 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1309 )""" 1310 args = { 1311 'pat': self._payload[self._idx['pk_patient']], 1312 'enc': self.pk_obj 1313 } 1314 rows, idx = gmPG2.run_ro_queries ( 1315 queries = [{ 1316 'cmd': cmd, 1317 'args': args 1318 }] 1319 ) 1320 return rows[0][0]1321 #--------------------------------------------------------1323 cmd = u""" 1324 select exists ( 1325 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 1326 )""" 1327 args = { 1328 'pat': self._payload[self._idx['pk_patient']], 1329 'enc': self.pk_obj 1330 } 1331 rows, idx = gmPG2.run_ro_queries ( 1332 queries = [{ 1333 'cmd': cmd, 1334 'args': args 1335 }] 1336 ) 1337 return rows[0][0]1338 #--------------------------------------------------------1340 """soap_cats: <space> = admin category""" 1341 1342 if soap_cats is None: 1343 soap_cats = u'soap ' 1344 else: 1345 soap_cats = soap_cats.lower() 1346 1347 cats = [] 1348 for cat in soap_cats: 1349 if cat in u'soap': 1350 cats.append(cat) 1351 continue 1352 if cat == u' ': 1353 cats.append(None) 1354 1355 cmd = u""" 1356 SELECT EXISTS ( 1357 SELECT 1 FROM clin.clin_narrative 1358 WHERE 1359 fk_encounter = %(enc)s 1360 AND 1361 soap_cat IN %(cats)s 1362 LIMIT 1 1363 ) 1364 """ 1365 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 1366 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 1367 return rows[0][0]1368 #--------------------------------------------------------1370 cmd = u""" 1371 select exists ( 1372 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1373 )""" 1374 args = { 1375 'pat': self._payload[self._idx['pk_patient']], 1376 'enc': self.pk_obj 1377 } 1378 rows, idx = gmPG2.run_ro_queries ( 1379 queries = [{ 1380 'cmd': cmd, 1381 'args': args 1382 }] 1383 ) 1384 return rows[0][0]1385 #--------------------------------------------------------1387 1388 if soap_cat is not None: 1389 soap_cat = soap_cat.lower() 1390 1391 if episode is None: 1392 epi_part = u'fk_episode is null' 1393 else: 1394 epi_part = u'fk_episode = %(epi)s' 1395 1396 cmd = u""" 1397 select narrative 1398 from clin.clin_narrative 1399 where 1400 fk_encounter = %%(enc)s 1401 and 1402 soap_cat = %%(cat)s 1403 and 1404 %s 1405 order by clin_when desc 1406 limit 1 1407 """ % epi_part 1408 1409 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 1410 1411 rows, idx = gmPG2.run_ro_queries ( 1412 queries = [{ 1413 'cmd': cmd, 1414 'args': args 1415 }] 1416 ) 1417 if len(rows) == 0: 1418 return None 1419 1420 return rows[0][0]1421 #--------------------------------------------------------1423 cmd = u""" 1424 SELECT * FROM clin.v_pat_episodes 1425 WHERE 1426 pk_episode IN ( 1427 1428 SELECT DISTINCT fk_episode 1429 FROM clin.clin_root_item 1430 WHERE fk_encounter = %%(enc)s 1431 1432 UNION 1433 1434 SELECT DISTINCT fk_episode 1435 FROM blobs.doc_med 1436 WHERE fk_encounter = %%(enc)s 1437 ) 1438 %s""" 1439 args = {'enc': self.pk_obj} 1440 if exclude is not None: 1441 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s' 1442 args['excluded'] = tuple(exclude) 1443 else: 1444 cmd = cmd % u'' 1445 1446 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1447 1448 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]1449 #--------------------------------------------------------1451 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1452 if field == u'rfe': 1453 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1454 elif field == u'aoe': 1455 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1456 else: 1457 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1458 args = { 1459 'item': self._payload[self._idx['pk_encounter']], 1460 'code': pk_code 1461 } 1462 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1463 return True1464 #--------------------------------------------------------1466 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1467 if field == u'rfe': 1468 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1469 elif field == u'aoe': 1470 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1471 else: 1472 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1473 args = { 1474 'item': self._payload[self._idx['pk_encounter']], 1475 'code': pk_code 1476 } 1477 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1478 return True1479 #--------------------------------------------------------1480 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soap', emr=None, issues=None):1481 1482 lines = [] 1483 for soap_cat in soap_cats: 1484 soap_cat_narratives = emr.get_clin_narrative ( 1485 episodes = episodes, 1486 issues = issues, 1487 encounters = [self._payload[self._idx['pk_encounter']]], 1488 soap_cats = [soap_cat] 1489 ) 1490 if soap_cat_narratives is None: 1491 continue 1492 if len(soap_cat_narratives) == 0: 1493 continue 1494 1495 lines.append(u'%s%s %s %s' % ( 1496 gmTools.u_box_top_left_arc, 1497 gmTools.u_box_horiz_single, 1498 gmClinNarrative.soap_cat2l10n_str[soap_cat], 1499 gmTools.u_box_horiz_single * 5 1500 )) 1501 for soap_entry in soap_cat_narratives: 1502 txt = gmTools.wrap ( 1503 text = soap_entry['narrative'], 1504 width = 75, 1505 initial_indent = u'', 1506 subsequent_indent = (u' ' * left_margin) 1507 ) 1508 lines.append(txt) 1509 when = gmDateTime.pydt_strftime ( 1510 soap_entry['date'], 1511 format = '%Y-%m-%d %H:%M', 1512 accuracy = gmDateTime.acc_minutes 1513 ) 1514 txt = u'%s%s %.8s, %s %s' % ( 1515 u' ' * 40, 1516 gmTools.u_box_horiz_light_heavy, 1517 soap_entry['provider'], 1518 when, 1519 gmTools.u_box_horiz_heavy_light 1520 ) 1521 lines.append(txt) 1522 lines.append('') 1523 1524 return lines1525 #--------------------------------------------------------1527 1528 nothing2format = ( 1529 (self._payload[self._idx['reason_for_encounter']] is None) 1530 and 1531 (self._payload[self._idx['assessment_of_encounter']] is None) 1532 and 1533 (self.has_soap_narrative(soap_cats = u'soap') is False) 1534 ) 1535 if nothing2format: 1536 return u'' 1537 1538 if date_format is None: 1539 date_format = '%A, %B %d %Y' 1540 1541 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 1542 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 1543 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()), 1544 self._payload[self._idx['started']].strftime('%H:%M'), 1545 self._payload[self._idx['last_affirmed']].strftime('%H:%M') 1546 ) 1547 tex += u'\\hline \\tabularnewline \n' 1548 1549 for epi in self.get_episodes(): 1550 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 1551 if len(soaps) == 0: 1552 continue 1553 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1554 gmTools.tex_escape_string(_('Problem')), 1555 gmTools.tex_escape_string(epi['description']), 1556 gmTools.coalesce ( 1557 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 1558 instead = u'', 1559 template_initial = u' {\\footnotesize [%s]}', 1560 none_equivalents = [None, u''] 1561 ) 1562 ) 1563 if epi['pk_health_issue'] is not None: 1564 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1565 gmTools.tex_escape_string(_('Health issue')), 1566 gmTools.tex_escape_string(epi['health_issue']), 1567 gmTools.coalesce ( 1568 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 1569 instead = u'', 1570 template_initial = u' {\\footnotesize [%s]}', 1571 none_equivalents = [None, u''] 1572 ) 1573 ) 1574 for soap in soaps: 1575 tex += u'{\\small %s} & {\\small %s} \\tabularnewline \n' % ( 1576 gmClinNarrative.soap_cat2l10n[soap['soap_cat']], 1577 gmTools.tex_escape_string(soap['narrative'].strip(u'\n')) 1578 ) 1579 tex += u' & \\tabularnewline \n' 1580 1581 if self._payload[self._idx['reason_for_encounter']] is not None: 1582 tex += u'%s & %s \\tabularnewline \n' % ( 1583 gmTools.tex_escape_string(_('RFE')), 1584 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 1585 ) 1586 if self._payload[self._idx['assessment_of_encounter']] is not None: 1587 tex += u'%s & %s \\tabularnewline \n' % ( 1588 gmTools.tex_escape_string(_('AOE')), 1589 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 1590 ) 1591 1592 tex += u'\\hline \\tabularnewline \n' 1593 tex += u' & \\tabularnewline \n' 1594 1595 return tex1596 #--------------------------------------------------------1597 - 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):1598 """Format an encounter. 1599 1600 with_co_encountlet_hints: 1601 - whether to include which *other* episodes were discussed during this encounter 1602 - (only makes sense if episodes != None) 1603 """ 1604 lines = [] 1605 1606 if fancy_header: 1607 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1608 u' ' * left_margin, 1609 self._payload[self._idx['l10n_type']], 1610 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1611 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1612 self._payload[self._idx['source_time_zone']], 1613 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 1614 self._payload[self._idx['pk_encounter']] 1615 )) 1616 1617 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1618 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1619 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1620 gmDateTime.current_local_iso_numeric_timezone_string, 1621 gmTools.bool2subst ( 1622 gmDateTime.dst_currently_in_effect, 1623 gmDateTime.py_dst_timezone_name, 1624 gmDateTime.py_timezone_name 1625 ), 1626 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1627 )) 1628 1629 if self._payload[self._idx['reason_for_encounter']] is not None: 1630 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1631 codes = self.generic_codes_rfe 1632 for c in codes: 1633 lines.append(u' %s: %s (%s - %s)' % ( 1634 c['code'], 1635 c['term'], 1636 c['name_short'], 1637 c['version'] 1638 )) 1639 if len(codes) > 0: 1640 lines.append(u'') 1641 1642 if self._payload[self._idx['assessment_of_encounter']] is not None: 1643 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1644 codes = self.generic_codes_aoe 1645 for c in codes: 1646 lines.append(u' %s: %s (%s - %s)' % ( 1647 c['code'], 1648 c['term'], 1649 c['name_short'], 1650 c['version'] 1651 )) 1652 if len(codes) > 0: 1653 lines.append(u'') 1654 del codes 1655 1656 else: 1657 lines.append(u'%s%s: %s - %s%s' % ( 1658 u' ' * left_margin, 1659 self._payload[self._idx['l10n_type']], 1660 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1661 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1662 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1663 )) 1664 if with_rfe_aoe: 1665 if self._payload[self._idx['reason_for_encounter']] is not None: 1666 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1667 codes = self.generic_codes_rfe 1668 for c in codes: 1669 lines.append(u' %s: %s (%s - %s)' % ( 1670 c['code'], 1671 c['term'], 1672 c['name_short'], 1673 c['version'] 1674 )) 1675 if len(codes) > 0: 1676 lines.append(u'') 1677 if self._payload[self._idx['assessment_of_encounter']] is not None: 1678 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1679 codes = self.generic_codes_aoe 1680 if len(codes) > 0: 1681 lines.append(u'') 1682 for c in codes: 1683 lines.append(u' %s: %s (%s - %s)' % ( 1684 c['code'], 1685 c['term'], 1686 c['name_short'], 1687 c['version'] 1688 )) 1689 if len(codes) > 0: 1690 lines.append(u'') 1691 del codes 1692 1693 if with_soap: 1694 lines.append(u'') 1695 1696 if patient.ID != self._payload[self._idx['pk_patient']]: 1697 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 1698 patient.ID, 1699 self._payload[self._idx['pk_encounter']], 1700 self._payload[self._idx['pk_patient']] 1701 ) 1702 raise ValueError(msg) 1703 1704 emr = patient.get_emr() 1705 1706 lines.extend(self.format_soap ( 1707 episodes = episodes, 1708 left_margin = left_margin, 1709 soap_cats = 'soap', 1710 emr = emr, 1711 issues = issues 1712 )) 1713 1714 # # family history 1715 # if with_family_history: 1716 # if episodes is not None: 1717 # fhx = emr.get_family_history(episodes = episodes) 1718 # if len(fhx) > 0: 1719 # lines.append(u'') 1720 # lines.append(_('Family History: %s') % len(fhx)) 1721 # for f in fhx: 1722 # lines.append(f.format ( 1723 # left_margin = (left_margin + 1), 1724 # include_episode = False, 1725 # include_comment = True 1726 # )) 1727 # del fhx 1728 1729 # test results 1730 if with_tests: 1731 tests = emr.get_test_results_by_date ( 1732 episodes = episodes, 1733 encounter = self._payload[self._idx['pk_encounter']] 1734 ) 1735 if len(tests) > 0: 1736 lines.append('') 1737 lines.append(_('Measurements and Results:')) 1738 1739 for t in tests: 1740 lines.extend(t.format()) 1741 1742 del tests 1743 1744 # vaccinations 1745 if with_vaccinations: 1746 vaccs = emr.get_vaccinations ( 1747 episodes = episodes, 1748 encounters = [ self._payload[self._idx['pk_encounter']] ] 1749 ) 1750 1751 if len(vaccs) > 0: 1752 lines.append(u'') 1753 lines.append(_('Vaccinations:')) 1754 1755 for vacc in vaccs: 1756 lines.extend(vacc.format ( 1757 with_indications = True, 1758 with_comment = True, 1759 with_reaction = True, 1760 date_format = '%Y-%m-%d' 1761 )) 1762 del vaccs 1763 1764 # documents 1765 if with_docs: 1766 doc_folder = patient.get_document_folder() 1767 docs = doc_folder.get_documents ( 1768 episodes = episodes, 1769 encounter = self._payload[self._idx['pk_encounter']] 1770 ) 1771 1772 if len(docs) > 0: 1773 lines.append(u'') 1774 lines.append(_('Documents:')) 1775 1776 for d in docs: 1777 lines.append(u' %s %s:%s%s' % ( 1778 d['clin_when'].strftime('%Y-%m-%d'), 1779 d['l10n_type'], 1780 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1781 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1782 )) 1783 1784 del docs 1785 1786 # co-encountlets 1787 if with_co_encountlet_hints: 1788 if episodes is not None: 1789 other_epis = self.get_episodes(exclude = episodes) 1790 if len(other_epis) > 0: 1791 lines.append(u'') 1792 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 1793 for epi in other_epis: 1794 lines.append(u' %s%s%s%s' % ( 1795 gmTools.u_left_double_angle_quote, 1796 epi['description'], 1797 gmTools.u_right_double_angle_quote, 1798 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 1799 )) 1800 1801 eol_w_margin = u'\n%s' % (u' ' * left_margin) 1802 return u'%s\n' % eol_w_margin.join(lines)1803 #-------------------------------------------------------- 1804 # properties 1805 #--------------------------------------------------------1807 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 1808 return [] 1809 1810 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1811 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 1812 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1813 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]18141816 queries = [] 1817 # remove all codes 1818 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 1819 queries.append ({ 1820 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 1821 'args': { 1822 'enc': self._payload[self._idx['pk_encounter']], 1823 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 1824 } 1825 }) 1826 # add new codes 1827 for pk_code in pk_codes: 1828 queries.append ({ 1829 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 1830 'args': { 1831 'enc': self._payload[self._idx['pk_encounter']], 1832 'pk_code': pk_code 1833 } 1834 }) 1835 if len(queries) == 0: 1836 return 1837 # run it all in one transaction 1838 rows, idx = gmPG2.run_rw_queries(queries = queries) 1839 return1840 1841 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 1842 #--------------------------------------------------------1844 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 1845 return [] 1846 1847 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1848 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 1849 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1850 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]18511853 queries = [] 1854 # remove all codes 1855 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 1856 queries.append ({ 1857 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 1858 'args': { 1859 'enc': self._payload[self._idx['pk_encounter']], 1860 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 1861 } 1862 }) 1863 # add new codes 1864 for pk_code in pk_codes: 1865 queries.append ({ 1866 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 1867 'args': { 1868 'enc': self._payload[self._idx['pk_encounter']], 1869 'pk_code': pk_code 1870 } 1871 }) 1872 if len(queries) == 0: 1873 return 1874 # run it all in one transaction 1875 rows, idx = gmPG2.run_rw_queries(queries = queries) 1876 return1877 1878 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)1881 """Creates a new encounter for a patient. 1882 1883 fk_patient - patient PK 1884 fk_location - encounter location 1885 enc_type - type of encounter 1886 1887 FIXME: we don't deal with location yet 1888 """ 1889 if enc_type is None: 1890 enc_type = u'in surgery' 1891 # insert new encounter 1892 queries = [] 1893 try: 1894 enc_type = int(enc_type) 1895 cmd = u""" 1896 INSERT INTO clin.encounter ( 1897 fk_patient, fk_location, fk_type 1898 ) VALUES ( 1899 %(pat)s, 1900 -1, 1901 %(typ)s 1902 ) RETURNING pk""" 1903 except ValueError: 1904 enc_type = enc_type 1905 cmd = u""" 1906 insert into clin.encounter ( 1907 fk_patient, fk_location, fk_type 1908 ) values ( 1909 %(pat)s, 1910 -1, 1911 coalesce((select pk from clin.encounter_type where description = %(typ)s), 0) 1912 ) RETURNING pk""" 1913 args = {'pat': fk_patient, 'typ': enc_type} 1914 queries.append({'cmd': cmd, 'args': args}) 1915 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 1916 encounter = cEncounter(aPK_obj = rows[0]['pk']) 1917 1918 return encounter1919 #-----------------------------------------------------------1921 1922 rows, idx = gmPG2.run_rw_queries( 1923 queries = [{ 1924 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 1925 'args': {'desc': description, 'l10n_desc': l10n_description} 1926 }], 1927 return_data = True 1928 ) 1929 1930 success = rows[0][0] 1931 if not success: 1932 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 1933 1934 return {'description': description, 'l10n_description': l10n_description}1935 #-----------------------------------------------------------1937 """This will attempt to create a NEW encounter type.""" 1938 1939 # need a system name, so derive one if necessary 1940 if description is None: 1941 description = l10n_description 1942 1943 args = { 1944 'desc': description, 1945 'l10n_desc': l10n_description 1946 } 1947 1948 _log.debug('creating encounter type: %s, %s', description, l10n_description) 1949 1950 # does it exist already ? 1951 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 1952 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1953 1954 # yes 1955 if len(rows) > 0: 1956 # both system and l10n name are the same so all is well 1957 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 1958 _log.info('encounter type [%s] already exists with the proper translation') 1959 return {'description': description, 'l10n_description': l10n_description} 1960 1961 # or maybe there just wasn't a translation to 1962 # the current language for this type yet ? 1963 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 1964 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1965 1966 # there was, so fail 1967 if rows[0][0]: 1968 _log.error('encounter type [%s] already exists but with another translation') 1969 return None 1970 1971 # else set it 1972 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 1973 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1974 return {'description': description, 'l10n_description': l10n_description} 1975 1976 # no 1977 queries = [ 1978 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 1979 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 1980 ] 1981 rows, idx = gmPG2.run_rw_queries(queries = queries) 1982 1983 return {'description': description, 'l10n_description': l10n_description}1984 #-----------------------------------------------------------1986 cmd = u"select _(description) as l10n_description, description from clin.encounter_type" 1987 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 1988 return rows1989 #-----------------------------------------------------------1991 cmd = u"SELECT * from clin.encounter_type where description = %s" 1992 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 1993 return rows1994 #-----------------------------------------------------------1996 cmd = u"delete from clin.encounter_type where description = %(desc)s" 1997 args = {'desc': description} 1998 try: 1999 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2000 except gmPG2.dbapi.IntegrityError, e: 2001 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 2002 return False 2003 raise 2004 2005 return True2006 #============================================================2008 """Represents one problem. 2009 2010 problems are the aggregation of 2011 .clinically_relevant=True issues and 2012 .is_open=True episodes 2013 """ 2014 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 2015 _cmds_store_payload = [u"select 1"] 2016 _updatable_fields = [] 2017 2018 #--------------------------------------------------------2129 #-----------------------------------------------------------2020 """Initialize. 2021 2022 aPK_obj must contain the keys 2023 pk_patient 2024 pk_episode 2025 pk_health_issue 2026 """ 2027 if aPK_obj is None: 2028 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 2029 2030 # As problems are rows from a view of different emr struct items, 2031 # the PK can't be a single field and, as some of the values of the 2032 # composed PK may be None, they must be queried using 'is null', 2033 # so we must programmatically construct the SQL query 2034 where_parts = [] 2035 pk = {} 2036 for col_name in aPK_obj.keys(): 2037 val = aPK_obj[col_name] 2038 if val is None: 2039 where_parts.append('%s IS NULL' % col_name) 2040 else: 2041 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 2042 pk[col_name] = val 2043 2044 # try to instantiate from true problem view 2045 cProblem._cmd_fetch_payload = u""" 2046 SELECT *, False as is_potential_problem 2047 FROM clin.v_problem_list 2048 WHERE %s""" % u' AND '.join(where_parts) 2049 2050 try: 2051 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 2052 return 2053 except gmExceptions.ConstructorError: 2054 _log.exception('actual problem not found, trying "potential" problems') 2055 if try_potential_problems is False: 2056 raise 2057 2058 # try to instantiate from potential-problems view 2059 cProblem._cmd_fetch_payload = u""" 2060 SELECT *, True as is_potential_problem 2061 FROM clin.v_potential_problem_list 2062 WHERE %s""" % u' AND '.join(where_parts) 2063 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)2064 #--------------------------------------------------------2066 """ 2067 Retrieve the cEpisode instance equivalent to this problem. 2068 The problem's type attribute must be 'episode' 2069 """ 2070 if self._payload[self._idx['type']] != 'episode': 2071 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2072 return None 2073 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])2074 #--------------------------------------------------------2076 """ 2077 Retrieve the cHealthIssue instance equivalent to this problem. 2078 The problem's type attribute must be 'issue' 2079 """ 2080 if self._payload[self._idx['type']] != 'issue': 2081 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2082 return None 2083 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])2084 #--------------------------------------------------------2086 2087 if self._payload[self._idx['type']] == u'issue': 2088 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 2089 #xxxxxxxxxxxxx 2090 2091 emr = patient.get_emr() 2092 2093 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 2094 return doc_folder.get_visual_progress_notes ( 2095 health_issue = self._payload[self._idx['pk_health_issue']], 2096 episode = self._payload[self._idx['pk_episode']] 2097 )2098 #-------------------------------------------------------- 2099 # properties 2100 #-------------------------------------------------------- 2101 # doubles as 'diagnostic_certainty_description' getter:2103 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])2104 2105 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 2106 #--------------------------------------------------------2108 if self._payload[self._idx['type']] == u'issue': 2109 cmd = u""" 2110 SELECT * FROM clin.v_linked_codes WHERE 2111 item_table = 'clin.lnk_code2h_issue'::regclass 2112 AND 2113 pk_item = %(item)s 2114 """ 2115 args = {'item': self._payload[self._idx['pk_health_issue']]} 2116 else: 2117 cmd = u""" 2118 SELECT * FROM clin.v_linked_codes WHERE 2119 item_table = 'clin.lnk_code2episode'::regclass 2120 AND 2121 pk_item = %(item)s 2122 """ 2123 args = {'item': self._payload[self._idx['pk_episode']]} 2124 2125 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2126 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]2127 2128 generic_codes = property(_get_generic_codes, lambda x:x)2131 """Retrieve the cEpisode instance equivalent to the given problem. 2132 2133 The problem's type attribute must be 'episode' 2134 2135 @param problem: The problem to retrieve its related episode for 2136 @type problem: A gmEMRStructItems.cProblem instance 2137 """ 2138 if isinstance(problem, cEpisode): 2139 return problem 2140 2141 exc = TypeError('cannot convert [%s] to episode' % problem) 2142 2143 if not isinstance(problem, cProblem): 2144 raise exc 2145 2146 if problem['type'] != 'episode': 2147 raise exc 2148 2149 return cEpisode(aPK_obj = problem['pk_episode'])2150 #-----------------------------------------------------------2152 """Retrieve the cIssue instance equivalent to the given problem. 2153 2154 The problem's type attribute must be 'issue'. 2155 2156 @param problem: The problem to retrieve the corresponding issue for 2157 @type problem: A gmEMRStructItems.cProblem instance 2158 """ 2159 if isinstance(problem, cHealthIssue): 2160 return problem 2161 2162 exc = TypeError('cannot convert [%s] to health issue' % problem) 2163 2164 if not isinstance(problem, cProblem): 2165 raise exc 2166 2167 if problem['type'] != 'issue': 2168 raise exc 2169 2170 return cHealthIssue(aPK_obj = problem['pk_health_issue'])2171 #-----------------------------------------------------------2173 """Transform given problem into either episode or health issue instance. 2174 """ 2175 if isinstance(problem, (cEpisode, cHealthIssue)): 2176 return problem 2177 2178 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 2179 2180 if not isinstance(problem, cProblem): 2181 _log.debug(u'%s' % problem) 2182 raise exc 2183 2184 if problem['type'] == 'episode': 2185 return cEpisode(aPK_obj = problem['pk_episode']) 2186 2187 if problem['type'] == 'issue': 2188 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 2189 2190 raise exc2191 #============================================================2193 2194 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 2195 _cmds_store_payload = [ 2196 u"""update clin.hospital_stay set 2197 clin_when = %(admission)s, 2198 discharge = %(discharge)s, 2199 narrative = gm.nullify_empty_string(%(hospital)s), 2200 fk_episode = %(pk_episode)s, 2201 fk_encounter = %(pk_encounter)s 2202 where 2203 pk = %(pk_hospital_stay)s and 2204 xmin = %(xmin_hospital_stay)s""", 2205 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 2206 ] 2207 _updatable_fields = [ 2208 'admission', 2209 'discharge', 2210 'hospital', 2211 'pk_episode', 2212 'pk_encounter' 2213 ] 2214 #-------------------------------------------------------2233 #-----------------------------------------------------------2216 2217 if self._payload[self._idx['discharge']] is not None: 2218 dis = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2219 else: 2220 dis = u'' 2221 2222 line = u'%s%s%s%s: %s%s%s' % ( 2223 u' ' * left_margin, 2224 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2225 dis, 2226 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 2227 gmTools.u_left_double_angle_quote, 2228 self._payload[self._idx['episode']], 2229 gmTools.u_right_double_angle_quote 2230 ) 2231 2232 return line2235 2236 queries = [{ 2237 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission', 2238 'args': {'pat': patient} 2239 }] 2240 2241 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2242 2243 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]2244 #-----------------------------------------------------------2246 2247 queries = [{ 2248 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk', 2249 'args': {'enc': encounter, 'epi': episode} 2250 }] 2251 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2252 2253 return cHospitalStay(aPK_obj = rows[0][0])2254 #-----------------------------------------------------------2256 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 2257 args = {'pk': stay} 2258 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2259 return True2260 #============================================================2262 2263 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 2264 _cmds_store_payload = [ 2265 u"""UPDATE clin.procedure SET 2266 soap_cat = 'p', 2267 clin_when = %(clin_when)s, 2268 clin_end = %(clin_end)s, 2269 is_ongoing = %(is_ongoing)s, 2270 clin_where = NULLIF ( 2271 COALESCE ( 2272 %(pk_hospital_stay)s::TEXT, 2273 gm.nullify_empty_string(%(clin_where)s) 2274 ), 2275 %(pk_hospital_stay)s::TEXT 2276 ), 2277 narrative = gm.nullify_empty_string(%(performed_procedure)s), 2278 fk_hospital_stay = %(pk_hospital_stay)s, 2279 fk_episode = %(pk_episode)s, 2280 fk_encounter = %(pk_encounter)s 2281 WHERE 2282 pk = %(pk_procedure)s AND 2283 xmin = %(xmin_procedure)s 2284 RETURNING xmin as xmin_procedure""" 2285 ] 2286 _updatable_fields = [ 2287 'clin_when', 2288 'clin_end', 2289 'is_ongoing', 2290 'clin_where', 2291 'performed_procedure', 2292 'pk_hospital_stay', 2293 'pk_episode', 2294 'pk_encounter' 2295 ] 2296 #-------------------------------------------------------2402 #-----------------------------------------------------------2298 2299 if (attribute == 'pk_hospital_stay') and (value is not None): 2300 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 2301 2302 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 2303 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 2304 2305 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)2306 #-------------------------------------------------------2308 2309 if self._payload[self._idx['is_ongoing']]: 2310 end = _(' (ongoing)') 2311 else: 2312 end = self._payload[self._idx['clin_end']] 2313 if end is None: 2314 end = u'' 2315 else: 2316 end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2317 2318 line = u'%s%s%s, %s: %s' % ( 2319 (u' ' * left_margin), 2320 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2321 end, 2322 self._payload[self._idx['clin_where']], 2323 self._payload[self._idx['performed_procedure']] 2324 ) 2325 if include_episode: 2326 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 2327 2328 if include_codes: 2329 codes = self.generic_codes 2330 if len(codes) > 0: 2331 line += u'\n' 2332 for c in codes: 2333 line += u'%s %s: %s (%s - %s)\n' % ( 2334 (u' ' * left_margin), 2335 c['code'], 2336 c['term'], 2337 c['name_short'], 2338 c['version'] 2339 ) 2340 del codes 2341 2342 return line2343 #--------------------------------------------------------2345 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2346 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 2347 args = { 2348 'issue': self._payload[self._idx['pk_procedure']], 2349 'code': pk_code 2350 } 2351 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2352 return True2353 #--------------------------------------------------------2355 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2356 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 2357 args = { 2358 'issue': self._payload[self._idx['pk_procedure']], 2359 'code': pk_code 2360 } 2361 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2362 return True2363 #-------------------------------------------------------- 2364 # properties 2365 #--------------------------------------------------------2367 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 2368 return [] 2369 2370 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2371 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 2372 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2373 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]23742376 queries = [] 2377 # remove all codes 2378 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 2379 queries.append ({ 2380 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 2381 'args': { 2382 'proc': self._payload[self._idx['pk_procedure']], 2383 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 2384 } 2385 }) 2386 # add new codes 2387 for pk_code in pk_codes: 2388 queries.append ({ 2389 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 2390 'args': { 2391 'proc': self._payload[self._idx['pk_procedure']], 2392 'pk_code': pk_code 2393 } 2394 }) 2395 if len(queries) == 0: 2396 return 2397 # run it all in one transaction 2398 rows, idx = gmPG2.run_rw_queries(queries = queries) 2399 return2400 2401 generic_codes = property(_get_generic_codes, _set_generic_codes)2404 2405 queries = [ 2406 { 2407 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 2408 'args': {'pat': patient} 2409 } 2410 ] 2411 2412 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2413 2414 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]2415 #-----------------------------------------------------------2416 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):2417 2418 queries = [{ 2419 'cmd': u""" 2420 INSERT INTO clin.procedure ( 2421 fk_encounter, 2422 fk_episode, 2423 soap_cat, 2424 clin_where, 2425 fk_hospital_stay, 2426 narrative 2427 ) VALUES ( 2428 %(enc)s, 2429 %(epi)s, 2430 'p', 2431 gm.nullify_empty_string(%(loc)s), 2432 %(stay)s, 2433 gm.nullify_empty_string(%(proc)s) 2434 ) 2435 RETURNING pk""", 2436 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 2437 }] 2438 2439 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2440 2441 return cPerformedProcedure(aPK_obj = rows[0][0])2442 #-----------------------------------------------------------2444 cmd = u'delete from clin.procedure where pk = %(pk)s' 2445 args = {'pk': procedure} 2446 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2447 return True2448 #============================================================ 2449 # main - unit testing 2450 #------------------------------------------------------------ 2451 if __name__ == '__main__': 2452 2453 if len(sys.argv) < 2: 2454 sys.exit() 2455 2456 if sys.argv[1] != 'test': 2457 sys.exit() 2458 2459 #-------------------------------------------------------- 2460 # define tests 2461 #--------------------------------------------------------2463 print "\nProblem test" 2464 print "------------" 2465 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 2466 print prob 2467 fields = prob.get_fields() 2468 for field in fields: 2469 print field, ':', prob[field] 2470 print '\nupdatable:', prob.get_updatable_fields() 2471 epi = prob.get_as_episode() 2472 print '\nas episode:' 2473 if epi is not None: 2474 for field in epi.get_fields(): 2475 print ' .%s : %s' % (field, epi[field])2476 #--------------------------------------------------------2478 print "\nhealth issue test" 2479 print "-----------------" 2480 h_issue = cHealthIssue(aPK_obj=2) 2481 print h_issue 2482 fields = h_issue.get_fields() 2483 for field in fields: 2484 print field, ':', h_issue[field] 2485 print "has open episode:", h_issue.has_open_episode() 2486 print "open episode:", h_issue.get_open_episode() 2487 print "updateable:", h_issue.get_updatable_fields() 2488 h_issue.close_expired_episode(ttl=7300) 2489 h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 2490 print h_issue 2491 print h_issue.format_as_journal()2492 #--------------------------------------------------------2494 print "\nepisode test" 2495 print "------------" 2496 episode = cEpisode(aPK_obj=1) 2497 print episode 2498 fields = episode.get_fields() 2499 for field in fields: 2500 print field, ':', episode[field] 2501 print "updatable:", episode.get_updatable_fields() 2502 raw_input('ENTER to continue') 2503 2504 old_description = episode['description'] 2505 old_enc = cEncounter(aPK_obj = 1) 2506 2507 desc = '1-%s' % episode['description'] 2508 print "==> renaming to", desc 2509 successful = episode.rename ( 2510 description = desc 2511 ) 2512 if not successful: 2513 print "error" 2514 else: 2515 print "success" 2516 for field in fields: 2517 print field, ':', episode[field] 2518 2519 print "episode range:", episode.get_access_range() 2520 2521 raw_input('ENTER to continue')2522 2523 #--------------------------------------------------------2525 print "\nencounter test" 2526 print "--------------" 2527 encounter = cEncounter(aPK_obj=1) 2528 print encounter 2529 fields = encounter.get_fields() 2530 for field in fields: 2531 print field, ':', encounter[field] 2532 print "updatable:", encounter.get_updatable_fields()2533 #--------------------------------------------------------2535 encounter = cEncounter(aPK_obj=1) 2536 print encounter 2537 print "" 2538 print encounter.format_latex()2539 #--------------------------------------------------------2541 procs = get_performed_procedures(patient = 12) 2542 for proc in procs: 2543 print proc.format(left_margin=2)2544 #--------------------------------------------------------2546 stay = create_hospital_stay(encounter = 1, episode = 2) 2547 stay['hospital'] = u'Starfleet Galaxy General Hospital' 2548 stay.save_payload() 2549 print stay 2550 for s in get_patient_hospital_stays(12): 2551 print s 2552 delete_hospital_stay(stay['pk_hospital_stay']) 2553 stay = create_hospital_stay(encounter = 1, episode = 4)2554 #--------------------------------------------------------2556 tests = [None, 'A', 'B', 'C', 'D', 'E'] 2557 2558 for t in tests: 2559 print type(t), t 2560 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)2561 #-------------------------------------------------------- 2566 #-------------------------------------------------------- 2567 # run them 2568 #test_episode() 2569 #test_problem() 2570 #test_encounter() 2571 #test_health_issue() 2572 #test_hospital_stay() 2573 #test_performed_procedure() 2574 #test_diagnostic_certainty_classification_map() 2575 #test_encounter2latex() 2576 test_episode_codes() 2577 #============================================================ 2578
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Thu Jul 28 03:57:02 2011 | http://epydoc.sourceforge.net |