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

Source Code for Module Gnumed.business.gmEMRStructItems

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