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