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 909 #--------------------------------------------------------
911 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
912 913 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 914 915 #--------------------------------------------------------
917 cmd = """SELECT 918 'NONE (live row)'::text as audit__action_applied, 919 NULL AS audit__action_when, 920 NULL AS audit__action_by, 921 pk_audit, 922 row_version, 923 modified_when, 924 modified_by, 925 pk, 926 description, 927 laterality, 928 age_noted, 929 is_active, 930 clinically_relevant, 931 is_confidential, 932 is_cause_of_death, 933 fk_encounter, 934 grouping, 935 diagnostic_certainty_classification, 936 summary 937 FROM clin.health_issue 938 WHERE pk = %(pk_health_issue)s 939 UNION ALL ( 940 SELECT 941 audit_action as audit__action_applied, 942 audit_when as audit__action_when, 943 audit_by as audit__action_by, 944 pk_audit, 945 orig_version as row_version, 946 orig_when as modified_when, 947 orig_by as modified_by, 948 pk, 949 description, 950 laterality, 951 age_noted, 952 is_active, 953 clinically_relevant, 954 is_confidential, 955 is_cause_of_death, 956 fk_encounter, 957 grouping, 958 diagnostic_certainty_classification, 959 summary 960 FROM audit.log_health_issue 961 WHERE pk = %(pk_health_issue)s 962 ) 963 ORDER BY row_version DESC 964 """ 965 args = {'pk_health_issue': self.pk_obj} 966 title = _('Health issue: %s%s%s') % ( 967 gmTools.u_left_double_angle_quote, 968 self._payload[self._idx['description']], 969 gmTools.u_right_double_angle_quote 970 ) 971 return '\n'.join(self._get_revision_history(cmd, args, title))
972 973 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x) 974 #--------------------------------------------------------
975 - def _get_generic_codes(self):
976 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 977 return [] 978 979 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 980 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 981 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 982 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
983
984 - def _set_generic_codes(self, pk_codes):
985 queries = [] 986 # remove all codes 987 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 988 queries.append ({ 989 'cmd': 'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s', 990 'args': { 991 'issue': self._payload[self._idx['pk_health_issue']], 992 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 993 } 994 }) 995 # add new codes 996 for pk_code in pk_codes: 997 queries.append ({ 998 'cmd': 'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)', 999 'args': { 1000 'issue': self._payload[self._idx['pk_health_issue']], 1001 'pk_code': pk_code 1002 } 1003 }) 1004 if len(queries) == 0: 1005 return 1006 # run it all in one transaction 1007 rows, idx = gmPG2.run_rw_queries(queries = queries) 1008 return
1009 1010 generic_codes = property(_get_generic_codes, _set_generic_codes)
1011 1012 #============================================================
1013 -def create_health_issue(description=None, encounter=None, patient=None):
1014 """Creates a new health issue for a given patient. 1015 1016 description - health issue name 1017 """ 1018 try: 1019 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 1020 return h_issue 1021 except gmExceptions.NoSuchBusinessObjectError: 1022 pass 1023 1024 queries = [] 1025 cmd = "insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 1026 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 1027 1028 cmd = "select currval('clin.health_issue_pk_seq')" 1029 queries.append({'cmd': cmd}) 1030 1031 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1032 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 1033 1034 return h_issue
1035 1036 #-----------------------------------------------------------
1037 -def delete_health_issue(health_issue=None):
1038 if isinstance(health_issue, cHealthIssue): 1039 args = {'pk': health_issue['pk_health_issue']} 1040 else: 1041 args = {'pk': int(health_issue)} 1042 try: 1043 gmPG2.run_rw_queries(queries = [{'cmd': 'DELETE FROM clin.health_issue WHERE pk = %(pk)s', 'args': args}]) 1044 except gmPG2.dbapi.IntegrityError: 1045 # should be parsing pgcode/and or error message 1046 _log.exception('cannot delete health issue') 1047 return False 1048 1049 return True
1050 1051 #------------------------------------------------------------ 1052 # use as dummy for unassociated episodes
1053 -def get_dummy_health_issue():
1054 issue = { 1055 'pk_health_issue': None, 1056 'description': _('Unattributed episodes'), 1057 'age_noted': None, 1058 'laterality': 'na', 1059 'is_active': True, 1060 'clinically_relevant': True, 1061 'is_confidential': None, 1062 'is_cause_of_death': False, 1063 'is_dummy': True, 1064 'grouping': None 1065 } 1066 return issue
1067 1068 #-----------------------------------------------------------
1069 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
1070 return cProblem ( 1071 aPK_obj = { 1072 'pk_patient': health_issue['pk_patient'], 1073 'pk_health_issue': health_issue['pk_health_issue'], 1074 'pk_episode': None 1075 }, 1076 try_potential_problems = allow_irrelevant 1077 )
1078 1079 #============================================================ 1080 # episodes API 1081 #============================================================
1082 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
1083 """Represents one clinical episode. 1084 """ 1085 _cmd_fetch_payload = "select * from clin.v_pat_episodes where pk_episode=%s" 1086 _cmds_store_payload = [ 1087 """update clin.episode set 1088 fk_health_issue = %(pk_health_issue)s, 1089 is_open = %(episode_open)s::boolean, 1090 description = %(description)s, 1091 summary = gm.nullify_empty_string(%(summary)s), 1092 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 1093 where 1094 pk = %(pk_episode)s and 1095 xmin = %(xmin_episode)s""", 1096 """select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 1097 ] 1098 _updatable_fields = [ 1099 'pk_health_issue', 1100 'episode_open', 1101 'description', 1102 'summary', 1103 'diagnostic_certainty_classification' 1104 ] 1105 #--------------------------------------------------------
1106 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None, link_obj=None):
1107 pk = aPK_obj 1108 if pk is None and row is None: 1109 1110 where_parts = ['description = %(desc)s'] 1111 1112 if id_patient is not None: 1113 where_parts.append('pk_patient = %(pat)s') 1114 1115 if health_issue is not None: 1116 where_parts.append('pk_health_issue = %(issue)s') 1117 1118 if encounter is not None: 1119 where_parts.append('pk_patient = (SELECT fk_patient FROM clin.encounter WHERE pk = %(enc)s)') 1120 1121 args = { 1122 'pat': id_patient, 1123 'issue': health_issue, 1124 'enc': encounter, 1125 'desc': name 1126 } 1127 1128 cmd = 'SELECT * FROM clin.v_pat_episodes WHERE %s' % ' AND '.join(where_parts) 1129 1130 rows, idx = gmPG2.run_ro_queries ( 1131 link_obj = link_obj, 1132 queries = [{'cmd': cmd, 'args': args}], 1133 get_col_idx=True 1134 ) 1135 1136 if len(rows) == 0: 1137 raise gmExceptions.NoSuchBusinessObjectError('no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter)) 1138 1139 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 1140 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 1141 1142 else: 1143 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row, link_obj = link_obj)
1144 1145 #-------------------------------------------------------- 1146 # external API 1147 #--------------------------------------------------------
1148 - def get_patient(self):
1149 return self._payload[self._idx['pk_patient']]
1150 1151 #--------------------------------------------------------
1152 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
1153 return gmClinNarrative.get_narrative ( 1154 soap_cats = soap_cats, 1155 encounters = encounters, 1156 episodes = [self.pk_obj], 1157 order_by = order_by 1158 )
1159 1160 #--------------------------------------------------------
1161 - def rename(self, description=None):
1162 """Method for episode editing, that is, episode renaming. 1163 1164 @param description 1165 - the new descriptive name for the encounter 1166 @type description 1167 - a string instance 1168 """ 1169 # sanity check 1170 if description.strip() == '': 1171 _log.error('<description> must be a non-empty string instance') 1172 return False 1173 # update the episode description 1174 old_description = self._payload[self._idx['description']] 1175 self._payload[self._idx['description']] = description.strip() 1176 self._is_modified = True 1177 successful, data = self.save_payload() 1178 if not successful: 1179 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 1180 self._payload[self._idx['description']] = old_description 1181 return False 1182 return True
1183 1184 #--------------------------------------------------------
1185 - def add_code(self, pk_code=None):
1186 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1187 1188 if pk_code in self._payload[self._idx['pk_generic_codes']]: 1189 return 1190 1191 cmd = """ 1192 INSERT INTO clin.lnk_code2episode 1193 (fk_item, fk_generic_code) 1194 SELECT 1195 %(item)s, 1196 %(code)s 1197 WHERE NOT EXISTS ( 1198 SELECT 1 FROM clin.lnk_code2episode 1199 WHERE 1200 fk_item = %(item)s 1201 AND 1202 fk_generic_code = %(code)s 1203 )""" 1204 args = { 1205 'item': self._payload[self._idx['pk_episode']], 1206 'code': pk_code 1207 } 1208 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1209 return
1210 1211 #--------------------------------------------------------
1212 - def remove_code(self, pk_code=None):
1213 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 1214 cmd = "DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 1215 args = { 1216 'item': self._payload[self._idx['pk_episode']], 1217 'code': pk_code 1218 } 1219 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1220 return True
1221 1222 #--------------------------------------------------------
1223 - def format_as_journal(self, left_margin=0, date_format='%Y %b %d, %a'):
1224 rows = gmClinNarrative.get_as_journal ( 1225 episodes = (self.pk_obj,), 1226 order_by = 'pk_encounter, clin_when, scr, src_table' 1227 #order_by = u'pk_encounter, scr, clin_when, src_table' 1228 ) 1229 1230 if len(rows) == 0: 1231 return '' 1232 1233 lines = [] 1234 1235 lines.append(_('Clinical data generated during encounters within this episode:')) 1236 1237 left_margin = ' ' * left_margin 1238 1239 prev_enc = None 1240 for row in rows: 1241 if row['pk_encounter'] != prev_enc: 1242 lines.append('') 1243 prev_enc = row['pk_encounter'] 1244 1245 when = row['clin_when'].strftime(date_format) 1246 top_row = '%s%s %s (%s) %s' % ( 1247 gmTools.u_box_top_left_arc, 1248 gmTools.u_box_horiz_single, 1249 gmSoapDefs.soap_cat2l10n_str[row['real_soap_cat']], 1250 when, 1251 gmTools.u_box_horiz_single * 5 1252 ) 1253 soap = gmTools.wrap ( 1254 text = row['narrative'], 1255 width = 60, 1256 initial_indent = ' ', 1257 subsequent_indent = ' ' + left_margin 1258 ) 1259 row_ver = '' 1260 if row['row_version'] > 0: 1261 row_ver = 'v%s: ' % row['row_version'] 1262 bottom_row = '%s%s %s, %s%s %s' % ( 1263 ' ' * 40, 1264 gmTools.u_box_horiz_light_heavy, 1265 row['modified_by'], 1266 row_ver, 1267 gmDateTime.pydt_strftime(row['modified_when'], date_format), 1268 gmTools.u_box_horiz_heavy_light 1269 ) 1270 1271 lines.append(top_row) 1272 lines.append(soap) 1273 lines.append(bottom_row) 1274 1275 eol_w_margin = '\n%s' % left_margin 1276 return left_margin + eol_w_margin.join(lines) + '\n'
1277 1278 #--------------------------------------------------------
1279 - def format_maximum_information(self, patient=None):
1280 if patient is None: 1281 from Gnumed.business.gmPerson import gmCurrentPatient, cPerson 1282 if self._payload[self._idx['pk_patient']] == gmCurrentPatient().ID: 1283 patient = gmCurrentPatient() 1284 else: 1285 patient = cPerson(self._payload[self._idx['pk_patient']]) 1286 1287 return self.format ( 1288 patient = patient, 1289 with_summary = True, 1290 with_codes = True, 1291 with_encounters = True, 1292 with_documents = True, 1293 with_hospital_stays = True, 1294 with_procedures = True, 1295 with_family_history = True, 1296 with_tests = False, # does not inform on the episode itself 1297 with_vaccinations = True, 1298 with_health_issue = True, 1299 return_list = True 1300 )
1301 1302 #--------------------------------------------------------
1303 - def format(self, left_margin=0, patient=None, 1304 with_summary=True, 1305 with_codes=True, 1306 with_encounters=True, 1307 with_documents=True, 1308 with_hospital_stays=True, 1309 with_procedures=True, 1310 with_family_history=True, 1311 with_tests=True, 1312 with_vaccinations=True, 1313 with_health_issue=False, 1314 return_list=False 1315 ):
1316 1317 if patient is not None: 1318 if patient.ID != self._payload[self._idx['pk_patient']]: 1319 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 1320 patient.ID, 1321 self._payload[self._idx['pk_episode']], 1322 self._payload[self._idx['pk_patient']] 1323 ) 1324 raise ValueError(msg) 1325 emr = patient.emr 1326 else: 1327 with_encounters = False 1328 with_documents = False 1329 with_hospital_stays = False 1330 with_procedures = False 1331 with_family_history = False 1332 with_tests = False 1333 with_vaccinations = False 1334 1335 lines = [] 1336 1337 # episode details 1338 lines.append (_('Episode %s%s%s [#%s]') % ( 1339 gmTools.u_left_double_angle_quote, 1340 self._payload[self._idx['description']], 1341 gmTools.u_right_double_angle_quote, 1342 self._payload[self._idx['pk_episode']] 1343 )) 1344 1345 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 1346 lines.append (' ' + _('Created during encounter: %s (%s - %s) [#%s]') % ( 1347 enc['l10n_type'], 1348 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1349 enc['last_affirmed_original_tz'].strftime('%H:%M'), 1350 self._payload[self._idx['pk_encounter']] 1351 )) 1352 1353 if patient is not None: 1354 range_str, range_str_verb, duration_str = self.formatted_clinical_duration 1355 lines.append(_(' Duration: %s (%s)') % (duration_str, range_str_verb)) 1356 1357 lines.append(' ' + _('Status') + ': %s%s' % ( 1358 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 1359 gmTools.coalesce ( 1360 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 1361 instead = '', 1362 template_initial = ', %s', 1363 none_equivalents = [None, ''] 1364 ) 1365 )) 1366 1367 if with_health_issue: 1368 lines.append(' ' + _('Health issue') + ': %s' % gmTools.coalesce ( 1369 self._payload[self._idx['health_issue']], 1370 _('none associated') 1371 )) 1372 1373 if with_summary: 1374 if self._payload[self._idx['summary']] is not None: 1375 lines.append(' %s:' % _('Synopsis')) 1376 lines.append(gmTools.wrap ( 1377 text = self._payload[self._idx['summary']], 1378 width = 60, 1379 initial_indent = ' ', 1380 subsequent_indent = ' ' 1381 ) 1382 ) 1383 1384 # codes 1385 if with_codes: 1386 codes = self.generic_codes 1387 if len(codes) > 0: 1388 lines.append('') 1389 for c in codes: 1390 lines.append(' %s: %s (%s - %s)' % ( 1391 c['code'], 1392 c['term'], 1393 c['name_short'], 1394 c['version'] 1395 )) 1396 del codes 1397 1398 lines.append('') 1399 1400 # encounters 1401 if with_encounters: 1402 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 1403 if encs is None: 1404 lines.append(_('Error retrieving encounters for this episode.')) 1405 elif len(encs) == 0: 1406 #lines.append(_('There are no encounters for this issue.')) 1407 pass 1408 else: 1409 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 1410 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 1411 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 1412 if len(encs) < 4: 1413 line = _('%s encounter(s) (%s - %s):') 1414 else: 1415 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):') 1416 lines.append(line % ( 1417 len(encs), 1418 first_encounter['started'].strftime('%m/%Y'), 1419 last_encounter['last_affirmed'].strftime('%m/%Y') 1420 )) 1421 lines.append(' %s - %s (%s):%s' % ( 1422 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1423 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 1424 first_encounter['l10n_type'], 1425 gmTools.coalesce ( 1426 first_encounter['assessment_of_encounter'], 1427 gmTools.coalesce ( 1428 first_encounter['reason_for_encounter'], 1429 '', 1430 ' \u00BB%s\u00AB' + (' (%s)' % _('RFE')) 1431 ), 1432 ' \u00BB%s\u00AB' + (' (%s)' % _('AOE')) 1433 ) 1434 )) 1435 if len(encs) > 4: 1436 lines.append(_(' %s %s skipped %s') % ( 1437 gmTools.u_ellipsis, 1438 (len(encs) - 4), 1439 gmTools.u_ellipsis 1440 )) 1441 for enc in encs[1:][-3:]: 1442 lines.append(' %s - %s (%s):%s' % ( 1443 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 1444 enc['last_affirmed_original_tz'].strftime('%H:%M'), 1445 enc['l10n_type'], 1446 gmTools.coalesce ( 1447 enc['assessment_of_encounter'], 1448 gmTools.coalesce ( 1449 enc['reason_for_encounter'], 1450 '', 1451 ' \u00BB%s\u00AB' + (' (%s)' % _('RFE')) 1452 ), 1453 ' \u00BB%s\u00AB' + (' (%s)' % _('AOE')) 1454 ) 1455 )) 1456 del encs 1457 # spell out last encounter 1458 if last_encounter is not None: 1459 lines.append('') 1460 lines.append(_('Progress notes in most recent encounter:')) 1461 lines.extend(last_encounter.format_soap ( 1462 episodes = [ self._payload[self._idx['pk_episode']] ], 1463 left_margin = left_margin, 1464 soap_cats = 'soapu', 1465 emr = emr 1466 )) 1467 1468 # documents 1469 if with_documents: 1470 doc_folder = patient.get_document_folder() 1471 docs = doc_folder.get_documents ( 1472 pk_episodes = [ self._payload[self._idx['pk_episode']] ] 1473 ) 1474 if len(docs) > 0: 1475 lines.append('') 1476 lines.append(_('Documents: %s') % len(docs)) 1477 for d in docs: 1478 lines.append(' ' + d.format(single_line = True)) 1479 del docs 1480 1481 # hospitalizations 1482 if with_hospital_stays: 1483 stays = emr.get_hospital_stays(episodes = [ self._payload[self._idx['pk_episode']] ]) 1484 if len(stays) > 0: 1485 lines.append('') 1486 lines.append(_('Hospitalizations: %s') % len(stays)) 1487 for s in stays: 1488 lines.append(s.format(left_margin = (left_margin + 1))) 1489 del stays 1490 1491 # procedures 1492 if with_procedures: 1493 procs = emr.get_performed_procedures(episodes = [ self._payload[self._idx['pk_episode']] ]) 1494 if len(procs) > 0: 1495 lines.append('') 1496 lines.append(_('Procedures performed: %s') % len(procs)) 1497 for p in procs: 1498 lines.append(p.format ( 1499 left_margin = (left_margin + 1), 1500 include_episode = False, 1501 include_codes = True 1502 )) 1503 del procs 1504 1505 # family history 1506 if with_family_history: 1507 fhx = emr.get_family_history(episodes = [ self._payload[self._idx['pk_episode']] ]) 1508 if len(fhx) > 0: 1509 lines.append('') 1510 lines.append(_('Family History: %s') % len(fhx)) 1511 for f in fhx: 1512 lines.append(f.format ( 1513 left_margin = (left_margin + 1), 1514 include_episode = False, 1515 include_comment = True, 1516 include_codes = True 1517 )) 1518 del fhx 1519 1520 # test results 1521 if with_tests: 1522 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 1523 if len(tests) > 0: 1524 lines.append('') 1525 lines.append(_('Measurements and Results:')) 1526 for t in tests: 1527 lines.append(' ' + t.format_concisely(date_format = '%Y %b %d', with_notes = True)) 1528 del tests 1529 1530 # vaccinations 1531 if with_vaccinations: 1532 vaccs = emr.get_vaccinations ( 1533 episodes = [ self._payload[self._idx['pk_episode']] ], 1534 order_by = 'date_given DESC, vaccine' 1535 ) 1536 if len(vaccs) > 0: 1537 lines.append('') 1538 lines.append(_('Vaccinations:')) 1539 for vacc in vaccs: 1540 lines.extend(vacc.format ( 1541 with_indications = True, 1542 with_comment = True, 1543 with_reaction = True, 1544 date_format = '%Y-%m-%d' 1545 )) 1546 del vaccs 1547 1548 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = '\n') 1549 if return_list: 1550 return lines 1551 1552 left_margin = ' ' * left_margin 1553 eol_w_margin = '\n%s' % left_margin 1554 return left_margin + eol_w_margin.join(lines) + '\n'
1555 1556 #-------------------------------------------------------- 1557 # properties 1558 #--------------------------------------------------------
1560 cmd = """SELECT MIN(earliest) FROM 1561 ( 1562 -- last modification of episode, 1563 -- earliest-possible thereof = when created, 1564 -- should actually go all the way back into audit.log_episode 1565 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s) 1566 1567 UNION ALL 1568 1569 -- last modification of encounter in which created, 1570 -- earliest-possible thereof = initial creation of that encounter 1571 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1572 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1573 )) 1574 UNION ALL 1575 1576 -- start of encounter in which created, 1577 -- earliest-possible thereof = explicitely set by user 1578 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1579 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1580 )) 1581 UNION ALL 1582 1583 -- start of encounters of clinical items linked to this episode, 1584 -- earliest-possible thereof = explicitely set by user 1585 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN ( 1586 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1587 )) 1588 UNION ALL 1589 1590 -- .clin_when of clinical items linked to this episode, 1591 -- earliest-possible thereof = explicitely set by user 1592 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1593 1594 UNION ALL 1595 1596 -- earliest modification time of clinical items linked to this episode 1597 -- this CAN be used since if an item is linked to an episode it can be 1598 -- assumed the episode (should have) existed at the time of creation 1599 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1600 1601 UNION ALL 1602 1603 -- there may not be items, but there may still be documents ... 1604 (SELECT MIN(clin_when) AS earliest FROM blobs.doc_med WHERE fk_episode = %(pk)s) 1605 ) AS candidates""" 1606 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1607 return rows[0][0]
1608 1609 best_guess_clinical_start_date = property(_get_best_guess_clinical_start_date) 1610 1611 #--------------------------------------------------------
1613 if self._payload[self._idx['episode_open']]: 1614 return None 1615 1616 cmd = """SELECT COALESCE ( 1617 (SELECT 1618 latest --, source_type 1619 FROM ( 1620 -- latest explicit .clin_when of clinical items linked to this episode 1621 (SELECT 1622 MAX(clin_when) AS latest, 1623 'clin.episode.pk = clin.clin_root_item.fk_episode -> .clin_when'::text AS source_type 1624 FROM clin.clin_root_item 1625 WHERE fk_episode = %(pk)s 1626 ) 1627 UNION ALL 1628 -- latest explicit .clin_when of documents linked to this episode 1629 (SELECT 1630 MAX(clin_when) AS latest, 1631 'clin.episode.pk = blobs.doc_med.fk_episode -> .clin_when'::text AS source_type 1632 FROM blobs.doc_med 1633 WHERE fk_episode = %(pk)s 1634 ) 1635 ) AS candidates 1636 ORDER BY latest DESC NULLS LAST 1637 LIMIT 1 1638 ), 1639 -- last ditch, always exists, only use when no clinical items or documents linked: 1640 -- last modification, latest = when last changed to the current state 1641 (SELECT c_epi.modified_when AS latest --, 'clin.episode.modified_when'::text AS source_type 1642 FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s 1643 ) 1644 )""" 1645 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False) 1646 return rows[0][0]
1647 1648 best_guess_clinical_end_date = property(_get_best_guess_clinical_end_date) 1649 1650 #--------------------------------------------------------
1652 start = self.best_guess_clinical_start_date 1653 end = self.best_guess_clinical_end_date 1654 if end is None: 1655 range_str = '%s-%s' % ( 1656 gmDateTime.pydt_strftime(start, "%b'%y"), 1657 gmTools.u_ellipsis 1658 ) 1659 range_str_verb = '%s - %s' % ( 1660 gmDateTime.pydt_strftime(start, '%b %d %Y'), 1661 gmTools.u_ellipsis 1662 ) 1663 duration_str = _('%s so far') % gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - start) 1664 return (range_str, range_str_verb, duration_str) 1665 1666 duration_str = gmDateTime.format_interval_medically(end - start) 1667 # year different: 1668 if end.year != start.year: 1669 range_str = '%s-%s' % ( 1670 gmDateTime.pydt_strftime(start, "%b'%y"), 1671 gmDateTime.pydt_strftime(end, "%b'%y") 1672 ) 1673 range_str_verb = '%s - %s' % ( 1674 gmDateTime.pydt_strftime(start, '%b %d %Y'), 1675 gmDateTime.pydt_strftime(end, '%b %d %Y') 1676 ) 1677 return (range_str, range_str_verb, duration_str) 1678 # same year: 1679 if end.month != start.month: 1680 range_str = '%s-%s' % ( 1681 gmDateTime.pydt_strftime(start, '%b'), 1682 gmDateTime.pydt_strftime(end, "%b'%y") 1683 ) 1684 range_str_verb = '%s - %s' % ( 1685 gmDateTime.pydt_strftime(start, '%b %d'), 1686 gmDateTime.pydt_strftime(end, '%b %d %Y') 1687 ) 1688 return (range_str, range_str_verb, duration_str) 1689 1690 # same year and same month 1691 range_str = gmDateTime.pydt_strftime(start, "%b'%y") 1692 range_str_verb = gmDateTime.pydt_strftime(start, '%b %d %Y') 1693 return (range_str, range_str_verb, duration_str)
1694 1695 formatted_clinical_duration = property(_get_formatted_clinical_duration) 1696 1697 #--------------------------------------------------------
1698 - def _get_latest_access_date(self):
1699 cmd = """SELECT MAX(latest) FROM ( 1700 -- last modification, latest = when last changed to the current state 1701 (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) 1702 1703 UNION ALL 1704 1705 -- last modification of encounter in which created, latest = initial creation of that encounter 1706 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer 1707 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1708 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1709 --)) 1710 1711 -- end of encounter in which created, latest = explicitely set 1712 -- DO NOT USE: we can retrospectively create episodes which 1713 -- DO NOT USE: are long since finished 1714 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = ( 1715 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s 1716 --)) 1717 1718 -- latest end of encounters of clinical items linked to this episode 1719 (SELECT 1720 MAX(last_affirmed) AS latest, 1721 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate 1722 FROM clin.encounter 1723 WHERE pk IN ( 1724 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s 1725 )) 1726 UNION ALL 1727 1728 -- latest explicit .clin_when of clinical items linked to this episode 1729 (SELECT 1730 MAX(clin_when) AS latest, 1731 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate 1732 FROM clin.clin_root_item 1733 WHERE fk_episode = %(pk)s 1734 ) 1735 1736 -- latest modification time of clinical items linked to this episode 1737 -- this CAN be used since if an item is linked to an episode it can be 1738 -- assumed the episode (should have) existed at the time of creation 1739 -- DO NOT USE, because typo fixes should not extend the episode 1740 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s) 1741 1742 -- not sure about this one: 1743 -- .pk -> clin.clin_root_item.fk_encounter.modified_when 1744 1745 ) AS candidates""" 1746 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 1747 return rows[0][0]
1748 1749 latest_access_date = property(_get_latest_access_date) 1750 1751 #--------------------------------------------------------
1753 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1754 1755 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 1756 1757 #--------------------------------------------------------
1759 cmd = """SELECT 1760 'NONE (live row)'::text as audit__action_applied, 1761 NULL AS audit__action_when, 1762 NULL AS audit__action_by, 1763 pk_audit, 1764 row_version, 1765 modified_when, 1766 modified_by, 1767 pk, fk_health_issue, description, is_open, fk_encounter, 1768 diagnostic_certainty_classification, 1769 summary 1770 FROM clin.episode 1771 WHERE pk = %(pk_episode)s 1772 UNION ALL ( 1773 SELECT 1774 audit_action as audit__action_applied, 1775 audit_when as audit__action_when, 1776 audit_by as audit__action_by, 1777 pk_audit, 1778 orig_version as row_version, 1779 orig_when as modified_when, 1780 orig_by as modified_by, 1781 pk, fk_health_issue, description, is_open, fk_encounter, 1782 diagnostic_certainty_classification, 1783 summary 1784 FROM audit.log_episode 1785 WHERE pk = %(pk_episode)s 1786 ) 1787 ORDER BY row_version DESC 1788 """ 1789 args = {'pk_episode': self.pk_obj} 1790 title = _('Episode: %s%s%s') % ( 1791 gmTools.u_left_double_angle_quote, 1792 self._payload[self._idx['description']], 1793 gmTools.u_right_double_angle_quote 1794 ) 1795 return '\n'.join(self._get_revision_history(cmd, args, title))
1796 1797 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x) 1798 1799 #--------------------------------------------------------
1800 - def _get_generic_codes(self):
1801 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 1802 return [] 1803 1804 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 1805 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 1806 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1807 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1808
1809 - def _set_generic_codes(self, pk_codes):
1810 queries = [] 1811 # remove all codes 1812 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 1813 queries.append ({ 1814 'cmd': 'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s', 1815 'args': { 1816 'epi': self._payload[self._idx['pk_episode']], 1817 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 1818 } 1819 }) 1820 # add new codes 1821 for pk_code in pk_codes: 1822 queries.append ({ 1823 'cmd': 'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)', 1824 'args': { 1825 'epi': self._payload[self._idx['pk_episode']], 1826 'pk_code': pk_code 1827 } 1828 }) 1829 if len(queries) == 0: 1830 return 1831 # run it all in one transaction 1832 rows, idx = gmPG2.run_rw_queries(queries = queries) 1833 return
1834 1835 generic_codes = property(_get_generic_codes, _set_generic_codes) 1836 1837 #--------------------------------------------------------
1838 - def _get_has_narrative(self):
1839 cmd = """SELECT EXISTS ( 1840 SELECT 1 FROM clin.clin_narrative 1841 WHERE 1842 fk_episode = %(epi)s 1843 AND 1844 fk_encounter IN ( 1845 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 1846 ) 1847 )""" 1848 args = { 1849 'pat': self._payload[self._idx['pk_patient']], 1850 'epi': self._payload[self._idx['pk_episode']] 1851 } 1852 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 1853 return rows[0][0]
1854 1855 has_narrative = property(_get_has_narrative, lambda x:x) 1856 1857 #--------------------------------------------------------
1858 - def _get_health_issue(self):
1859 if self._payload[self._idx['pk_health_issue']] is None: 1860 return None 1861 return cHealthIssue(self._payload[self._idx['pk_health_issue']])
1862 1863 health_issue = property(_get_health_issue)
1864 1865 #============================================================
1866 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None, link_obj=None):
1867 """Creates a new episode for a given patient's health issue. 1868 1869 pk_health_issue - given health issue PK 1870 episode_name - name of episode 1871 """ 1872 if not allow_dupes: 1873 try: 1874 episode = cEpisode(name = episode_name, health_issue = pk_health_issue, encounter = encounter, link_obj = link_obj) 1875 if episode['episode_open'] != is_open: 1876 episode['episode_open'] = is_open 1877 episode.save_payload() 1878 return episode 1879 except gmExceptions.ConstructorError: 1880 pass 1881 1882 queries = [] 1883 cmd = "INSERT INTO clin.episode (fk_health_issue, description, is_open, fk_encounter) VALUES (%s, %s, %s::boolean, %s)" 1884 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 1885 queries.append({'cmd': cEpisode._cmd_fetch_payload % "currval('clin.episode_pk_seq')"}) 1886 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data=True, get_col_idx=True) 1887 1888 episode = cEpisode(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 1889 return episode
1890 1891 #-----------------------------------------------------------
1892 -def delete_episode(episode=None):
1893 if isinstance(episode, cEpisode): 1894 pk = episode['pk_episode'] 1895 else: 1896 pk = int(episode) 1897 1898 cmd = 'DELETE FROM clin.episode WHERE pk = %(pk)s' 1899 1900 try: 1901 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}]) 1902 except gmPG2.dbapi.IntegrityError: 1903 # should be parsing pgcode/and or error message 1904 _log.exception('cannot delete episode, it is in use') 1905 return False 1906 1907 return True
1908 #-----------------------------------------------------------
1909 -def episode2problem(episode=None, allow_closed=False):
1910 return cProblem ( 1911 aPK_obj = { 1912 'pk_patient': episode['pk_patient'], 1913 'pk_episode': episode['pk_episode'], 1914 'pk_health_issue': episode['pk_health_issue'] 1915 }, 1916 try_potential_problems = allow_closed 1917 )
1918 1919 #============================================================ 1920 # encounter API 1921 #============================================================ 1922 SQL_get_encounters = "SELECT * FROM clin.v_pat_encounters WHERE %s" 1923
1924 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1925 """Represents one encounter.""" 1926 1927 _cmd_fetch_payload = SQL_get_encounters % 'pk_encounter = %s' 1928 _cmds_store_payload = [ 1929 """UPDATE clin.encounter SET 1930 started = %(started)s, 1931 last_affirmed = %(last_affirmed)s, 1932 fk_location = %(pk_org_unit)s, 1933 fk_type = %(pk_type)s, 1934 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1935 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1936 WHERE 1937 pk = %(pk_encounter)s AND 1938 xmin = %(xmin_encounter)s 1939 """, 1940 # need to return all fields so we can survive in-place upgrades 1941 "SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s" 1942 ] 1943 _updatable_fields = [ 1944 'started', 1945 'last_affirmed', 1946 'pk_org_unit', 1947 'pk_type', 1948 'reason_for_encounter', 1949 'assessment_of_encounter' 1950 ] 1951 #--------------------------------------------------------
1952 - def set_active(self):
1953 """Set the encounter as the active one. 1954 1955 "Setting active" means making sure the encounter 1956 row has the youngest "last_affirmed" timestamp of 1957 all encounter rows for this patient. 1958 """ 1959 self['last_affirmed'] = gmDateTime.pydt_now_here() 1960 self.save()
1961 #--------------------------------------------------------
1962 - def lock(self, exclusive=False, link_obj=None):
1963 return lock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1964 #--------------------------------------------------------
1965 - def unlock(self, exclusive=False, link_obj=None):
1966 return unlock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1967 #--------------------------------------------------------
1968 - def transfer_clinical_data(self, source_episode=None, target_episode=None):
1969 """ 1970 Moves every element currently linked to the current encounter 1971 and the source_episode onto target_episode. 1972 1973 @param source_episode The episode the elements are currently linked to. 1974 @type target_episode A cEpisode intance. 1975 @param target_episode The episode the elements will be relinked to. 1976 @type target_episode A cEpisode intance. 1977 """ 1978 if source_episode['pk_episode'] == target_episode['pk_episode']: 1979 return True 1980 1981 queries = [] 1982 cmd = """ 1983 UPDATE clin.clin_root_item 1984 SET fk_episode = %(trg)s 1985 WHERE 1986 fk_encounter = %(enc)s AND 1987 fk_episode = %(src)s 1988 """ 1989 rows, idx = gmPG2.run_rw_queries(queries = [{ 1990 'cmd': cmd, 1991 'args': { 1992 'trg': target_episode['pk_episode'], 1993 'enc': self.pk_obj, 1994 'src': source_episode['pk_episode'] 1995 } 1996 }]) 1997 self.refetch_payload() 1998 return True
1999 2000 #--------------------------------------------------------
2001 - def transfer_all_data_to_another_encounter(self, pk_target_encounter=None):
2002 if pk_target_encounter == self.pk_obj: 2003 return True 2004 cmd = "SELECT clin.transfer_all_encounter_data(%(src)s, %(trg)s)" 2005 args = { 2006 'src': self.pk_obj, 2007 'trg': pk_target_encounter 2008 } 2009 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2010 return True
2011 2012 # conn = gmPG2.get_connection() 2013 # curs = conn.cursor() 2014 # curs.callproc('clin.get_hints_for_patient', [pk_identity]) 2015 # rows = curs.fetchall() 2016 # idx = gmPG2.get_col_indices(curs) 2017 # curs.close() 2018 # conn.rollback() 2019 2020 #--------------------------------------------------------
2021 - def same_payload(self, another_object=None):
2022 2023 relevant_fields = [ 2024 'pk_org_unit', 2025 'pk_type', 2026 'pk_patient', 2027 'reason_for_encounter', 2028 'assessment_of_encounter' 2029 ] 2030 for field in relevant_fields: 2031 if self._payload[self._idx[field]] != another_object[field]: 2032 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 2033 return False 2034 2035 relevant_fields = [ 2036 'started', 2037 'last_affirmed', 2038 ] 2039 for field in relevant_fields: 2040 if self._payload[self._idx[field]] is None: 2041 if another_object[field] is None: 2042 continue 2043 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field]) 2044 return False 2045 2046 if another_object[field] is None: 2047 return False 2048 2049 # compares at seconds granularity 2050 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'): 2051 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field]) 2052 return False 2053 2054 # compare codes 2055 # 1) RFE 2056 if another_object['pk_generic_codes_rfe'] is None: 2057 if self._payload[self._idx['pk_generic_codes_rfe']] is not None: 2058 return False 2059 if another_object['pk_generic_codes_rfe'] is not None: 2060 if self._payload[self._idx['pk_generic_codes_rfe']] is None: 2061 return False 2062 if ( 2063 (another_object['pk_generic_codes_rfe'] is None) 2064 and 2065 (self._payload[self._idx['pk_generic_codes_rfe']] is None) 2066 ) is False: 2067 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]): 2068 return False 2069 # 2) AOE 2070 if another_object['pk_generic_codes_aoe'] is None: 2071 if self._payload[self._idx['pk_generic_codes_aoe']] is not None: 2072 return False 2073 if another_object['pk_generic_codes_aoe'] is not None: 2074 if self._payload[self._idx['pk_generic_codes_aoe']] is None: 2075 return False 2076 if ( 2077 (another_object['pk_generic_codes_aoe'] is None) 2078 and 2079 (self._payload[self._idx['pk_generic_codes_aoe']] is None) 2080 ) is False: 2081 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]): 2082 return False 2083 2084 return True
2085 #--------------------------------------------------------
2086 - def has_clinical_data(self):
2087 cmd = """ 2088 select exists ( 2089 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 2090 union all 2091 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 2092 )""" 2093 args = { 2094 'pat': self._payload[self._idx['pk_patient']], 2095 'enc': self.pk_obj 2096 } 2097 rows, idx = gmPG2.run_ro_queries ( 2098 queries = [{ 2099 'cmd': cmd, 2100 'args': args 2101 }] 2102 ) 2103 return rows[0][0]
2104 2105 #--------------------------------------------------------
2106 - def has_narrative(self):
2107 cmd = """ 2108 select exists ( 2109 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 2110 )""" 2111 args = { 2112 'pat': self._payload[self._idx['pk_patient']], 2113 'enc': self.pk_obj 2114 } 2115 rows, idx = gmPG2.run_ro_queries ( 2116 queries = [{ 2117 'cmd': cmd, 2118 'args': args 2119 }] 2120 ) 2121 return rows[0][0]
2122 #--------------------------------------------------------
2123 - def has_soap_narrative(self, soap_cats=None):
2124 """soap_cats: <space> = admin category""" 2125 2126 if soap_cats is None: 2127 soap_cats = 'soap ' 2128 else: 2129 soap_cats = soap_cats.lower() 2130 2131 cats = [] 2132 for cat in soap_cats: 2133 if cat in 'soapu': 2134 cats.append(cat) 2135 continue 2136 if cat == ' ': 2137 cats.append(None) 2138 2139 cmd = """ 2140 SELECT EXISTS ( 2141 SELECT 1 FROM clin.clin_narrative 2142 WHERE 2143 fk_encounter = %(enc)s 2144 AND 2145 soap_cat IN %(cats)s 2146 LIMIT 1 2147 ) 2148 """ 2149 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 2150 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 2151 return rows[0][0]
2152 #--------------------------------------------------------
2153 - def has_documents(self):
2154 cmd = """ 2155 select exists ( 2156 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 2157 )""" 2158 args = { 2159 'pat': self._payload[self._idx['pk_patient']], 2160 'enc': self.pk_obj 2161 } 2162 rows, idx = gmPG2.run_ro_queries ( 2163 queries = [{ 2164 'cmd': cmd, 2165 'args': args 2166 }] 2167 ) 2168 return rows[0][0]
2169 #--------------------------------------------------------
2170 - def get_latest_soap(self, soap_cat=None, episode=None):
2171 2172 if soap_cat is not None: 2173 soap_cat = soap_cat.lower() 2174 2175 if episode is None: 2176 epi_part = 'fk_episode is null' 2177 else: 2178 epi_part = 'fk_episode = %(epi)s' 2179 2180 cmd = """ 2181 select narrative 2182 from clin.clin_narrative 2183 where 2184 fk_encounter = %%(enc)s 2185 and 2186 soap_cat = %%(cat)s 2187 and 2188 %s 2189 order by clin_when desc 2190 limit 1 2191 """ % epi_part 2192 2193 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 2194 2195 rows, idx = gmPG2.run_ro_queries ( 2196 queries = [{ 2197 'cmd': cmd, 2198 'args': args 2199 }] 2200 ) 2201 if len(rows) == 0: 2202 return None 2203 2204 return rows[0][0]
2205 #--------------------------------------------------------
2206 - def get_episodes(self, exclude=None):
2207 cmd = """ 2208 SELECT * FROM clin.v_pat_episodes 2209 WHERE pk_episode IN ( 2210 SELECT DISTINCT fk_episode 2211 FROM clin.clin_root_item 2212 WHERE fk_encounter = %%(enc)s 2213 2214 UNION 2215 2216 SELECT DISTINCT fk_episode 2217 FROM blobs.doc_med 2218 WHERE fk_encounter = %%(enc)s 2219 ) %s""" 2220 args = {'enc': self.pk_obj} 2221 if exclude is not None: 2222 cmd = cmd % 'AND pk_episode NOT IN %(excluded)s' 2223 args['excluded'] = tuple(exclude) 2224 else: 2225 cmd = cmd % '' 2226 2227 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2228 2229 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
2230 2231 episodes = property(get_episodes, lambda x:x) 2232 #--------------------------------------------------------
2233 - def add_code(self, pk_code=None, field=None):
2234 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2235 if field == 'rfe': 2236 cmd = "INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 2237 elif field == 'aoe': 2238 cmd = "INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)" 2239 else: 2240 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 2241 args = { 2242 'item': self._payload[self._idx['pk_encounter']], 2243 'code': pk_code 2244 } 2245 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2246 return True
2247 #--------------------------------------------------------
2248 - def remove_code(self, pk_code=None, field=None):
2249 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 2250 if field == 'rfe': 2251 cmd = "DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 2252 elif field == 'aoe': 2253 cmd = "DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 2254 else: 2255 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field) 2256 args = { 2257 'item': self._payload[self._idx['pk_encounter']], 2258 'code': pk_code 2259 } 2260 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2261 return True
2262 2263 #-------------------------------------------------------- 2264 # data formatting 2265 #--------------------------------------------------------
2266 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):
2267 2268 lines = [] 2269 for soap_cat in gmSoapDefs.soap_cats2list(soap_cats): 2270 soap_cat_narratives = emr.get_clin_narrative ( 2271 episodes = episodes, 2272 issues = issues, 2273 encounters = [self._payload[self._idx['pk_encounter']]], 2274 soap_cats = [soap_cat] 2275 ) 2276 if soap_cat_narratives is None: 2277 continue 2278 if len(soap_cat_narratives) == 0: 2279 continue 2280 2281 lines.append('%s%s %s %s' % ( 2282 gmTools.u_box_top_left_arc, 2283 gmTools.u_box_horiz_single, 2284 gmSoapDefs.soap_cat2l10n_str[soap_cat], 2285 gmTools.u_box_horiz_single * 5 2286 )) 2287 for soap_entry in soap_cat_narratives: 2288 txt = gmTools.wrap ( 2289 text = soap_entry['narrative'], 2290 width = 75, 2291 initial_indent = '', 2292 subsequent_indent = (' ' * left_margin) 2293 ) 2294 lines.append(txt) 2295 when = gmDateTime.pydt_strftime ( 2296 soap_entry['date'], 2297 format = '%Y-%m-%d %H:%M', 2298 accuracy = gmDateTime.acc_minutes 2299 ) 2300 txt = '%s%s %.8s, %s %s' % ( 2301 ' ' * 40, 2302 gmTools.u_box_horiz_light_heavy, 2303 soap_entry['modified_by'], 2304 when, 2305 gmTools.u_box_horiz_heavy_light 2306 ) 2307 lines.append(txt) 2308 lines.append('') 2309 2310 return lines
2311 2312 #--------------------------------------------------------
2313 - def format_latex(self, date_format=None, soap_cats=None, soap_order=None):
2314 2315 nothing2format = ( 2316 (self._payload[self._idx['reason_for_encounter']] is None) 2317 and 2318 (self._payload[self._idx['assessment_of_encounter']] is None) 2319 and 2320 (self.has_soap_narrative(soap_cats = 'soapu') is False) 2321 ) 2322 if nothing2format: 2323 return '' 2324 2325 if date_format is None: 2326 date_format = '%A, %b %d %Y' 2327 2328 tex = '% -------------------------------------------------------------\n' 2329 tex += '% much recommended: \\usepackage(tabu)\n' 2330 tex += '% much recommended: \\usepackage(longtable)\n' 2331 tex += '% best wrapped in: "\\begin{longtabu} to \\textwidth {lX[,L]}"\n' 2332 tex += '% -------------------------------------------------------------\n' 2333 tex += '\\hline \n' 2334 tex += '\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 2335 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 2336 gmTools.tex_escape_string ( 2337 gmDateTime.pydt_strftime ( 2338 self._payload[self._idx['started']], 2339 date_format, 2340 accuracy = gmDateTime.acc_days 2341 ) 2342 ), 2343 gmTools.tex_escape_string ( 2344 gmDateTime.pydt_strftime ( 2345 self._payload[self._idx['started']], 2346 '%H:%M', 2347 accuracy = gmDateTime.acc_minutes 2348 ) 2349 ), 2350 gmTools.tex_escape_string ( 2351 gmDateTime.pydt_strftime ( 2352 self._payload[self._idx['last_affirmed']], 2353 '%H:%M', 2354 accuracy = gmDateTime.acc_minutes 2355 ) 2356 ) 2357 ) 2358 tex += '\\hline \n' 2359 2360 if self._payload[self._idx['reason_for_encounter']] is not None: 2361 tex += '%s & %s \\tabularnewline \n' % ( 2362 gmTools.tex_escape_string(_('RFE')), 2363 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 2364 ) 2365 if self._payload[self._idx['assessment_of_encounter']] is not None: 2366 tex += '%s & %s \\tabularnewline \n' % ( 2367 gmTools.tex_escape_string(_('AOE')), 2368 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 2369 ) 2370 2371 for epi in self.get_episodes(): 2372 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 2373 if len(soaps) == 0: 2374 continue 2375 tex += '\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 2376 gmTools.tex_escape_string(_('Problem')), 2377 gmTools.tex_escape_string(epi['description']), 2378 gmTools.tex_escape_string ( 2379 gmTools.coalesce ( 2380 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 2381 instead = '', 2382 template_initial = ' {\\footnotesize [%s]}', 2383 none_equivalents = [None, ''] 2384 ) 2385 ) 2386 ) 2387 if epi['pk_health_issue'] is not None: 2388 tex += '\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 2389 gmTools.tex_escape_string(_('Health issue')), 2390 gmTools.tex_escape_string(epi['health_issue']), 2391 gmTools.tex_escape_string ( 2392 gmTools.coalesce ( 2393 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 2394 instead = '', 2395 template_initial = ' {\\footnotesize [%s]}', 2396 none_equivalents = [None, ''] 2397 ) 2398 ) 2399 ) 2400 for soap in soaps: 2401 tex += '{\\small %s} & {\\small %s} \\tabularnewline \n' % ( 2402 gmTools.tex_escape_string(gmSoapDefs.soap_cat2l10n[soap['soap_cat']]), 2403 gmTools.tex_escape_string(soap['narrative'], replace_eol = True) 2404 ) 2405 tex += ' & \\tabularnewline \n' 2406 2407 return tex
2408 2409 #--------------------------------------------------------
2410 - def __format_header_fancy(self, left_margin=0):
2411 lines = [] 2412 2413 lines.append('%s%s: %s - %s (@%s)%s [#%s]' % ( 2414 ' ' * left_margin, 2415 self._payload[self._idx['l10n_type']], 2416 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 2417 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 2418 self._payload[self._idx['source_time_zone']], 2419 gmTools.coalesce ( 2420 self._payload[self._idx['assessment_of_encounter']], 2421 '', 2422 ' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote) 2423 ), 2424 self._payload[self._idx['pk_encounter']] 2425 )) 2426 2427 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 2428 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 2429 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 2430 gmDateTime.current_local_iso_numeric_timezone_string, 2431 gmTools.bool2subst ( 2432 gmDateTime.dst_currently_in_effect, 2433 gmDateTime.py_dst_timezone_name, 2434 gmDateTime.py_timezone_name 2435 ), 2436 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, ' - ' + _('daylight savings time in effect'), '') 2437 )) 2438 2439 if self._payload[self._idx['praxis_branch']] is not None: 2440 lines.append(_('Location: %s (%s)') % (self._payload[self._idx['praxis_branch']], self._payload[self._idx['praxis']])) 2441 2442 if self._payload[self._idx['reason_for_encounter']] is not None: 2443 lines.append('%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 2444 codes = self.generic_codes_rfe 2445 for c in codes: 2446 lines.append(' %s: %s (%s - %s)' % ( 2447 c['code'], 2448 c['term'], 2449 c['name_short'], 2450 c['version'] 2451 )) 2452 if len(codes) > 0: 2453 lines.append('') 2454 2455 if self._payload[self._idx['assessment_of_encounter']] is not None: 2456 lines.append('%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2457 codes = self.generic_codes_aoe 2458 for c in codes: 2459 lines.append(' %s: %s (%s - %s)' % ( 2460 c['code'], 2461 c['term'], 2462 c['name_short'], 2463 c['version'] 2464 )) 2465 if len(codes) > 0: 2466 lines.append('') 2467 del codes 2468 return lines
2469 2470 #--------------------------------------------------------
2471 - def format_header(self, fancy_header=True, left_margin=0, with_rfe_aoe=False):
2472 lines = [] 2473 2474 if fancy_header: 2475 return self.__format_header_fancy(left_margin = left_margin) 2476 2477 now = gmDateTime.pydt_now_here() 2478 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'): 2479 start = '%s %s' % ( 2480 _('today'), 2481 self._payload[self._idx['started_original_tz']].strftime('%H:%M') 2482 ) 2483 else: 2484 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M') 2485 lines.append('%s%s: %s - %s%s%s' % ( 2486 ' ' * left_margin, 2487 self._payload[self._idx['l10n_type']], 2488 start, 2489 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 2490 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], '', ' \u00BB%s\u00AB'), 2491 gmTools.coalesce(self._payload[self._idx['praxis_branch']], '', ' @%s') 2492 )) 2493 if with_rfe_aoe: 2494 if self._payload[self._idx['reason_for_encounter']] is not None: 2495 lines.append('%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 2496 codes = self.generic_codes_rfe 2497 for c in codes: 2498 lines.append(' %s: %s (%s - %s)' % ( 2499 c['code'], 2500 c['term'], 2501 c['name_short'], 2502 c['version'] 2503 )) 2504 if len(codes) > 0: 2505 lines.append('') 2506 if self._payload[self._idx['assessment_of_encounter']] is not None: 2507 lines.append('%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 2508 codes = self.generic_codes_aoe 2509 if len(codes) > 0: 2510 lines.append('') 2511 for c in codes: 2512 lines.append(' %s: %s (%s - %s)' % ( 2513 c['code'], 2514 c['term'], 2515 c['name_short'], 2516 c['version'] 2517 )) 2518 if len(codes) > 0: 2519 lines.append('') 2520 del codes 2521 2522 return lines
2523 2524 #--------------------------------------------------------
2525 - 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):
2526 2527 if patient is not None: 2528 emr = patient.emr 2529 2530 lines = [] 2531 if episodes is None: 2532 episodes = [ e['pk_episode'] for e in self.episodes ] 2533 2534 for pk in episodes: 2535 epi = cEpisode(aPK_obj = pk) 2536 lines.append(_('\nEpisode %s%s%s%s:') % ( 2537 gmTools.u_left_double_angle_quote, 2538 epi['description'], 2539 gmTools.u_right_double_angle_quote, 2540 gmTools.coalesce(epi['health_issue'], '', ' (%s)') 2541 )) 2542 2543 # soap 2544 if with_soap: 2545 if patient.ID != self._payload[self._idx['pk_patient']]: 2546 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2547 patient.ID, 2548 self._payload[self._idx['pk_encounter']], 2549 self._payload[self._idx['pk_patient']] 2550 ) 2551 raise ValueError(msg) 2552 lines.extend(self.format_soap ( 2553 episodes = [pk], 2554 left_margin = left_margin, 2555 soap_cats = None, # meaning: all 2556 emr = emr, 2557 issues = issues 2558 )) 2559 2560 # test results 2561 if with_tests: 2562 tests = emr.get_test_results_by_date ( 2563 episodes = [pk], 2564 encounter = self._payload[self._idx['pk_encounter']] 2565 ) 2566 if len(tests) > 0: 2567 lines.append('') 2568 lines.append(_('Measurements and Results:')) 2569 2570 for t in tests: 2571 lines.append(t.format()) 2572 2573 del tests 2574 2575 # vaccinations 2576 if with_vaccinations: 2577 vaccs = emr.get_vaccinations ( 2578 episodes = [pk], 2579 encounters = [ self._payload[self._idx['pk_encounter']] ], 2580 order_by = 'date_given DESC, vaccine' 2581 ) 2582 if len(vaccs) > 0: 2583 lines.append('') 2584 lines.append(_('Vaccinations:')) 2585 for vacc in vaccs: 2586 lines.extend(vacc.format ( 2587 with_indications = True, 2588 with_comment = True, 2589 with_reaction = True, 2590 date_format = '%Y-%m-%d' 2591 )) 2592 del vaccs 2593 2594 # family history 2595 if with_family_history: 2596 fhx = emr.get_family_history(episodes = [pk]) 2597 if len(fhx) > 0: 2598 lines.append('') 2599 lines.append(_('Family History: %s') % len(fhx)) 2600 for f in fhx: 2601 lines.append(f.format ( 2602 left_margin = (left_margin + 1), 2603 include_episode = False, 2604 include_comment = True 2605 )) 2606 del fhx 2607 2608 # documents 2609 if with_docs: 2610 doc_folder = patient.get_document_folder() 2611 docs = doc_folder.get_documents ( 2612 pk_episodes = [pk], 2613 encounter = self._payload[self._idx['pk_encounter']] 2614 ) 2615 if len(docs) > 0: 2616 lines.append('') 2617 lines.append(_('Documents:')) 2618 for d in docs: 2619 lines.append(' ' + d.format(single_line = True)) 2620 del docs 2621 2622 return lines
2623 2624 #--------------------------------------------------------
2625 - def format_maximum_information(self, patient=None):
2626 if patient is None: 2627 from Gnumed.business.gmPerson import gmCurrentPatient, cPerson 2628 if self._payload[self._idx['pk_patient']] == gmCurrentPatient().ID: 2629 patient = gmCurrentPatient() 2630 else: 2631 patient = cPerson(self._payload[self._idx['pk_patient']]) 2632 2633 return self.format ( 2634 patient = patient, 2635 fancy_header = True, 2636 with_rfe_aoe = True, 2637 with_soap = True, 2638 with_docs = True, 2639 with_tests = False, 2640 with_vaccinations = True, 2641 with_co_encountlet_hints = True, 2642 with_family_history = True, 2643 by_episode = False, 2644 return_list = True 2645 )
2646 2647 #--------------------------------------------------------
2648 - 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):
2649 """Format an encounter. 2650 2651 with_co_encountlet_hints: 2652 - whether to include which *other* episodes were discussed during this encounter 2653 - (only makes sense if episodes != None) 2654 """ 2655 lines = self.format_header ( 2656 fancy_header = fancy_header, 2657 left_margin = left_margin, 2658 with_rfe_aoe = with_rfe_aoe 2659 ) 2660 2661 if patient is None: 2662 _log.debug('no patient, cannot load patient related data') 2663 with_soap = False 2664 with_tests = False 2665 with_vaccinations = False 2666 with_docs = False 2667 2668 if by_episode: 2669 lines.extend(self.format_by_episode ( 2670 episodes = episodes, 2671 issues = issues, 2672 left_margin = left_margin, 2673 patient = patient, 2674 with_soap = with_soap, 2675 with_tests = with_tests, 2676 with_docs = with_docs, 2677 with_vaccinations = with_vaccinations, 2678 with_family_history = with_family_history 2679 )) 2680 else: 2681 if with_soap: 2682 lines.append('') 2683 if patient.ID != self._payload[self._idx['pk_patient']]: 2684 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 2685 patient.ID, 2686 self._payload[self._idx['pk_encounter']], 2687 self._payload[self._idx['pk_patient']] 2688 ) 2689 raise ValueError(msg) 2690 emr = patient.emr 2691 lines.extend(self.format_soap ( 2692 episodes = episodes, 2693 left_margin = left_margin, 2694 soap_cats = None, # meaning: all 2695 emr = emr, 2696 issues = issues 2697 )) 2698 2699 # # family history 2700 # if with_family_history: 2701 # if episodes is not None: 2702 # fhx = emr.get_family_history(episodes = episodes) 2703 # if len(fhx) > 0: 2704 # lines.append(u'') 2705 # lines.append(_('Family History: %s') % len(fhx)) 2706 # for f in fhx: 2707 # lines.append(f.format ( 2708 # left_margin = (left_margin + 1), 2709 # include_episode = False, 2710 # include_comment = True 2711 # )) 2712 # del fhx 2713 2714 # test results 2715 if with_tests: 2716 emr = patient.emr 2717 tests = emr.get_test_results_by_date ( 2718 episodes = episodes, 2719 encounter = self._payload[self._idx['pk_encounter']] 2720 ) 2721 if len(tests) > 0: 2722 lines.append('') 2723 lines.append(_('Measurements and Results:')) 2724 for t in tests: 2725 lines.append(t.format()) 2726 del tests 2727 2728 # vaccinations 2729 if with_vaccinations: 2730 emr = patient.emr 2731 vaccs = emr.get_vaccinations ( 2732 episodes = episodes, 2733 encounters = [ self._payload[self._idx['pk_encounter']] ], 2734 order_by = 'date_given DESC, vaccine' 2735 ) 2736 if len(vaccs) > 0: 2737 lines.append('') 2738 lines.append(_('Vaccinations:')) 2739 for vacc in vaccs: 2740 lines.extend(vacc.format ( 2741 with_indications = True, 2742 with_comment = True, 2743 with_reaction = True, 2744 date_format = '%Y-%m-%d' 2745 )) 2746 del vaccs 2747 2748 # documents 2749 if with_docs: 2750 doc_folder = patient.get_document_folder() 2751 docs = doc_folder.get_documents ( 2752 pk_episodes = episodes, 2753 encounter = self._payload[self._idx['pk_encounter']] 2754 ) 2755 if len(docs) > 0: 2756 lines.append('') 2757 lines.append(_('Documents:')) 2758 for d in docs: 2759 lines.append(' ' + d.format(single_line = True)) 2760 del docs 2761 2762 # co-encountlets 2763 if with_co_encountlet_hints: 2764 if episodes is not None: 2765 other_epis = self.get_episodes(exclude = episodes) 2766 if len(other_epis) > 0: 2767 lines.append('') 2768 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 2769 for epi in other_epis: 2770 lines.append(' %s%s%s%s' % ( 2771 gmTools.u_left_double_angle_quote, 2772 epi['description'], 2773 gmTools.u_right_double_angle_quote, 2774 gmTools.coalesce(epi['health_issue'], '', ' (%s)') 2775 )) 2776 2777 if return_list: 2778 return lines 2779 2780 eol_w_margin = '\n%s' % (' ' * left_margin) 2781 return '%s\n' % eol_w_margin.join(lines)
2782 2783 #-------------------------------------------------------- 2784 # properties 2785 #--------------------------------------------------------
2786 - def _get_generic_codes_rfe(self):
2787 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0: 2788 return [] 2789 2790 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 2791 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])} 2792 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2793 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2794
2795 - def _set_generic_codes_rfe(self, pk_codes):
2796 queries = [] 2797 # remove all codes 2798 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0: 2799 queries.append ({ 2800 'cmd': 'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2801 'args': { 2802 'enc': self._payload[self._idx['pk_encounter']], 2803 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']]) 2804 } 2805 }) 2806 # add new codes 2807 for pk_code in pk_codes: 2808 queries.append ({ 2809 'cmd': 'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2810 'args': { 2811 'enc': self._payload[self._idx['pk_encounter']], 2812 'pk_code': pk_code 2813 } 2814 }) 2815 if len(queries) == 0: 2816 return 2817 # run it all in one transaction 2818 rows, idx = gmPG2.run_rw_queries(queries = queries) 2819 self.refetch_payload() 2820 return
2821 2822 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe) 2823 #--------------------------------------------------------
2824 - def _get_generic_codes_aoe(self):
2825 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0: 2826 return [] 2827 2828 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 2829 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])} 2830 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2831 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2832
2833 - def _set_generic_codes_aoe(self, pk_codes):
2834 queries = [] 2835 # remove all codes 2836 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0: 2837 queries.append ({ 2838 'cmd': 'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s', 2839 'args': { 2840 'enc': self._payload[self._idx['pk_encounter']], 2841 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']]) 2842 } 2843 }) 2844 # add new codes 2845 for pk_code in pk_codes: 2846 queries.append ({ 2847 'cmd': 'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)', 2848 'args': { 2849 'enc': self._payload[self._idx['pk_encounter']], 2850 'pk_code': pk_code 2851 } 2852 }) 2853 if len(queries) == 0: 2854 return 2855 # run it all in one transaction 2856 rows, idx = gmPG2.run_rw_queries(queries = queries) 2857 self.refetch_payload() 2858 return
2859 2860 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe) 2861 #--------------------------------------------------------
2862 - def _get_praxis_branch(self):
2863 if self._payload[self._idx['pk_org_unit']] is None: 2864 return None 2865 return gmPraxis.get_praxis_branch_by_org_unit(pk_org_unit = self._payload[self._idx['pk_org_unit']])
2866 2867 praxis_branch = property(_get_praxis_branch, lambda x:x) 2868 #--------------------------------------------------------
2869 - def _get_org_unit(self):
2870 if self._payload[self._idx['pk_org_unit']] is None: 2871 return None 2872 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])
2873 2874 org_unit = property(_get_org_unit, lambda x:x) 2875 #--------------------------------------------------------
2877 cmd = """SELECT 2878 'NONE (live row)'::text as audit__action_applied, 2879 NULL AS audit__action_when, 2880 NULL AS audit__action_by, 2881 pk_audit, 2882 row_version, 2883 modified_when, 2884 modified_by, 2885 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed 2886 FROM clin.encounter 2887 WHERE pk = %(pk_encounter)s 2888 UNION ALL ( 2889 SELECT 2890 audit_action as audit__action_applied, 2891 audit_when as audit__action_when, 2892 audit_by as audit__action_by, 2893 pk_audit, 2894 orig_version as row_version, 2895 orig_when as modified_when, 2896 orig_by as modified_by, 2897 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed 2898 FROM audit.log_encounter 2899 WHERE pk = %(pk_encounter)s 2900 ) 2901 ORDER BY row_version DESC 2902 """ 2903 args = {'pk_encounter': self._payload[self._idx['pk_encounter']]} 2904 title = _('Encounter: %s%s%s') % ( 2905 gmTools.u_left_double_angle_quote, 2906 self._payload[self._idx['l10n_type']], 2907 gmTools.u_right_double_angle_quote 2908 ) 2909 return '\n'.join(self._get_revision_history(cmd, args, title))
2910 2911 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
2912 2913 #-----------------------------------------------------------
2914 -def create_encounter(fk_patient=None, enc_type=None):
2915 """Creates a new encounter for a patient. 2916 2917 fk_patient - patient PK 2918 enc_type - type of encounter 2919 """ 2920 if enc_type is None: 2921 enc_type = 'in surgery' 2922 # insert new encounter 2923 queries = [] 2924 try: 2925 enc_type = int(enc_type) 2926 cmd = """ 2927 INSERT INTO clin.encounter (fk_patient, fk_type, fk_location) 2928 VALUES (%(pat)s, %(typ)s, %(prax)s) RETURNING pk""" 2929 except ValueError: 2930 enc_type = enc_type 2931 cmd = """ 2932 INSERT INTO clin.encounter (fk_patient, fk_location, fk_type) 2933 VALUES ( 2934 %(pat)s, 2935 %(prax)s, 2936 coalesce ( 2937 (select pk from clin.encounter_type where description = %(typ)s), 2938 -- pick the first available 2939 (select pk from clin.encounter_type limit 1) 2940 ) 2941 ) RETURNING pk""" 2942 praxis = gmPraxis.gmCurrentPraxisBranch() 2943 args = {'pat': fk_patient, 'typ': enc_type, 'prax': praxis['pk_org_unit']} 2944 queries.append({'cmd': cmd, 'args': args}) 2945 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 2946 encounter = cEncounter(aPK_obj = rows[0]['pk']) 2947 2948 return encounter
2949 2950 #------------------------------------------------------------
2951 -def lock_encounter(pk_encounter, exclusive=False, link_obj=None):
2952 """Used to protect against deletion of active encounter from another client.""" 2953 return gmPG2.lock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
2954 2955 #------------------------------------------------------------
2956 -def unlock_encounter(pk_encounter, exclusive=False, link_obj=None):
2957 return gmPG2.unlock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
2958 2959 #-----------------------------------------------------------
2960 -def delete_encounter(pk_encounter):
2961 """Deletes an encounter by PK. 2962 2963 - attempts to obtain an exclusive lock which should 2964 fail if the encounter is the active encounter in 2965 this or any other client 2966 - catches DB exceptions which should mostly be related 2967 to clinical data already having been attached to 2968 the encounter thus making deletion fail 2969 """ 2970 conn = gmPG2.get_connection(readonly = False) 2971 if not lock_encounter(pk_encounter, exclusive = True, link_obj = conn): 2972 _log.debug('cannot lock encounter [%s] for deletion, it seems in use', pk_encounter) 2973 return False 2974 cmd = """DELETE FROM clin.encounter WHERE pk = %(enc)s""" 2975 args = {'enc': pk_encounter} 2976 try: 2977 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 2978 except gmPG2.dbapi.Error: 2979 _log.exception('cannot delete encounter [%s]', pk_encounter) 2980 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn) 2981 return False 2982 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn) 2983 return True
2984 2985 #----------------------------------------------------------- 2986 # encounter types handling 2987 #-----------------------------------------------------------
2988 -def update_encounter_type(description=None, l10n_description=None):
2989 2990 rows, idx = gmPG2.run_rw_queries( 2991 queries = [{ 2992 'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 2993 'args': {'desc': description, 'l10n_desc': l10n_description} 2994 }], 2995 return_data = True 2996 ) 2997 2998 success = rows[0][0] 2999 if not success: 3000 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 3001 3002 return {'description': description, 'l10n_description': l10n_description}
3003 #-----------------------------------------------------------
3004 -def create_encounter_type(description=None, l10n_description=None):
3005 """This will attempt to create a NEW encounter type.""" 3006 3007 # need a system name, so derive one if necessary 3008 if description is None: 3009 description = l10n_description 3010 3011 args = { 3012 'desc': description, 3013 'l10n_desc': l10n_description 3014 } 3015 3016 _log.debug('creating encounter type: %s, %s', description, l10n_description) 3017 3018 # does it exist already ? 3019 cmd = "select description, _(description) from clin.encounter_type where description = %(desc)s" 3020 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3021 3022 # yes 3023 if len(rows) > 0: 3024 # both system and l10n name are the same so all is well 3025 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 3026 _log.info('encounter type [%s] already exists with the proper translation') 3027 return {'description': description, 'l10n_description': l10n_description} 3028 3029 # or maybe there just wasn't a translation to 3030 # the current language for this type yet ? 3031 cmd = "select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 3032 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3033 3034 # there was, so fail 3035 if rows[0][0]: 3036 _log.error('encounter type [%s] already exists but with another translation') 3037 return None 3038 3039 # else set it 3040 cmd = "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 3041 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3042 return {'description': description, 'l10n_description': l10n_description} 3043 3044 # no 3045 queries = [ 3046 {'cmd': "insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 3047 {'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 3048 ] 3049 rows, idx = gmPG2.run_rw_queries(queries = queries) 3050 3051 return {'description': description, 'l10n_description': l10n_description}
3052 3053 #-----------------------------------------------------------
3054 -def get_most_commonly_used_encounter_type():
3055 cmd = """ 3056 SELECT 3057 COUNT(1) AS type_count, 3058 fk_type 3059 FROM clin.encounter 3060 GROUP BY fk_type 3061 ORDER BY type_count DESC 3062 LIMIT 1 3063 """ 3064 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 3065 if len(rows) == 0: 3066 return None 3067 return rows[0]['fk_type']
3068 3069 #-----------------------------------------------------------
3070 -def get_encounter_types():
3071 cmd = """ 3072 SELECT 3073 _(description) AS l10n_description, 3074 description 3075 FROM 3076 clin.encounter_type 3077 ORDER BY 3078 l10n_description 3079 """ 3080 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False) 3081 return rows
3082 3083 #-----------------------------------------------------------
3084 -def get_encounter_type(description=None):
3085 cmd = "SELECT * from clin.encounter_type where description = %s" 3086 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 3087 return rows
3088 3089 #-----------------------------------------------------------
3090 -def delete_encounter_type(description=None):
3091 cmd = "delete from clin.encounter_type where description = %(desc)s" 3092 args = {'desc': description} 3093 try: 3094 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3095 except gmPG2.dbapi.IntegrityError as e: 3096 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 3097 return False 3098 raise 3099 3100 return True
3101 3102 #============================================================
3103 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
3104 """Represents one problem. 3105 3106 problems are the aggregation of 3107 .clinically_relevant=True issues and 3108 .is_open=True episodes 3109 """ 3110 _cmd_fetch_payload = '' # will get programmatically defined in __init__ 3111 _cmds_store_payload = ["select 1"] 3112 _updatable_fields = [] 3113 3114 #--------------------------------------------------------
3115 - def __init__(self, aPK_obj=None, try_potential_problems=False):
3116 """Initialize. 3117 3118 aPK_obj must contain the keys 3119 pk_patient 3120 pk_episode 3121 pk_health_issue 3122 """ 3123 if aPK_obj is None: 3124 raise gmExceptions.ConstructorError('cannot instatiate cProblem for PK: [%s]' % (aPK_obj)) 3125 3126 # As problems are rows from a view of different emr struct items, 3127 # the PK can't be a single field and, as some of the values of the 3128 # composed PK may be None, they must be queried using 'is null', 3129 # so we must programmatically construct the SQL query 3130 where_parts = [] 3131 pk = {} 3132 for col_name in aPK_obj.keys(): 3133 val = aPK_obj[col_name] 3134 if val is None: 3135 where_parts.append('%s IS NULL' % col_name) 3136 else: 3137 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 3138 pk[col_name] = val 3139 3140 # try to instantiate from true problem view 3141 cProblem._cmd_fetch_payload = """ 3142 SELECT *, False as is_potential_problem 3143 FROM clin.v_problem_list 3144 WHERE %s""" % ' AND '.join(where_parts) 3145 3146 try: 3147 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 3148 return 3149 except gmExceptions.ConstructorError: 3150 _log.exception('actual problem not found, trying "potential" problems') 3151 if try_potential_problems is False: 3152 raise 3153 3154 # try to instantiate from potential-problems view 3155 cProblem._cmd_fetch_payload = """ 3156 SELECT *, True as is_potential_problem 3157 FROM clin.v_potential_problem_list 3158 WHERE %s""" % ' AND '.join(where_parts) 3159 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3160 #--------------------------------------------------------
3161 - def get_as_episode(self):
3162 """ 3163 Retrieve the cEpisode instance equivalent to this problem. 3164 The problem's type attribute must be 'episode' 3165 """ 3166 if self._payload[self._idx['type']] != 'episode': 3167 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 3168 return None 3169 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
3170 #--------------------------------------------------------
3171 - def get_as_health_issue(self):
3172 """ 3173 Retrieve the cHealthIssue instance equivalent to this problem. 3174 The problem's type attribute must be 'issue' 3175 """ 3176 if self._payload[self._idx['type']] != 'issue': 3177 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 3178 return None 3179 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
3180 #--------------------------------------------------------
3181 - def get_visual_progress_notes(self, encounter_id=None):
3182 3183 if self._payload[self._idx['type']] == 'issue': 3184 latest = cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode 3185 if latest is None: 3186 return [] 3187 episodes = [ latest ] 3188 3189 emr = patient.emr 3190 3191 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 3192 return doc_folder.get_visual_progress_notes ( 3193 health_issue = self._payload[self._idx['pk_health_issue']], 3194 episode = self._payload[self._idx['pk_episode']] 3195 )
3196 3197 #-------------------------------------------------------- 3198 # properties 3199 #-------------------------------------------------------- 3200 # doubles as 'diagnostic_certainty_description' getter:
3202 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
3203 3204 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x) 3205 #--------------------------------------------------------
3206 - def _get_generic_codes(self):
3207 if self._payload[self._idx['type']] == 'issue': 3208 cmd = """ 3209 SELECT * FROM clin.v_linked_codes WHERE 3210 item_table = 'clin.lnk_code2h_issue'::regclass 3211 AND 3212 pk_item = %(item)s 3213 """ 3214 args = {'item': self._payload[self._idx['pk_health_issue']]} 3215 else: 3216 cmd = """ 3217 SELECT * FROM clin.v_linked_codes WHERE 3218 item_table = 'clin.lnk_code2episode'::regclass 3219 AND 3220 pk_item = %(item)s 3221 """ 3222 args = {'item': self._payload[self._idx['pk_episode']]} 3223 3224 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 3225 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3226 3227 generic_codes = property(_get_generic_codes, lambda x:x)
3228 #-----------------------------------------------------------
3229 -def problem2episode(problem=None):
3230 """Retrieve the cEpisode instance equivalent to the given problem. 3231 3232 The problem's type attribute must be 'episode' 3233 3234 @param problem: The problem to retrieve its related episode for 3235 @type problem: A gmEMRStructItems.cProblem instance 3236 """ 3237 if isinstance(problem, cEpisode): 3238 return problem 3239 3240 exc = TypeError('cannot convert [%s] to episode' % problem) 3241 3242 if not isinstance(problem, cProblem): 3243 raise exc 3244 3245 if problem['type'] != 'episode': 3246 raise exc 3247 3248 return cEpisode(aPK_obj = problem['pk_episode'])
3249 #-----------------------------------------------------------
3250 -def problem2issue(problem=None):
3251 """Retrieve the cIssue instance equivalent to the given problem. 3252 3253 The problem's type attribute must be 'issue'. 3254 3255 @param problem: The problem to retrieve the corresponding issue for 3256 @type problem: A gmEMRStructItems.cProblem instance 3257 """ 3258 if isinstance(problem, cHealthIssue): 3259 return problem 3260 3261 exc = TypeError('cannot convert [%s] to health issue' % problem) 3262 3263 if not isinstance(problem, cProblem): 3264 raise exc 3265 3266 if problem['type'] != 'issue': 3267 raise exc 3268 3269 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3270 #-----------------------------------------------------------
3271 -def reclass_problem(self, problem=None):
3272 """Transform given problem into either episode or health issue instance. 3273 """ 3274 if isinstance(problem, (cEpisode, cHealthIssue)): 3275 return problem 3276 3277 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 3278 3279 if not isinstance(problem, cProblem): 3280 _log.debug('%s' % problem) 3281 raise exc 3282 3283 if problem['type'] == 'episode': 3284 return cEpisode(aPK_obj = problem['pk_episode']) 3285 3286 if problem['type'] == 'issue': 3287 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 3288 3289 raise exc
3290 3291 #============================================================ 3292 _SQL_get_hospital_stays = "select * from clin.v_hospital_stays where %s" 3293
3294 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
3295 3296 _cmd_fetch_payload = _SQL_get_hospital_stays % "pk_hospital_stay = %s" 3297 _cmds_store_payload = [ 3298 """UPDATE clin.hospital_stay SET 3299 clin_when = %(admission)s, 3300 discharge = %(discharge)s, 3301 fk_org_unit = %(pk_org_unit)s, 3302 narrative = gm.nullify_empty_string(%(comment)s), 3303 fk_episode = %(pk_episode)s, 3304 fk_encounter = %(pk_encounter)s 3305 WHERE 3306 pk = %(pk_hospital_stay)s 3307 AND 3308 xmin = %(xmin_hospital_stay)s 3309 RETURNING 3310 xmin AS xmin_hospital_stay 3311 """ 3312 ] 3313 _updatable_fields = [ 3314 'admission', 3315 'discharge', 3316 'pk_org_unit', 3317 'pk_episode', 3318 'pk_encounter', 3319 'comment' 3320 ] 3321 3322 #--------------------------------------------------------
3323 - def format_maximum_information(self, patient=None):
3324 return self.format ( 3325 include_procedures = True, 3326 include_docs = True 3327 ).split('\n')
3328 3329 #-------------------------------------------------------
3330 - def format(self, left_margin=0, include_procedures=False, include_docs=False, include_episode=True):
3331 3332 if self._payload[self._idx['discharge']] is not None: 3333 discharge = ' - %s' % gmDateTime.pydt_strftime(self._payload[self._idx['discharge']], '%Y %b %d') 3334 else: 3335 discharge = '' 3336 3337 episode = '' 3338 if include_episode: 3339 episode = ': %s%s%s' % ( 3340 gmTools.u_left_double_angle_quote, 3341 self._payload[self._idx['episode']], 3342 gmTools.u_right_double_angle_quote 3343 ) 3344 3345 lines = ['%s%s%s (%s@%s)%s' % ( 3346 ' ' * left_margin, 3347 gmDateTime.pydt_strftime(self._payload[self._idx['admission']], '%Y %b %d'), 3348 discharge, 3349 self._payload[self._idx['ward']], 3350 self._payload[self._idx['hospital']], 3351 episode 3352 )] 3353 3354 if include_docs: 3355 for doc in self.documents: 3356 lines.append('%s%s: %s\n' % ( 3357 ' ' * left_margin, 3358 _('Document'), 3359 doc.format(single_line = True) 3360 )) 3361 3362 return '\n'.join(lines)
3363 3364 #--------------------------------------------------------
3365 - def _get_documents(self):
3366 return [ gmDocuments.cDocument(aPK_obj = pk_doc) for pk_doc in self._payload[self._idx['pk_documents']] ]
3367 3368 documents = property(_get_documents, lambda x:x)
3369 3370 #-----------------------------------------------------------
3371 -def get_latest_patient_hospital_stay(patient=None):
3372 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1" 3373 queries = [{ 3374 # this assumes non-overarching stays 3375 #'cmd': u'SELECT * FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1', 3376 'cmd': cmd, 3377 'args': {'pat': patient} 3378 }] 3379 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3380 if len(rows) == 0: 3381 return None 3382 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
3383 3384 #-----------------------------------------------------------
3385 -def get_patient_hospital_stays(patient=None, ongoing_only=False):
3386 args = {'pat': patient} 3387 if ongoing_only: 3388 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s AND discharge is NULL ORDER BY admission" 3389 else: 3390 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission" 3391 3392 queries = [{'cmd': cmd, 'args': args}] 3393 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3394 3395 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
3396 3397 #-----------------------------------------------------------
3398 -def create_hospital_stay(encounter=None, episode=None, fk_org_unit=None):
3399 3400 queries = [{ 3401 'cmd': 'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk', 3402 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit} 3403 }] 3404 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 3405 3406 return cHospitalStay(aPK_obj = rows[0][0])
3407 3408 #-----------------------------------------------------------
3409 -def delete_hospital_stay(stay=None):
3410 cmd = 'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 3411 args = {'pk': stay} 3412 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3413 return True
3414 3415 #============================================================ 3416 _SQL_get_procedures = "select * from clin.v_procedures where %s" 3417
3418 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
3419 3420 _cmd_fetch_payload = _SQL_get_procedures % "pk_procedure = %s" 3421 _cmds_store_payload = [ 3422 """UPDATE clin.procedure SET 3423 soap_cat = 'p', 3424 clin_when = %(clin_when)s, 3425 clin_end = %(clin_end)s, 3426 is_ongoing = %(is_ongoing)s, 3427 narrative = gm.nullify_empty_string(%(performed_procedure)s), 3428 fk_hospital_stay = %(pk_hospital_stay)s, 3429 fk_org_unit = (CASE 3430 WHEN %(pk_hospital_stay)s IS NULL THEN %(pk_org_unit)s 3431 ELSE NULL 3432 END)::integer, 3433 fk_episode = %(pk_episode)s, 3434 fk_encounter = %(pk_encounter)s, 3435 fk_doc = %(pk_doc)s, 3436 comment = gm.nullify_empty_string(%(comment)s) 3437 WHERE 3438 pk = %(pk_procedure)s AND 3439 xmin = %(xmin_procedure)s 3440 RETURNING xmin as xmin_procedure""" 3441 ] 3442 _updatable_fields = [ 3443 'clin_when', 3444 'clin_end', 3445 'is_ongoing', 3446 'performed_procedure', 3447 'pk_hospital_stay', 3448 'pk_org_unit', 3449 'pk_episode', 3450 'pk_encounter', 3451 'pk_doc', 3452 'comment' 3453 ] 3454 #-------------------------------------------------------
3455 - def __setitem__(self, attribute, value):
3456 3457 if (attribute == 'pk_hospital_stay') and (value is not None): 3458 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_org_unit', None) 3459 3460 if (attribute == 'pk_org_unit') and (value is not None): 3461 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 3462 3463 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
3464 3465 #--------------------------------------------------------
3466 - def format_maximum_information(self, left_margin=0, patient=None):
3467 return self.format ( 3468 left_margin = left_margin, 3469 include_episode = True, 3470 include_codes = True, 3471 include_address = True, 3472 include_comm = True, 3473 include_doc = True 3474 ).split('\n')
3475 3476 #-------------------------------------------------------
3477 - def format(self, left_margin=0, include_episode=True, include_codes=False, include_address=False, include_comm=False, include_doc=False):
3478 3479 if self._payload[self._idx['is_ongoing']]: 3480 end = _(' (ongoing)') 3481 else: 3482 end = self._payload[self._idx['clin_end']] 3483 if end is None: 3484 end = '' 3485 else: 3486 end = ' - %s' % gmDateTime.pydt_strftime(end, '%Y %b %d') 3487 3488 line = '%s%s%s: %s%s [%s @ %s]' % ( 3489 (' ' * left_margin), 3490 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d'), 3491 end, 3492 self._payload[self._idx['performed_procedure']], 3493 gmTools.bool2str(include_episode, ' (%s)' % self._payload[self._idx['episode']], ''), 3494 self._payload[self._idx['unit']], 3495 self._payload[self._idx['organization']] 3496 ) 3497 3498 line += gmTools.coalesce(self._payload[self._idx['comment']], '', '\n' + (' ' * left_margin) + _('Comment: ') + '%s') 3499 3500 if include_comm: 3501 for channel in self.org_unit.comm_channels: 3502 line += ('\n%(comm_type)s: %(url)s' % channel) 3503 3504 if include_address: 3505 adr = self.org_unit.address 3506 if adr is not None: 3507 line += '\n' 3508 line += '\n'.join(adr.format(single_line = False, show_type = False)) 3509 line += '\n' 3510 3511 if include_doc: 3512 doc = self.doc 3513 if doc is not None: 3514 line += '\n' 3515 line += _('Document') + ': ' + doc.format(single_line = True) 3516 line += '\n' 3517 3518 if include_codes: 3519 codes = self.generic_codes 3520 if len(codes) > 0: 3521 line += '\n' 3522 for c in codes: 3523 line += '%s %s: %s (%s - %s)\n' % ( 3524 (' ' * left_margin), 3525 c['code'], 3526 c['term'], 3527 c['name_short'], 3528 c['version'] 3529 ) 3530 del codes 3531 3532 return line
3533 3534 #--------------------------------------------------------
3535 - def add_code(self, pk_code=None):
3536 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 3537 cmd = "INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)" 3538 args = { 3539 'issue': self._payload[self._idx['pk_procedure']], 3540 'code': pk_code 3541 } 3542 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3543 return True
3544 3545 #--------------------------------------------------------
3546 - def remove_code(self, pk_code=None):
3547 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 3548 cmd = "DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s" 3549 args = { 3550 'issue': self._payload[self._idx['pk_procedure']], 3551 'code': pk_code 3552 } 3553 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3554 return True
3555 3556 #-------------------------------------------------------- 3557 # properties 3558 #--------------------------------------------------------
3559 - def _get_stay(self):
3560 if self._payload[self._idx['pk_hospital_stay']] is None: 3561 return None 3562 return cHospitalStay(aPK_obj = self._payload[self._idx['pk_hospital_stay']])
3563 3564 hospital_stay = property(_get_stay, lambda x:x) 3565 3566 #--------------------------------------------------------
3567 - def _get_org_unit(self):
3568 return gmOrganization.cOrgUnit(self._payload[self._idx['pk_org_unit']])
3569 3570 org_unit = property(_get_org_unit, lambda x:x) 3571 3572 #--------------------------------------------------------
3573 - def _get_doc(self):
3574 if self._payload[self._idx['pk_doc']] is None: 3575 return None 3576 return gmDocuments.cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
3577 3578 doc = property(_get_doc, lambda x:x) 3579 3580 #--------------------------------------------------------
3581 - def _get_generic_codes(self):
3582 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 3583 return [] 3584 3585 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 3586 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 3587 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 3588 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3589
3590 - def _set_generic_codes(self, pk_codes):
3591 queries = [] 3592 # remove all codes 3593 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 3594 queries.append ({ 3595 'cmd': 'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s', 3596 'args': { 3597 'proc': self._payload[self._idx['pk_procedure']], 3598 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 3599 } 3600 }) 3601 # add new codes 3602 for pk_code in pk_codes: 3603 queries.append ({ 3604 'cmd': 'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)', 3605 'args': { 3606 'proc': self._payload[self._idx['pk_procedure']], 3607 'pk_code': pk_code 3608 } 3609 }) 3610 if len(queries) == 0: 3611 return 3612 # run it all in one transaction 3613 rows, idx = gmPG2.run_rw_queries(queries = queries) 3614 return
3615 3616 generic_codes = property(_get_generic_codes, _set_generic_codes)
3617 3618 #-----------------------------------------------------------
3619 -def get_performed_procedures(patient=None):
3620 3621 queries = [{ 3622 'cmd': 'SELECT * FROM clin.v_procedures WHERE pk_patient = %(pat)s ORDER BY clin_when', 3623 'args': {'pat': patient} 3624 }] 3625 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3626 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3627 3628 #-----------------------------------------------------------
3629 -def get_procedures4document(pk_document=None):
3630 args = {'pk_doc': pk_document} 3631 cmd = _SQL_get_procedures % 'pk_doc = %(pk_doc)s' 3632 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 3633 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3634 3635 #-----------------------------------------------------------
3636 -def get_latest_performed_procedure(patient=None):
3637 queries = [{ 3638 'cmd': 'select * FROM clin.v_procedures WHERE pk_patient = %(pat)s ORDER BY clin_when DESC LIMIT 1', 3639 'args': {'pat': patient} 3640 }] 3641 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 3642 if len(rows) == 0: 3643 return None 3644 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})
3645 3646 #-----------------------------------------------------------
3647 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
3648 3649 queries = [{ 3650 'cmd': """ 3651 INSERT INTO clin.procedure ( 3652 fk_encounter, 3653 fk_episode, 3654 soap_cat, 3655 fk_org_unit, 3656 fk_hospital_stay, 3657 narrative 3658 ) VALUES ( 3659 %(enc)s, 3660 %(epi)s, 3661 'p', 3662 %(loc)s, 3663 %(stay)s, 3664 gm.nullify_empty_string(%(proc)s) 3665 ) 3666 RETURNING pk""", 3667 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 3668 }] 3669 3670 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 3671 3672 return cPerformedProcedure(aPK_obj = rows[0][0])
3673 3674 #-----------------------------------------------------------
3675 -def delete_performed_procedure(procedure=None):
3676 cmd = 'delete from clin.procedure where pk = %(pk)s' 3677 args = {'pk': procedure} 3678 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 3679 return True
3680 3681 #============================================================
3682 -def export_emr_structure(patient=None, filename=None):
3683 3684 if filename is None: 3685 filename = gmTools.get_unique_filename(prefix = 'gm-emr_struct-%s-' % patient.subdir_name, suffix = '.txt') 3686 3687 f = io.open(filename, 'w+', encoding = 'utf8') 3688 3689 f.write('patient [%s]\n' % patient['description_gender']) 3690 emr = patient.emr 3691 for issue in emr.health_issues: 3692 f.write('\n') 3693 f.write('\n') 3694 f.write('issue [%s] #%s\n' % (issue['description'], issue['pk_health_issue'])) 3695 f.write(' is active : %s\n' % issue['is_active']) 3696 f.write(' has open epi : %s\n' % issue['has_open_episode']) 3697 f.write(' possible start: %s\n' % issue.possible_start_date) 3698 f.write(' safe start : %s\n' % issue.safe_start_date) 3699 end = issue.clinical_end_date 3700 if end is None: 3701 f.write(' end : active and/or open episode\n') 3702 else: 3703 f.write(' end : %s\n' % end) 3704 f.write(' latest access : %s\n' % issue.latest_access_date) 3705 first = issue.first_episode 3706 if first is not None: 3707 first = first['description'] 3708 f.write(' 1st episode : %s\n' % first) 3709 last = issue.latest_episode 3710 if last is not None: 3711 last = last['description'] 3712 f.write(' latest episode: %s\n' % last) 3713 epis = sorted(issue.get_episodes(), key = lambda e: e.best_guess_clinical_start_date) 3714 for epi in epis: 3715 f.write('\n') 3716 f.write(' episode [%s] #%s\n' % (epi['description'], epi['pk_episode'])) 3717 f.write(' is open : %s\n' % epi['episode_open']) 3718 f.write(' best guess start: %s\n' % epi.best_guess_clinical_start_date) 3719 f.write(' best guess end : %s\n' % epi.best_guess_clinical_end_date) 3720 f.write(' latest access : %s\n' % epi.latest_access_date) 3721 f.write(' start 1st enc : %s\n' % epi['started_first']) 3722 f.write(' start last enc : %s\n' % epi['started_last']) 3723 f.write(' end last enc : %s\n' % epi['last_affirmed']) 3724 3725 f.close() 3726 return filename
3727 3728 #============================================================ 3729 # tools 3730 #------------------------------------------------------------
3731 -def check_fk_encounter_fk_episode_x_ref():
3732 3733 aggregate_result = 0 3734 3735 fks_linking2enc = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'encounter', column = 'pk') 3736 tables_linking2enc = set([ r['referencing_table'] for r in fks_linking2enc ]) 3737 3738 fks_linking2epi = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'episode', column = 'pk') 3739 tables_linking2epi = [ r['referencing_table'] for r in fks_linking2epi ] 3740 3741 tables_linking2both = tables_linking2enc.intersection(tables_linking2epi) 3742 3743 tables_linking2enc = {} 3744 for fk in fks_linking2enc: 3745 table = fk['referencing_table'] 3746 tables_linking2enc[table] = fk 3747 3748 tables_linking2epi = {} 3749 for fk in fks_linking2epi: 3750 table = fk['referencing_table'] 3751 tables_linking2epi[table] = fk 3752 3753 for t in tables_linking2both: 3754 3755 table_file_name = 'x-check_enc_epi_xref-%s.log' % t 3756 table_file = io.open(table_file_name, 'w+', encoding = 'utf8') 3757 3758 # get PK column 3759 args = {'table': t} 3760 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': gmPG2.SQL_get_pk_col_def, 'args': args}]) 3761 pk_col = rows[0][0] 3762 print("checking table:", t, '- pk col:', pk_col) 3763 print(' =>', table_file_name) 3764 table_file.write('table: %s\n' % t) 3765 table_file.write('PK col: %s\n' % pk_col) 3766 3767 # get PKs 3768 cmd = 'select %s from %s' % (pk_col, t) 3769 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 3770 pks = [ r[0] for r in rows ] 3771 for pk in pks: 3772 args = {'pk': pk, 'tbl': t} 3773 enc_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from %s where %s = %%(pk)s)" % (t, pk_col) 3774 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) 3775 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': enc_cmd, 'args': args}]) 3776 epi_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': epi_cmd, 'args': args}]) 3777 enc_pat = enc_rows[0][0] 3778 epi_pat = epi_rows[0][0] 3779 args['pat_enc'] = enc_pat 3780 args['pat_epi'] = epi_pat 3781 if epi_pat != enc_pat: 3782 print(' mismatch: row pk=%s, enc pat=%s, epi pat=%s' % (pk, enc_pat, epi_pat)) 3783 aggregate_result = -2 3784 3785 table_file.write('--------------------------------------------------------------------------------\n') 3786 table_file.write('mismatch on row with pk: %s\n' % pk) 3787 table_file.write('\n') 3788 3789 table_file.write('journal entry:\n') 3790 cmd = 'SELECT * from clin.v_emr_journal where src_table = %(tbl)s AND src_pk = %(pk)s' 3791 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3792 if len(rows) > 0: 3793 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3794 table_file.write('\n\n') 3795 3796 table_file.write('row data:\n') 3797 cmd = 'SELECT * from %s where %s = %%(pk)s' % (t, pk_col) 3798 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3799 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3800 table_file.write('\n\n') 3801 3802 table_file.write('episode:\n') 3803 cmd = 'SELECT * from clin.v_pat_episodes WHERE pk_episode = (select fk_episode from %s where %s = %%(pk)s)' % (t, pk_col) 3804 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3805 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3806 table_file.write('\n\n') 3807 3808 table_file.write('patient of episode:\n') 3809 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_epi)s' 3810 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3811 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3812 table_file.write('\n\n') 3813 3814 table_file.write('encounter:\n') 3815 cmd = 'SELECT * from clin.v_pat_encounters WHERE pk_encounter = (select fk_encounter from %s where %s = %%(pk)s)' % (t, pk_col) 3816 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3817 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3818 table_file.write('\n\n') 3819 3820 table_file.write('patient of encounter:\n') 3821 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_enc)s' 3822 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 3823 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None)) 3824 table_file.write('\n') 3825 3826 table_file.write('done\n') 3827 table_file.close() 3828 3829 return aggregate_result
3830 3831 #------------------------------------------------------------
3832 -def export_patient_emr_structure():
3833 from Gnumed.business import gmPersonSearch 3834 praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0]) 3835 pat = gmPersonSearch.ask_for_patient() 3836 while pat is not None: 3837 print('patient:', pat['description_gender']) 3838 fname = os.path.expanduser('~/gnumed/gm-emr_structure-%s.txt' % pat.subdir_name) 3839 print('exported into:', export_emr_structure(patient = pat, filename = fname)) 3840 pat = gmPersonSearch.ask_for_patient() 3841 3842 return 0
3843 3844 #============================================================ 3845 # main - unit testing 3846 #------------------------------------------------------------ 3847 if __name__ == '__main__': 3848 3849 if len(sys.argv) < 2: 3850 sys.exit() 3851 3852 if sys.argv[1] != 'test': 3853 sys.exit() 3854 3855 #-------------------------------------------------------- 3856 # define tests 3857 #--------------------------------------------------------
3858 - def test_problem():
3859 print("\nProblem test") 3860 print("------------") 3861 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 3862 print(prob) 3863 fields = prob.get_fields() 3864 for field in fields: 3865 print(field, ':', prob[field]) 3866 print('\nupdatable:', prob.get_updatable_fields()) 3867 epi = prob.get_as_episode() 3868 print('\nas episode:') 3869 if epi is not None: 3870 for field in epi.get_fields(): 3871 print(' .%s : %s' % (field, epi[field]))
3872 3873 #--------------------------------------------------------
3874 - def test_health_issue():
3875 print("\nhealth issue test") 3876 print("-----------------") 3877 h_issue = cHealthIssue(aPK_obj=2) 3878 print(h_issue) 3879 print(h_issue.latest_access_date) 3880 print(h_issue.clinical_end_date) 3881 # fields = h_issue.get_fields() 3882 # for field in fields: 3883 # print field, ':', h_issue[field] 3884 # print "has open episode:", h_issue.has_open_episode() 3885 # print "open episode:", h_issue.get_open_episode() 3886 # print "updateable:", h_issue.get_updatable_fields() 3887 # h_issue.close_expired_episode(ttl=7300) 3888 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 3889 # print h_issue 3890 # print h_issue.format_as_journal() 3891 print(h_issue.formatted_revision_history)
3892 3893 #--------------------------------------------------------
3894 - def test_episode():
3895 print("episode test") 3896 print("------------") 3897 episode = cEpisode(aPK_obj = 1322) #1674) #1354) #1461) #1299) 3898 3899 print(episode['description']) 3900 print('start:', episode.best_guess_clinical_start_date) 3901 print('end :', episode.best_guess_clinical_end_date) 3902 return 3903 3904 print(episode) 3905 fields = episode.get_fields() 3906 for field in fields: 3907 print(field, ':', episode[field]) 3908 print("updatable:", episode.get_updatable_fields()) 3909 input('ENTER to continue') 3910 3911 old_description = episode['description'] 3912 old_enc = cEncounter(aPK_obj = 1) 3913 3914 desc = '1-%s' % episode['description'] 3915 print("==> renaming to", desc) 3916 successful = episode.rename ( 3917 description = desc 3918 ) 3919 if not successful: 3920 print("error") 3921 else: 3922 print("success") 3923 for field in fields: 3924 print(field, ':', episode[field]) 3925 3926 print(episode.formatted_revision_history) 3927 3928 input('ENTER to continue')
3929 3930 #--------------------------------------------------------
3931 - def test_encounter():
3932 print("\nencounter test") 3933 print("--------------") 3934 encounter = cEncounter(aPK_obj=1) 3935 print(encounter) 3936 fields = encounter.get_fields() 3937 for field in fields: 3938 print(field, ':', encounter[field]) 3939 print("updatable:", encounter.get_updatable_fields()) 3940 #print encounter.formatted_revision_history 3941 print(encounter.transfer_all_data_to_another_encounter(pk_target_encounter = 2))
3942 3943 #--------------------------------------------------------
3944 - def test_encounter2latex():
3945 encounter = cEncounter(aPK_obj=1) 3946 print(encounter) 3947 print("") 3948 print(encounter.format_latex())
3949 #--------------------------------------------------------
3950 - def test_performed_procedure():
3951 procs = get_performed_procedures(patient = 12) 3952 for proc in procs: 3953 print(proc.format(left_margin=2))
3954 #--------------------------------------------------------
3955 - def test_hospital_stay():
3956 stay = create_hospital_stay(encounter = 1, episode = 2, fk_org_unit = 1) 3957 # stay['hospital'] = u'Starfleet Galaxy General Hospital' 3958 # stay.save_payload() 3959 print(stay) 3960 for s in get_patient_hospital_stays(12): 3961 print(s) 3962 delete_hospital_stay(stay['pk_hospital_stay']) 3963 stay = create_hospital_stay(encounter = 1, episode = 4, fk_org_unit = 1)
3964 #--------------------------------------------------------
3965 - def test_diagnostic_certainty_classification_map():
3966 tests = [None, 'A', 'B', 'C', 'D', 'E'] 3967 3968 for t in tests: 3969 print(type(t), t) 3970 print(type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t))
3971 #--------------------------------------------------------
3972 - def test_episode_codes():
3973 epi = cEpisode(aPK_obj = 2) 3974 print(epi) 3975 print(epi.generic_codes)
3976 #--------------------------------------------------------
3977 - def test_episode_encounters():
3978 epi = cEpisode(aPK_obj = 1638) 3979 print(epi.format())
3980 3981 #--------------------------------------------------------
3982 - def test_export_emr_structure():
3983 export_patient_emr_structure()
3984 #praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0]) 3985 #from Gnumed.business import gmPerson 3986 ## 12 / 20 / 138 / 58 / 20 / 5 / 14 3987 #pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(aPK_obj = 138)) 3988 #fname = os.path.expanduser(u'~/gnumed/emr_structure-%s.txt' % pat.subdir_name) 3989 #print export_emr_structure(patient = pat, filename = fname) 3990 3991 #-------------------------------------------------------- 3992 # run them 3993 #test_episode() 3994 #test_episode_encounters() 3995 #test_problem() 3996 #test_encounter() 3997 #test_health_issue() 3998 #test_hospital_stay() 3999 #test_performed_procedure() 4000 #test_diagnostic_certainty_classification_map() 4001 #test_encounter2latex() 4002 #test_episode_codes() 4003 4004 test_export_emr_structure() 4005 4006 #============================================================ 4007