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 #--------------------------------------------------------1152 #============================================================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 ( 1069 episodes = [ self._payload[self._idx['pk_episode']] ], 1070 order_by = u'date_given DESC, vaccine' 1071 ) 1072 1073 if len(vaccs) > 0: 1074 lines.append(u'') 1075 lines.append(_('Vaccinations:')) 1076 1077 for vacc in vaccs: 1078 lines.extend(vacc.format ( 1079 with_indications = True, 1080 with_comment = True, 1081 with_reaction = True, 1082 date_format = '%Y-%m-%d' 1083 )) 1084 del vaccs 1085 1086 left_margin = u' ' * left_margin 1087 eol_w_margin = u'\n%s' % left_margin 1088 return left_margin + eol_w_margin.join(lines) + u'\n'1089 #-------------------------------------------------------- 1090 # properties 1091 #--------------------------------------------------------1093 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])1094 1095 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 1096 #--------------------------------------------------------1098 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 1099 return [] 1100 1101 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1102 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 1103 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1104 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]11051107 queries = [] 1108 # remove all codes 1109 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 1110 queries.append ({ 1111 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s', 1112 'args': { 1113 'epi': self._payload[self._idx['pk_episode']], 1114 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 1115 } 1116 }) 1117 # add new codes 1118 for pk_code in pk_codes: 1119 queries.append ({ 1120 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)', 1121 'args': { 1122 'epi': self._payload[self._idx['pk_episode']], 1123 'pk_code': pk_code 1124 } 1125 }) 1126 if len(queries) == 0: 1127 return 1128 # run it all in one transaction 1129 rows, idx = gmPG2.run_rw_queries(queries = queries) 1130 return1131 1132 generic_codes = property(_get_generic_codes, _set_generic_codes) 1133 #--------------------------------------------------------1135 cmd = u"""SELECT EXISTS ( 1136 SELECT 1 FROM clin.clin_narrative 1137 WHERE 1138 fk_episode = %(epi)s 1139 AND 1140 fk_encounter IN ( 1141 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1142 ) 1143 )""" 1144 args = { 1145 u'pat': self._payload[self._idx['pk_patient']], 1146 u'epi': self._payload[self._idx['pk_episode']] 1147 } 1148 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1149 return rows[0][0]1150 1151 has_narrative = property(_get_has_narrative, lambda x:x)1153 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):1154 """Creates a new episode for a given patient's health issue. 1155 1156 pk_health_issue - given health issue PK 1157 episode_name - name of episode 1158 """ 1159 if not allow_dupes: 1160 try: 1161 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 1162 if episode['episode_open'] != is_open: 1163 episode['episode_open'] = is_open 1164 episode.save_payload() 1165 return episode 1166 except gmExceptions.ConstructorError: 1167 pass 1168 1169 queries = [] 1170 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 1171 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 1172 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 1173 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 1174 1175 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 1176 return episode1177 #-----------------------------------------------------------1179 if isinstance(episode, cEpisode): 1180 pk = episode['pk_episode'] 1181 else: 1182 pk = int(episode) 1183 1184 try: 1185 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.episode where pk=%(pk)s', 'args': {'pk': pk}}]) 1186 except gmPG2.dbapi.IntegrityError: 1187 # should be parsing pgcode/and or error message 1188 _log.exception('cannot delete episode') 1189 raise gmExceptions.DatabaseObjectInUseError('cannot delete episode, it is in use')1190 #-----------------------------------------------------------1192 return cProblem ( 1193 aPK_obj = { 1194 'pk_patient': episode['pk_patient'], 1195 'pk_episode': episode['pk_episode'], 1196 'pk_health_issue': episode['pk_health_issue'] 1197 }, 1198 try_potential_problems = allow_closed 1199 )1200 #============================================================ 1201 # encounter API 1202 #============================================================1204 """Represents one encounter.""" 1205 1206 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 1207 _cmds_store_payload = [ 1208 u"""update clin.encounter set 1209 started = %(started)s, 1210 last_affirmed = %(last_affirmed)s, 1211 fk_location = %(pk_location)s, 1212 fk_type = %(pk_type)s, 1213 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1214 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1215 where 1216 pk = %(pk_encounter)s and 1217 xmin = %(xmin_encounter)s""", 1218 u"""select xmin_encounter from clin.v_pat_encounters where pk_encounter=%(pk_encounter)s""" 1219 ] 1220 _updatable_fields = [ 1221 'started', 1222 'last_affirmed', 1223 'pk_location', 1224 'pk_type', 1225 'reason_for_encounter', 1226 'assessment_of_encounter' 1227 ] 1228 #--------------------------------------------------------1883 #-----------------------------------------------------------1230 """Set the encounter as the active one. 1231 1232 "Setting active" means making sure the encounter 1233 row has the youngest "last_affirmed" timestamp of 1234 all encounter rows for this patient. 1235 """ 1236 self['last_affirmed'] = gmDateTime.pydt_now_here() 1237 self.save()1238 #--------------------------------------------------------1240 """ 1241 Moves every element currently linked to the current encounter 1242 and the source_episode onto target_episode. 1243 1244 @param source_episode The episode the elements are currently linked to. 1245 @type target_episode A cEpisode intance. 1246 @param target_episode The episode the elements will be relinked to. 1247 @type target_episode A cEpisode intance. 1248 """ 1249 if source_episode['pk_episode'] == target_episode['pk_episode']: 1250 return True 1251 1252 queries = [] 1253 cmd = u""" 1254 UPDATE clin.clin_root_item 1255 SET fk_episode = %(trg)s 1256 WHERE 1257 fk_encounter = %(enc)s AND 1258 fk_episode = %(src)s 1259 """ 1260 rows, idx = gmPG2.run_rw_queries(queries = [{ 1261 'cmd': cmd, 1262 'args': { 1263 'trg': target_episode['pk_episode'], 1264 'enc': self.pk_obj, 1265 'src': source_episode['pk_episode'] 1266 } 1267 }]) 1268 self.refetch_payload() 1269 return True1270 #--------------------------------------------------------1272 1273 relevant_fields = [ 1274 'pk_location', 1275 'pk_type', 1276 'pk_patient', 1277 'reason_for_encounter', 1278 'assessment_of_encounter' 1279 ] 1280 for field in relevant_fields: 1281 if self._payload[self._idx[field]] != another_object[field]: 1282 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1283 return False 1284 1285 relevant_fields = [ 1286 'started', 1287 'last_affirmed', 1288 ] 1289 for field in relevant_fields: 1290 if self._payload[self._idx[field]] is None: 1291 if another_object[field] is None: 1292 continue 1293 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1294 return False 1295 1296 if another_object[field] is None: 1297 return False 1298 1299 #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'): 1300 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'): 1301 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1302 return False 1303 1304 return True1305 #--------------------------------------------------------1307 cmd = u""" 1308 select exists ( 1309 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 1310 union all 1311 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1312 )""" 1313 args = { 1314 'pat': self._payload[self._idx['pk_patient']], 1315 'enc': self.pk_obj 1316 } 1317 rows, idx = gmPG2.run_ro_queries ( 1318 queries = [{ 1319 'cmd': cmd, 1320 'args': args 1321 }] 1322 ) 1323 return rows[0][0]1324 #--------------------------------------------------------1326 cmd = u""" 1327 select exists ( 1328 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 1329 )""" 1330 args = { 1331 'pat': self._payload[self._idx['pk_patient']], 1332 'enc': self.pk_obj 1333 } 1334 rows, idx = gmPG2.run_ro_queries ( 1335 queries = [{ 1336 'cmd': cmd, 1337 'args': args 1338 }] 1339 ) 1340 return rows[0][0]1341 #--------------------------------------------------------1343 """soap_cats: <space> = admin category""" 1344 1345 if soap_cats is None: 1346 soap_cats = u'soap ' 1347 else: 1348 soap_cats = soap_cats.lower() 1349 1350 cats = [] 1351 for cat in soap_cats: 1352 if cat in u'soap': 1353 cats.append(cat) 1354 continue 1355 if cat == u' ': 1356 cats.append(None) 1357 1358 cmd = u""" 1359 SELECT EXISTS ( 1360 SELECT 1 FROM clin.clin_narrative 1361 WHERE 1362 fk_encounter = %(enc)s 1363 AND 1364 soap_cat IN %(cats)s 1365 LIMIT 1 1366 ) 1367 """ 1368 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 1369 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 1370 return rows[0][0]1371 #--------------------------------------------------------1373 cmd = u""" 1374 select exists ( 1375 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1376 )""" 1377 args = { 1378 'pat': self._payload[self._idx['pk_patient']], 1379 'enc': self.pk_obj 1380 } 1381 rows, idx = gmPG2.run_ro_queries ( 1382 queries = [{ 1383 'cmd': cmd, 1384 'args': args 1385 }] 1386 ) 1387 return rows[0][0]1388 #--------------------------------------------------------1390 1391 if soap_cat is not None: 1392 soap_cat = soap_cat.lower() 1393 1394 if episode is None: 1395 epi_part = u'fk_episode is null' 1396 else: 1397 epi_part = u'fk_episode = %(epi)s' 1398 1399 cmd = u""" 1400 select narrative 1401 from clin.clin_narrative 1402 where 1403 fk_encounter = %%(enc)s 1404 and 1405 soap_cat = %%(cat)s 1406 and 1407 %s 1408 order by clin_when desc 1409 limit 1 1410 """ % epi_part 1411 1412 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 1413 1414 rows, idx = gmPG2.run_ro_queries ( 1415 queries = [{ 1416 'cmd': cmd, 1417 'args': args 1418 }] 1419 ) 1420 if len(rows) == 0: 1421 return None 1422 1423 return rows[0][0]1424 #--------------------------------------------------------1426 cmd = u""" 1427 SELECT * FROM clin.v_pat_episodes 1428 WHERE 1429 pk_episode IN ( 1430 1431 SELECT DISTINCT fk_episode 1432 FROM clin.clin_root_item 1433 WHERE fk_encounter = %%(enc)s 1434 1435 UNION 1436 1437 SELECT DISTINCT fk_episode 1438 FROM blobs.doc_med 1439 WHERE fk_encounter = %%(enc)s 1440 ) 1441 %s""" 1442 args = {'enc': self.pk_obj} 1443 if exclude is not None: 1444 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s' 1445 args['excluded'] = tuple(exclude) 1446 else: 1447 cmd = cmd % u'' 1448 1449 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1450 1451 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]1452 #--------------------------------------------------------1454 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1455 if field == u'rfe': 1456 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1457 elif field == u'aoe': 1458 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 1459 else: 1460 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1461 args = { 1462 'item': self._payload[self._idx['pk_encounter']], 1463 'code': pk_code 1464 } 1465 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1466 return True1467 #--------------------------------------------------------1469 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1470 if field == u'rfe': 1471 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1472 elif field == u'aoe': 1473 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1474 else: 1475 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 1476 args = { 1477 'item': self._payload[self._idx['pk_encounter']], 1478 'code': pk_code 1479 } 1480 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1481 return True1482 #--------------------------------------------------------1483 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soap', emr=None, issues=None):1484 1485 lines = [] 1486 for soap_cat in soap_cats: 1487 soap_cat_narratives = emr.get_clin_narrative ( 1488 episodes = episodes, 1489 issues = issues, 1490 encounters = [self._payload[self._idx['pk_encounter']]], 1491 soap_cats = [soap_cat] 1492 ) 1493 if soap_cat_narratives is None: 1494 continue 1495 if len(soap_cat_narratives) == 0: 1496 continue 1497 1498 lines.append(u'%s%s %s %s' % ( 1499 gmTools.u_box_top_left_arc, 1500 gmTools.u_box_horiz_single, 1501 gmClinNarrative.soap_cat2l10n_str[soap_cat], 1502 gmTools.u_box_horiz_single * 5 1503 )) 1504 for soap_entry in soap_cat_narratives: 1505 txt = gmTools.wrap ( 1506 text = soap_entry['narrative'], 1507 width = 75, 1508 initial_indent = u'', 1509 subsequent_indent = (u' ' * left_margin) 1510 ) 1511 lines.append(txt) 1512 when = gmDateTime.pydt_strftime ( 1513 soap_entry['date'], 1514 format = '%Y-%m-%d %H:%M', 1515 accuracy = gmDateTime.acc_minutes 1516 ) 1517 txt = u'%s%s %.8s, %s %s' % ( 1518 u' ' * 40, 1519 gmTools.u_box_horiz_light_heavy, 1520 soap_entry['provider'], 1521 when, 1522 gmTools.u_box_horiz_heavy_light 1523 ) 1524 lines.append(txt) 1525 lines.append('') 1526 1527 return lines1528 #--------------------------------------------------------1530 1531 nothing2format = ( 1532 (self._payload[self._idx['reason_for_encounter']] is None) 1533 and 1534 (self._payload[self._idx['assessment_of_encounter']] is None) 1535 and 1536 (self.has_soap_narrative(soap_cats = u'soap') is False) 1537 ) 1538 if nothing2format: 1539 return u'' 1540 1541 if date_format is None: 1542 date_format = '%A, %B %d %Y' 1543 1544 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 1545 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 1546 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()), 1547 self._payload[self._idx['started']].strftime('%H:%M'), 1548 self._payload[self._idx['last_affirmed']].strftime('%H:%M') 1549 ) 1550 tex += u'\\hline \\tabularnewline \n' 1551 1552 for epi in self.get_episodes(): 1553 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 1554 if len(soaps) == 0: 1555 continue 1556 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1557 gmTools.tex_escape_string(_('Problem')), 1558 gmTools.tex_escape_string(epi['description']), 1559 gmTools.coalesce ( 1560 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 1561 instead = u'', 1562 template_initial = u' {\\footnotesize [%s]}', 1563 none_equivalents = [None, u''] 1564 ) 1565 ) 1566 if epi['pk_health_issue'] is not None: 1567 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1568 gmTools.tex_escape_string(_('Health issue')), 1569 gmTools.tex_escape_string(epi['health_issue']), 1570 gmTools.coalesce ( 1571 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 1572 instead = u'', 1573 template_initial = u' {\\footnotesize [%s]}', 1574 none_equivalents = [None, u''] 1575 ) 1576 ) 1577 for soap in soaps: 1578 tex += u'{\\small %s} & {\\small %s} \\tabularnewline \n' % ( 1579 gmClinNarrative.soap_cat2l10n[soap['soap_cat']], 1580 gmTools.tex_escape_string(soap['narrative'].strip(u'\n')) 1581 ) 1582 tex += u' & \\tabularnewline \n' 1583 1584 if self._payload[self._idx['reason_for_encounter']] is not None: 1585 tex += u'%s & %s \\tabularnewline \n' % ( 1586 gmTools.tex_escape_string(_('RFE')), 1587 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 1588 ) 1589 if self._payload[self._idx['assessment_of_encounter']] is not None: 1590 tex += u'%s & %s \\tabularnewline \n' % ( 1591 gmTools.tex_escape_string(_('AOE')), 1592 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 1593 ) 1594 1595 tex += u'\\hline \\tabularnewline \n' 1596 tex += u' & \\tabularnewline \n' 1597 1598 return tex1599 #--------------------------------------------------------1600 - 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):1601 """Format an encounter. 1602 1603 with_co_encountlet_hints: 1604 - whether to include which *other* episodes were discussed during this encounter 1605 - (only makes sense if episodes != None) 1606 """ 1607 lines = [] 1608 1609 if fancy_header: 1610 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1611 u' ' * left_margin, 1612 self._payload[self._idx['l10n_type']], 1613 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1614 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1615 self._payload[self._idx['source_time_zone']], 1616 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 1617 self._payload[self._idx['pk_encounter']] 1618 )) 1619 1620 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1621 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1622 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1623 gmDateTime.current_local_iso_numeric_timezone_string, 1624 gmTools.bool2subst ( 1625 gmDateTime.dst_currently_in_effect, 1626 gmDateTime.py_dst_timezone_name, 1627 gmDateTime.py_timezone_name 1628 ), 1629 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1630 )) 1631 1632 if self._payload[self._idx['reason_for_encounter']] is not None: 1633 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1634 codes = self.generic_codes_rfe 1635 for c in codes: 1636 lines.append(u' %s: %s (%s - %s)' % ( 1637 c['code'], 1638 c['term'], 1639 c['name_short'], 1640 c['version'] 1641 )) 1642 if len(codes) > 0: 1643 lines.append(u'') 1644 1645 if self._payload[self._idx['assessment_of_encounter']] is not None: 1646 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1647 codes = self.generic_codes_aoe 1648 for c in codes: 1649 lines.append(u' %s: %s (%s - %s)' % ( 1650 c['code'], 1651 c['term'], 1652 c['name_short'], 1653 c['version'] 1654 )) 1655 if len(codes) > 0: 1656 lines.append(u'') 1657 del codes 1658 1659 else: 1660 lines.append(u'%s%s: %s - %s%s' % ( 1661 u' ' * left_margin, 1662 self._payload[self._idx['l10n_type']], 1663 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1664 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1665 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1666 )) 1667 if with_rfe_aoe: 1668 if self._payload[self._idx['reason_for_encounter']] is not None: 1669 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1670 codes = self.generic_codes_rfe 1671 for c in codes: 1672 lines.append(u' %s: %s (%s - %s)' % ( 1673 c['code'], 1674 c['term'], 1675 c['name_short'], 1676 c['version'] 1677 )) 1678 if len(codes) > 0: 1679 lines.append(u'') 1680 if self._payload[self._idx['assessment_of_encounter']] is not None: 1681 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1682 codes = self.generic_codes_aoe 1683 if len(codes) > 0: 1684 lines.append(u'') 1685 for c in codes: 1686 lines.append(u' %s: %s (%s - %s)' % ( 1687 c['code'], 1688 c['term'], 1689 c['name_short'], 1690 c['version'] 1691 )) 1692 if len(codes) > 0: 1693 lines.append(u'') 1694 del codes 1695 1696 if with_soap: 1697 lines.append(u'') 1698 1699 if patient.ID != self._payload[self._idx['pk_patient']]: 1700 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 1701 patient.ID, 1702 self._payload[self._idx['pk_encounter']], 1703 self._payload[self._idx['pk_patient']] 1704 ) 1705 raise ValueError(msg) 1706 1707 emr = patient.get_emr() 1708 1709 lines.extend(self.format_soap ( 1710 episodes = episodes, 1711 left_margin = left_margin, 1712 soap_cats = 'soap', 1713 emr = emr, 1714 issues = issues 1715 )) 1716 1717 # # family history 1718 # if with_family_history: 1719 # if episodes is not None: 1720 # fhx = emr.get_family_history(episodes = episodes) 1721 # if len(fhx) > 0: 1722 # lines.append(u'') 1723 # lines.append(_('Family History: %s') % len(fhx)) 1724 # for f in fhx: 1725 # lines.append(f.format ( 1726 # left_margin = (left_margin + 1), 1727 # include_episode = False, 1728 # include_comment = True 1729 # )) 1730 # del fhx 1731 1732 # test results 1733 if with_tests: 1734 tests = emr.get_test_results_by_date ( 1735 episodes = episodes, 1736 encounter = self._payload[self._idx['pk_encounter']] 1737 ) 1738 if len(tests) > 0: 1739 lines.append('') 1740 lines.append(_('Measurements and Results:')) 1741 1742 for t in tests: 1743 lines.extend(t.format()) 1744 1745 del tests 1746 1747 # vaccinations 1748 if with_vaccinations: 1749 vaccs = emr.get_vaccinations ( 1750 episodes = episodes, 1751 encounters = [ self._payload[self._idx['pk_encounter']] ], 1752 order_by = u'date_given DESC, vaccine' 1753 ) 1754 1755 if len(vaccs) > 0: 1756 lines.append(u'') 1757 lines.append(_('Vaccinations:')) 1758 1759 for vacc in vaccs: 1760 lines.extend(vacc.format ( 1761 with_indications = True, 1762 with_comment = True, 1763 with_reaction = True, 1764 date_format = '%Y-%m-%d' 1765 )) 1766 del vaccs 1767 1768 # documents 1769 if with_docs: 1770 doc_folder = patient.get_document_folder() 1771 docs = doc_folder.get_documents ( 1772 episodes = episodes, 1773 encounter = self._payload[self._idx['pk_encounter']] 1774 ) 1775 1776 if len(docs) > 0: 1777 lines.append(u'') 1778 lines.append(_('Documents:')) 1779 1780 for d in docs: 1781 lines.append(u' %s %s:%s%s' % ( 1782 d['clin_when'].strftime('%Y-%m-%d'), 1783 d['l10n_type'], 1784 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1785 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1786 )) 1787 1788 del docs 1789 1790 # co-encountlets 1791 if with_co_encountlet_hints: 1792 if episodes is not None: 1793 other_epis = self.get_episodes(exclude = episodes) 1794 if len(other_epis) > 0: 1795 lines.append(u'') 1796 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 1797 for epi in other_epis: 1798 lines.append(u' %s%s%s%s' % ( 1799 gmTools.u_left_double_angle_quote, 1800 epi['description'], 1801 gmTools.u_right_double_angle_quote, 1802 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 1803 )) 1804 1805 eol_w_margin = u'\n%s' % (u' ' * left_margin) 1806 return u'%s\n' % eol_w_margin.join(lines)1807 #-------------------------------------------------------- 1808 # properties 1809 #--------------------------------------------------------1811 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 1812 return [] 1813 1814 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1815 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 1816 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1817 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]18181820 queries = [] 1821 # remove all codes 1822 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 1823 queries.append ({ 1824 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 1825 'args': { 1826 'enc': self._payload[self._idx['pk_encounter']], 1827 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 1828 } 1829 }) 1830 # add new codes 1831 for pk_code in pk_codes: 1832 queries.append ({ 1833 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 1834 'args': { 1835 'enc': self._payload[self._idx['pk_encounter']], 1836 'pk_code': pk_code 1837 } 1838 }) 1839 if len(queries) == 0: 1840 return 1841 # run it all in one transaction 1842 rows, idx = gmPG2.run_rw_queries(queries = queries) 1843 return1844 1845 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 1846 #--------------------------------------------------------1848 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 1849 return [] 1850 1851 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 1852 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 1853 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1854 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]18551857 queries = [] 1858 # remove all codes 1859 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 1860 queries.append ({ 1861 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 1862 'args': { 1863 'enc': self._payload[self._idx['pk_encounter']], 1864 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 1865 } 1866 }) 1867 # add new codes 1868 for pk_code in pk_codes: 1869 queries.append ({ 1870 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 1871 'args': { 1872 'enc': self._payload[self._idx['pk_encounter']], 1873 'pk_code': pk_code 1874 } 1875 }) 1876 if len(queries) == 0: 1877 return 1878 # run it all in one transaction 1879 rows, idx = gmPG2.run_rw_queries(queries = queries) 1880 return1881 1882 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)1885 """Creates a new encounter for a patient. 1886 1887 fk_patient - patient PK 1888 fk_location - encounter location 1889 enc_type - type of encounter 1890 1891 FIXME: we don't deal with location yet 1892 """ 1893 if enc_type is None: 1894 enc_type = u'in surgery' 1895 # insert new encounter 1896 queries = [] 1897 try: 1898 enc_type = int(enc_type) 1899 cmd = u""" 1900 INSERT INTO clin.encounter ( 1901 fk_patient, fk_location, fk_type 1902 ) VALUES ( 1903 %(pat)s, 1904 -1, 1905 %(typ)s 1906 ) RETURNING pk""" 1907 except ValueError: 1908 enc_type = enc_type 1909 cmd = u""" 1910 insert into clin.encounter ( 1911 fk_patient, fk_location, fk_type 1912 ) values ( 1913 %(pat)s, 1914 -1, 1915 coalesce((select pk from clin.encounter_type where description = %(typ)s), 0) 1916 ) RETURNING pk""" 1917 args = {'pat': fk_patient, 'typ': enc_type} 1918 queries.append({'cmd': cmd, 'args': args}) 1919 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 1920 encounter = cEncounter(aPK_obj = rows[0]['pk']) 1921 1922 return encounter1923 #-----------------------------------------------------------1925 1926 rows, idx = gmPG2.run_rw_queries( 1927 queries = [{ 1928 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 1929 'args': {'desc': description, 'l10n_desc': l10n_description} 1930 }], 1931 return_data = True 1932 ) 1933 1934 success = rows[0][0] 1935 if not success: 1936 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 1937 1938 return {'description': description, 'l10n_description': l10n_description}1939 #-----------------------------------------------------------1941 """This will attempt to create a NEW encounter type.""" 1942 1943 # need a system name, so derive one if necessary 1944 if description is None: 1945 description = l10n_description 1946 1947 args = { 1948 'desc': description, 1949 'l10n_desc': l10n_description 1950 } 1951 1952 _log.debug('creating encounter type: %s, %s', description, l10n_description) 1953 1954 # does it exist already ? 1955 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 1956 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1957 1958 # yes 1959 if len(rows) > 0: 1960 # both system and l10n name are the same so all is well 1961 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 1962 _log.info('encounter type [%s] already exists with the proper translation') 1963 return {'description': description, 'l10n_description': l10n_description} 1964 1965 # or maybe there just wasn't a translation to 1966 # the current language for this type yet ? 1967 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 1968 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1969 1970 # there was, so fail 1971 if rows[0][0]: 1972 _log.error('encounter type [%s] already exists but with another translation') 1973 return None 1974 1975 # else set it 1976 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 1977 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1978 return {'description': description, 'l10n_description': l10n_description} 1979 1980 # no 1981 queries = [ 1982 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 1983 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 1984 ] 1985 rows, idx = gmPG2.run_rw_queries(queries = queries) 1986 1987 return {'description': description, 'l10n_description': l10n_description}1988 #-----------------------------------------------------------1990 cmd = u""" 1991 SELECT 1992 _(description) AS l10n_description, 1993 description 1994 FROM 1995 clin.encounter_type 1996 ORDER BY 1997 l10n_description 1998 """ 1999 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 2000 return rows2001 #-----------------------------------------------------------2003 cmd = u"SELECT * from clin.encounter_type where description = %s" 2004 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 2005 return rows2006 #-----------------------------------------------------------2008 cmd = u"delete from clin.encounter_type where description = %(desc)s" 2009 args = {'desc': description} 2010 try: 2011 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2012 except gmPG2.dbapi.IntegrityError, e: 2013 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 2014 return False 2015 raise 2016 2017 return True2018 #============================================================2020 """Represents one problem. 2021 2022 problems are the aggregation of 2023 .clinically_relevant=True issues and 2024 .is_open=True episodes 2025 """ 2026 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 2027 _cmds_store_payload = [u"select 1"] 2028 _updatable_fields = [] 2029 2030 #--------------------------------------------------------2141 #-----------------------------------------------------------2032 """Initialize. 2033 2034 aPK_obj must contain the keys 2035 pk_patient 2036 pk_episode 2037 pk_health_issue 2038 """ 2039 if aPK_obj is None: 2040 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 2041 2042 # As problems are rows from a view of different emr struct items, 2043 # the PK can't be a single field and, as some of the values of the 2044 # composed PK may be None, they must be queried using 'is null', 2045 # so we must programmatically construct the SQL query 2046 where_parts = [] 2047 pk = {} 2048 for col_name in aPK_obj.keys(): 2049 val = aPK_obj[col_name] 2050 if val is None: 2051 where_parts.append('%s IS NULL' % col_name) 2052 else: 2053 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 2054 pk[col_name] = val 2055 2056 # try to instantiate from true problem view 2057 cProblem._cmd_fetch_payload = u""" 2058 SELECT *, False as is_potential_problem 2059 FROM clin.v_problem_list 2060 WHERE %s""" % u' AND '.join(where_parts) 2061 2062 try: 2063 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 2064 return 2065 except gmExceptions.ConstructorError: 2066 _log.exception('actual problem not found, trying "potential" problems') 2067 if try_potential_problems is False: 2068 raise 2069 2070 # try to instantiate from potential-problems view 2071 cProblem._cmd_fetch_payload = u""" 2072 SELECT *, True as is_potential_problem 2073 FROM clin.v_potential_problem_list 2074 WHERE %s""" % u' AND '.join(where_parts) 2075 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)2076 #--------------------------------------------------------2078 """ 2079 Retrieve the cEpisode instance equivalent to this problem. 2080 The problem's type attribute must be 'episode' 2081 """ 2082 if self._payload[self._idx['type']] != 'episode': 2083 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2084 return None 2085 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])2086 #--------------------------------------------------------2088 """ 2089 Retrieve the cHealthIssue instance equivalent to this problem. 2090 The problem's type attribute must be 'issue' 2091 """ 2092 if self._payload[self._idx['type']] != 'issue': 2093 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 2094 return None 2095 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])2096 #--------------------------------------------------------2098 2099 if self._payload[self._idx['type']] == u'issue': 2100 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 2101 #xxxxxxxxxxxxx 2102 2103 emr = patient.get_emr() 2104 2105 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 2106 return doc_folder.get_visual_progress_notes ( 2107 health_issue = self._payload[self._idx['pk_health_issue']], 2108 episode = self._payload[self._idx['pk_episode']] 2109 )2110 #-------------------------------------------------------- 2111 # properties 2112 #-------------------------------------------------------- 2113 # doubles as 'diagnostic_certainty_description' getter:2115 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])2116 2117 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 2118 #--------------------------------------------------------2120 if self._payload[self._idx['type']] == u'issue': 2121 cmd = u""" 2122 SELECT * FROM clin.v_linked_codes WHERE 2123 item_table = 'clin.lnk_code2h_issue'::regclass 2124 AND 2125 pk_item = %(item)s 2126 """ 2127 args = {'item': self._payload[self._idx['pk_health_issue']]} 2128 else: 2129 cmd = u""" 2130 SELECT * FROM clin.v_linked_codes WHERE 2131 item_table = 'clin.lnk_code2episode'::regclass 2132 AND 2133 pk_item = %(item)s 2134 """ 2135 args = {'item': self._payload[self._idx['pk_episode']]} 2136 2137 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2138 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]2139 2140 generic_codes = property(_get_generic_codes, lambda x:x)2143 """Retrieve the cEpisode instance equivalent to the given problem. 2144 2145 The problem's type attribute must be 'episode' 2146 2147 @param problem: The problem to retrieve its related episode for 2148 @type problem: A gmEMRStructItems.cProblem instance 2149 """ 2150 if isinstance(problem, cEpisode): 2151 return problem 2152 2153 exc = TypeError('cannot convert [%s] to episode' % problem) 2154 2155 if not isinstance(problem, cProblem): 2156 raise exc 2157 2158 if problem['type'] != 'episode': 2159 raise exc 2160 2161 return cEpisode(aPK_obj = problem['pk_episode'])2162 #-----------------------------------------------------------2164 """Retrieve the cIssue instance equivalent to the given problem. 2165 2166 The problem's type attribute must be 'issue'. 2167 2168 @param problem: The problem to retrieve the corresponding issue for 2169 @type problem: A gmEMRStructItems.cProblem instance 2170 """ 2171 if isinstance(problem, cHealthIssue): 2172 return problem 2173 2174 exc = TypeError('cannot convert [%s] to health issue' % problem) 2175 2176 if not isinstance(problem, cProblem): 2177 raise exc 2178 2179 if problem['type'] != 'issue': 2180 raise exc 2181 2182 return cHealthIssue(aPK_obj = problem['pk_health_issue'])2183 #-----------------------------------------------------------2185 """Transform given problem into either episode or health issue instance. 2186 """ 2187 if isinstance(problem, (cEpisode, cHealthIssue)): 2188 return problem 2189 2190 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 2191 2192 if not isinstance(problem, cProblem): 2193 _log.debug(u'%s' % problem) 2194 raise exc 2195 2196 if problem['type'] == 'episode': 2197 return cEpisode(aPK_obj = problem['pk_episode']) 2198 2199 if problem['type'] == 'issue': 2200 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 2201 2202 raise exc2203 #============================================================2205 2206 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 2207 _cmds_store_payload = [ 2208 u"""update clin.hospital_stay set 2209 clin_when = %(admission)s, 2210 discharge = %(discharge)s, 2211 narrative = gm.nullify_empty_string(%(hospital)s), 2212 fk_episode = %(pk_episode)s, 2213 fk_encounter = %(pk_encounter)s 2214 where 2215 pk = %(pk_hospital_stay)s and 2216 xmin = %(xmin_hospital_stay)s""", 2217 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 2218 ] 2219 _updatable_fields = [ 2220 'admission', 2221 'discharge', 2222 'hospital', 2223 'pk_episode', 2224 'pk_encounter' 2225 ] 2226 #-------------------------------------------------------2245 #-----------------------------------------------------------2228 2229 if self._payload[self._idx['discharge']] is not None: 2230 dis = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2231 else: 2232 dis = u'' 2233 2234 line = u'%s%s%s%s: %s%s%s' % ( 2235 u' ' * left_margin, 2236 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2237 dis, 2238 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 2239 gmTools.u_left_double_angle_quote, 2240 self._payload[self._idx['episode']], 2241 gmTools.u_right_double_angle_quote 2242 ) 2243 2244 return line2247 2248 queries = [{ 2249 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission', 2250 'args': {'pat': patient} 2251 }] 2252 2253 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2254 2255 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]2256 #-----------------------------------------------------------2258 2259 queries = [{ 2260 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk', 2261 'args': {'enc': encounter, 'epi': episode} 2262 }] 2263 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2264 2265 return cHospitalStay(aPK_obj = rows[0][0])2266 #-----------------------------------------------------------2268 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 2269 args = {'pk': stay} 2270 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2271 return True2272 #============================================================2274 2275 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 2276 _cmds_store_payload = [ 2277 u"""UPDATE clin.procedure SET 2278 soap_cat = 'p', 2279 clin_when = %(clin_when)s, 2280 clin_end = %(clin_end)s, 2281 is_ongoing = %(is_ongoing)s, 2282 clin_where = NULLIF ( 2283 COALESCE ( 2284 %(pk_hospital_stay)s::TEXT, 2285 gm.nullify_empty_string(%(clin_where)s) 2286 ), 2287 %(pk_hospital_stay)s::TEXT 2288 ), 2289 narrative = gm.nullify_empty_string(%(performed_procedure)s), 2290 fk_hospital_stay = %(pk_hospital_stay)s, 2291 fk_episode = %(pk_episode)s, 2292 fk_encounter = %(pk_encounter)s 2293 WHERE 2294 pk = %(pk_procedure)s AND 2295 xmin = %(xmin_procedure)s 2296 RETURNING xmin as xmin_procedure""" 2297 ] 2298 _updatable_fields = [ 2299 'clin_when', 2300 'clin_end', 2301 'is_ongoing', 2302 'clin_where', 2303 'performed_procedure', 2304 'pk_hospital_stay', 2305 'pk_episode', 2306 'pk_encounter' 2307 ] 2308 #-------------------------------------------------------2414 #-----------------------------------------------------------2310 2311 if (attribute == 'pk_hospital_stay') and (value is not None): 2312 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 2313 2314 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 2315 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 2316 2317 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)2318 #-------------------------------------------------------2320 2321 if self._payload[self._idx['is_ongoing']]: 2322 end = _(' (ongoing)') 2323 else: 2324 end = self._payload[self._idx['clin_end']] 2325 if end is None: 2326 end = u'' 2327 else: 2328 end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding()) 2329 2330 line = u'%s%s%s, %s: %s' % ( 2331 (u' ' * left_margin), 2332 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 2333 end, 2334 self._payload[self._idx['clin_where']], 2335 self._payload[self._idx['performed_procedure']] 2336 ) 2337 if include_episode: 2338 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 2339 2340 if include_codes: 2341 codes = self.generic_codes 2342 if len(codes) > 0: 2343 line += u'\n' 2344 for c in codes: 2345 line += u'%s %s: %s (%s - %s)\n' % ( 2346 (u' ' * left_margin), 2347 c['code'], 2348 c['term'], 2349 c['name_short'], 2350 c['version'] 2351 ) 2352 del codes 2353 2354 return line2355 #--------------------------------------------------------2357 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2358 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 2359 args = { 2360 'issue': self._payload[self._idx['pk_procedure']], 2361 'code': pk_code 2362 } 2363 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2364 return True2365 #--------------------------------------------------------2367 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2368 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 2369 args = { 2370 'issue': self._payload[self._idx['pk_procedure']], 2371 'code': pk_code 2372 } 2373 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2374 return True2375 #-------------------------------------------------------- 2376 # properties 2377 #--------------------------------------------------------2379 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 2380 return [] 2381 2382 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 2383 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 2384 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2385 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]23862388 queries = [] 2389 # remove all codes 2390 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 2391 queries.append ({ 2392 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 2393 'args': { 2394 'proc': self._payload[self._idx['pk_procedure']], 2395 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 2396 } 2397 }) 2398 # add new codes 2399 for pk_code in pk_codes: 2400 queries.append ({ 2401 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 2402 'args': { 2403 'proc': self._payload[self._idx['pk_procedure']], 2404 'pk_code': pk_code 2405 } 2406 }) 2407 if len(queries) == 0: 2408 return 2409 # run it all in one transaction 2410 rows, idx = gmPG2.run_rw_queries(queries = queries) 2411 return2412 2413 generic_codes = property(_get_generic_codes, _set_generic_codes)2416 2417 queries = [ 2418 { 2419 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 2420 'args': {'pat': patient} 2421 } 2422 ] 2423 2424 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 2425 2426 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]2427 #-----------------------------------------------------------2428 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):2429 2430 queries = [{ 2431 'cmd': u""" 2432 INSERT INTO clin.procedure ( 2433 fk_encounter, 2434 fk_episode, 2435 soap_cat, 2436 clin_where, 2437 fk_hospital_stay, 2438 narrative 2439 ) VALUES ( 2440 %(enc)s, 2441 %(epi)s, 2442 'p', 2443 gm.nullify_empty_string(%(loc)s), 2444 %(stay)s, 2445 gm.nullify_empty_string(%(proc)s) 2446 ) 2447 RETURNING pk""", 2448 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 2449 }] 2450 2451 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 2452 2453 return cPerformedProcedure(aPK_obj = rows[0][0])2454 #-----------------------------------------------------------2456 cmd = u'delete from clin.procedure where pk = %(pk)s' 2457 args = {'pk': procedure} 2458 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2459 return True2460 #============================================================ 2461 # main - unit testing 2462 #------------------------------------------------------------ 2463 if __name__ == '__main__': 2464 2465 if len(sys.argv) < 2: 2466 sys.exit() 2467 2468 if sys.argv[1] != 'test': 2469 sys.exit() 2470 2471 #-------------------------------------------------------- 2472 # define tests 2473 #--------------------------------------------------------2475 print "\nProblem test" 2476 print "------------" 2477 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 2478 print prob 2479 fields = prob.get_fields() 2480 for field in fields: 2481 print field, ':', prob[field] 2482 print '\nupdatable:', prob.get_updatable_fields() 2483 epi = prob.get_as_episode() 2484 print '\nas episode:' 2485 if epi is not None: 2486 for field in epi.get_fields(): 2487 print ' .%s : %s' % (field, epi[field])2488 #--------------------------------------------------------2490 print "\nhealth issue test" 2491 print "-----------------" 2492 h_issue = cHealthIssue(aPK_obj=2) 2493 print h_issue 2494 fields = h_issue.get_fields() 2495 for field in fields: 2496 print field, ':', h_issue[field] 2497 print "has open episode:", h_issue.has_open_episode() 2498 print "open episode:", h_issue.get_open_episode() 2499 print "updateable:", h_issue.get_updatable_fields() 2500 h_issue.close_expired_episode(ttl=7300) 2501 h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 2502 print h_issue 2503 print h_issue.format_as_journal()2504 #--------------------------------------------------------2506 print "\nepisode test" 2507 print "------------" 2508 episode = cEpisode(aPK_obj=1) 2509 print episode 2510 fields = episode.get_fields() 2511 for field in fields: 2512 print field, ':', episode[field] 2513 print "updatable:", episode.get_updatable_fields() 2514 raw_input('ENTER to continue') 2515 2516 old_description = episode['description'] 2517 old_enc = cEncounter(aPK_obj = 1) 2518 2519 desc = '1-%s' % episode['description'] 2520 print "==> renaming to", desc 2521 successful = episode.rename ( 2522 description = desc 2523 ) 2524 if not successful: 2525 print "error" 2526 else: 2527 print "success" 2528 for field in fields: 2529 print field, ':', episode[field] 2530 2531 print "episode range:", episode.get_access_range() 2532 2533 raw_input('ENTER to continue')2534 2535 #--------------------------------------------------------2537 print "\nencounter test" 2538 print "--------------" 2539 encounter = cEncounter(aPK_obj=1) 2540 print encounter 2541 fields = encounter.get_fields() 2542 for field in fields: 2543 print field, ':', encounter[field] 2544 print "updatable:", encounter.get_updatable_fields()2545 #--------------------------------------------------------2547 encounter = cEncounter(aPK_obj=1) 2548 print encounter 2549 print "" 2550 print encounter.format_latex()2551 #--------------------------------------------------------2553 procs = get_performed_procedures(patient = 12) 2554 for proc in procs: 2555 print proc.format(left_margin=2)2556 #--------------------------------------------------------2558 stay = create_hospital_stay(encounter = 1, episode = 2) 2559 stay['hospital'] = u'Starfleet Galaxy General Hospital' 2560 stay.save_payload() 2561 print stay 2562 for s in get_patient_hospital_stays(12): 2563 print s 2564 delete_hospital_stay(stay['pk_hospital_stay']) 2565 stay = create_hospital_stay(encounter = 1, episode = 4)2566 #--------------------------------------------------------2568 tests = [None, 'A', 'B', 'C', 'D', 'E'] 2569 2570 for t in tests: 2571 print type(t), t 2572 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)2573 #-------------------------------------------------------- 2578 #-------------------------------------------------------- 2579 # run them 2580 #test_episode() 2581 #test_problem() 2582 #test_encounter() 2583 #test_health_issue() 2584 #test_hospital_stay() 2585 #test_performed_procedure() 2586 #test_diagnostic_certainty_classification_map() 2587 #test_encounter2latex() 2588 test_episode_codes() 2589 #============================================================ 2590
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Wed Sep 7 03:58:45 2011 | http://epydoc.sourceforge.net |