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