Package Gnumed :: Package business :: Module gmEMRStructItems
[frames] | no frames]

Source Code for Module Gnumed.business.gmEMRStructItems

   1  # -*- coding: utf-8 -*- 
   2  """GNUmed health related business object. 
   3   
   4  license: GPL v2 or later 
   5  """ 
   6  #============================================================ 
   7  __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>" 
   8   
   9  import sys 
  10  import datetime 
  11  import logging 
  12  import io 
  13  import os 
  14   
  15   
  16  if __name__ == '__main__': 
  17          sys.path.insert(0, '../../') 
  18  from Gnumed.pycommon import gmPG2 
  19  from Gnumed.pycommon import gmI18N 
  20  from Gnumed.pycommon import gmTools 
  21  from Gnumed.pycommon import gmDateTime 
  22  from Gnumed.pycommon import gmBusinessDBObject 
  23  from Gnumed.pycommon import gmNull 
  24  from Gnumed.pycommon import gmExceptions 
  25   
  26  from Gnumed.business import gmClinNarrative 
  27  from Gnumed.business import gmSoapDefs 
  28  from Gnumed.business import gmCoding 
  29  from Gnumed.business import gmPraxis 
  30  from Gnumed.business import gmOrganization 
  31  from Gnumed.business import gmExternalCare 
  32  from Gnumed.business import gmDocuments 
  33   
  34   
  35  _log = logging.getLogger('gm.emr') 
  36   
  37   
  38  if __name__ == '__main__': 
  39          gmI18N.activate_locale() 
  40          gmI18N.install_domain('gnumed') 
  41   
  42  #============================================================ 
  43  # diagnostic certainty classification 
  44  #============================================================ 
  45  __diagnostic_certainty_classification_map = None 
  46   
47 -def diagnostic_certainty_classification2str(classification):
48 49 global __diagnostic_certainty_classification_map 50 51 if __diagnostic_certainty_classification_map is None: 52 __diagnostic_certainty_classification_map = { 53 None: '', 54 'A': _('A: Sign'), 55 'B': _('B: Cluster of signs'), 56 'C': _('C: Syndromic diagnosis'), 57 'D': _('D: Scientific diagnosis') 58 } 59 60 try: 61 return __diagnostic_certainty_classification_map[classification] 62 except KeyError: 63 return _('<%s>: unknown diagnostic certainty classification') % classification
64 65 #============================================================ 66 # Health Issues API 67 #============================================================ 68 laterality2str = { 69 None: '?', 70 'na': '', 71 'sd': _('bilateral'), 72 'ds': _('bilateral'), 73 's': _('left'), 74 'd': _('right') 75 } 76 77 #============================================================
78 -class cHealthIssue(gmBusinessDBObject.cBusinessDBObject):
79 """Represents one health issue.""" 80 81 #_cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s" 82 _cmd_fetch_payload = "select * from clin.v_health_issues where pk_health_issue = %s" 83 _cmds_store_payload = [ 84 """update clin.health_issue set 85 description = %(description)s, 86 summary = gm.nullify_empty_string(%(summary)s), 87 age_noted = %(age_noted)s, 88 laterality = gm.nullify_empty_string(%(laterality)s), 89 grouping = gm.nullify_empty_string(%(grouping)s), 90 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s), 91 is_active = %(is_active)s, 92 clinically_relevant = %(clinically_relevant)s, 93 is_confidential = %(is_confidential)s, 94 is_cause_of_death = %(is_cause_of_death)s 95 WHERE 96 pk = %(pk_health_issue)s 97 AND 98 xmin = %(xmin_health_issue)s""", 99 "select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s" 100 ] 101 _updatable_fields = [ 102 'description', 103 'summary', 104 'grouping', 105 'age_noted', 106 'laterality', 107 'is_active', 108 'clinically_relevant', 109 'is_confidential', 110 'is_cause_of_death', 111 'diagnostic_certainty_classification' 112 ] 113 114 #--------------------------------------------------------
115 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
116 pk = aPK_obj 117 118 if (pk is not None) or (row is not None): 119 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row) 120 return 121 122 if patient is None: 123 cmd = """select *, xmin_health_issue from clin.v_health_issues 124 where 125 description = %(desc)s 126 and 127 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)""" 128 else: 129 cmd = """select *, xmin_health_issue from clin.v_health_issues 130 where 131 description = %(desc)s 132 and 133 pk_patient = %(pat)s""" 134 135 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}] 136 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 137 138 if len(rows) == 0: 139 raise gmExceptions.NoSuchBusinessObjectError('no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient)) 140 141 pk = rows[0][0] 142 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'} 143 144 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
145 146 #-------------------------------------------------------- 147 # external API 148 #--------------------------------------------------------
149 - def rename(self, description=None):
150 """Method for issue renaming. 151 152 @param description 153 - the new descriptive name for the issue 154 @type description 155 - a string instance 156 """ 157 # sanity check 158 if not type(description) in [str, str] or description.strip() == '': 159 _log.error('<description> must be a non-empty string') 160 return False 161 # update the issue description 162 old_description = self._payload[self._idx['description']] 163 self._payload[self._idx['description']] = description.strip() 164 self._is_modified = True 165 successful, data = self.save_payload() 166 if not successful: 167 _log.error('cannot rename health issue [%s] with [%s]' % (self, description)) 168 self._payload[self._idx['description']] = old_description 169 return False 170 return True
171 172 #--------------------------------------------------------
173 - def get_episodes(self):
174 cmd = "SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s" 175 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True) 176 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
177 178 #--------------------------------------------------------
179 - def close_expired_episode(self, ttl=180):
180 """ttl in days""" 181 open_episode = self.open_episode 182 if open_episode is None: 183 return True 184 clinical_end = open_episode.best_guess_clinical_end_date 185 ttl = datetime.timedelta(ttl) 186 now = datetime.datetime.now(tz = clinical_end.tzinfo) 187 if (clinical_end + ttl) > now: 188 return False 189 open_episode['episode_open'] = False 190 success, data = open_episode.save_payload() 191 if success: 192 return True 193 return False # should be an exception
194 195 #--------------------------------------------------------
196 - def close_episode(self):
197 open_episode = self.get_open_episode() 198 open_episode['episode_open'] = False 199 success, data = open_episode.save_payload() 200 if success: 201 return True 202 return False
203 204 #--------------------------------------------------------
205 - def has_open_episode(self):
206 return self._payload[self._idx['has_open_episode']]
207 208 #--------------------------------------------------------
209 - def get_open_episode(self):
210 cmd = "select pk from clin.episode where fk_health_issue = %s and is_open IS True LIMIT 1" 211 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 212 if len(rows) == 0: 213 return None 214 return cEpisode(aPK_obj=rows[0][0])
215 216 #--------------------------------------------------------
217 - def age_noted_human_readable(self):
218 if self._payload[self._idx['age_noted']] is None: 219 return '<???>' 220 221 # since we've already got an interval we are bound to use it, 222 # further transformation will only introduce more errors, 223 # later we can improve this deeper inside 224 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
225 226 #--------------------------------------------------------
227 - def add_code(self, pk_code=None):
228 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 229 cmd = "INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 230 args = { 231 'item': self._payload[self._idx['pk_health_issue']], 232 'code': pk_code 233 } 234 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 235 return True
236 237 #--------------------------------------------------------
238 - def remove_code(self, pk_code=None):
239 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 240 cmd = "DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 241 args = { 242 'item': self._payload[self._idx['pk_health_issue']], 243 'code': pk_code 244 } 245 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 246 return True
247 248 #--------------------------------------------------------
249 - def format_as_journal(self, left_margin=0, date_format='%Y %b %d, %a'):
250 rows = gmClinNarrative.get_as_journal ( 251 issues = (self.pk_obj,), 252 order_by = 'pk_episode, pk_encounter, clin_when, scr, src_table' 253 ) 254 255 if len(rows) == 0: 256 return '' 257 258 left_margin = ' ' * left_margin 259 260 lines = [] 261 lines.append(_('Clinical data generated during encounters under this health issue:')) 262 263 prev_epi = None 264 for row in rows: 265 if row['pk_episode'] != prev_epi: 266 lines.append('') 267 prev_epi = row['pk_episode'] 268 269 when = gmDateTime.pydt_strftime(row['clin_when'], date_format) 270 top_row = '%s%s %s (%s) %s' % ( 271 gmTools.u_box_top_left_arc, 272 gmTools.u_box_horiz_single, 273 gmSoapDefs.soap_cat2l10n_str[row['real_soap_cat']], 274 when, 275 gmTools.u_box_horiz_single * 5 276 ) 277 soap = gmTools.wrap ( 278 text = row['narrative'], 279 width = 60, 280 initial_indent = ' ', 281 subsequent_indent = ' ' + left_margin 282 ) 283 row_ver = '' 284 if row['row_version'] > 0: 285 row_ver = 'v%s: ' % row['row_version'] 286 bottom_row = '%s%s %s, %s%s %s' % ( 287 ' ' * 40, 288 gmTools.u_box_horiz_light_heavy, 289 row['modified_by'], 290 row_ver, 291 gmDateTime.pydt_strftime(row['modified_when'], date_format), 292 gmTools.u_box_horiz_heavy_light 293 ) 294 295 lines.append(top_row) 296 lines.append(soap) 297 lines.append(bottom_row) 298 299 eol_w_margin = '\n%s' % left_margin 300 return left_margin + eol_w_margin.join(lines) + '\n'
301 302 #--------------------------------------------------------
303 - def format (self, left_margin=0, patient=None, 304 with_summary=True, 305 with_codes=True, 306 with_episodes=True, 307 with_encounters=True, 308 with_medications=True, 309 with_hospital_stays=True, 310 with_procedures=True, 311 with_family_history=True, 312 with_documents=True, 313 with_tests=True, 314 with_vaccinations=True, 315 with_external_care=True 316 ):
317 318 lines = [] 319 320 lines.append(_('Health Issue %s%s%s%s [#%s]') % ( 321 '\u00BB', 322 self._payload[self._idx['description']], 323 '\u00AB', 324 gmTools.coalesce ( 325 initial = self.laterality_description, 326 instead = '', 327 template_initial = ' (%s)', 328 none_equivalents = [None, '', '?'] 329 ), 330 self._payload[self._idx['pk_health_issue']] 331 )) 332 333 if self._payload[self._idx['is_confidential']]: 334 lines.append('') 335 lines.append(_(' ***** CONFIDENTIAL *****')) 336 lines.append('') 337 338 if self._payload[self._idx['is_cause_of_death']]: 339 lines.append('') 340 lines.append(_(' contributed to death of patient')) 341 lines.append('') 342 343 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 344 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % ( 345 enc['l10n_type'], 346 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 347 enc['last_affirmed_original_tz'].strftime('%H:%M'), 348 self._payload[self._idx['pk_encounter']] 349 )) 350 351 if self._payload[self._idx['age_noted']] is not None: 352 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable()) 353 354 lines.append(' ' + _('Status') + ': %s, %s%s' % ( 355 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')), 356 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')), 357 gmTools.coalesce ( 358 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 359 instead = '', 360 template_initial = ', %s', 361 none_equivalents = [None, ''] 362 ) 363 )) 364 365 if with_summary: 366 if self._payload[self._idx['summary']] is not None: 367 lines.append(' %s:' % _('Synopsis')) 368 lines.append(gmTools.wrap ( 369 text = self._payload[self._idx['summary']], 370 width = 60, 371 initial_indent = ' ', 372 subsequent_indent = ' ' 373 )) 374 375 # codes ? 376 if with_codes: 377 codes = self.generic_codes 378 if len(codes) > 0: 379 lines.append('') 380 for c in codes: 381 lines.append(' %s: %s (%s - %s)' % ( 382 c['code'], 383 c['term'], 384 c['name_short'], 385 c['version'] 386 )) 387 del codes 388 389 lines.append('') 390 391 # patient/emr dependant 392 if patient is not None: 393 if patient.ID != self._payload[self._idx['pk_patient']]: 394 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % ( 395 patient.ID, 396 self._payload[self._idx['pk_health_issue']], 397 self._payload[self._idx['pk_patient']] 398 ) 399 raise ValueError(msg) 400 emr = patient.emr 401 402 # episodes 403 if with_episodes: 404 epis = self.get_episodes() 405 if epis is None: 406 lines.append(_('Error retrieving episodes for this health issue.')) 407 elif len(epis) == 0: 408 lines.append(_('There are no episodes for this health issue.')) 409 else: 410 lines.append ( 411 _('Episodes: %s (most recent: %s%s%s)') % ( 412 len(epis), 413 gmTools.u_left_double_angle_quote, 414 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'], 415 gmTools.u_right_double_angle_quote 416 ) 417 ) 418 for epi in epis: 419 lines.append(' \u00BB%s\u00AB (%s)' % ( 420 epi['description'], 421 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed')) 422 )) 423 lines.append('') 424 425 # encounters 426 if with_encounters: 427 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 428 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 429 430 if first_encounter is None or last_encounter is None: 431 lines.append(_('No encounters found for this health issue.')) 432 else: 433 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]]) 434 lines.append(_('Encounters: %s (%s - %s):') % ( 435 len(encs), 436 first_encounter['started_original_tz'].strftime('%m/%Y'), 437 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y') 438 )) 439 lines.append(_(' Most recent: %s - %s') % ( 440 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 441 last_encounter['last_affirmed_original_tz'].strftime('%H:%M') 442 )) 443 444 # medications 445 if with_medications: 446 meds = emr.get_current_medications ( 447 issues = [ self._payload[self._idx['pk_health_issue']] ], 448 order_by = 'is_currently_active DESC, started, substance' 449 ) 450 if len(meds) > 0: 451 lines.append('') 452 lines.append(_('Medications and Substances')) 453 for m in meds: 454 lines.append(m.format(left_margin = (left_margin + 1))) 455 del meds 456 457 # hospitalizations 458 if with_hospital_stays: 459 stays = emr.get_hospital_stays ( 460 issues = [ self._payload[self._idx['pk_health_issue']] ] 461 ) 462 if len(stays) > 0: 463 lines.append('') 464 lines.append(_('Hospitalizations: %s') % len(stays)) 465 for s in stays: 466 lines.append(s.format(left_margin = (left_margin + 1))) 467 del stays 468 469 # procedures 470 if with_procedures: 471 procs = emr.get_performed_procedures ( 472 issues = [ self._payload[self._idx['pk_health_issue']] ] 473 ) 474 if len(procs) > 0: 475 lines.append('') 476 lines.append(_('Procedures performed: %s') % len(procs)) 477 for p in procs: 478 lines.append(p.format(left_margin = (left_margin + 1))) 479 del procs 480 481 # family history 482 if with_family_history: 483 fhx = emr.get_family_history(issues = [ self._payload[self._idx['pk_health_issue']] ]) 484 if len(fhx) > 0: 485 lines.append('') 486 lines.append(_('Family History: %s') % len(fhx)) 487 for f in fhx: 488 lines.append(f.format ( 489 left_margin = (left_margin + 1), 490 include_episode = True, 491 include_comment = True, 492 include_codes = False 493 )) 494 del fhx 495 496 epis = self.get_episodes() 497 if len(epis) > 0: 498 epi_pks = [ e['pk_episode'] for e in epis ] 499 500 # documents 501 if with_documents: 502 doc_folder = patient.get_document_folder() 503 docs = doc_folder.get_documents(pk_episodes = epi_pks) 504 if len(docs) > 0: 505 lines.append('') 506 lines.append(_('Documents: %s') % len(docs)) 507 del docs 508 509 # test results 510 if with_tests: 511 tests = emr.get_test_results_by_date(episodes = epi_pks) 512 if len(tests) > 0: 513 lines.append('') 514 lines.append(_('Measurements and Results: %s') % len(tests)) 515 del tests 516 517 # vaccinations 518 if with_vaccinations: 519 vaccs = emr.get_vaccinations(episodes = epi_pks, order_by = 'date_given, vaccine') 520 if len(vaccs) > 0: 521 lines.append('') 522 lines.append(_('Vaccinations:')) 523 for vacc in vaccs: 524 lines.extend(vacc.format(with_reaction = True)) 525 del vaccs 526 527 del epis 528 529 if with_external_care: 530 care = self._get_external_care(order_by = 'organization, unit, provider') 531 if len(care) > 0: 532 lines.append('') 533 lines.append(_('External care:')) 534 for item in care: 535 lines.append(' %s%s@%s%s' % ( 536 gmTools.coalesce(item['provider'], '', '%s: '), 537 item['unit'], 538 item['organization'], 539 gmTools.coalesce(item['comment'], '', ' (%s)') 540 )) 541 542 left_margin = ' ' * left_margin 543 eol_w_margin = '\n%s' % left_margin 544 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = '\n') 545 return left_margin + eol_w_margin.join(lines) + '\n'
546 #-------------------------------------------------------- 547 # properties 548 #--------------------------------------------------------
549 - def _get_external_care(self, order_by=None):
550 return gmExternalCare.get_external_care_items(pk_health_issue = self.pk_obj, order_by = order_by)
551 552 external_care = property(_get_external_care, lambda x:x) 553 554 #-------------------------------------------------------- 555 episodes = property(get_episodes, lambda x:x) 556 557 open_episode = property(get_open_episode, lambda x:x) 558 559 has_open_episode = property(has_open_episode, lambda x:x) 560 561 #--------------------------------------------------------
562 - def _get_first_episode(self):
563 564 args = {'pk_issue': self.pk_obj} 565 566 cmd = """SELECT 567 earliest, pk_episode 568 FROM ( 569 -- .modified_when of all episodes of this issue, 570 -- earliest-possible thereof = when created, 571 -- should actually go all the way back into audit.log_episode 572 (SELECT 573 c_epi.modified_when AS earliest, 574 c_epi.pk AS pk_episode 575 FROM clin.episode c_epi 576 WHERE c_epi.fk_health_issue = %(pk_issue)s 577 ) 578 UNION ALL 579 580 -- last modification of encounter in which episodes of this issue were created, 581 -- earliest-possible thereof = initial creation of that encounter 582 (SELECT 583 c_enc.modified_when AS earliest, 584 c_epi.pk AS pk_episode 585 FROM 586 clin.episode c_epi 587 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter) 588 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue) 589 WHERE c_hi.pk = %(pk_issue)s 590 ) 591 UNION ALL 592 593 -- start of encounter in which episodes of this issue were created, 594 -- earliest-possible thereof = set by user 595 (SELECT 596 c_enc.started AS earliest, 597 c_epi.pk AS pk_episode 598 FROM 599 clin.episode c_epi 600 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter) 601 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue) 602 WHERE c_hi.pk = %(pk_issue)s 603 ) 604 UNION ALL 605 606 -- start of encounters of clinical items linked to episodes of this issue, 607 -- earliest-possible thereof = explicitely set by user 608 (SELECT 609 c_enc.started AS earliest, 610 c_epi.pk AS pk_episode 611 FROM 612 clin.clin_root_item c_cri 613 INNER JOIN clin.encounter c_enc ON (c_cri.fk_encounter = c_enc.pk) 614 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk) 615 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk) 616 WHERE c_hi.pk = %(pk_issue)s 617 ) 618 UNION ALL 619 620 -- .clin_when of clinical items linked to episodes of this issue, 621 -- earliest-possible thereof = explicitely set by user 622 (SELECT 623 c_cri.clin_when AS earliest, 624 c_epi.pk AS pk_episode 625 FROM 626 clin.clin_root_item c_cri 627 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk) 628 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk) 629 WHERE c_hi.pk = %(pk_issue)s 630 ) 631 UNION ALL 632 633 -- earliest modification time of clinical items linked to episodes of this issue 634 -- this CAN be used since if an item is linked to an episode it can be 635 -- assumed the episode (should have) existed at the time of creation 636 (SELECT 637 c_cri.modified_when AS earliest, 638 c_epi.pk AS pk_episode 639 FROM 640 clin.clin_root_item c_cri 641 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk) 642 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk) 643 WHERE c_hi.pk = %(pk_issue)s 644 ) 645 UNION ALL 646 647 -- there may not be items, but there may still be documents ... 648 (SELECT 649 b_dm.clin_when AS earliest, 650 c_epi.pk AS pk_episode 651 FROM 652 blobs.doc_med b_dm 653 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk) 654 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk) 655 WHERE c_hi.pk = %(pk_issue)s 656 ) 657 ) AS candidates 658 ORDER BY earliest NULLS LAST 659 LIMIT 1""" 660 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 661 if len(rows) == 0: 662 return None 663 return cEpisode(aPK_obj = rows[0]['pk_episode'])
664 665 first_episode = property(_get_first_episode, lambda x:x) 666 667 #--------------------------------------------------------
668 - def _get_latest_episode(self):
669 670 # explicit always wins: 671 if self._payload[self._idx['has_open_episode']]: 672 return self.open_episode 673 674 args = {'pk_issue': self.pk_obj} 675 676 # cheap query first: any episodes at all ? 677 cmd = "SELECT 1 FROM clin.episode WHERE fk_health_issue = %(pk_issue)s" 678 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 679 if len(rows) == 0: 680 return None 681 682 cmd = """SELECT 683 latest, pk_episode 684 FROM ( 685 -- .clin_when of clinical items linked to episodes of this issue, 686 -- latest-possible thereof = explicitely set by user 687 (SELECT 688 c_cri.clin_when AS latest, 689 c_epi.pk AS pk_episode, 690 1 AS rank 691 FROM 692 clin.clin_root_item c_cri 693 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk) 694 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk) 695 WHERE c_hi.pk = %(pk_issue)s 696 ) 697 UNION ALL 698 699 -- .clin_when of documents linked to episodes of this issue 700 (SELECT 701 b_dm.clin_when AS latest, 702 c_epi.pk AS pk_episode, 703 1 AS rank 704 FROM 705 blobs.doc_med b_dm 706 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk) 707 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk) 708 WHERE c_hi.pk = %(pk_issue)s 709 ) 710 UNION ALL 711 712 -- last_affirmed of encounter in which episodes of this issue were created, 713 -- earliest-possible thereof = set by user 714 (SELECT 715 c_enc.last_affirmed AS latest, 716 c_epi.pk AS pk_episode, 717 2 AS rank 718 FROM 719 clin.episode c_epi 720 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter) 721 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue) 722 WHERE c_hi.pk = %(pk_issue)s 723 ) 724 725 ) AS candidates 726 WHERE 727 -- weed out NULL rows due to episodes w/o clinical items and w/o documents 728 latest IS NOT NULL 729 ORDER BY 730 rank, 731 latest DESC 732 LIMIT 1 733 """ 734 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 735 if len(rows) == 0: 736 # there were no episodes for this issue 737 return None 738 return cEpisode(aPK_obj = rows[0]['pk_episode'])
739 740 latest_episode = property(_get_latest_episode, lambda x:x) 741 742 #-------------------------------------------------------- 743 # Steffi suggested we divide into safe and assumed (= possible) start dates
744 - def _get_safe_start_date(self):
745 """This returns the date when we can assume to safely KNOW 746 the health issue existed (because the provider said so).""" 747 748 args = { 749 'enc': self._payload[self._idx['pk_encounter']], 750 'pk': self._payload[self._idx['pk_health_issue']] 751 } 752 cmd = """SELECT COALESCE ( 753 -- this one must override all: 754 -- .age_noted if not null and DOB is known 755 (CASE 756 WHEN c_hi.age_noted IS NULL 757 THEN NULL::timestamp with time zone 758 WHEN 759 (SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = ( 760 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s 761 )) IS NULL 762 THEN NULL::timestamp with time zone 763 ELSE 764 c_hi.age_noted + ( 765 SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = ( 766 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s 767 ) 768 ) 769 END), 770 771 -- look at best_guess_clinical_start_date of all linked episodes 772 773 -- start of encounter in which created, earliest = explicitely set 774 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = c_hi.fk_encounter) 775 ) 776 FROM clin.health_issue c_hi 777 WHERE c_hi.pk = %(pk)s""" 778 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 779 return rows[0][0]
780 781 safe_start_date = property(_get_safe_start_date, lambda x:x) 782 783 #--------------------------------------------------------
784 - def _get_possible_start_date(self):
785 args = {'pk': self._payload[self._idx['pk_health_issue']]} 786 cmd = """ 787 SELECT MIN(earliest) FROM ( 788 -- last modification, earliest = when created in/changed to the current state 789 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s) 790 791 UNION ALL 792 -- last modification of encounter in which created, earliest = initial creation of that encounter 793 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 794 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s 795 )) 796 797 UNION ALL 798 -- earliest explicit .clin_when of clinical items linked to this health_issue 799 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s) 800 801 UNION ALL 802 -- earliest modification time of clinical items linked to this health issue 803 -- this CAN be used since if an item is linked to a health issue it can be 804 -- assumed the health issue (should have) existed at the time of creation 805 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s) 806 807 UNION ALL 808 -- earliest start of encounters of clinical items linked to this episode 809 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN ( 810 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s 811 )) 812 813 -- here we should be looking at 814 -- .best_guess_clinical_start_date of all episodes linked to this encounter 815 816 ) AS candidates""" 817 818 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 819 return rows[0][0]
820 821 possible_start_date = property(_get_possible_start_date) 822 823 #--------------------------------------------------------
824 - def _get_clinical_end_date(self):
825 if self._payload[self._idx['is_active']]: 826 return None 827 if self._payload[self._idx['has_open_episode']]: 828 return None 829 latest_episode = self.latest_episode 830 if latest_episode is not None: 831 return latest_episode.best_guess_clinical_end_date 832 # apparently, there are no episodes for this issue 833 # and the issue is not active either 834 # so, we simply do not know, the safest assumption is: 835 return self.safe_start_date
836 837 clinical_end_date = property(_get_clinical_end_date) 838 839 #--------------------------------------------------------
840 - def _get_latest_access_date(self):
841 args = { 842 'enc': self._payload[self._idx['pk_encounter']], 843 'pk': self._payload[self._idx['pk_health_issue']] 844 } 845 cmd = """ 846 SELECT 847 MAX(latest) 848 FROM ( 849 -- last modification, latest = when last changed to the current state 850 -- DO NOT USE: database upgrades may change this field 851 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s) 852 853 --UNION ALL 854 -- last modification of encounter in which created, latest = initial creation of that encounter 855 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer 856 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 857 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 858 -- ) 859 --) 860 861 --UNION ALL 862 -- end of encounter in which created, latest = explicitely set 863 -- DO NOT USE: we can retrospectively create issues which 864 -- DO NOT USE: are long since finished 865 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 866 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 867 -- ) 868 --) 869 870 UNION ALL 871 -- latest end of encounters of clinical items linked to this issue 872 (SELECT 873 MAX(last_affirmed) AS latest 874 FROM clin.encounter 875 WHERE pk IN ( 876 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s 877 ) 878 ) 879 880 UNION ALL 881 -- latest explicit .clin_when of clinical items linked to this issue 882 (SELECT 883 MAX(clin_when) AS latest 884 FROM clin.v_pat_items 885 WHERE pk_health_issue = %(pk)s 886 ) 887 888 -- latest modification time of clinical items linked to this issue 889 -- this CAN be used since if an item is linked to an issue it can be 890 -- assumed the issue (should have) existed at the time of modification 891 -- DO NOT USE, because typo fixes should not extend the issue 892 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 893 894 ) AS candidates""" 895 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 896 return rows[0][0]
897 898 latest_access_date = property(_get_latest_access_date) 899 900 #--------------------------------------------------------
902 try: 903 return laterality2str[self._payload[self._idx['laterality']]] 904 except KeyError: 905 return '<???>'
906 907 laterality_description = property(_get_laterality_description, lambda x:x) 908 #--------------------------------------------------------
910 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
911 912 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 913 914 #--------------------------------------------------------
916 cmd = """SELECT 917 'NONE (live row)'::text as audit__action_applied, 918 NULL AS audit__action_when, 919 NULL AS audit__action_by, 920 pk_audit, 921 row_version, 922 modified_when, 923 modified_by, 924 pk, 925 description, 926 laterality, 927 age_noted, 928 is_active, 929 clinically_relevant, 930 is_confidential, 931 is_cause_of_death, 932 fk_encounter, 933 grouping, 934 diagnostic_certainty_classification, 935 summary 936 FROM clin.health_issue 937 WHERE pk = %(pk_health_issue)s 938 UNION ALL ( 939 SELECT 940 audit_action as audit__action_applied, 941 audit_when as audit__action_when, 942 audit_by as audit__action_by, 943 pk_audit, 944 orig_version as row_version, 945 orig_when as modified_when, 946 orig_by as modified_by, 947 pk, 948 description, 949 laterality, 950 age_noted, 951 is_active, 952 clinically_relevant, 953 is_confidential, 954 is_cause_of_death, 955 fk_encounter, 956 grouping, 957 diagnostic_certainty_classification, 958 summary 959 FROM audit.log_health_issue 960 WHERE pk = %(pk_health_issue)s 961 ) 962 ORDER BY row_version DESC 963 """ 964 args = {'pk_health_issue': self.pk_obj} 965 title = _('Health issue: %s%s%s') % ( 966 gmTools.u_left_double_angle_quote, 967 self._payload[self._idx['description']], 968 gmTools.u_right_double_angle_quote 969 ) 970 return '\n'.join(self._get_revision_history(cmd, args, title))
971 972 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x) 973 #--------------------------------------------------------
974 - def _get_generic_codes(self):
975 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 976 return [] 977 978 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 979 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 980 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 981 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
982
983 - def _set_generic_codes(self, pk_codes):
984 queries = [] 985 # remove all codes 986 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 987 queries.append ({ 988 'cmd': 'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s', 989 'args': { 990 'issue': self._payload[self._idx['pk_health_issue']], 991 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 992 } 993 }) 994 # add new codes 995 for pk_code in pk_codes: 996 queries.append ({ 997 'cmd': 'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)', 998 'args': { 999 'issue': self._payload[self._idx['pk_health_issue']], 1000 'pk_code': pk_code 1001 } 1002 }) 1003 if len(queries) == 0: 1004 return 1005 # run it all in one transaction 1006 rows, idx = gmPG2.run_rw_queries(queries = queries) 1007 return
1008 1009 generic_codes = property(_get_generic_codes, _set_generic_codes)
1010 1011 #============================================================
1012 -def create_health_issue(description=None, encounter=None, patient=None):
1013 """Creates a new health issue for a given patient. 1014 1015 description - health issue name 1016 """ 1017 try: 1018 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 1019 return h_issue 1020 except gmExceptions.NoSuchBusinessObjectError: 1021 pass 1022 1023 queries = [] 1024 cmd = "insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 1025 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 1026 1027 cmd = "select currval('clin.health_issue_pk_seq')" 1028 queries.append({'cmd': cmd}) 1029 1030 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1031 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 1032 1033 return h_issue
1034 1035 #-----------------------------------------------------------
1036 -def delete_health_issue(health_issue=None):
1037 if isinstance(health_issue, cHealthIssue): 1038 args = {'pk': health_issue['pk_health_issue']} 1039 else: 1040 args = {'pk': int(health_issue)} 1041 try: 1042 gmPG2.run_rw_queries(queries = [{'cmd': 'DELETE FROM clin.health_issue WHERE pk = %(pk)s', 'args': args}]) 1043 except gmPG2.dbapi.IntegrityError: 1044 # should be parsing pgcode/and or error message 1045 _log.exception('cannot delete health issue') 1046 return False 1047 1048 return True
1049 1050 #------------------------------------------------------------ 1051 # use as dummy for unassociated episodes
1052 -def get_dummy_health_issue():
1053 issue = { 1054 'pk_health_issue': None, 1055 'description': _('Unattributed episodes'), 1056 'age_noted': None, 1057 'laterality': 'na', 1058 'is_active': True, 1059 'clinically_relevant': True, 1060 'is_confidential': None, 1061 'is_cause_of_death': False, 1062 'is_dummy': True, 1063 'grouping': None 1064 } 1065 return issue
1066 1067 #-----------------------------------------------------------
1068 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
1069 return cProblem ( 1070 aPK_obj = { 1071 'pk_patient': health_issue['pk_patient'], 1072 'pk_health_issue': health_issue['pk_health_issue'], 1073 'pk_episode': None 1074 }, 1075 try_potential_problems = allow_irrelevant 1076 )
1077 1078 #============================================================ 1079 # episodes API 1080 #============================================================
1081 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
1082 """Represents one clinical episode. 1083 """ 1084 _cmd_fetch_payload = "select * from clin.v_pat_episodes where pk_episode=%s" 1085 _cmds_store_payload = [ 1086 """update clin.episode set 1087 fk_health_issue = %(pk_health_issue)s, 1088 is_open = %(episode_open)s::boolean, 1089 description = %(description)s, 1090 summary = gm.nullify_empty_string(%(summary)s), 1091 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 1092 where 1093 pk = %(pk_episode)s and 1094 xmin = %(xmin_episode)s""", 1095 """select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 1096 ] 1097 _updatable_fields = [ 1098 'pk_health_issue', 1099 'episode_open', 1100 'description', 1101 'summary', 1102 'diagnostic_certainty_classification' 1103 ] 1104 #--------------------------------------------------------
1105 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None, link_obj=None):
1106 pk = aPK_obj 1107 if pk is None and row is None: 1108 1109 where_parts = ['description = %(desc)s'] 1110 1111 if id_patient is not None: 1112 where_parts.append('pk_patient = %(pat)s') 1113 1114 if health_issue is not None: 1115 where_parts.append('pk_health_issue = %(issue)s') 1116 1117 if encounter is not None: 1118 where_parts.append('pk_patient = (SELECT fk_patient FROM clin.encounter WHERE pk = %(enc)s)') 1119 1120 args = { 1121 'pat': id_patient, 1122 'issue': health_issue, 1123 'enc': encounter, 1124 'desc': name 1125 } 1126 1127 cmd = 'SELECT * FROM clin.v_pat_episodes WHERE %s' % ' AND '.join(where_parts) 1128 1129 rows, idx = gmPG2.run_ro_queries ( 1130 link_obj = link_obj, 1131 queries = [{'cmd': cmd, 'args': args}], 1132 get_col_idx=True 1133 ) 1134 1135 if len(rows) == 0: 1136 raise gmExceptions.NoSuchBusinessObjectError('no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter)) 1137 1138 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 1139 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 1140 1141 else: 1142 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row, link_obj = link_obj)
1143 1144 #-------------------------------------------------------- 1145 # external API 1146 #--------------------------------------------------------
1147 - def get_patient(self):
1148 return self._payload[self._idx['pk_patient']]
1149 1150 #--------------------------------------------------------
1151 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
1152 return gmClinNarrative.get_narrative ( 1153 soap_cats = soap_cats, 1154 encounters = encounters, 1155 episodes = [self.pk_obj], 1156 order_by = order_by 1157 )
1158 #--------------------------------------------------------
1159 - def rename(self, description=None):
1160 """Method for episode editing, that is, episode renaming. 1161 1162 @param description 1163 - the new descriptive name for the encounter 1164 @type description 1165 - a string instance 1166 """ 1167 # sanity check 1168 if description.strip() == '': 1169 _log.error('<description> must be a non-empty string instance') 1170 return False 1171 # update the episode description 1172 old_description = self._payload[self._idx['description']] 1173 self._payload[self._idx['description']] = description.strip() 1174 self._is_modified = True 1175 successful, data = self.save_payload() 1176 if not successful: 1177 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 1178 self._payload[self._idx['description']] = old_description 1179 return False 1180 return True
1181 1182 #--------------------------------------------------------
1183 - def add_code(self, pk_code=None):
1184 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1185 1186 if pk_code in self._payload[self._idx['pk_generic_codes']]: 1187 return 1188 1189 cmd = """ 1190 INSERT INTO clin.lnk_code2episode 1191 (fk_item, fk_generic_code) 1192 SELECT 1193 %(item)s, 1194 %(code)s 1195 WHERE NOT EXISTS ( 1196 SELECT 1 FROM clin.lnk_code2episode 1197 WHERE 1198 fk_item = %(item)s 1199 AND 1200 fk_generic_code = %(code)s 1201 )""" 1202 args = { 1203 'item': self._payload[self._idx['pk_episode']], 1204 'code': pk_code 1205 } 1206 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1207 return
1208 1209 #--------------------------------------------------------
1210 - def remove_code(self, pk_code=None):
1211 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1212 cmd = "DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1213 args = { 1214 'item': self._payload[self._idx['pk_episode']], 1215 'code': pk_code 1216 } 1217 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1218 return True
1219 1220 #--------------------------------------------------------
1221 - def format_as_journal(self, left_margin=0, date_format='%Y %b %d, %a'):
1222 rows = gmClinNarrative.get_as_journal ( 1223 episodes = (self.pk_obj,), 1224 order_by = 'pk_encounter, clin_when, scr, src_table' 1225 #order_by = u'pk_encounter, scr, clin_when, src_table' 1226 ) 1227 1228 if len(rows) == 0: 1229 return '' 1230 1231 lines = [] 1232 1233 lines.append(_('Clinical data generated during encounters within this episode:')) 1234 1235 left_margin = ' ' * left_margin 1236 1237 prev_enc = None 1238 for row in rows: 1239 if row['pk_encounter'] != prev_enc: 1240 lines.append('') 1241 prev_enc = row['pk_encounter'] 1242 1243 when = row['clin_when'].strftime(date_format) 1244 top_row = '%s%s %s (%s) %s' % ( 1245 gmTools.u_box_top_left_arc, 1246 gmTools.u_box_horiz_single, 1247 gmSoapDefs.soap_cat2l10n_str[row['real_soap_cat']], 1248 when, 1249 gmTools.u_box_horiz_single * 5 1250 ) 1251 soap = gmTools.wrap ( 1252 text = row['narrative'], 1253 width = 60, 1254 initial_indent = ' ', 1255 subsequent_indent = ' ' + left_margin 1256 ) 1257 row_ver = '' 1258 if row['row_version'] > 0: 1259 row_ver = 'v%s: ' % row['row_version'] 1260 bottom_row = '%s%s %s, %s%s %s' % ( 1261 ' ' * 40, 1262 gmTools.u_box_horiz_light_heavy, 1263 row['modified_by'], 1264 row_ver, 1265 gmDateTime.pydt_strftime(row['modified_when'], date_format), 1266 gmTools.u_box_horiz_heavy_light 1267 ) 1268 1269 lines.append(top_row) 1270 lines.append(soap) 1271 lines.append(bottom_row) 1272 1273 eol_w_margin = '\n%s' % left_margin 1274 return left_margin + eol_w_margin.join(lines) + '\n'
1275 1276 #--------------------------------------------------------
1277 - def format_maximum_information(self, patient=None):
1278 if patient is None: 1279 from Gnumed.business.gmPerson import gmCurrentPatient, cPerson 1280 if self._payload[self._idx['pk_patient']] == gmCurrentPatient().ID: 1281 patient = gmCurrentPatient() 1282 else: 1283 patient = cPerson(self._payload[self._idx['pk_patient']]) 1284 1285 return self.format ( 1286 patient = patient, 1287 with_summary = True, 1288 with_codes = True, 1289 with_encounters = True, 1290 with_documents = True, 1291 with_hospital_stays = True, 1292 with_procedures = True, 1293 with_family_history = True, 1294 with_tests = False, # does not inform on the episode itself 1295 with_vaccinations = True, 1296 with_health_issue = True, 1297 return_list = True 1298 )
1299 1300 #--------------------------------------------------------
1301 - def format(self, left_margin=0, patient=None, 1302 with_summary=True, 1303 with_codes=True, 1304 with_encounters=True, 1305 with_documents=True, 1306 with_hospital_stays=True, 1307 with_procedures=True, 1308 with_family_history=True, 1309 with_tests=True, 1310 with_vaccinations=True, 1311 with_health_issue=False, 1312 return_list=False 1313 ):
1314 1315 if patient is not None: 1316 if patient.ID != self._payload[self._idx['pk_patient']]: 1317 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 1318 patient.ID, 1319 self._payload[self._idx['pk_episode']], 1320 self._payload[self._idx['pk_patient']] 1321 ) 1322 raise ValueError(msg) 1323 emr = patient.emr 1324 else: 1325 with_encounters = False 1326 with_documents = False 1327 with_hospital_stays = False 1328 with_procedures = False 1329 with_family_history = False 1330 with_tests = False 1331 with_vaccinations = False 1332 1333 lines = [] 1334 1335 # episode details 1336 lines.append (_('Episode %s%s%s [#%s]') % ( 1337 gmTools.u_left_double_angle_quote, 1338 self._payload[self._idx['description']], 1339 gmTools.u_right_double_angle_quote, 1340 self._payload[self._idx['pk_episode']] 1341 )) 1342 1343 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 1344 lines.append (' ' + _('Created during encounter: %s (%s - %s) [#%s]') % ( 1345 enc['l10n_type'], 1346 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1347 enc['last_affirmed_original_tz'].strftime('%H:%M'), 1348 self._payload[self._idx['pk_encounter']] 1349 )) 1350 1351 if patient is not None: 1352 range_str, range_str_verb, duration_str = self.formatted_clinical_duration 1353 lines.append(_(' Duration: %s (%s)') % (duration_str, range_str_verb)) 1354 1355 lines.append(' ' + _('Status') + ': %s%s' % ( 1356 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 1357 gmTools.coalesce ( 1358 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 1359 instead = '', 1360 template_initial = ', %s', 1361 none_equivalents = [None, ''] 1362 ) 1363 )) 1364 1365 if with_health_issue: 1366 lines.append(' ' + _('Health issue') + ': %s' % gmTools.coalesce ( 1367 self._payload[self._idx['health_issue']], 1368 _('none associated') 1369 )) 1370 1371 if with_summary: 1372 if self._payload[self._idx['summary']] is not None: 1373 lines.append(' %s:' % _('Synopsis')) 1374 lines.append(gmTools.wrap ( 1375 text = self._payload[self._idx['summary']], 1376 width = 60, 1377 initial_indent = ' ', 1378 subsequent_indent = ' ' 1379 ) 1380 ) 1381 1382 # codes 1383 if with_codes: 1384 codes = self.generic_codes 1385 if len(codes) > 0: 1386 lines.append('') 1387 for c in codes: 1388 lines.append(' %s: %s (%s - %s)' % ( 1389 c['code'], 1390 c['term'], 1391 c['name_short'], 1392 c['version'] 1393 )) 1394 del codes 1395 1396 lines.append('') 1397 1398 # encounters 1399 if with_encounters: 1400 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 1401 if encs is None: 1402 lines.append(_('Error retrieving encounters for this episode.')) 1403 elif len(encs) == 0: 1404 #lines.append(_('There are no encounters for this issue.')) 1405 pass 1406 else: 1407 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 1408 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 1409 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 1410 if len(encs) < 4: 1411 line = _('%s encounter(s) (%s - %s):') 1412 else: 1413 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):') 1414 lines.append(line % ( 1415 len(encs), 1416 first_encounter['started'].strftime('%m/%Y'), 1417 last_encounter['last_affirmed'].strftime('%m/%Y') 1418 )) 1419 lines.append(' %s - %s (%s):%s' % ( 1420 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1421 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 1422 first_encounter['l10n_type'], 1423 gmTools.coalesce ( 1424 first_encounter['assessment_of_encounter'], 1425 gmTools.coalesce ( 1426 first_encounter['reason_for_encounter'], 1427 '', 1428 ' \u00BB%s\u00AB' + (' (%s)' % _('RFE')) 1429 ), 1430 ' \u00BB%s\u00AB' + (' (%s)' % _('AOE')) 1431 ) 1432 )) 1433 if len(encs) > 4: 1434 lines.append(_(' %s %s skipped %s') % ( 1435 gmTools.u_ellipsis, 1436 (len(encs) - 4), 1437 gmTools.u_ellipsis 1438 )) 1439 for enc in encs[1:][-3:]: 1440 lines.append(' %s - %s (%s):%s' % ( 1441 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1442 enc['last_affirmed_original_tz'].strftime('%H:%M'), 1443 enc['l10n_type'], 1444 gmTools.coalesce ( 1445 enc['assessment_of_encounter'], 1446 gmTools.coalesce ( 1447 enc['reason_for_encounter'], 1448 '', 1449 ' \u00BB%s\u00AB' + (' (%s)' % _('RFE')) 1450 ), 1451 ' \u00BB%s\u00AB' + (' (%s)' % _('AOE')) 1452 ) 1453 )) 1454 del encs 1455 # spell out last encounter 1456 if last_encounter is not None: 1457 lines.append('') 1458 lines.append(_('Progress notes in most recent encounter:')) 1459 lines.extend(last_encounter.format_soap ( 1460 episodes = [ self._payload[self._idx['pk_episode']] ], 1461 left_margin = left_margin, 1462 soap_cats = 'soapu', 1463 emr = emr 1464 )) 1465 1466 # documents 1467 if with_documents: 1468 doc_folder = patient.get_document_folder() 1469 docs = doc_folder.get_documents ( 1470 pk_episodes = [ self._payload[self._idx['pk_episode']] ] 1471 ) 1472 if len(docs) > 0: 1473 lines.append('') 1474 lines.append(_('Documents: %s') % len(docs)) 1475 for d in docs: 1476 lines.append(' ' + d.format(single_line = True)) 1477 del docs 1478 1479 # hospitalizations 1480 if with_hospital_stays: 1481 stays = emr.get_hospital_stays(episodes = [ self._payload[self._idx['pk_episode']] ]) 1482 if len(stays) > 0: 1483 lines.append('') 1484 lines.append(_('Hospitalizations: %s') % len(stays)) 1485 for s in stays: 1486 lines.append(s.format(left_margin = (left_margin + 1))) 1487 del stays 1488 1489 # procedures 1490 if with_procedures: 1491 procs = emr.get_performed_procedures(episodes = [ self._payload[self._idx['pk_episode']] ]) 1492 if len(procs) > 0: 1493 lines.append('') 1494 lines.append(_('Procedures performed: %s') % len(procs)) 1495 for p in procs: 1496 lines.append(p.format ( 1497 left_margin = (left_margin + 1), 1498 include_episode = False, 1499 include_codes = True 1500 )) 1501 del procs 1502 1503 # family history 1504 if with_family_history: 1505 fhx = emr.get_family_history(episodes = [ self._payload[self._idx['pk_episode']] ]) 1506 if len(fhx) > 0: 1507 lines.append('') 1508 lines.append(_('Family History: %s') % len(fhx)) 1509 for f in fhx: 1510 lines.append(f.format ( 1511 left_margin = (left_margin + 1), 1512 include_episode = False, 1513 include_comment = True, 1514 include_codes = True 1515 )) 1516 del fhx 1517 1518 # test results 1519 if with_tests: 1520 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 1521 if len(tests) > 0: 1522 lines.append('') 1523 lines.append(_('Measurements and Results:')) 1524 for t in tests: 1525 lines.append(' ' + t.format_concisely(date_format = '%Y %b %d', with_notes = True)) 1526 del tests 1527 1528 # vaccinations 1529 if with_vaccinations: 1530 vaccs = emr.get_vaccinations ( 1531 episodes = [ self._payload[self._idx['pk_episode']] ], 1532 order_by = 'date_given DESC, vaccine' 1533 ) 1534 if len(vaccs) > 0: 1535 lines.append('') 1536 lines.append(_('Vaccinations:')) 1537 for vacc in vaccs: 1538 lines.extend(vacc.format ( 1539 with_indications = True, 1540 with_comment = True, 1541 with_reaction = True, 1542 date_format = '%Y-%m-%d' 1543 )) 1544 del vaccs 1545 1546 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = '\n') 1547 if return_list: 1548 return lines 1549 1550 left_margin = ' ' * left_margin 1551 eol_w_margin = '\n%s' % left_margin 1552 return left_margin + eol_w_margin.join(lines) + '\n'
1553 1554 #-------------------------------------------------------- 1555 # properties 1556 #--------------------------------------------------------
1558 cmd = """SELECT MIN(earliest) FROM 1559 ( 1560 -- last modification of episode, 1561 -- earliest-possible thereof = when created, 1562 -- should actually go all the way back into audit.log_episode 1563 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s) 1564 1565 UNION ALL 1566 1567 -- last modification of encounter in which created, 1568 -- earliest-possible thereof = initial creation of that encounter 1569 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1570 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1571 )) 1572 UNION ALL 1573 1574 -- start of encounter in which created, 1575 -- earliest-possible thereof = explicitely set by user 1576 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1577 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1578 )) 1579 UNION ALL 1580 1581 -- start of encounters of clinical items linked to this episode, 1582 -- earliest-possible thereof = explicitely set by user 1583 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN ( 1584 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1585 )) 1586 UNION ALL 1587 1588 -- .clin_when of clinical items linked to this episode, 1589 -- earliest-possible thereof = explicitely set by user 1590 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1591 1592 UNION ALL 1593 1594 -- earliest modification time of clinical items linked to this episode 1595 -- this CAN be used since if an item is linked to an episode it can be 1596 -- assumed the episode (should have) existed at the time of creation 1597 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1598 1599 UNION ALL 1600 1601 -- there may not be items, but there may still be documents ... 1602 (SELECT MIN(clin_when) AS earliest FROM blobs.doc_med WHERE fk_episode = %(pk)s) 1603 ) AS candidates""" 1604 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1605 return rows[0][0]
1606 1607 best_guess_clinical_start_date = property(_get_best_guess_clinical_start_date) 1608 1609 #--------------------------------------------------------
1611 if self._payload[self._idx['episode_open']]: 1612 return None 1613 1614 cmd = """SELECT COALESCE ( 1615 (SELECT 1616 latest --, source_type 1617 FROM ( 1618 -- latest explicit .clin_when of clinical items linked to this episode 1619 (SELECT 1620 MAX(clin_when) AS latest, 1621 'clin.episode.pk = clin.clin_root_item.fk_episode -> .clin_when'::text AS source_type 1622 FROM clin.clin_root_item 1623 WHERE fk_episode = %(pk)s 1624 ) 1625 UNION ALL 1626 -- latest explicit .clin_when of documents linked to this episode 1627 (SELECT 1628 MAX(clin_when) AS latest, 1629 'clin.episode.pk = blobs.doc_med.fk_episode -> .clin_when'::text AS source_type 1630 FROM blobs.doc_med 1631 WHERE fk_episode = %(pk)s 1632 ) 1633 ) AS candidates 1634 ORDER BY latest DESC NULLS LAST 1635 LIMIT 1 1636 ), 1637 -- last ditch, always exists, only use when no clinical items or documents linked: 1638 -- last modification, latest = when last changed to the current state 1639 (SELECT c_epi.modified_when AS latest --, 'clin.episode.modified_when'::text AS source_type 1640 FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s 1641 ) 1642 )""" 1643 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False) 1644 return rows[0][0]
1645 1646 best_guess_clinical_end_date = property(_get_best_guess_clinical_end_date) 1647 1648 #--------------------------------------------------------
1650 start = self.best_guess_clinical_start_date 1651 end = self.best_guess_clinical_end_date 1652 if end is None: 1653 range_str = '%s-%s' % ( 1654 gmDateTime.pydt_strftime(start, "%b'%y"), 1655 gmTools.u_ellipsis 1656 ) 1657 range_str_verb = '%s - %s' % ( 1658 gmDateTime.pydt_strftime(start, '%b %d %Y'), 1659 gmTools.u_ellipsis 1660 ) 1661 duration_str = _('%s so far') % gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - start) 1662 return (range_str, range_str_verb, duration_str) 1663 1664 duration_str = gmDateTime.format_interval_medically(end - start) 1665 # year different: 1666 if end.year != start.year: 1667 range_str = '%s-%s' % ( 1668 gmDateTime.pydt_strftime(start, "%b'%y"), 1669 gmDateTime.pydt_strftime(end, "%b'%y") 1670 ) 1671 range_str_verb = '%s - %s' % ( 1672 gmDateTime.pydt_strftime(start, '%b %d %Y'), 1673 gmDateTime.pydt_strftime(end, '%b %d %Y') 1674 ) 1675 return (range_str, range_str_verb, duration_str) 1676 # same year: 1677 if end.month != start.month: 1678 range_str = '%s-%s' % ( 1679 gmDateTime.pydt_strftime(start, '%b'), 1680 gmDateTime.pydt_strftime(end, "%b'%y") 1681 ) 1682 range_str_verb = '%s - %s' % ( 1683 gmDateTime.pydt_strftime(start, '%b %d'), 1684 gmDateTime.pydt_strftime(end, '%b %d %Y') 1685 ) 1686 return (range_str, range_str_verb, duration_str) 1687 1688 # same year and same month 1689 range_str = gmDateTime.pydt_strftime(start, "%b'%y") 1690 range_str_verb = gmDateTime.pydt_strftime(start, '%b %d %Y') 1691 return (range_str, range_str_verb, duration_str)
1692 1693 formatted_clinical_duration = property(_get_formatted_clinical_duration) 1694 1695 #--------------------------------------------------------
1696 - def _get_latest_access_date(self):
1697 cmd = """SELECT MAX(latest) FROM ( 1698 -- last modification, latest = when last changed to the current state 1699 (SELECT c_epi.modified_when AS latest, 'clin.episode.modified_when'::text AS candidate FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s) 1700 1701 UNION ALL 1702 1703 -- last modification of encounter in which created, latest = initial creation of that encounter 1704 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer 1705 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1706 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1707 --)) 1708 1709 -- end of encounter in which created, latest = explicitely set 1710 -- DO NOT USE: we can retrospectively create episodes which 1711 -- DO NOT USE: are long since finished 1712 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1713 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1714 --)) 1715 1716 -- latest end of encounters of clinical items linked to this episode 1717 (SELECT 1718 MAX(last_affirmed) AS latest, 1719 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate 1720 FROM clin.encounter 1721 WHERE pk IN ( 1722 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1723 )) 1724 UNION ALL 1725 1726 -- latest explicit .clin_when of clinical items linked to this episode 1727 (SELECT 1728 MAX(clin_when) AS latest, 1729 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate 1730 FROM clin.clin_root_item 1731 WHERE fk_episode = %(pk)s 1732 ) 1733 1734 -- latest modification time of clinical items linked to this episode 1735 -- this CAN be used since if an item is linked to an episode it can be 1736 -- assumed the episode (should have) existed at the time of creation 1737 -- DO NOT USE, because typo fixes should not extend the episode 1738 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1739 1740 -- not sure about this one: 1741 -- .pk -> clin.clin_root_item.fk_encounter.modified_when 1742 1743 ) AS candidates""" 1744 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1745 return rows[0][0]
1746 1747 latest_access_date = property(_get_latest_access_date) 1748 1749 #--------------------------------------------------------
1751 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1752 1753 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 1754 1755 #--------------------------------------------------------
1757 cmd = """SELECT 1758 'NONE (live row)'::text as audit__action_applied, 1759 NULL AS audit__action_when, 1760 NULL AS audit__action_by, 1761 pk_audit, 1762 row_version, 1763 modified_when, 1764 modified_by, 1765 pk, fk_health_issue, description, is_open, fk_encounter, 1766 diagnostic_certainty_classification, 1767 summary 1768 FROM clin.episode 1769 WHERE pk = %(pk_episode)s 1770 UNION ALL ( 1771 SELECT 1772 audit_action as audit__action_applied, 1773 audit_when as audit__action_when, 1774 audit_by as audit__action_by, 1775 pk_audit, 1776 orig_version as row_version, 1777 orig_when as modified_when, 1778 orig_by as modified_by, 1779 pk, fk_health_issue, description, is_open, fk_encounter, 1780 diagnostic_certainty_classification, 1781 summary 1782 FROM audit.log_episode 1783 WHERE pk = %(pk_episode)s 1784 ) 1785 ORDER BY row_version DESC 1786 """ 1787 args = {'pk_episode': self.pk_obj} 1788 title = _('Episode: %s%s%s') % ( 1789 gmTools.u_left_double_angle_quote, 1790 self._payload[self._idx['description']], 1791 gmTools.u_right_double_angle_quote 1792 ) 1793 return '\n'.join(self._get_revision_history(cmd, args, title))
1794 1795 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x) 1796 1797 #--------------------------------------------------------
1798 - def _get_generic_codes(self):
1799 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 1800 return [] 1801 1802 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 1803 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 1804 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1805 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1806
1807 - def _set_generic_codes(self, pk_codes):
1808 queries = [] 1809 # remove all codes 1810 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 1811 queries.append ({ 1812 'cmd': 'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s', 1813 'args': { 1814 'epi': self._payload[self._idx['pk_episode']], 1815 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 1816 } 1817 }) 1818 # add new codes 1819 for pk_code in pk_codes: 1820 queries.append ({ 1821 'cmd': 'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)', 1822 'args': { 1823 'epi': self._payload[self._idx['pk_episode']], 1824 'pk_code': pk_code 1825 } 1826 }) 1827 if len(queries) == 0: 1828 return 1829 # run it all in one transaction 1830 rows, idx = gmPG2.run_rw_queries(queries = queries) 1831 return
1832 1833 generic_codes = property(_get_generic_codes, _set_generic_codes) 1834 1835 #--------------------------------------------------------
1836 - def _get_has_narrative(self):
1837 cmd = """SELECT EXISTS ( 1838 SELECT 1 FROM clin.clin_narrative 1839 WHERE 1840 fk_episode = %(epi)s 1841 AND 1842 fk_encounter IN ( 1843 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1844 ) 1845 )""" 1846 args = { 1847 'pat': self._payload[self._idx['pk_patient']], 1848 'epi': self._payload[self._idx['pk_episode']] 1849 } 1850 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1851 return rows[0][0]
1852 1853 has_narrative = property(_get_has_narrative, lambda x:x)
1854 1855 #============================================================
1856 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None, link_obj=None):
1857 """Creates a new episode for a given patient's health issue. 1858 1859 pk_health_issue - given health issue PK 1860 episode_name - name of episode 1861 """ 1862 if not allow_dupes: 1863 try: 1864 episode = cEpisode(name = episode_name, health_issue = pk_health_issue, encounter = encounter, link_obj = link_obj) 1865 if episode['episode_open'] != is_open: 1866 episode['episode_open'] = is_open 1867 episode.save_payload() 1868 return episode 1869 except gmExceptions.ConstructorError: 1870 pass 1871 1872 queries = [] 1873 cmd = "INSERT INTO clin.episode (fk_health_issue, description, is_open, fk_encounter) VALUES (%s, %s, %s::boolean, %s)" 1874 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 1875 queries.append({'cmd': cEpisode._cmd_fetch_payload % "currval('clin.episode_pk_seq')"}) 1876 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data=True, get_col_idx=True) 1877 1878 episode = cEpisode(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 1879 return episode
1880 1881 #-----------------------------------------------------------
1882 -def delete_episode(episode=None):
1883 if isinstance(episode, cEpisode): 1884 pk = episode['pk_episode'] 1885 else: 1886 pk = int(episode) 1887 1888 cmd = 'DELETE FROM clin.episode WHERE pk = %(pk)s' 1889 1890 try: 1891 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}]) 1892 except gmPG2.dbapi.IntegrityError: 1893 # should be parsing pgcode/and or error message 1894 _log.exception('cannot delete episode, it is in use') 1895 return False 1896 1897 return True
1898 #-----------------------------------------------------------
1899 -def episode2problem(episode=None, allow_closed=False):
1900 return cProblem ( 1901 aPK_obj = { 1902 'pk_patient': episode['pk_patient'], 1903 'pk_episode': episode['pk_episode'], 1904 'pk_health_issue': episode['pk_health_issue'] 1905 }, 1906 try_potential_problems = allow_closed 1907 )
1908 1909 #============================================================ 1910 # encounter API 1911 #============================================================ 1912 SQL_get_encounters = "SELECT * FROM clin.v_pat_encounters WHERE %s" 1913
1914 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1915 """Represents one encounter.""" 1916 1917 _cmd_fetch_payload = SQL_get_encounters % 'pk_encounter = %s' 1918 _cmds_store_payload = [ 1919 """UPDATE clin.encounter SET 1920 started = %(started)s, 1921 last_affirmed = %(last_affirmed)s, 1922 fk_location = %(pk_org_unit)s, 1923 fk_type = %(pk_type)s, 1924 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1925 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1926 WHERE 1927 pk = %(pk_encounter)s AND 1928 xmin = %(xmin_encounter)s 1929 """, 1930 # need to return all fields so we can survive in-place upgrades 1931 "SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s" 1932 ] 1933 _updatable_fields = [ 1934 'started', 1935 'last_affirmed', 1936 'pk_org_unit', 1937 'pk_type', 1938 'reason_for_encounter', 1939 'assessment_of_encounter' 1940 ] 1941 #--------------------------------------------------------
1942 - def set_active(self):
1943 """Set the encounter as the active one. 1944 1945 "Setting active" means making sure the encounter 1946 row has the youngest "last_affirmed" timestamp of 1947 all encounter rows for this patient. 1948 """ 1949 self['last_affirmed'] = gmDateTime.pydt_now_here() 1950 self.save()
1951 #--------------------------------------------------------
1952 - def lock(self, exclusive=False, link_obj=None):
1953 return lock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1954 #--------------------------------------------------------
1955 - def unlock(self, exclusive=False, link_obj=None):
1956 return unlock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1957 #--------------------------------------------------------
1958 - def transfer_clinical_data(self, source_episode=None, target_episode=None):
1959 """ 1960 Moves every element currently linked to the current encounter 1961 and the source_episode onto target_episode. 1962 1963 @param source_episode The episode the elements are currently linked to. 1964 @type target_episode A cEpisode intance. 1965 @param target_episode The episode the elements will be relinked to. 1966 @type target_episode A cEpisode intance. 1967 """ 1968 if source_episode['pk_episode'] == target_episode['pk_episode']: 1969 return True 1970 1971 queries = [] 1972 cmd = """ 1973 UPDATE clin.clin_root_item 1974 SET fk_episode = %(trg)s 1975 WHERE 1976 fk_encounter = %(enc)s AND 1977 fk_episode = %(src)s 1978 """ 1979 rows, idx = gmPG2.run_rw_queries(queries = [{ 1980 'cmd': cmd, 1981 'args': { 1982 'trg': target_episode['pk_episode'], 1983 'enc': self.pk_obj, 1984 'src': source_episode['pk_episode'] 1985 } 1986 }]) 1987 self.refetch_payload() 1988 return True
1989 1990 #--------------------------------------------------------
1991 - def transfer_all_data_to_another_encounter(self, pk_target_encounter=None):
1992 if pk_target_encounter == self.pk_obj: 1993 return True 1994 cmd = "SELECT clin.transfer_all_encounter_data(%(src)s, %(trg)s)" 1995 args = { 1996 'src': self.pk_obj, 1997 'trg': pk_target_encounter 1998 } 1999 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2000 return True
2001 2002 # conn = gmPG2.get_connection() 2003 # curs = conn.cursor() 2004 # curs.callproc('clin.get_hints_for_patient', [pk_identity]) 2005 # rows = curs.fetchall() 2006 # idx = gmPG2.get_col_indices(curs) 2007 # curs.close() 2008 # conn.rollback() 2009 2010 #--------------------------------------------------------
2011 - def same_payload(self, another_object=None):
2012 2013 relevant_fields = [ 2014 'pk_org_unit', 2015 'pk_type', 2016 'pk_patient', 2017 'reason_for_encounter', 2018 'assessment_of_encounter' 2019 ] 2020 for field in relevant_fields: 2021 if self._payload[self._idx[field]] != another_object[field]: 2022 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 2023 return False 2024 2025 relevant_fields = [ 2026 'started', 2027 'last_affirmed', 2028 ] 2029 for field in relevant_fields: 2030 if self._payload[self._idx[field]] is None: 2031 if another_object[field] is None: 2032 continue 2033 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field]) 2034 return False 2035 2036 if another_object[field] is None: 2037 return False 2038 2039 # compares at seconds granularity 2040 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'): 2041 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field]) 2042 return False 2043 2044 # compare codes 2045 # 1) RFE 2046 if another_object['pk_generic_codes_rfe'] is None: 2047 if self._payload[self._idx['pk_generic_codes_rfe']] is not None: 2048 return False 2049 if another_object['pk_generic_codes_rfe'] is not None: 2050 if self._payload[self._idx['pk_generic_codes_rfe']] is None: 2051 return False 2052 if ( 2053 (another_object['pk_generic_codes_rfe'] is None) 2054 and 2055 (self._payload[self._idx['pk_generic_codes_rfe']] is None) 2056 ) is False: 2057 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]): 2058 return False 2059 # 2) AOE 2060 if another_object['pk_generic_codes_aoe'] is None: 2061 if self._payload[self._idx['pk_generic_codes_aoe']] is not None: 2062 return False 2063 if another_object['pk_generic_codes_aoe'] is not None: 2064 if self._payload[self._idx['pk_generic_codes_aoe']] is None: 2065 return False 2066 if ( 2067 (another_object['pk_generic_codes_aoe'] is None) 2068 and 2069 (self._payload[self._idx['pk_generic_codes_aoe']] is None) 2070 ) is False: 2071 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]): 2072 return False 2073 2074 return True
2075 #--------------------------------------------------------
2076 - def has_clinical_data(self):
2077 cmd = """ 2078 select exists ( 2079 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 2080 union all 2081 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 2082 )""" 2083 args = { 2084 'pat': self._payload[self._idx['pk_patient']], 2085 'enc': self.pk_obj 2086 } 2087 rows, idx = gmPG2.run_ro_queries ( 2088 queries = [{ 2089 'cmd': cmd, 2090 'args': args 2091 }] 2092 ) 2093 return rows[0][0]
2094 2095 #--------------------------------------------------------
2096 - def has_narrative(self):
2097 cmd = """ 2098 select exists ( 2099 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 2100 )""" 2101 args = { 2102 'pat': self._payload[self._idx['pk_patient']], 2103 'enc': self.pk_obj 2104 } 2105 rows, idx = gmPG2.run_ro_queries ( 2106 queries = [{ 2107 'cmd': cmd, 2108 'args': args 2109 }] 2110 ) 2111 return rows[0][0]
2112 #--------------------------------------------------------
2113 - def has_soap_narrative(self, soap_cats=None):
2114 """soap_cats: <space> = admin category""" 2115 2116 if soap_cats is None: 2117 soap_cats = 'soap ' 2118 else: 2119 soap_cats = soap_cats.lower() 2120 2121 cats = [] 2122 for cat in soap_cats: 2123 if cat in 'soapu': 2124 cats.append(cat) 2125 continue 2126 if cat == ' ': 2127 cats.append(None) 2128 2129 cmd = """ 2130 SELECT EXISTS ( 2131 SELECT 1 FROM clin.clin_narrative 2132 WHERE 2133 fk_encounter = %(enc)s 2134 AND 2135 soap_cat IN %(cats)s 2136 LIMIT 1 2137 ) 2138 """ 2139 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 2140 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 2141 return rows[0][0]
2142 #--------------------------------------------------------
2143 - def has_documents(self):
2144 cmd = """ 2145 select exists ( 2146 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 2147 )""" 2148 args = { 2149 'pat': self._payload[self._idx['pk_patient']], 2150 'enc': self.pk_obj 2151 } 2152 rows, idx = gmPG2.run_ro_queries ( 2153 queries = [{ 2154 'cmd': cmd, 2155 'args': args 2156 }] 2157 ) 2158 return rows[0][0]
2159 #--------------------------------------------------------
2160 - def get_latest_soap(self, soap_cat=None, episode=None):
2161 2162 if soap_cat is not None: 2163 soap_cat = soap_cat.lower() 2164 2165 if episode is None: 2166 epi_part = 'fk_episode is null' 2167 else: 2168 epi_part = 'fk_episode = %(epi)s' 2169 2170 cmd = """ 2171 select narrative 2172 from clin.clin_narrative 2173 where 2174 fk_encounter = %%(enc)s 2175 and 2176 soap_cat = %%(cat)s 2177 and 2178 %s 2179 order by clin_when desc 2180 limit 1 2181 """ % epi_part 2182 2183 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 2184 2185 rows, idx = gmPG2.run_ro_queries ( 2186 queries = [{ 2187 'cmd': cmd, 2188 'args': args 2189 }] 2190 ) 2191 if len(rows) == 0: 2192 return None 2193 2194 return rows[0][0]
2195 #--------------------------------------------------------
2196 - def get_episodes(self, exclude=None):
2197 cmd = """ 2198 SELECT * FROM clin.v_pat_episodes 2199 WHERE pk_episode IN ( 2200 SELECT DISTINCT fk_episode 2201 FROM clin.clin_root_item 2202 WHERE fk_encounter = %%(enc)s 2203 2204 UNION 2205 2206 SELECT DISTINCT fk_episode 2207 FROM blobs.doc_med 2208 WHERE fk_encounter = %%(enc)s 2209 ) %s""" 2210 args = {'enc': self.pk_obj} 2211 if exclude is not None: 2212 cmd = cmd % 'AND pk_episode NOT IN %(excluded)s' 2213 args['excluded'] = tuple(exclude) 2214 else: 2215 cmd = cmd % '' 2216 2217 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2218 2219 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
2220 2221 episodes = property(get_episodes, lambda x:x) 2222 #--------------------------------------------------------
2223 - def add_code(self, pk_code=None, field=None):
2224 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2225 if field == 'rfe': 2226 cmd = "INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 2227 elif field == 'aoe': 2228 cmd = "INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 2229 else: 2230 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 2231 args = { 2232 'item': self._payload[self._idx['pk_encounter']], 2233 'code': pk_code 2234 } 2235 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2236 return True
2237 #--------------------------------------------------------
2238 - def remove_code(self, pk_code=None, field=None):
2239 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2240 if field == 'rfe': 2241 cmd = "DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 2242 elif field == 'aoe': 2243 cmd = "DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 2244 else: 2245 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 2246 args = { 2247 'item': self._payload[self._idx['pk_encounter']], 2248 'code': pk_code 2249 } 2250 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2251 return True
2252 2253 #-------------------------------------------------------- 2254 # data formatting 2255 #--------------------------------------------------------
2256 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):
2257 2258 lines = [] 2259 for soap_cat in gmSoapDefs.soap_cats2list(soap_cats): 2260 soap_cat_narratives = emr.get_clin_narrative ( 2261 episodes = episodes, 2262 issues = issues, 2263 encounters = [self._payload[self._idx['pk_encounter']]], 2264 soap_cats = [soap_cat] 2265 ) 2266 if soap_cat_narratives is None: 2267 continue 2268 if len(soap_cat_narratives) == 0: 2269 continue 2270 2271 lines.append('%s%s %s %s' % ( 2272 gmTools.u_box_top_left_arc, 2273 gmTools.u_box_horiz_single, 2274 gmSoapDefs.soap_cat2l10n_str[soap_cat], 2275 gmTools.u_box_horiz_single * 5 2276 )) 2277 for soap_entry in soap_cat_narratives: 2278 txt = gmTools.wrap ( 2279 text = soap_entry['narrative'], 2280 width = 75, 2281 initial_indent = '', 2282 subsequent_indent = (' ' * left_margin) 2283 ) 2284 lines.append(txt) 2285 when = gmDateTime.pydt_strftime ( 2286 soap_entry['date'], 2287 format = '%Y-%m-%d %H:%M', 2288 accuracy = gmDateTime.acc_minutes 2289 ) 2290 txt = '%s%s %.8s, %s %s' % ( 2291 ' ' * 40, 2292 gmTools.u_box_horiz_light_heavy, 2293 soap_entry['modified_by'], 2294 when, 2295 gmTools.u_box_horiz_heavy_light 2296 ) 2297 lines.append(txt) 2298 lines.append('') 2299 2300 return lines
2301 2302 #--------------------------------------------------------
2303 - def format_latex(self, date_format=None, soap_cats=None, soap_order=None):
2304 2305 nothing2format = ( 2306 (self._payload[self._idx['reason_for_encounter']] is None) 2307 and 2308 (self._payload[self._idx['assessment_of_encounter']] is None) 2309 and 2310 (self.has_soap_narrative(soap_cats = 'soapu') is False) 2311 ) 2312 if nothing2format: 2313 return '' 2314 2315 if date_format is None: 2316 date_format = '%A, %b %d %Y' 2317 2318 tex = '% -------------------------------------------------------------\n' 2319 tex += '% much recommended: \\usepackage(tabu)\n' 2320 tex += '% much recommended: \\usepackage(longtable)\n' 2321 tex += '% best wrapped in: "\\begin{longtabu} to \\textwidth {lX[,L]}"\n' 2322 tex += '% -------------------------------------------------------------\n' 2323 tex += '\\hline \n' 2324 tex += '\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 2325 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 2326 gmTools.tex_escape_string ( 2327 gmDateTime.pydt_strftime ( 2328 self._payload[self._idx['started']], 2329 date_format, 2330 accuracy = gmDateTime.acc_days 2331 ) 2332 ), 2333 gmTools.tex_escape_string ( 2334 gmDateTime.pydt_strftime ( 2335 self._payload[self._idx['started']], 2336 '%H:%M', 2337 accuracy = gmDateTime.acc_minutes 2338 ) 2339 ), 2340 gmTools.tex_escape_string ( 2341 gmDateTime.pydt_strftime ( 2342 self._payload[self._idx['last_affirmed']], 2343 '%H:%M', 2344 accuracy = gmDateTime.acc_minutes 2345 ) 2346 ) 2347 ) 2348 tex += '\\hline \n' 2349 2350 if self._payload[self._idx['reason_for_encounter']] is not None: 2351 tex += '%s & %s \\tabularnewline \n' % ( 2352 gmTools.tex_escape_string(_('RFE')), 2353 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 2354 ) 2355 if self._payload[self._idx['assessment_of_encounter']] is not None: 2356 tex += '%s & %s \\tabularnewline \n' % ( 2357 gmTools.tex_escape_string(_('AOE')), 2358 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 2359 ) 2360 2361 for epi in self.get_episodes(): 2362 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 2363 if len(soaps) == 0: 2364 continue 2365 tex += '\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 2366 gmTools.tex_escape_string(_('Problem')), 2367 gmTools.tex_escape_string(epi['description']), 2368 gmTools.tex_escape_string ( 2369 gmTools.coalesce ( 2370 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 2371 instead = '', 2372 template_initial = ' {\\footnotesize [%s]}', 2373 none_equivalents = [None, ''] 2374 ) 2375 ) 2376 ) 2377 if epi['pk_health_issue'] is not None: 2378 tex += '\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 2379 gmTools.tex_escape_string(_('Health issue')), 2380 gmTools.tex_escape_string(epi['health_issue']), 2381 gmTools.tex_escape_string ( 2382 gmTools.coalesce ( 2383 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 2384 instead = '', 2385 template_initial = ' {\\footnotesize [%s]}', 2386 none_equivalents = [None, ''] 2387 ) 2388 ) 2389 ) 2390 for soap in soaps: 2391 tex += '{\\small %s} & {\\small %s} \\tabularnewline \n' % ( 2392 gmTools.tex_escape_string(gmSoapDefs.soap_cat2l10n[soap['soap_cat']]), 2393 gmTools.tex_escape_string(soap['narrative'], replace_eol = True) 2394 ) 2395 tex += ' & \\tabularnewline \n' 2396 2397 return tex
2398 2399 #--------------------------------------------------------
2400 - def __format_header_fancy(self, left_margin=0):
2401 lines = [] 2402 2403 lines.append('%s%s: %s - %s (@%s)%s [#%s]' % ( 2404 ' ' * left_margin, 2405 self._payload[self._idx['l10n_type']], 2406 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 2407 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 2408 self._payload[self._idx['source_time_zone']], 2409 gmTools.coalesce ( 2410 self._payload[self._idx['assessment_of_encounter']], 2411 '', 2412 ' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 2413 ), 2414 self._payload[self._idx['pk_encounter']] 2415 )) 2416 2417 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 2418 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 2419 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 2420 gmDateTime.current_local_iso_numeric_timezone_string, 2421 gmTools.bool2subst ( 2422 gmDateTime.dst_currently_in_effect, 2423 gmDateTime.py_dst_timezone_name, 2424 gmDateTime.py_timezone_name 2425 ), 2426 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, ' - ' + _('daylight savings time in effect'), '') 2427 )) 2428 2429 if self._payload[self._idx['praxis_branch']] is not None: 2430 lines.append(_('Location: %s (%s)') % (self._payload[self._idx['praxis_branch']], self._payload[self._idx['praxis']])) 2431 2432 if self._payload[self._idx['reason_for_encounter']] is not None: 2433 lines.append('%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 2434 codes = self.generic_codes_rfe 2435 for c in codes: 2436 lines.append(' %s: %s (%s - %s)' % ( 2437 c['code'], 2438 c['term'], 2439 c['name_short'], 2440 c['version'] 2441 )) 2442 if len(codes) > 0: 2443 lines.append('') 2444 2445 if self._payload[self._idx['assessment_of_encounter']] is not None: 2446 lines.append('%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2447 codes = self.generic_codes_aoe 2448 for c in codes: 2449 lines.append(' %s: %s (%s - %s)' % ( 2450 c['code'], 2451 c['term'], 2452 c['name_short'], 2453 c['version'] 2454 )) 2455 if len(codes) > 0: 2456 lines.append('') 2457 del codes 2458 return lines
2459 2460 #--------------------------------------------------------
2461 - def format_header(self, fancy_header=True, left_margin=0, with_rfe_aoe=False):
2462 lines = [] 2463 2464 if fancy_header: 2465 return self.__format_header_fancy(left_margin = left_margin) 2466 2467 now = gmDateTime.pydt_now_here() 2468 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'): 2469 start = '%s %s' % ( 2470 _('today'), 2471 self._payload[self._idx['started_original_tz']].strftime('%H:%M') 2472 ) 2473 else: 2474 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M') 2475 lines.append('%s%s: %s - %s%s%s' % ( 2476 ' ' * left_margin, 2477 self._payload[self._idx['l10n_type']], 2478 start, 2479 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 2480 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], '', ' \u00BB%s\u00AB'), 2481 gmTools.coalesce(self._payload[self._idx['praxis_branch']], '', ' @%s') 2482 )) 2483 if with_rfe_aoe: 2484 if self._payload[self._idx['reason_for_encounter']] is not None: 2485 lines.append('%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 2486 codes = self.generic_codes_rfe 2487 for c in codes: 2488 lines.append(' %s: %s (%s - %s)' % ( 2489 c['code'], 2490 c['term'], 2491 c['name_short'], 2492 c['version'] 2493 )) 2494 if len(codes) > 0: 2495 lines.append('') 2496 if self._payload[self._idx['assessment_of_encounter']] is not None: 2497 lines.append('%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2498 codes = self.generic_codes_aoe 2499 if len(codes) > 0: 2500 lines.append('') 2501 for c in codes: 2502 lines.append(' %s: %s (%s - %s)' % ( 2503 c['code'], 2504 c['term'], 2505 c['name_short'], 2506 c['version'] 2507 )) 2508 if len(codes) > 0: 2509 lines.append('') 2510 del codes 2511 2512 return lines
2513 2514 #--------------------------------------------------------
2515 - def format_by_episode(self, episodes=None, issues=None, left_margin=0, patient=None, with_soap=False, with_tests=True, with_docs=True, with_vaccinations=True, with_family_history=True):
2516 2517 if patient is not None: 2518 emr = patient.emr 2519 2520 lines = [] 2521 if episodes is None: 2522 episodes = [ e['pk_episode'] for e in self.episodes ] 2523 2524 for pk in episodes: 2525 epi = cEpisode(aPK_obj = pk) 2526 lines.append(_('\nEpisode %s%s%s%s:') % ( 2527 gmTools.u_left_double_angle_quote, 2528 epi['description'], 2529 gmTools.u_right_double_angle_quote, 2530 gmTools.coalesce(epi['health_issue'], '', ' (%s)') 2531 )) 2532 2533 # soap 2534 if with_soap: 2535 if patient.ID != self._payload[self._idx['pk_patient']]: 2536 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2537 patient.ID, 2538 self._payload[self._idx['pk_encounter']], 2539 self._payload[self._idx['pk_patient']] 2540 ) 2541 raise ValueError(msg) 2542 lines.extend(self.format_soap ( 2543 episodes = [pk], 2544 left_margin = left_margin, 2545 soap_cats = None, # meaning: all 2546 emr = emr, 2547 issues = issues 2548 )) 2549 2550 # test results 2551 if with_tests: 2552 tests = emr.get_test_results_by_date ( 2553 episodes = [pk], 2554 encounter = self._payload[self._idx['pk_encounter']] 2555 ) 2556 if len(tests) > 0: 2557 lines.append('') 2558 lines.append(_('Measurements and Results:')) 2559 2560 for t in tests: 2561 lines.append(t.format()) 2562 2563 del tests 2564 2565 # vaccinations 2566 if with_vaccinations: 2567 vaccs = emr.get_vaccinations ( 2568 episodes = [pk], 2569 encounters = [ self._payload[self._idx['pk_encounter']] ], 2570 order_by = 'date_given DESC, vaccine' 2571 ) 2572 if len(vaccs) > 0: 2573 lines.append('') 2574 lines.append(_('Vaccinations:')) 2575 for vacc in vaccs: 2576 lines.extend(vacc.format ( 2577 with_indications = True, 2578 with_comment = True, 2579 with_reaction = True, 2580 date_format = '%Y-%m-%d' 2581 )) 2582 del vaccs 2583 2584 # family history 2585 if with_family_history: 2586 fhx = emr.get_family_history(episodes = [pk]) 2587 if len(fhx) > 0: 2588 lines.append('') 2589 lines.append(_('Family History: %s') % len(fhx)) 2590 for f in fhx: 2591 lines.append(f.format ( 2592 left_margin = (left_margin + 1), 2593 include_episode = False, 2594 include_comment = True 2595 )) 2596 del fhx 2597 2598 # documents 2599 if with_docs: 2600 doc_folder = patient.get_document_folder() 2601 docs = doc_folder.get_documents ( 2602 pk_episodes = [pk], 2603 encounter = self._payload[self._idx['pk_encounter']] 2604 ) 2605 if len(docs) > 0: 2606 lines.append('') 2607 lines.append(_('Documents:')) 2608 for d in docs: 2609 lines.append(' ' + d.format(single_line = True)) 2610 del docs 2611 2612 return lines
2613 2614 #--------------------------------------------------------
2615 - def format_maximum_information(self, patient=None):
2616 if patient is None: 2617 from Gnumed.business.gmPerson import gmCurrentPatient, cPerson 2618 if self._payload[self._idx['pk_patient']] == gmCurrentPatient().ID: 2619 patient = gmCurrentPatient() 2620 else: 2621 patient = cPerson(self._payload[self._idx['pk_patient']]) 2622 2623 return self.format ( 2624 patient = patient, 2625 fancy_header = True, 2626 with_rfe_aoe = True, 2627 with_soap = True, 2628 with_docs = True, 2629 with_tests = False, 2630 with_vaccinations = True, 2631 with_co_encountlet_hints = True, 2632 with_family_history = True, 2633 by_episode = False, 2634 return_list = True 2635 )
2636 2637 #--------------------------------------------------------
2638 - def format(self, episodes=None, with_soap=False, left_margin=0, patient=None, issues=None, with_docs=True, with_tests=True, fancy_header=True, with_vaccinations=True, with_co_encountlet_hints=False, with_rfe_aoe=False, with_family_history=True, by_episode=False, return_list=False):
2639 """Format an encounter. 2640 2641 with_co_encountlet_hints: 2642 - whether to include which *other* episodes were discussed during this encounter 2643 - (only makes sense if episodes != None) 2644 """ 2645 lines = self.format_header ( 2646 fancy_header = fancy_header, 2647 left_margin = left_margin, 2648 with_rfe_aoe = with_rfe_aoe 2649 ) 2650 2651 if patient is None: 2652 _log.debug('no patient, cannot load patient related data') 2653 with_soap = False 2654 with_tests = False 2655 with_vaccinations = False 2656 with_docs = False 2657 2658 if by_episode: 2659 lines.extend(self.format_by_episode ( 2660 episodes = episodes, 2661 issues = issues, 2662 left_margin = left_margin, 2663 patient = patient, 2664 with_soap = with_soap, 2665 with_tests = with_tests, 2666 with_docs = with_docs, 2667 with_vaccinations = with_vaccinations, 2668 with_family_history = with_family_history 2669 )) 2670 else: 2671 if with_soap: 2672 lines.append('') 2673 if patient.ID != self._payload[self._idx['pk_patient']]: 2674 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2675 patient.ID, 2676 self._payload[self._idx['pk_encounter']], 2677 self._payload[self._idx['pk_patient']] 2678 ) 2679 raise ValueError(msg) 2680 emr = patient.emr 2681 lines.extend(self.format_soap ( 2682 episodes = episodes, 2683 left_margin = left_margin, 2684 soap_cats = None, # meaning: all 2685 emr = emr, 2686 issues = issues 2687 )) 2688 2689 # # family history 2690 # if with_family_history: 2691 # if episodes is not None: 2692 # fhx = emr.get_family_history(episodes = episodes) 2693 # if len(fhx) > 0: 2694 # lines.append(u'') 2695 # lines.append(_('Family History: %s') % len(fhx)) 2696 # for f in fhx: 2697 # lines.append(f.format ( 2698 # left_margin = (left_margin + 1), 2699 # include_episode = False, 2700 # include_comment = True 2701 # )) 2702 # del fhx 2703 2704 # test results 2705 if with_tests: 2706 emr = patient.emr 2707 tests = emr.get_test_results_by_date ( 2708 episodes = episodes, 2709 encounter = self._payload[self._idx['pk_encounter']] 2710 ) 2711 if len(tests) > 0: 2712 lines.append('') 2713 lines.append(_('Measurements and Results:')) 2714 for t in tests: 2715 lines.append(t.format()) 2716 del tests 2717 2718 # vaccinations 2719 if with_vaccinations: 2720 emr = patient.emr 2721 vaccs = emr.get_vaccinations ( 2722 episodes = episodes, 2723 encounters = [ self._payload[self._idx['pk_encounter']] ], 2724 order_by = 'date_given DESC, vaccine' 2725 ) 2726 if len(vaccs) > 0: 2727 lines.append('') 2728 lines.append(_('Vaccinations:')) 2729 for vacc in vaccs: 2730 lines.extend(vacc.format ( 2731 with_indications = True, 2732 with_comment = True, 2733 with_reaction = True, 2734 date_format = '%Y-%m-%d' 2735 )) 2736 del vaccs 2737 2738 # documents 2739 if with_docs: 2740 doc_folder = patient.get_document_folder() 2741 docs = doc_folder.get_documents ( 2742 pk_episodes = episodes, 2743 encounter = self._payload[self._idx['pk_encounter']] 2744 ) 2745 if len(docs) > 0: 2746 lines.append('') 2747 lines.append(_('Documents:')) 2748 for d in docs: 2749 lines.append(' ' + d.format(single_line = True)) 2750 del docs 2751 2752 # co-encountlets 2753 if with_co_encountlet_hints: 2754 if episodes is not None: 2755 other_epis = self.get_episodes(exclude = episodes) 2756 if len(other_epis) > 0: 2757 lines.append('') 2758 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 2759 for epi in other_epis: 2760 lines.append(' %s%s%s%s' % ( 2761 gmTools.u_left_double_angle_quote, 2762 epi['description'], 2763 gmTools.u_right_double_angle_quote, 2764 gmTools.coalesce(epi['health_issue'], '', ' (%s)') 2765 )) 2766 2767 if return_list: 2768 return lines 2769 2770 eol_w_margin = '\n%s' % (' ' * left_margin) 2771 return '%s\n' % eol_w_margin.join(lines)
2772 2773 #-------------------------------------------------------- 2774 # properties 2775 #--------------------------------------------------------
2776 - def _get_generic_codes_rfe(self):
2777 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 2778 return [] 2779 2780 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 2781 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 2782 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2783 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2784
2785 - def _set_generic_codes_rfe(self, pk_codes):
2786 queries = [] 2787 # remove all codes 2788 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 2789 queries.append ({ 2790 'cmd': 'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2791 'args': { 2792 'enc': self._payload[self._idx['pk_encounter']], 2793 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 2794 } 2795 }) 2796 # add new codes 2797 for pk_code in pk_codes: 2798 queries.append ({ 2799 'cmd': 'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2800 'args': { 2801 'enc': self._payload[self._idx['pk_encounter']], 2802 'pk_code': pk_code 2803 } 2804 }) 2805 if len(queries) == 0: 2806 return 2807 # run it all in one transaction 2808 rows, idx = gmPG2.run_rw_queries(queries = queries) 2809 self.refetch_payload() 2810 return
2811 2812 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 2813 #--------------------------------------------------------
2814 - def _get_generic_codes_aoe(self):
2815 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 2816 return [] 2817 2818 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 2819 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 2820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2821 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2822
2823 - def _set_generic_codes_aoe(self, pk_codes):
2824 queries = [] 2825 # remove all codes 2826 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 2827 queries.append ({ 2828 'cmd': 'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2829 'args': { 2830 'enc': self._payload[self._idx['pk_encounter']], 2831 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 2832 } 2833 }) 2834 # add new codes 2835 for pk_code in pk_codes: 2836 queries.append ({ 2837 'cmd': 'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2838 'args': { 2839 'enc': self._payload[self._idx['pk_encounter']], 2840 'pk_code': pk_code 2841 } 2842 }) 2843 if len(queries) == 0: 2844 return 2845 # run it all in one transaction 2846 rows, idx = gmPG2.run_rw_queries(queries = queries) 2847 self.refetch_payload() 2848 return
2849 2850 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe) 2851 #--------------------------------------------------------
2852 - def _get_praxis_branch(self):
2853 if self._payload[self._idx['pk_org_unit']] is None: 2854 return None 2855 return gmPraxis.get_praxis_branch_by_org_unit(pk_org_unit = self._payload[self._idx['pk_org_unit']])
2856 2857 praxis_branch = property(_get_praxis_branch, lambda x:x) 2858 #--------------------------------------------------------
2859 - def _get_org_unit(self):
2860 if self._payload[self._idx['pk_org_unit']] is None: 2861 return None 2862 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])
2863 2864 org_unit = property(_get_org_unit, lambda x:x) 2865 #--------------------------------------------------------
2867 cmd = """SELECT 2868 'NONE (live row)'::text as audit__action_applied, 2869 NULL AS audit__action_when, 2870 NULL AS audit__action_by, 2871 pk_audit, 2872 row_version, 2873 modified_when, 2874 modified_by, 2875 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed 2876 FROM clin.encounter 2877 WHERE pk = %(pk_encounter)s 2878 UNION ALL ( 2879 SELECT 2880 audit_action as audit__action_applied, 2881 audit_when as audit__action_when, 2882 audit_by as audit__action_by, 2883 pk_audit, 2884 orig_version as row_version, 2885 orig_when as modified_when, 2886 orig_by as modified_by, 2887 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed 2888 FROM audit.log_encounter 2889 WHERE pk = %(pk_encounter)s 2890 ) 2891 ORDER BY row_version DESC 2892 """ 2893 args = {'pk_encounter': self._payload[self._idx['pk_encounter']]} 2894 title = _('Encounter: %s%s%s') % ( 2895 gmTools.u_left_double_angle_quote, 2896 self._payload[self._idx['l10n_type']], 2897 gmTools.u_right_double_angle_quote 2898 ) 2899 return '\n'.join(self._get_revision_history(cmd, args, title))
2900 2901 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
2902 2903 #-----------------------------------------------------------
2904 -def create_encounter(fk_patient=None, enc_type=None):
2905 """Creates a new encounter for a patient. 2906 2907 fk_patient - patient PK 2908 enc_type - type of encounter 2909 """ 2910 if enc_type is None: 2911 enc_type = 'in surgery' 2912 # insert new encounter 2913 queries = [] 2914 try: 2915 enc_type = int(enc_type) 2916 cmd = """ 2917 INSERT INTO clin.encounter (fk_patient, fk_type, fk_location) 2918 VALUES (%(pat)s, %(typ)s, %(prax)s) RETURNING pk""" 2919 except ValueError: 2920 enc_type = enc_type 2921 cmd = """ 2922 INSERT INTO clin.encounter (fk_patient, fk_location, fk_type) 2923 VALUES ( 2924 %(pat)s, 2925 %(prax)s, 2926 coalesce ( 2927 (select pk from clin.encounter_type where description = %(typ)s), 2928 -- pick the first available 2929 (select pk from clin.encounter_type limit 1) 2930 ) 2931 ) RETURNING pk""" 2932 praxis = gmPraxis.gmCurrentPraxisBranch() 2933 args = {'pat': fk_patient, 'typ': enc_type, 'prax': praxis['pk_org_unit']} 2934 queries.append({'cmd': cmd, 'args': args}) 2935 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 2936 encounter = cEncounter(aPK_obj = rows[0]['pk']) 2937 2938 return encounter
2939 2940 #------------------------------------------------------------
2941 -def lock_encounter(pk_encounter, exclusive=False, link_obj=None):
2942 """Used to protect against deletion of active encounter from another client.""" 2943 return gmPG2.lock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
2944 2945 #------------------------------------------------------------
2946 -def unlock_encounter(pk_encounter, exclusive=False, link_obj=None):
2947 return gmPG2.unlock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
2948 2949 #-----------------------------------------------------------
2950 -def delete_encounter(pk_encounter):
2951 """Deletes an encounter by PK. 2952 2953 - attempts to obtain an exclusive lock which should 2954 fail if the encounter is the active encounter in 2955 this or any other client 2956 - catches DB exceptions which should mostly be related 2957 to clinical data already having been attached to 2958 the encounter thus making deletion fail 2959 """ 2960 conn = gmPG2.get_connection(readonly = False) 2961 if not lock_encounter(pk_encounter, exclusive = True, link_obj = conn): 2962 _log.debug('cannot lock encounter [%s] for deletion, it seems in use', pk_encounter) 2963 return False 2964 cmd = """DELETE FROM clin.encounter WHERE pk = %(enc)s""" 2965 args = {'enc': pk_encounter} 2966 try: 2967 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2968 except gmPG2.dbapi.Error: 2969 _log.exception('cannot delete encounter [%s]', pk_encounter) 2970 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn) 2971 return False 2972 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn) 2973 return True
2974 2975 #----------------------------------------------------------- 2976 # encounter types handling 2977 #-----------------------------------------------------------
2978 -def update_encounter_type(description=None, l10n_description=None):
2979 2980 rows, idx = gmPG2.run_rw_queries( 2981 queries = [{ 2982 'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 2983 'args': {'desc': description, 'l10n_desc': l10n_description} 2984 }], 2985 return_data = True 2986 ) 2987 2988 success = rows[0][0] 2989 if not success: 2990 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 2991 2992 return {'description': description, 'l10n_description': l10n_description}
2993 #-----------------------------------------------------------
2994 -def create_encounter_type(description=None, l10n_description=None):
2995 """This will attempt to create a NEW encounter type.""" 2996 2997 # need a system name, so derive one if necessary 2998 if description is None: 2999 description = l10n_description 3000 3001 args = { 3002 'desc': description, 3003 'l10n_desc': l10n_description 3004 } 3005 3006 _log.debug('creating encounter type: %s, %s', description, l10n_description) 3007 3008 # does it exist already ? 3009 cmd = "select description, _(description) from clin.encounter_type where description = %(desc)s" 3010 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3011 3012 # yes 3013 if len(rows) > 0: 3014 # both system and l10n name are the same so all is well 3015 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 3016 _log.info('encounter type [%s] already exists with the proper translation') 3017 return {'description': description, 'l10n_description': l10n_description} 3018 3019 # or maybe there just wasn't a translation to 3020 # the current language for this type yet ? 3021 cmd = "select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 3022 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3023 3024 # there was, so fail 3025 if rows[0][0]: 3026 _log.error('encounter type [%s] already exists but with another translation') 3027 return None 3028 3029 # else set it 3030 cmd = "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 3031 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3032 return {'description': description, 'l10n_description': l10n_description} 3033 3034 # no 3035 queries = [ 3036 {'cmd': "insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 3037 {'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 3038 ] 3039 rows, idx = gmPG2.run_rw_queries(queries = queries) 3040 3041 return {'description': description, 'l10n_description': l10n_description}
3042 3043 #-----------------------------------------------------------
3044 -def get_most_commonly_used_encounter_type():
3045 cmd = """ 3046 SELECT 3047 COUNT(1) AS type_count, 3048 fk_type 3049 FROM clin.encounter 3050 GROUP BY fk_type 3051 ORDER BY type_count DESC 3052 LIMIT 1 3053 """ 3054 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 3055 if len(rows) == 0: 3056 return None 3057 return rows[0]['fk_type']
3058 3059 #-----------------------------------------------------------
3060 -def get_encounter_types():
3061 cmd = """ 3062 SELECT 3063 _(description) AS l10n_description, 3064 description 3065 FROM 3066 clin.encounter_type 3067 ORDER BY 3068 l10n_description 3069 """ 3070 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 3071 return rows
3072 3073 #-----------------------------------------------------------
3074 -def get_encounter_type(description=None):
3075 cmd = "SELECT * from clin.encounter_type where description = %s" 3076 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 3077 return rows
3078 3079 #-----------------------------------------------------------
3080 -def delete_encounter_type(description=None):
3081 cmd = "delete from clin.encounter_type where description = %(desc)s" 3082 args = {'desc': description} 3083 try: 3084 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3085 except gmPG2.dbapi.IntegrityError as e: 3086 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 3087 return False 3088 raise 3089 3090 return True
3091 3092 #============================================================
3093 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
3094 """Represents one problem. 3095 3096 problems are the aggregation of 3097 .clinically_relevant=True issues and 3098 .is_open=True episodes 3099 """ 3100 _cmd_fetch_payload = '' # will get programmatically defined in __init__ 3101 _cmds_store_payload = ["select 1"] 3102 _updatable_fields = [] 3103 3104 #--------------------------------------------------------
3105 - def __init__(self, aPK_obj=None, try_potential_problems=False):
3106 """Initialize. 3107 3108 aPK_obj must contain the keys 3109 pk_patient 3110 pk_episode 3111 pk_health_issue 3112 """ 3113 if aPK_obj is None: 3114 raise gmExceptions.ConstructorError('cannot instatiate cProblem for PK: [%s]' % (aPK_obj)) 3115 3116 # As problems are rows from a view of different emr struct items, 3117 # the PK can't be a single field and, as some of the values of the 3118 # composed PK may be None, they must be queried using 'is null', 3119 # so we must programmatically construct the SQL query 3120 where_parts = [] 3121 pk = {} 3122 for col_name in aPK_obj.keys(): 3123 val = aPK_obj[col_name] 3124 if val is None: 3125 where_parts.append('%s IS NULL' % col_name) 3126 else: 3127 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 3128 pk[col_name] = val 3129 3130 # try to instantiate from true problem view 3131 cProblem._cmd_fetch_payload = """ 3132 SELECT *, False as is_potential_problem 3133 FROM clin.v_problem_list 3134 WHERE %s""" % ' AND '.join(where_parts) 3135 3136 try: 3137 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 3138 return 3139 except gmExceptions.ConstructorError: 3140 _log.exception('actual problem not found, trying "potential" problems') 3141 if try_potential_problems is False: 3142 raise 3143 3144 # try to instantiate from potential-problems view 3145 cProblem._cmd_fetch_payload = """ 3146 SELECT *, True as is_potential_problem 3147 FROM clin.v_potential_problem_list 3148 WHERE %s""" % ' AND '.join(where_parts) 3149 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3150 #--------------------------------------------------------
3151 - def get_as_episode(self):
3152 """ 3153 Retrieve the cEpisode instance equivalent to this problem. 3154 The problem's type attribute must be 'episode' 3155 """ 3156 if self._payload[self._idx['type']] != 'episode': 3157 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 3158 return None 3159 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
3160 #--------------------------------------------------------
3161 - def get_as_health_issue(self):
3162 """ 3163 Retrieve the cHealthIssue instance equivalent to this problem. 3164 The problem's type attribute must be 'issue' 3165 """ 3166 if self._payload[self._idx['type']] != 'issue': 3167 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 3168 return None 3169 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
3170 #--------------------------------------------------------
3171 - def get_visual_progress_notes(self, encounter_id=None):
3172 3173 if self._payload[self._idx['type']] == 'issue': 3174 latest = cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode 3175 if latest is None: 3176 return [] 3177 episodes = [ latest ] 3178 3179 emr = patient.emr 3180 3181 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 3182 return doc_folder.get_visual_progress_notes ( 3183 health_issue = self._payload[self._idx['pk_health_issue']], 3184 episode = self._payload[self._idx['pk_episode']] 3185 )
3186 3187 #-------------------------------------------------------- 3188 # properties 3189 #-------------------------------------------------------- 3190 # doubles as 'diagnostic_certainty_description' getter:
3192 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
3193 3194 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 3195 #--------------------------------------------------------
3196 - def _get_generic_codes(self):
3197 if self._payload[self._idx['type']] == 'issue': 3198 cmd = """ 3199 SELECT * FROM clin.v_linked_codes WHERE 3200 item_table = 'clin.lnk_code2h_issue'::regclass 3201 AND 3202 pk_item = %(item)s 3203 """ 3204 args = {'item': self._payload[self._idx['pk_health_issue']]} 3205 else: 3206 cmd = """ 3207 SELECT * FROM clin.v_linked_codes WHERE 3208 item_table = 'clin.lnk_code2episode'::regclass 3209 AND 3210 pk_item = %(item)s 3211 """ 3212 args = {'item': self._payload[self._idx['pk_episode']]} 3213 3214 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 3215 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3216 3217 generic_codes = property(_get_generic_codes, lambda x:x)
3218 #-----------------------------------------------------------
3219 -def problem2episode(problem=None):
3220 """Retrieve the cEpisode instance equivalent to the given problem. 3221 3222 The problem's type attribute must be 'episode' 3223 3224 @param problem: The problem to retrieve its related episode for 3225 @type problem: A gmEMRStructItems.cProblem instance 3226 """ 3227 if isinstance(problem, cEpisode): 3228 return problem 3229 3230 exc = TypeError('cannot convert [%s] to episode' % problem) 3231 3232 if not isinstance(problem, cProblem): 3233 raise exc 3234 3235 if problem['type'] != 'episode': 3236 raise exc 3237 3238 return cEpisode(aPK_obj = problem['pk_episode'])
3239 #-----------------------------------------------------------
3240 -def problem2issue(problem=None):
3241 """Retrieve the cIssue instance equivalent to the given problem. 3242 3243 The problem's type attribute must be 'issue'. 3244 3245 @param problem: The problem to retrieve the corresponding issue for 3246 @type problem: A gmEMRStructItems.cProblem instance 3247 """ 3248 if isinstance(problem, cHealthIssue): 3249 return problem 3250 3251 exc = TypeError('cannot convert [%s] to health issue' % problem) 3252 3253 if not isinstance(problem, cProblem): 3254 raise exc 3255 3256 if problem['type'] != 'issue': 3257 raise exc 3258 3259 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3260 #-----------------------------------------------------------
3261 -def reclass_problem(self, problem=None):
3262 """Transform given problem into either episode or health issue instance. 3263 """ 3264 if isinstance(problem, (cEpisode, cHealthIssue)): 3265 return problem 3266 3267 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 3268 3269 if not isinstance(problem, cProblem): 3270 _log.debug('%s' % problem) 3271 raise exc 3272 3273 if problem['type'] == 'episode': 3274 return cEpisode(aPK_obj = problem['pk_episode']) 3275 3276 if problem['type'] == 'issue': 3277 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 3278 3279 raise exc
3280 3281 #============================================================ 3282 _SQL_get_hospital_stays = "select * from clin.v_hospital_stays where %s" 3283
3284 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
3285 3286 _cmd_fetch_payload = _SQL_get_hospital_stays % "pk_hospital_stay = %s" 3287 _cmds_store_payload = [ 3288 """UPDATE clin.hospital_stay SET 3289 clin_when = %(admission)s, 3290 discharge = %(discharge)s, 3291 fk_org_unit = %(pk_org_unit)s, 3292 narrative = gm.nullify_empty_string(%(comment)s), 3293 fk_episode = %(pk_episode)s, 3294 fk_encounter = %(pk_encounter)s 3295 WHERE 3296 pk = %(pk_hospital_stay)s 3297 AND 3298 xmin = %(xmin_hospital_stay)s 3299 RETURNING 3300 xmin AS xmin_hospital_stay 3301 """ 3302 ] 3303 _updatable_fields = [ 3304 'admission', 3305 'discharge', 3306 'pk_org_unit', 3307 'pk_episode', 3308 'pk_encounter', 3309 'comment' 3310 ] 3311 3312 #--------------------------------------------------------
3313 - def format_maximum_information(self, patient=None):
3314 return self.format ( 3315 include_procedures = True, 3316 include_docs = True 3317 ).split('\n')
3318 3319 #-------------------------------------------------------
3320 - def format(self, left_margin=0, include_procedures=False, include_docs=False, include_episode=True):
3321 3322 if self._payload[self._idx['discharge']] is not None: 3323 discharge = ' - %s' % gmDateTime.pydt_strftime(self._payload[self._idx['discharge']], '%Y %b %d') 3324 else: 3325 discharge = '' 3326 3327 episode = '' 3328 if include_episode: 3329 episode = ': %s%s%s' % ( 3330 gmTools.u_left_double_angle_quote, 3331 self._payload[self._idx['episode']], 3332 gmTools.u_right_double_angle_quote 3333 ) 3334 3335 lines = ['%s%s%s (%s@%s)%s' % ( 3336 ' ' * left_margin, 3337 gmDateTime.pydt_strftime(self._payload[self._idx['admission']], '%Y %b %d'), 3338 discharge, 3339 self._payload[self._idx['ward']], 3340 self._payload[self._idx['hospital']], 3341 episode 3342 )] 3343 3344 if include_docs: 3345 for doc in self.documents: 3346 lines.append('%s%s: %s\n' % ( 3347 ' ' * left_margin, 3348 _('Document'), 3349 doc.format(single_line = True) 3350 )) 3351 3352 return '\n'.join(lines)
3353 3354 #--------------------------------------------------------
3355 - def _get_documents(self):
3356 return [ gmDocuments.cDocument(aPK_obj = pk_doc) for pk_doc in self._payload[self._idx['pk_documents']] ]
3357 3358 documents = property(_get_documents, lambda x:x)
3359 3360 #-----------------------------------------------------------
3361 -def get_latest_patient_hospital_stay(patient=None):
3362 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1" 3363 queries = [{ 3364 # this assumes non-overarching stays 3365 #'cmd': u'SELECT * FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1', 3366 'cmd': cmd, 3367 'args': {'pat': patient} 3368 }] 3369 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3370 if len(rows) == 0: 3371 return None 3372 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
3373 3374 #-----------------------------------------------------------
3375 -def get_patient_hospital_stays(patient=None, ongoing_only=False):
3376 args = {'pat': patient} 3377 if ongoing_only: 3378 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s AND discharge is NULL ORDER BY admission" 3379 else: 3380 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission" 3381 3382 queries = [{'cmd': cmd, 'args': args}] 3383 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3384 3385 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
3386 3387 #-----------------------------------------------------------
3388 -def create_hospital_stay(encounter=None, episode=None, fk_org_unit=None):
3389 3390 queries = [{ 3391 'cmd': 'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk', 3392 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit} 3393 }] 3394 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 3395 3396 return cHospitalStay(aPK_obj = rows[0][0])
3397 3398 #-----------------------------------------------------------
3399 -def delete_hospital_stay(stay=None):
3400 cmd = 'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 3401 args = {'pk': stay} 3402 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3403 return True
3404 3405 #============================================================ 3406 _SQL_get_procedures = "select * from clin.v_procedures where %s" 3407
3408 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
3409 3410 _cmd_fetch_payload = _SQL_get_procedures % "pk_procedure = %s" 3411 _cmds_store_payload = [ 3412 """UPDATE clin.procedure SET 3413 soap_cat = 'p', 3414 clin_when = %(clin_when)s, 3415 clin_end = %(clin_end)s, 3416 is_ongoing = %(is_ongoing)s, 3417 narrative = gm.nullify_empty_string(%(performed_procedure)s), 3418 fk_hospital_stay = %(pk_hospital_stay)s, 3419 fk_org_unit = (CASE 3420 WHEN %(pk_hospital_stay)s IS NULL THEN %(pk_org_unit)s 3421 ELSE NULL 3422 END)::integer, 3423 fk_episode = %(pk_episode)s, 3424 fk_encounter = %(pk_encounter)s, 3425 fk_doc = %(pk_doc)s, 3426 comment = gm.nullify_empty_string(%(comment)s) 3427 WHERE 3428 pk = %(pk_procedure)s AND 3429 xmin = %(xmin_procedure)s 3430 RETURNING xmin as xmin_procedure""" 3431 ] 3432 _updatable_fields = [ 3433 'clin_when', 3434 'clin_end', 3435 'is_ongoing', 3436 'performed_procedure', 3437 'pk_hospital_stay', 3438 'pk_org_unit', 3439 'pk_episode', 3440 'pk_encounter', 3441 'pk_doc', 3442 'comment' 3443 ] 3444 #-------------------------------------------------------
3445 - def __setitem__(self, attribute, value):
3446 3447 if (attribute == 'pk_hospital_stay') and (value is not None): 3448 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_org_unit', None) 3449 3450 if (attribute == 'pk_org_unit') and (value is not None): 3451 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 3452 3453 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
3454 3455 #--------------------------------------------------------
3456 - def format_maximum_information(self, left_margin=0, patient=None):
3457 return self.format ( 3458 left_margin = left_margin, 3459 include_episode = True, 3460 include_codes = True, 3461 include_address = True, 3462 include_comm = True, 3463 include_doc = True 3464 ).split('\n')
3465 3466 #-------------------------------------------------------
3467 - def format(self, left_margin=0, include_episode=True, include_codes=False, include_address=False, include_comm=False, include_doc=False):
3468 3469 if self._payload[self._idx['is_ongoing']]: 3470 end = _(' (ongoing)') 3471 else: 3472 end = self._payload[self._idx['clin_end']] 3473 if end is None: 3474 end = '' 3475 else: 3476 end = ' - %s' % gmDateTime.pydt_strftime(end, '%Y %b %d') 3477 3478 line = '%s%s%s: %s%s [%s @ %s]' % ( 3479 (' ' * left_margin), 3480 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d'), 3481 end, 3482 self._payload[self._idx['performed_procedure']], 3483 gmTools.bool2str(include_episode, ' (%s)' % self._payload[self._idx['episode']], ''), 3484 self._payload[self._idx['unit']], 3485 self._payload[self._idx['organization']] 3486 ) 3487 3488 line += gmTools.coalesce(self._payload[self._idx['comment']], '', '\n' + (' ' * left_margin) + _('Comment: ') + '%s') 3489 3490 if include_comm: 3491 for channel in self.org_unit.comm_channels: 3492 line += ('\n%(comm_type)s: %(url)s' % channel) 3493 3494 if include_address: 3495 adr = self.org_unit.address 3496 if adr is not None: 3497 line += '\n' 3498 line += '\n'.join(adr.format(single_line = False, show_type = False)) 3499 line += '\n' 3500 3501 if include_doc: 3502 doc = self.doc 3503 if doc is not None: 3504 line += '\n' 3505 line += _('Document') + ': ' + doc.format(single_line = True) 3506 line += '\n' 3507 3508 if include_codes: 3509 codes = self.generic_codes 3510 if len(codes) > 0: 3511 line += '\n' 3512 for c in codes: 3513 line += '%s %s: %s (%s - %s)\n' % ( 3514 (' ' * left_margin), 3515 c['code'], 3516 c['term'], 3517 c['name_short'], 3518 c['version'] 3519 ) 3520 del codes 3521 3522 return line
3523 3524 #--------------------------------------------------------
3525 - def add_code(self, pk_code=None):
3526 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 3527 cmd = "INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 3528 args = { 3529 'issue': self._payload[self._idx['pk_procedure']], 3530 'code': pk_code 3531 } 3532 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3533 return True
3534 3535 #--------------------------------------------------------
3536 - def remove_code(self, pk_code=None):
3537 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 3538 cmd = "DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 3539 args = { 3540 'issue': self._payload[self._idx['pk_procedure']], 3541 'code': pk_code 3542 } 3543 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3544 return True
3545 3546 #-------------------------------------------------------- 3547 # properties 3548 #--------------------------------------------------------
3549 - def _get_stay(self):
3550 if self._payload[self._idx['pk_hospital_stay']] is None: 3551 return None 3552 return cHospitalStay(aPK_obj = self._payload[self._idx['pk_hospital_stay']])
3553 3554 hospital_stay = property(_get_stay, lambda x:x) 3555 3556 #--------------------------------------------------------
3557 - def _get_org_unit(self):
3558 return gmOrganization.cOrgUnit(self._payload[self._idx['pk_org_unit']])
3559 3560 org_unit = property(_get_org_unit, lambda x:x) 3561 3562 #--------------------------------------------------------
3563 - def _get_doc(self):
3564 if self._payload[self._idx['pk_doc']] is None: 3565 return None 3566 return gmDocuments.cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
3567 3568 doc = property(_get_doc, lambda x:x) 3569 3570 #--------------------------------------------------------
3571 - def _get_generic_codes(self):
3572 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 3573 return [] 3574 3575 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 3576 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 3577 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 3578 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3579
3580 - def _set_generic_codes(self, pk_codes):
3581 queries = [] 3582 # remove all codes 3583 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 3584 queries.append ({ 3585 'cmd': 'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 3586 'args': { 3587 'proc': self._payload[self._idx['pk_procedure']], 3588 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 3589 } 3590 }) 3591 # add new codes 3592 for pk_code in pk_codes: 3593 queries.append ({ 3594 'cmd': 'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 3595 'args': { 3596 'proc': self._payload[self._idx['pk_procedure']], 3597 'pk_code': pk_code 3598 } 3599 }) 3600 if len(queries) == 0: 3601 return 3602 # run it all in one transaction 3603 rows, idx = gmPG2.run_rw_queries(queries = queries) 3604 return
3605 3606 generic_codes = property(_get_generic_codes, _set_generic_codes)
3607 3608 #-----------------------------------------------------------
3609 -def get_performed_procedures(patient=None):
3610 3611 queries = [{ 3612 'cmd': 'SELECT * FROM clin.v_procedures WHERE pk_patient = %(pat)s ORDER BY clin_when', 3613 'args': {'pat': patient} 3614 }] 3615 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3616 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3617 3618 #-----------------------------------------------------------
3619 -def get_procedures4document(pk_document=None):
3620 args = {'pk_doc': pk_document} 3621 cmd = _SQL_get_procedures % 'pk_doc = %(pk_doc)s' 3622 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 3623 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3624 3625 #-----------------------------------------------------------
3626 -def get_latest_performed_procedure(patient=None):
3627 queries = [{ 3628 'cmd': 'select * FROM clin.v_procedures WHERE pk_patient = %(pat)s ORDER BY clin_when DESC LIMIT 1', 3629 'args': {'pat': patient} 3630 }] 3631 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3632 if len(rows) == 0: 3633 return None 3634 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})
3635 3636 #-----------------------------------------------------------
3637 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
3638 3639 queries = [{ 3640 'cmd': """ 3641 INSERT INTO clin.procedure ( 3642 fk_encounter, 3643 fk_episode, 3644 soap_cat, 3645 fk_org_unit, 3646 fk_hospital_stay, 3647 narrative 3648 ) VALUES ( 3649 %(enc)s, 3650 %(epi)s, 3651 'p', 3652 %(loc)s, 3653 %(stay)s, 3654 gm.nullify_empty_string(%(proc)s) 3655 ) 3656 RETURNING pk""", 3657 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 3658 }] 3659 3660 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 3661 3662 return cPerformedProcedure(aPK_obj = rows[0][0])
3663 3664 #-----------------------------------------------------------
3665 -def delete_performed_procedure(procedure=None):
3666 cmd = 'delete from clin.procedure where pk = %(pk)s' 3667 args = {'pk': procedure} 3668 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3669 return True
3670 3671 #============================================================
3672 -def export_emr_structure(patient=None, filename=None):
3673 3674 if filename is None: 3675 filename = gmTools.get_unique_filename(prefix = 'gm-emr_struct-%s-' % patient.subdir_name, suffix = '.txt') 3676 3677 f = io.open(filename, 'w+', encoding = 'utf8') 3678 3679 f.write('patient [%s]\n' % patient['description_gender']) 3680 emr = patient.emr 3681 for issue in emr.health_issues: 3682 f.write('\n') 3683 f.write('\n') 3684 f.write('issue [%s] #%s\n' % (issue['description'], issue['pk_health_issue'])) 3685 f.write(' is active : %s\n' % issue['is_active']) 3686 f.write(' has open epi : %s\n' % issue['has_open_episode']) 3687 f.write(' possible start: %s\n' % issue.possible_start_date) 3688 f.write(' safe start : %s\n' % issue.safe_start_date) 3689 end = issue.clinical_end_date 3690 if end is None: 3691 f.write(' end : active and/or open episode\n') 3692 else: 3693 f.write(' end : %s\n' % end) 3694 f.write(' latest access : %s\n' % issue.latest_access_date) 3695 first = issue.first_episode 3696 if first is not None: 3697 first = first['description'] 3698 f.write(' 1st episode : %s\n' % first) 3699 last = issue.latest_episode 3700 if last is not None: 3701 last = last['description'] 3702 f.write(' latest episode: %s\n' % last) 3703 epis = sorted(issue.get_episodes(), key = lambda e: e.best_guess_clinical_start_date) 3704 for epi in epis: 3705 f.write('\n') 3706 f.write(' episode [%s] #%s\n' % (epi['description'], epi['pk_episode'])) 3707 f.write(' is open : %s\n' % epi['episode_open']) 3708 f.write(' best guess start: %s\n' % epi.best_guess_clinical_start_date) 3709 f.write(' best guess end : %s\n' % epi.best_guess_clinical_end_date) 3710 f.write(' latest access : %s\n' % epi.latest_access_date) 3711 f.write(' start 1st enc : %s\n' % epi['started_first']) 3712 f.write(' start last enc : %s\n' % epi['started_last']) 3713 f.write(' end last enc : %s\n' % epi['last_affirmed']) 3714 3715 f.close() 3716 return filename
3717 3718 #============================================================ 3719 # tools 3720 #------------------------------------------------------------
3721 -def check_fk_encounter_fk_episode_x_ref():
3722 3723 aggregate_result = 0 3724 3725 fks_linking2enc = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'encounter', column = 'pk') 3726 tables_linking2enc = set([ r['referencing_table'] for r in fks_linking2enc ]) 3727 3728 fks_linking2epi = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'episode', column = 'pk') 3729 tables_linking2epi = [ r['referencing_table'] for r in fks_linking2epi ] 3730 3731 tables_linking2both = tables_linking2enc.intersection(tables_linking2epi) 3732 3733 tables_linking2enc = {} 3734 for fk in fks_linking2enc: 3735 table = fk['referencing_table'] 3736 tables_linking2enc[table] = fk 3737 3738 tables_linking2epi = {} 3739 for fk in fks_linking2epi: 3740 table = fk['referencing_table'] 3741 tables_linking2epi[table] = fk 3742 3743 for t in tables_linking2both: 3744 3745 table_file_name = 'x-check_enc_epi_xref-%s.log' % t 3746 table_file = io.open(table_file_name, 'w+', encoding = 'utf8') 3747 3748 # get PK column 3749 args = {'table': t} 3750 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': gmPG2.SQL_get_pk_col_def, 'args': args}]) 3751 pk_col = rows[0][0] 3752 print("checking table:", t, '- pk col:', pk_col) 3753 print(' =>', table_file_name) 3754 table_file.write('table: %s\n' % t) 3755 table_file.write('PK col: %s\n' % pk_col) 3756 3757 # get PKs 3758 cmd = 'select %s from %s' % (pk_col, t) 3759 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 3760 pks = [ r[0] for r in rows ] 3761 for pk in pks: 3762 args = {'pk': pk, 'tbl': t} 3763 enc_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from %s where %s = %%(pk)s)" % (t, pk_col) 3764 epi_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from clin.episode where pk = (select fk_episode from %s where %s = %%(pk)s))" % (t, pk_col) 3765 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': enc_cmd, 'args': args}]) 3766 epi_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': epi_cmd, 'args': args}]) 3767 enc_pat = enc_rows[0][0] 3768 epi_pat = epi_rows[0][0] 3769 args['pat_enc'] = enc_pat 3770 args['pat_epi'] = epi_pat 3771 if epi_pat != enc_pat: 3772 print(' mismatch: row pk=%s, enc pat=%s, epi pat=%s' % (pk, enc_pat, epi_pat)) 3773 aggregate_result = -2 3774 3775 table_file.write('--------------------------------------------------------------------------------\n') 3776 table_file.write('mismatch on row with pk: %s\n' % pk) 3777 table_file.write('\n') 3778 3779 table_file.write('journal entry:\n') 3780 cmd = 'SELECT * from clin.v_emr_journal where src_table = %(tbl)s AND src_pk = %(pk)s' 3781 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3782 if len(rows) > 0: 3783 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3784 table_file.write('\n\n') 3785 3786 table_file.write('row data:\n') 3787 cmd = 'SELECT * from %s where %s = %%(pk)s' % (t, pk_col) 3788 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3789 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3790 table_file.write('\n\n') 3791 3792 table_file.write('episode:\n') 3793 cmd = 'SELECT * from clin.v_pat_episodes WHERE pk_episode = (select fk_episode from %s where %s = %%(pk)s)' % (t, pk_col) 3794 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3795 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3796 table_file.write('\n\n') 3797 3798 table_file.write('patient of episode:\n') 3799 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_epi)s' 3800 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3801 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3802 table_file.write('\n\n') 3803 3804 table_file.write('encounter:\n') 3805 cmd = 'SELECT * from clin.v_pat_encounters WHERE pk_encounter = (select fk_encounter from %s where %s = %%(pk)s)' % (t, pk_col) 3806 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3807 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3808 table_file.write('\n\n') 3809 3810 table_file.write('patient of encounter:\n') 3811 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_enc)s' 3812 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3813 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3814 table_file.write('\n') 3815 3816 table_file.write('done\n') 3817 table_file.close() 3818 3819 return aggregate_result
3820 3821 #------------------------------------------------------------
3822 -def export_patient_emr_structure():
3823 from Gnumed.business import gmPersonSearch 3824 praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0]) 3825 pat = gmPersonSearch.ask_for_patient() 3826 while pat is not None: 3827 print('patient:', pat['description_gender']) 3828 fname = os.path.expanduser('~/gnumed/gm-emr_structure-%s.txt' % pat.subdir_name) 3829 print('exported into:', export_emr_structure(patient = pat, filename = fname)) 3830 pat = gmPersonSearch.ask_for_patient() 3831 3832 return 0
3833 3834 #============================================================ 3835 # main - unit testing 3836 #------------------------------------------------------------ 3837 if __name__ == '__main__': 3838 3839 if len(sys.argv) < 2: 3840 sys.exit() 3841 3842 if sys.argv[1] != 'test': 3843 sys.exit() 3844 3845 #-------------------------------------------------------- 3846 # define tests 3847 #--------------------------------------------------------
3848 - def test_problem():
3849 print("\nProblem test") 3850 print("------------") 3851 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 3852 print(prob) 3853 fields = prob.get_fields() 3854 for field in fields: 3855 print(field, ':', prob[field]) 3856 print('\nupdatable:', prob.get_updatable_fields()) 3857 epi = prob.get_as_episode() 3858 print('\nas episode:') 3859 if epi is not None: 3860 for field in epi.get_fields(): 3861 print(' .%s : %s' % (field, epi[field]))
3862 3863 #--------------------------------------------------------
3864 - def test_health_issue():
3865 print("\nhealth issue test") 3866 print("-----------------") 3867 h_issue = cHealthIssue(aPK_obj=2) 3868 print(h_issue) 3869 print(h_issue.latest_access_date) 3870 print(h_issue.clinical_end_date) 3871 # fields = h_issue.get_fields() 3872 # for field in fields: 3873 # print field, ':', h_issue[field] 3874 # print "has open episode:", h_issue.has_open_episode() 3875 # print "open episode:", h_issue.get_open_episode() 3876 # print "updateable:", h_issue.get_updatable_fields() 3877 # h_issue.close_expired_episode(ttl=7300) 3878 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 3879 # print h_issue 3880 # print h_issue.format_as_journal() 3881 print(h_issue.formatted_revision_history)
3882 3883 #--------------------------------------------------------
3884 - def test_episode():
3885 print("episode test") 3886 print("------------") 3887 episode = cEpisode(aPK_obj = 1322) #1674) #1354) #1461) #1299) 3888 3889 print(episode['description']) 3890 print('start:', episode.best_guess_clinical_start_date) 3891 print('end :', episode.best_guess_clinical_end_date) 3892 return 3893 3894 print(episode) 3895 fields = episode.get_fields() 3896 for field in fields: 3897 print(field, ':', episode[field]) 3898 print("updatable:", episode.get_updatable_fields()) 3899 input('ENTER to continue') 3900 3901 old_description = episode['description'] 3902 old_enc = cEncounter(aPK_obj = 1) 3903 3904 desc = '1-%s' % episode['description'] 3905 print("==> renaming to", desc) 3906 successful = episode.rename ( 3907 description = desc 3908 ) 3909 if not successful: 3910 print("error") 3911 else: 3912 print("success") 3913 for field in fields: 3914 print(field, ':', episode[field]) 3915 3916 print(episode.formatted_revision_history) 3917 3918 input('ENTER to continue')
3919 3920 #--------------------------------------------------------
3921 - def test_encounter():
3922 print("\nencounter test") 3923 print("--------------") 3924 encounter = cEncounter(aPK_obj=1) 3925 print(encounter) 3926 fields = encounter.get_fields() 3927 for field in fields: 3928 print(field, ':', encounter[field]) 3929 print("updatable:", encounter.get_updatable_fields()) 3930 #print encounter.formatted_revision_history 3931 print(encounter.transfer_all_data_to_another_encounter(pk_target_encounter = 2))
3932 3933 #--------------------------------------------------------
3934 - def test_encounter2latex():
3935 encounter = cEncounter(aPK_obj=1) 3936 print(encounter) 3937 print("") 3938 print(encounter.format_latex())
3939 #--------------------------------------------------------
3940 - def test_performed_procedure():
3941 procs = get_performed_procedures(patient = 12) 3942 for proc in procs: 3943 print(proc.format(left_margin=2))
3944 #--------------------------------------------------------
3945 - def test_hospital_stay():
3946 stay = create_hospital_stay(encounter = 1, episode = 2, fk_org_unit = 1) 3947 # stay['hospital'] = u'Starfleet Galaxy General Hospital' 3948 # stay.save_payload() 3949 print(stay) 3950 for s in get_patient_hospital_stays(12): 3951 print(s) 3952 delete_hospital_stay(stay['pk_hospital_stay']) 3953 stay = create_hospital_stay(encounter = 1, episode = 4, fk_org_unit = 1)
3954 #--------------------------------------------------------
3955 - def test_diagnostic_certainty_classification_map():
3956 tests = [None, 'A', 'B', 'C', 'D', 'E'] 3957 3958 for t in tests: 3959 print(type(t), t) 3960 print(type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t))
3961 #--------------------------------------------------------
3962 - def test_episode_codes():
3963 epi = cEpisode(aPK_obj = 2) 3964 print(epi) 3965 print(epi.generic_codes)
3966 #--------------------------------------------------------
3967 - def test_episode_encounters():
3968 epi = cEpisode(aPK_obj = 1638) 3969 print(epi.format())
3970 3971 #--------------------------------------------------------
3972 - def test_export_emr_structure():
3973 export_patient_emr_structure()
3974 #praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0]) 3975 #from Gnumed.business import gmPerson 3976 ## 12 / 20 / 138 / 58 / 20 / 5 / 14 3977 #pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(aPK_obj = 138)) 3978 #fname = os.path.expanduser(u'~/gnumed/emr_structure-%s.txt' % pat.subdir_name) 3979 #print export_emr_structure(patient = pat, filename = fname) 3980 3981 #-------------------------------------------------------- 3982 # run them 3983 #test_episode() 3984 #test_episode_encounters() 3985 #test_problem() 3986 #test_encounter() 3987 #test_health_issue() 3988 #test_hospital_stay() 3989 #test_performed_procedure() 3990 #test_diagnostic_certainty_classification_map() 3991 #test_encounter2latex() 3992 #test_episode_codes() 3993 3994 test_export_emr_structure() 3995 3996 #============================================================ 3997