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

Source Code for Module Gnumed.business.gmEMRStructItems

   1  # -*- coding: utf8 -*- 
   2  """GNUmed health related business object. 
   3   
   4  license: GPL 
   5  """ 
   6  #============================================================ 
   7  __version__ = "$Revision: 1.157 $" 
   8  __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>" 
   9   
  10  import types, sys, string, datetime, logging, time 
  11   
  12   
  13  if __name__ == '__main__': 
  14          sys.path.insert(0, '../../') 
  15  from Gnumed.pycommon import gmPG2 
  16  from Gnumed.pycommon import gmI18N 
  17  from Gnumed.pycommon import gmTools 
  18  from Gnumed.pycommon import gmDateTime 
  19  from Gnumed.pycommon import gmBusinessDBObject 
  20  from Gnumed.pycommon import gmNull 
  21  from Gnumed.pycommon import gmExceptions 
  22   
  23  from Gnumed.business import gmClinNarrative 
  24   
  25   
  26  _log = logging.getLogger('gm.emr') 
  27  _log.info(__version__) 
  28   
  29  try: _ 
  30  except NameError: _ = lambda x:x 
  31  #============================================================ 
  32  # diagnostic certainty classification 
  33  #============================================================ 
  34  __diagnostic_certainty_classification_map = None 
  35   
36 -def diagnostic_certainty_classification2str(classification):
37 38 global __diagnostic_certainty_classification_map 39 40 if __diagnostic_certainty_classification_map is None: 41 __diagnostic_certainty_classification_map = { 42 None: u'', 43 u'A': _(u'A: Sign'), 44 u'B': _(u'B: Cluster of signs'), 45 u'C': _(u'C: Syndromic diagnosis'), 46 u'D': _(u'D: Scientific diagnosis') 47 } 48 49 try: 50 return __diagnostic_certainty_classification_map[classification] 51 except KeyError: 52 return _(u'<%s>: unknown diagnostic certainty classification') % classification
53 #============================================================ 54 # Health Issues API 55 #============================================================ 56 laterality2str = { 57 None: u'?', 58 u'na': u'', 59 u'sd': _('bilateral'), 60 u'ds': _('bilateral'), 61 u's': _('left'), 62 u'd': _('right') 63 } 64 65 #============================================================
66 -class cHealthIssue(gmBusinessDBObject.cBusinessDBObject):
67 """Represents one health issue.""" 68 69 _cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s" 70 _cmds_store_payload = [ 71 u"""update clin.health_issue set 72 description = %(description)s, 73 summary = gm.nullify_empty_string(%(summary)s), 74 age_noted = %(age_noted)s, 75 laterality = gm.nullify_empty_string(%(laterality)s), 76 grouping = gm.nullify_empty_string(%(grouping)s), 77 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s), 78 is_active = %(is_active)s, 79 clinically_relevant = %(clinically_relevant)s, 80 is_confidential = %(is_confidential)s, 81 is_cause_of_death = %(is_cause_of_death)s 82 where 83 pk = %(pk_health_issue)s and 84 xmin = %(xmin_health_issue)s""", 85 u"select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s" 86 ] 87 _updatable_fields = [ 88 'description', 89 'summary', 90 'grouping', 91 'age_noted', 92 'laterality', 93 'is_active', 94 'clinically_relevant', 95 'is_confidential', 96 'is_cause_of_death', 97 'diagnostic_certainty_classification' 98 ] 99 #--------------------------------------------------------
100 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
101 pk = aPK_obj 102 103 if (pk is not None) or (row is not None): 104 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row) 105 return 106 107 if patient is None: 108 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 109 where 110 description = %(desc)s 111 and 112 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)""" 113 else: 114 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 115 where 116 description = %(desc)s 117 and 118 pk_patient = %(pat)s""" 119 120 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}] 121 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 122 123 if len(rows) == 0: 124 raise gmExceptions.NoSuchBusinessObjectError, 'no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient) 125 126 pk = rows[0][0] 127 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'} 128 129 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
130 #-------------------------------------------------------- 131 # external API 132 #--------------------------------------------------------
133 - def rename(self, description=None):
134 """Method for issue renaming. 135 136 @param description 137 - the new descriptive name for the issue 138 @type description 139 - a string instance 140 """ 141 # sanity check 142 if not type(description) in [str, unicode] or description.strip() == '': 143 _log.error('<description> must be a non-empty string') 144 return False 145 # update the issue description 146 old_description = self._payload[self._idx['description']] 147 self._payload[self._idx['description']] = description.strip() 148 self._is_modified = True 149 successful, data = self.save_payload() 150 if not successful: 151 _log.error('cannot rename health issue [%s] with [%s]' % (self, description)) 152 self._payload[self._idx['description']] = old_description 153 return False 154 return True
155 #--------------------------------------------------------
156 - def get_episodes(self):
157 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s" 158 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True) 159 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
160 #--------------------------------------------------------
161 - def close_expired_episode(self, ttl=180):
162 """ttl in days""" 163 open_episode = self.get_open_episode() 164 if open_episode is None: 165 return True 166 earliest, latest = open_episode.get_access_range() 167 ttl = datetime.timedelta(ttl) 168 now = datetime.datetime.now(tz=latest.tzinfo) 169 if (latest + ttl) > now: 170 return False 171 open_episode['episode_open'] = False 172 success, data = open_episode.save_payload() 173 if success: 174 return True 175 return False # should be an exception
176 #--------------------------------------------------------
177 - def close_episode(self):
178 open_episode = self.get_open_episode() 179 open_episode['episode_open'] = False 180 success, data = open_episode.save_payload() 181 if success: 182 return True 183 return False
184 #--------------------------------------------------------
185 - def has_open_episode(self):
186 cmd = u"select exists (select 1 from clin.episode where fk_health_issue = %s and is_open is True)" 187 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 188 return rows[0][0]
189 #--------------------------------------------------------
190 - def get_open_episode(self):
191 cmd = u"select pk from clin.episode where fk_health_issue = %s and is_open is True" 192 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 193 if len(rows) == 0: 194 return None 195 return cEpisode(aPK_obj=rows[0][0])
196 #--------------------------------------------------------
197 - def age_noted_human_readable(self):
198 if self._payload[self._idx['age_noted']] is None: 199 return u'<???>' 200 201 # since we've already got an interval we are bound to use it, 202 # further transformation will only introduce more errors, 203 # later we can improve this deeper inside 204 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
205 #--------------------------------------------------------
206 - def format_as_journal(self, left_margin=0, date_format='%a, %b %d %Y'):
207 rows = gmClinNarrative.get_as_journal ( 208 issues = (self.pk_obj,), 209 order_by = u'pk_episode, pk_encounter, clin_when, scr, src_table' 210 ) 211 212 if len(rows) == 0: 213 return u'' 214 215 left_margin = u' ' * left_margin 216 217 lines = [] 218 lines.append(_('Clinical data generated during encounters under this health issue:')) 219 220 prev_epi = None 221 for row in rows: 222 if row['pk_episode'] != prev_epi: 223 lines.append(u'') 224 prev_epi = row['pk_episode'] 225 226 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding()) 227 top_row = u'%s%s %s (%s) %s' % ( 228 gmTools.u_box_top_left_arc, 229 gmTools.u_box_horiz_single, 230 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']], 231 when, 232 gmTools.u_box_horiz_single * 5 233 ) 234 soap = gmTools.wrap ( 235 text = row['narrative'], 236 width = 60, 237 initial_indent = u' ', 238 subsequent_indent = u' ' + left_margin 239 ) 240 row_ver = u'' 241 if row['row_version'] > 0: 242 row_ver = u'v%s: ' % row['row_version'] 243 bottom_row = u'%s%s %s, %s%s %s' % ( 244 u' ' * 40, 245 gmTools.u_box_horiz_light_heavy, 246 row['modified_by'], 247 row_ver, 248 row['date_modified'], 249 gmTools.u_box_horiz_heavy_light 250 ) 251 252 lines.append(top_row) 253 lines.append(soap) 254 lines.append(bottom_row) 255 256 eol_w_margin = u'\n%s' % left_margin 257 return left_margin + eol_w_margin.join(lines) + u'\n'
258 #--------------------------------------------------------
259 - def format(self, left_margin=0, patient=None):
260 261 if patient.ID != self._payload[self._idx['pk_patient']]: 262 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % ( 263 patient.ID, 264 self._payload[self._idx['pk_health_issue']], 265 self._payload[self._idx['pk_patient']] 266 ) 267 raise ValueError(msg) 268 269 lines = [] 270 271 lines.append(_('Health Issue %s%s%s%s [#%s]') % ( 272 u'\u00BB', 273 self._payload[self._idx['description']], 274 u'\u00AB', 275 gmTools.coalesce ( 276 initial = self.laterality_description, 277 instead = u'', 278 template_initial = u' (%s)', 279 none_equivalents = [None, u'', u'?'] 280 ), 281 self._payload[self._idx['pk_health_issue']] 282 )) 283 284 if self._payload[self._idx['is_confidential']]: 285 lines.append('') 286 lines.append(_(' ***** CONFIDENTIAL *****')) 287 lines.append('') 288 289 if self._payload[self._idx['is_cause_of_death']]: 290 lines.append('') 291 lines.append(_(' contributed to death of patient')) 292 lines.append('') 293 294 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 295 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % ( 296 enc['l10n_type'], 297 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 298 enc['last_affirmed_original_tz'].strftime('%H:%M'), 299 self._payload[self._idx['pk_encounter']] 300 )) 301 302 if self._payload[self._idx['age_noted']] is not None: 303 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable()) 304 305 lines.append(u' ' + _('Status') + u': %s, %s%s' % ( 306 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')), 307 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')), 308 gmTools.coalesce ( 309 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 310 instead = u'', 311 template_initial = u', %s', 312 none_equivalents = [None, u''] 313 ) 314 )) 315 316 if self._payload[self._idx['summary']] is not None: 317 lines.append(u'') 318 lines.append(gmTools.wrap ( 319 text = self._payload[self._idx['summary']], 320 width = 60, 321 initial_indent = u' ', 322 subsequent_indent = u' ' 323 )) 324 325 lines.append(u'') 326 327 emr = patient.get_emr() 328 329 # episodes 330 epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]]) 331 if epis is None: 332 lines.append(_('Error retrieving episodes for this health issue.')) 333 elif len(epis) == 0: 334 lines.append(_('There are no episodes for this health issue.')) 335 else: 336 lines.append ( 337 _('Episodes: %s (most recent: %s%s%s)') % ( 338 len(epis), 339 gmTools.u_left_double_angle_quote, 340 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'], 341 gmTools.u_right_double_angle_quote 342 ) 343 ) 344 for epi in epis: 345 lines.append(u' \u00BB%s\u00AB (%s)' % ( 346 epi['description'], 347 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed')) 348 )) 349 350 lines.append('') 351 352 # encounters 353 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 354 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 355 356 if first_encounter is None or last_encounter is None: 357 lines.append(_('No encounters found for this health issue.')) 358 else: 359 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]]) 360 lines.append(_('Encounters: %s (%s - %s):') % ( 361 len(encs), 362 first_encounter['started_original_tz'].strftime('%m/%Y'), 363 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y') 364 )) 365 lines.append(_(' Most recent: %s - %s') % ( 366 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 367 last_encounter['last_affirmed_original_tz'].strftime('%H:%M') 368 )) 369 370 # medications 371 meds = emr.get_current_substance_intake ( 372 issues = [ self._payload[self._idx['pk_health_issue']] ], 373 order_by = u'is_currently_active, started, substance' 374 ) 375 376 if len(meds) > 0: 377 lines.append(u'') 378 lines.append(_('Active medications: %s') % len(meds)) 379 for m in meds: 380 lines.append(m.format(left_margin = (left_margin + 1))) 381 del meds 382 383 # hospital stays 384 stays = emr.get_hospital_stays ( 385 issues = [ self._payload[self._idx['pk_health_issue']] ] 386 ) 387 388 if len(stays) > 0: 389 lines.append(u'') 390 lines.append(_('Hospital stays: %s') % len(stays)) 391 for s in stays: 392 lines.append(s.format(left_margin = (left_margin + 1))) 393 del stays 394 395 # procedures 396 procs = emr.get_performed_procedures ( 397 issues = [ self._payload[self._idx['pk_health_issue']] ] 398 ) 399 400 if len(procs) > 0: 401 lines.append(u'') 402 lines.append(_('Procedures performed: %s') % len(procs)) 403 for p in procs: 404 lines.append(p.format(left_margin = (left_margin + 1))) 405 del procs 406 407 epis = self.get_episodes() 408 if len(epis) > 0: 409 epi_pks = [ e['pk_episode'] for e in epis ] 410 411 # documents 412 doc_folder = patient.get_document_folder() 413 docs = doc_folder.get_documents(episodes = epi_pks) 414 if len(docs) > 0: 415 lines.append(u'') 416 lines.append(_('Documents: %s') % len(docs)) 417 del docs 418 419 # test results 420 tests = emr.get_test_results_by_date(episodes = epi_pks) 421 if len(tests) > 0: 422 lines.append(u'') 423 lines.append(_('Measurements and Results: %s') % len(tests)) 424 del tests 425 426 # vaccinations 427 vaccs = emr.get_vaccinations(episodes = epi_pks) 428 if len(vaccs) > 0: 429 lines.append(u'') 430 lines.append(_('Vaccinations:')) 431 for vacc in vaccs: 432 lines.extend(vacc.format(with_reaction = True)) 433 del vaccs 434 435 del epis 436 437 left_margin = u' ' * left_margin 438 eol_w_margin = u'\n%s' % left_margin 439 return left_margin + eol_w_margin.join(lines) + u'\n'
440 #-------------------------------------------------------- 441 # properties 442 #-------------------------------------------------------- 443 episodes = property(get_episodes, lambda x:x) 444 #-------------------------------------------------------- 445 open_episode = property(get_open_episode, lambda x:x) 446 #--------------------------------------------------------
447 - def _get_latest_episode(self):
448 cmd = u"""SELECT 449 coalesce ( 450 (SELECT pk FROM clin.episode WHERE fk_health_issue = %(issue)s AND is_open IS TRUE), 451 (SELECT pk FROM clin.v_pat_episodes WHERE fk_health_issue = %(issue)s ORDER BY last_affirmed DESC limit 1) 452 )""" 453 args = {'issue': self.pk_obj} 454 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 455 if len(rows) == 0: 456 return None 457 return cEpisode(aPK_obj = rows[0][0])
458 459 latest_episode = property(_get_latest_episode, lambda x:x) 460 #--------------------------------------------------------
462 try: 463 return laterality2str[self._payload[self._idx['laterality']]] 464 except KeyError: 465 return u'<???>'
466 467 laterality_description = property(_get_laterality_description, lambda x:x) 468 #--------------------------------------------------------
470 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
471 472 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
473 #============================================================
474 -def create_health_issue(description=None, encounter=None, patient=None):
475 """Creates a new health issue for a given patient. 476 477 description - health issue name 478 """ 479 try: 480 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 481 return h_issue 482 except gmExceptions.NoSuchBusinessObjectError: 483 pass 484 485 queries = [] 486 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 487 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 488 489 cmd = u"select currval('clin.health_issue_pk_seq')" 490 queries.append({'cmd': cmd}) 491 492 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 493 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 494 495 return h_issue
496 #-----------------------------------------------------------
497 -def delete_health_issue(health_issue=None):
498 if isinstance(health_issue, cHealthIssue): 499 pk = health_issue['pk_health_issue'] 500 else: 501 pk = int(health_issue) 502 503 try: 504 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}]) 505 except gmPG2.dbapi.IntegrityError: 506 # should be parsing pgcode/and or error message 507 _log.exception('cannot delete health issue') 508 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
509 #------------------------------------------------------------ 510 # use as dummy for unassociated episodes
511 -def get_dummy_health_issue():
512 issue = { 513 'pk_health_issue': None, 514 'description': _('Unattributed episodes'), 515 'age_noted': None, 516 'laterality': u'na', 517 'is_active': True, 518 'clinically_relevant': True, 519 'is_confidential': None, 520 'is_cause_of_death': False, 521 'is_dummy': True, 522 'grouping': None 523 } 524 return issue
525 #-----------------------------------------------------------
526 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
527 return cProblem ( 528 aPK_obj = { 529 'pk_patient': health_issue['pk_patient'], 530 'pk_health_issue': health_issue['pk_health_issue'], 531 'pk_episode': None 532 }, 533 try_potential_problems = allow_irrelevant 534 )
535 #============================================================ 536 # episodes API 537 #============================================================
538 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
539 """Represents one clinical episode. 540 """ 541 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s" 542 _cmds_store_payload = [ 543 u"""update clin.episode set 544 fk_health_issue = %(pk_health_issue)s, 545 is_open = %(episode_open)s::boolean, 546 description = %(description)s, 547 summary = gm.nullify_empty_string(%(summary)s), 548 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 549 where 550 pk = %(pk_episode)s and 551 xmin = %(xmin_episode)s""", 552 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 553 ] 554 _updatable_fields = [ 555 'pk_health_issue', 556 'episode_open', 557 'description', 558 'summary', 559 'diagnostic_certainty_classification' 560 ] 561 #--------------------------------------------------------
562 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
563 pk = aPK_obj 564 if pk is None and row is None: 565 566 where_parts = [u'description = %(desc)s'] 567 568 if id_patient is not None: 569 where_parts.append(u'pk_patient = %(pat)s') 570 571 if health_issue is not None: 572 where_parts.append(u'pk_health_issue = %(issue)s') 573 574 if encounter is not None: 575 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)') 576 577 args = { 578 'pat': id_patient, 579 'issue': health_issue, 580 'enc': encounter, 581 'desc': name 582 } 583 584 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts) 585 586 rows, idx = gmPG2.run_ro_queries( 587 queries = [{'cmd': cmd, 'args': args}], 588 get_col_idx=True 589 ) 590 591 if len(rows) == 0: 592 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter) 593 594 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 595 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 596 597 else: 598 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
599 #-------------------------------------------------------- 600 # external API 601 #--------------------------------------------------------
602 - def get_access_range(self):
603 """Get earliest and latest access to this episode. 604 605 Returns a tuple(earliest, latest). 606 """ 607 cmd = u""" 608 select 609 min(earliest), 610 max(latest) 611 from ( 612 (select 613 (case when clin_when < modified_when 614 then clin_when 615 else modified_when 616 end) as earliest, 617 (case when clin_when > modified_when 618 then clin_when 619 else modified_when 620 end) as latest 621 from 622 clin.clin_root_item 623 where 624 fk_episode = %(pk)s 625 626 ) union all ( 627 628 select 629 modified_when as earliest, 630 modified_when as latest 631 from 632 clin.episode 633 where 634 pk = %(pk)s 635 ) 636 ) as ranges""" 637 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 638 if len(rows) == 0: 639 return (gmNull.cNull(warn=False), gmNull.cNull(warn=False)) 640 return (rows[0][0], rows[0][1])
641 #--------------------------------------------------------
642 - def get_patient(self):
643 return self._payload[self._idx['pk_patient']]
644 #--------------------------------------------------------
645 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
646 return gmClinNarrative.get_narrative ( 647 soap_cats = soap_cats, 648 encounters = encounters, 649 episodes = [self.pk_obj], 650 order_by = order_by 651 )
652 #--------------------------------------------------------
653 - def rename(self, description=None):
654 """Method for episode editing, that is, episode renaming. 655 656 @param description 657 - the new descriptive name for the encounter 658 @type description 659 - a string instance 660 """ 661 # sanity check 662 if description.strip() == '': 663 _log.error('<description> must be a non-empty string instance') 664 return False 665 # update the episode description 666 old_description = self._payload[self._idx['description']] 667 self._payload[self._idx['description']] = description.strip() 668 self._is_modified = True 669 successful, data = self.save_payload() 670 if not successful: 671 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 672 self._payload[self._idx['description']] = old_description 673 return False 674 return True
675 #--------------------------------------------------------
676 - def format_as_journal(self, left_margin=0, date_format='%a, %b %d %Y'):
677 rows = gmClinNarrative.get_as_journal ( 678 episodes = (self.pk_obj,), 679 order_by = u'pk_encounter, clin_when, scr, src_table' 680 #order_by = u'pk_encounter, scr, clin_when, src_table' 681 ) 682 683 if len(rows) == 0: 684 return u'' 685 686 lines = [] 687 688 lines.append(_('Clinical data generated during encounters within this episode:')) 689 690 left_margin = u' ' * left_margin 691 692 prev_enc = None 693 for row in rows: 694 if row['pk_encounter'] != prev_enc: 695 lines.append(u'') 696 prev_enc = row['pk_encounter'] 697 698 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding()) 699 top_row = u'%s%s %s (%s) %s' % ( 700 gmTools.u_box_top_left_arc, 701 gmTools.u_box_horiz_single, 702 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']], 703 when, 704 gmTools.u_box_horiz_single * 5 705 ) 706 soap = gmTools.wrap ( 707 text = row['narrative'], 708 width = 60, 709 initial_indent = u' ', 710 subsequent_indent = u' ' + left_margin 711 ) 712 row_ver = u'' 713 if row['row_version'] > 0: 714 row_ver = u'v%s: ' % row['row_version'] 715 bottom_row = u'%s%s %s, %s%s %s' % ( 716 u' ' * 40, 717 gmTools.u_box_horiz_light_heavy, 718 row['modified_by'], 719 row_ver, 720 row['date_modified'], 721 gmTools.u_box_horiz_heavy_light 722 ) 723 724 lines.append(top_row) 725 lines.append(soap) 726 lines.append(bottom_row) 727 728 eol_w_margin = u'\n%s' % left_margin 729 return left_margin + eol_w_margin.join(lines) + u'\n'
730 #--------------------------------------------------------
731 - def format(self, left_margin=0, patient=None):
732 733 if patient.ID != self._payload[self._idx['pk_patient']]: 734 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 735 patient.ID, 736 self._payload[self._idx['pk_episode']], 737 self._payload[self._idx['pk_patient']] 738 ) 739 raise ValueError(msg) 740 741 lines = [] 742 743 # episode details 744 lines.append (_('Episode %s%s%s [#%s]') % ( 745 gmTools.u_left_double_angle_quote, 746 self._payload[self._idx['description']], 747 gmTools.u_right_double_angle_quote, 748 self._payload[self._idx['pk_episode']] 749 )) 750 751 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 752 lines.append (u' ' + _('Created during encounter: %s (%s - %s) [#%s]') % ( 753 enc['l10n_type'], 754 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 755 enc['last_affirmed_original_tz'].strftime('%H:%M'), 756 self._payload[self._idx['pk_encounter']] 757 )) 758 759 emr = patient.get_emr() 760 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 761 first_encounter = None 762 last_encounter = None 763 if (encs is not None) and (len(encs) > 0): 764 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 765 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 766 if self._payload[self._idx['episode_open']]: 767 end = gmDateTime.pydt_now_here() 768 end_str = gmTools.u_ellipsis 769 else: 770 end = last_encounter['last_affirmed'] 771 end_str = last_encounter['last_affirmed'].strftime('%m/%Y') 772 age = gmDateTime.format_interval_medically(end - first_encounter['started']) 773 lines.append(_(' Duration: %s (%s - %s)') % ( 774 age, 775 first_encounter['started'].strftime('%m/%Y'), 776 end_str 777 )) 778 779 lines.append(u' ' + _('Status') + u': %s%s' % ( 780 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 781 gmTools.coalesce ( 782 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 783 instead = u'', 784 template_initial = u', %s', 785 none_equivalents = [None, u''] 786 ) 787 )) 788 789 if self._payload[self._idx['summary']] is not None: 790 lines.append(u'') 791 lines.append(gmTools.wrap ( 792 text = self._payload[self._idx['summary']], 793 width = 60, 794 initial_indent = u' ', 795 subsequent_indent = u' ' 796 ) 797 ) 798 799 lines.append(u'') 800 801 # encounters 802 if encs is None: 803 lines.append(_('Error retrieving encounters for this episode.')) 804 elif len(encs) == 0: 805 lines.append(_('There are no encounters for this episode.')) 806 else: 807 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 808 809 if len(encs) < 4: 810 line = _('%s encounter(s) (%s - %s):') 811 else: 812 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):') 813 lines.append(line % ( 814 len(encs), 815 first_encounter['started'].strftime('%m/%Y'), 816 last_encounter['last_affirmed'].strftime('%m/%Y') 817 )) 818 819 lines.append(u' %s - %s (%s):%s' % ( 820 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 821 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 822 first_encounter['l10n_type'], 823 gmTools.coalesce ( 824 first_encounter['assessment_of_encounter'], 825 gmTools.coalesce ( 826 first_encounter['reason_for_encounter'], 827 u'', 828 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 829 ), 830 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 831 ) 832 )) 833 834 if len(encs) > 4: 835 lines.append(_(' ... %s skipped ...') % (len(encs) - 4)) 836 837 for enc in encs[1:][-3:]: 838 lines.append(u' %s - %s (%s):%s' % ( 839 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 840 enc['last_affirmed_original_tz'].strftime('%H:%M'), 841 enc['l10n_type'], 842 gmTools.coalesce ( 843 enc['assessment_of_encounter'], 844 gmTools.coalesce ( 845 enc['reason_for_encounter'], 846 u'', 847 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 848 ), 849 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 850 ) 851 )) 852 del encs 853 854 # spell out last encounter 855 if last_encounter is not None: 856 lines.append('') 857 lines.append(_('Progress notes in most recent encounter:')) 858 lines.extend(last_encounter.format_soap ( 859 episodes = [ self._payload[self._idx['pk_episode']] ], 860 left_margin = left_margin, 861 soap_cats = 'soap', 862 emr = emr 863 )) 864 865 # documents 866 doc_folder = patient.get_document_folder() 867 docs = doc_folder.get_documents ( 868 episodes = [ self._payload[self._idx['pk_episode']] ] 869 ) 870 871 if len(docs) > 0: 872 lines.append('') 873 lines.append(_('Documents: %s') % len(docs)) 874 875 for d in docs: 876 lines.append(u' %s %s:%s%s' % ( 877 d['clin_when'].strftime('%Y-%m-%d'), 878 d['l10n_type'], 879 gmTools.coalesce(d['comment'], u'', u' "%s"'), 880 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 881 )) 882 del docs 883 884 # hospital stays 885 stays = emr.get_hospital_stays ( 886 episodes = [ self._payload[self._idx['pk_episode']] ] 887 ) 888 889 if len(stays) > 0: 890 lines.append('') 891 lines.append(_('Hospital stays: %s') % len(stays)) 892 893 for s in stays: 894 lines.append(s.format(left_margin = (left_margin + 1))) 895 del stays 896 897 # procedures 898 procs = emr.get_performed_procedures ( 899 episodes = [ self._payload[self._idx['pk_episode']] ] 900 ) 901 902 if len(procs) > 0: 903 lines.append(u'') 904 lines.append(_('Procedures performed: %s') % len(procs)) 905 for p in procs: 906 lines.append(p.format(left_margin = (left_margin + 1), include_episode = False)) 907 del procs 908 909 # test results 910 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 911 912 if len(tests) > 0: 913 lines.append('') 914 lines.append(_('Measurements and Results:')) 915 916 for t in tests: 917 lines.extend(t.format ( 918 with_review = False, 919 with_comments = False, 920 date_format = '%Y-%m-%d' 921 )) 922 del tests 923 924 # vaccinations 925 vaccs = emr.get_vaccinations(episodes = [ self._payload[self._idx['pk_episode']] ]) 926 927 if len(vaccs) > 0: 928 lines.append(u'') 929 lines.append(_('Vaccinations:')) 930 931 for vacc in vaccs: 932 lines.extend(vacc.format ( 933 with_indications = True, 934 with_comment = True, 935 with_reaction = True, 936 date_format = '%Y-%m-%d' 937 )) 938 del vaccs 939 940 left_margin = u' ' * left_margin 941 eol_w_margin = u'\n%s' % left_margin 942 return left_margin + eol_w_margin.join(lines) + u'\n'
943 #-------------------------------------------------------- 944 # properties 945 #--------------------------------------------------------
947 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
948 949 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 950 #--------------------------------------------------------
951 - def _get_has_narrative(self):
952 cmd = u"""SELECT EXISTS ( 953 SELECT 1 FROM clin.clin_narrative 954 WHERE 955 fk_episode = %(epi)s 956 AND 957 fk_encounter IN ( 958 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s 959 ) 960 )""" 961 args = { 962 u'pat': self._payload[self._idx['pk_patient']], 963 u'epi': self._payload[self._idx['pk_episode']] 964 } 965 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 966 return rows[0][0]
967 968 has_narrative = property(_get_has_narrative, lambda x:x)
969 #============================================================
970 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
971 """Creates a new episode for a given patient's health issue. 972 973 pk_health_issue - given health issue PK 974 episode_name - name of episode 975 """ 976 if not allow_dupes: 977 try: 978 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 979 if episode['episode_open'] != is_open: 980 episode['episode_open'] = is_open 981 episode.save_payload() 982 return episode 983 except gmExceptions.ConstructorError: 984 pass 985 986 queries = [] 987 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 988 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 989 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 990 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 991 992 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 993 return episode
994 #-----------------------------------------------------------
995 -def delete_episode(episode=None):
996 if isinstance(episode, cEpisode): 997 pk = episode['pk_episode'] 998 else: 999 pk = int(episode) 1000 1001 try: 1002 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.episode where pk=%(pk)s', 'args': {'pk': pk}}]) 1003 except gmPG2.dbapi.IntegrityError: 1004 # should be parsing pgcode/and or error message 1005 _log.exception('cannot delete episode') 1006 raise gmExceptions.DatabaseObjectInUseError('cannot delete episode, it is in use')
1007 #-----------------------------------------------------------
1008 -def episode2problem(episode=None, allow_closed=False):
1009 return cProblem ( 1010 aPK_obj = { 1011 'pk_patient': episode['pk_patient'], 1012 'pk_episode': episode['pk_episode'], 1013 'pk_health_issue': episode['pk_health_issue'] 1014 }, 1015 try_potential_problems = allow_closed 1016 )
1017 #============================================================ 1018 # encounter API 1019 #============================================================
1020 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1021 """Represents one encounter.""" 1022 1023 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 1024 _cmds_store_payload = [ 1025 u"""update clin.encounter set 1026 started = %(started)s, 1027 last_affirmed = %(last_affirmed)s, 1028 fk_location = %(pk_location)s, 1029 fk_type = %(pk_type)s, 1030 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 1031 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 1032 where 1033 pk = %(pk_encounter)s and 1034 xmin = %(xmin_encounter)s""", 1035 u"""select xmin_encounter from clin.v_pat_encounters where pk_encounter=%(pk_encounter)s""" 1036 ] 1037 _updatable_fields = [ 1038 'started', 1039 'last_affirmed', 1040 'pk_location', 1041 'pk_type', 1042 'reason_for_encounter', 1043 'assessment_of_encounter' 1044 ] 1045 #--------------------------------------------------------
1046 - def set_active(self):
1047 """Set the enconter as the active one. 1048 1049 "Setting active" means making sure the encounter 1050 row has the youngest "last_affirmed" timestamp of 1051 all encounter rows for this patient. 1052 """ 1053 self['last_affirmed'] = gmDateTime.pydt_now_here() 1054 self.save()
1055 #--------------------------------------------------------
1056 - def transfer_clinical_data(self, source_episode=None, target_episode=None):
1057 """ 1058 Moves every element currently linked to the current encounter 1059 and the source_episode onto target_episode. 1060 1061 @param source_episode The episode the elements are currently linked to. 1062 @type target_episode A cEpisode intance. 1063 @param target_episode The episode the elements will be relinked to. 1064 @type target_episode A cEpisode intance. 1065 """ 1066 if source_episode['pk_episode'] == target_episode['pk_episode']: 1067 return True 1068 1069 queries = [] 1070 cmd = u""" 1071 UPDATE clin.clin_root_item 1072 SET fk_episode = %(trg)s 1073 WHERE 1074 fk_encounter = %(enc)s AND 1075 fk_episode = %(src)s 1076 """ 1077 rows, idx = gmPG2.run_rw_queries(queries = [{ 1078 'cmd': cmd, 1079 'args': { 1080 'trg': target_episode['pk_episode'], 1081 'enc': self.pk_obj, 1082 'src': source_episode['pk_episode'] 1083 } 1084 }]) 1085 self.refetch_payload() 1086 return True
1087 #--------------------------------------------------------
1088 - def same_payload(self, another_object=None):
1089 1090 relevant_fields = [ 1091 'pk_location', 1092 'pk_type', 1093 'pk_patient', 1094 'reason_for_encounter', 1095 'assessment_of_encounter' 1096 ] 1097 for field in relevant_fields: 1098 if self._payload[self._idx[field]] != another_object[field]: 1099 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1100 return False 1101 1102 relevant_fields = [ 1103 'started', 1104 'last_affirmed', 1105 ] 1106 for field in relevant_fields: 1107 if self._payload[self._idx[field]] is None: 1108 if another_object[field] is None: 1109 continue 1110 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1111 return False 1112 1113 if another_object[field] is None: 1114 return False 1115 1116 #if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S %Z') != another_object[field].strftime('%Y-%m-%d %H:%M:%S %Z'): 1117 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'): 1118 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 1119 return False 1120 1121 return True
1122 #--------------------------------------------------------
1123 - def has_clinical_data(self):
1124 cmd = u""" 1125 select exists ( 1126 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 1127 union all 1128 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1129 )""" 1130 args = { 1131 'pat': self._payload[self._idx['pk_patient']], 1132 'enc': self.pk_obj 1133 } 1134 rows, idx = gmPG2.run_ro_queries ( 1135 queries = [{ 1136 'cmd': cmd, 1137 'args': args 1138 }] 1139 ) 1140 return rows[0][0]
1141 #--------------------------------------------------------
1142 - def has_narrative(self):
1143 cmd = u""" 1144 select exists ( 1145 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 1146 )""" 1147 args = { 1148 'pat': self._payload[self._idx['pk_patient']], 1149 'enc': self.pk_obj 1150 } 1151 rows, idx = gmPG2.run_ro_queries ( 1152 queries = [{ 1153 'cmd': cmd, 1154 'args': args 1155 }] 1156 ) 1157 return rows[0][0]
1158 #--------------------------------------------------------
1159 - def has_soap_narrative(self, soap_cats=None):
1160 """soap_cats: <space> = admin category""" 1161 1162 if soap_cats is None: 1163 soap_cats = u'soap ' 1164 else: 1165 soap_cats = soap_cats.lower() 1166 1167 cats = [] 1168 for cat in soap_cats: 1169 if cat in u'soap': 1170 cats.append(cat) 1171 continue 1172 if cat == u' ': 1173 cats.append(None) 1174 1175 cmd = u""" 1176 SELECT EXISTS ( 1177 SELECT 1 FROM clin.clin_narrative 1178 WHERE 1179 fk_encounter = %(enc)s 1180 AND 1181 soap_cat IN %(cats)s 1182 LIMIT 1 1183 ) 1184 """ 1185 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)} 1186 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}]) 1187 return rows[0][0]
1188 #--------------------------------------------------------
1189 - def has_documents(self):
1190 cmd = u""" 1191 select exists ( 1192 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 1193 )""" 1194 args = { 1195 'pat': self._payload[self._idx['pk_patient']], 1196 'enc': self.pk_obj 1197 } 1198 rows, idx = gmPG2.run_ro_queries ( 1199 queries = [{ 1200 'cmd': cmd, 1201 'args': args 1202 }] 1203 ) 1204 return rows[0][0]
1205 #--------------------------------------------------------
1206 - def get_latest_soap(self, soap_cat=None, episode=None):
1207 1208 if soap_cat is not None: 1209 soap_cat = soap_cat.lower() 1210 1211 if episode is None: 1212 epi_part = u'fk_episode is null' 1213 else: 1214 epi_part = u'fk_episode = %(epi)s' 1215 1216 cmd = u""" 1217 select narrative 1218 from clin.clin_narrative 1219 where 1220 fk_encounter = %%(enc)s 1221 and 1222 soap_cat = %%(cat)s 1223 and 1224 %s 1225 order by clin_when desc 1226 limit 1 1227 """ % epi_part 1228 1229 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 1230 1231 rows, idx = gmPG2.run_ro_queries ( 1232 queries = [{ 1233 'cmd': cmd, 1234 'args': args 1235 }] 1236 ) 1237 if len(rows) == 0: 1238 return None 1239 1240 return rows[0][0]
1241 #--------------------------------------------------------
1242 - def get_episodes(self, exclude=None):
1243 cmd = u""" 1244 SELECT * FROM clin.v_pat_episodes 1245 WHERE 1246 pk_episode IN ( 1247 1248 SELECT DISTINCT fk_episode 1249 FROM clin.clin_root_item 1250 WHERE fk_encounter = %%(enc)s 1251 1252 UNION 1253 1254 SELECT DISTINCT fk_episode 1255 FROM blobs.doc_med 1256 WHERE fk_encounter = %%(enc)s 1257 ) 1258 %s""" 1259 args = {'enc': self.pk_obj} 1260 if exclude is not None: 1261 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s' 1262 args['excluded'] = tuple(exclude) 1263 else: 1264 cmd = cmd % u'' 1265 1266 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1267 1268 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1269 #--------------------------------------------------------
1270 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soap', emr=None, issues=None):
1271 1272 lines = [] 1273 for soap_cat in soap_cats: 1274 soap_cat_narratives = emr.get_clin_narrative ( 1275 episodes = episodes, 1276 issues = issues, 1277 encounters = [self._payload[self._idx['pk_encounter']]], 1278 soap_cats = [soap_cat] 1279 ) 1280 if soap_cat_narratives is None: 1281 continue 1282 if len(soap_cat_narratives) == 0: 1283 continue 1284 1285 lines.append(u'-- %s ----------' % gmClinNarrative.soap_cat2l10n_str[soap_cat]) 1286 for soap_entry in soap_cat_narratives: 1287 txt = gmTools.wrap ( 1288 text = u'%s\n (%.8s %s)' % ( 1289 soap_entry['narrative'], 1290 soap_entry['provider'], 1291 soap_entry['date'].strftime('%Y-%m-%d %H:%M') 1292 ), 1293 width = 75, 1294 initial_indent = u'', 1295 subsequent_indent = (u' ' * left_margin) 1296 ) 1297 lines.append(txt) 1298 lines.append('') 1299 1300 return lines
1301 #--------------------------------------------------------
1302 - def format_latex(self, date_format=None, soap_cats=None, soap_order=None):
1303 1304 nothing2format = ( 1305 (self._payload[self._idx['reason_for_encounter']] is None) 1306 and 1307 (self._payload[self._idx['assessment_of_encounter']] is None) 1308 and 1309 (self.has_soap_narrative(soap_cats = u'soap') is False) 1310 ) 1311 if nothing2format: 1312 return u'' 1313 1314 if date_format is None: 1315 date_format = '%A, %B %d %Y' 1316 1317 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % ( 1318 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]), 1319 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()), 1320 self._payload[self._idx['started']].strftime('%H:%M'), 1321 self._payload[self._idx['last_affirmed']].strftime('%H:%M') 1322 ) 1323 tex += u'\\hline \\tabularnewline \n' 1324 1325 for epi in self.get_episodes(): 1326 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order) 1327 if len(soaps) == 0: 1328 continue 1329 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1330 gmTools.tex_escape_string(_('Problem')), 1331 gmTools.tex_escape_string(epi['description']), 1332 gmTools.coalesce ( 1333 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']), 1334 instead = u'', 1335 template_initial = u' {\\footnotesize [%s]}', 1336 none_equivalents = [None, u''] 1337 ) 1338 ) 1339 if epi['pk_health_issue'] is not None: 1340 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % ( 1341 gmTools.tex_escape_string(_('Health issue')), 1342 gmTools.tex_escape_string(epi['health_issue']), 1343 gmTools.coalesce ( 1344 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']), 1345 instead = u'', 1346 template_initial = u' {\\footnotesize [%s]}', 1347 none_equivalents = [None, u''] 1348 ) 1349 ) 1350 for soap in soaps: 1351 tex += u'{\\small %s} & {\\small %s} \\tabularnewline \n' % ( 1352 gmClinNarrative.soap_cat2l10n[soap['soap_cat']], 1353 gmTools.tex_escape_string(soap['narrative'].strip(u'\n')) 1354 ) 1355 tex += u' & \\tabularnewline \n' 1356 1357 if self._payload[self._idx['reason_for_encounter']] is not None: 1358 tex += u'%s & %s \\tabularnewline \n' % ( 1359 gmTools.tex_escape_string(_('RFE')), 1360 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']]) 1361 ) 1362 if self._payload[self._idx['assessment_of_encounter']] is not None: 1363 tex += u'%s & %s \\tabularnewline \n' % ( 1364 gmTools.tex_escape_string(_('AOE')), 1365 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']]) 1366 ) 1367 1368 tex += u'\\hline \\tabularnewline \n' 1369 tex += u' & \\tabularnewline \n' 1370 1371 return tex
1372 #--------------------------------------------------------
1373 - 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):
1374 """Format an encounter. 1375 1376 with_co_encountlet_hints: 1377 - whether to include which *other* episodes were discussed during this encounter 1378 - (only makes sense if episodes != None) 1379 """ 1380 lines = [] 1381 1382 if fancy_header: 1383 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1384 u' ' * left_margin, 1385 self._payload[self._idx['l10n_type']], 1386 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1387 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1388 self._payload[self._idx['source_time_zone']], 1389 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 1390 self._payload[self._idx['pk_encounter']] 1391 )) 1392 1393 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1394 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1395 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1396 gmDateTime.current_local_iso_numeric_timezone_string, 1397 gmTools.bool2subst ( 1398 gmDateTime.dst_currently_in_effect, 1399 gmDateTime.py_dst_timezone_name, 1400 gmDateTime.py_timezone_name 1401 ), 1402 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1403 )) 1404 1405 if self._payload[self._idx['reason_for_encounter']] is not None: 1406 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1407 1408 if self._payload[self._idx['assessment_of_encounter']] is not None: 1409 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1410 1411 else: 1412 lines.append(u'%s%s: %s - %s%s' % ( 1413 u' ' * left_margin, 1414 self._payload[self._idx['l10n_type']], 1415 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1416 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1417 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1418 )) 1419 if with_rfe_aoe: 1420 if self._payload[self._idx['reason_for_encounter']] is not None: 1421 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']])) 1422 if self._payload[self._idx['assessment_of_encounter']] is not None: 1423 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']])) 1424 1425 if with_soap: 1426 lines.append(u'') 1427 1428 if patient.ID != self._payload[self._idx['pk_patient']]: 1429 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 1430 patient.ID, 1431 self._payload[self._idx['pk_encounter']], 1432 self._payload[self._idx['pk_patient']] 1433 ) 1434 raise ValueError(msg) 1435 1436 emr = patient.get_emr() 1437 1438 lines.extend(self.format_soap ( 1439 episodes = episodes, 1440 left_margin = left_margin, 1441 soap_cats = 'soap', 1442 emr = emr, 1443 issues = issues 1444 )) 1445 1446 # test results 1447 if with_tests: 1448 tests = emr.get_test_results_by_date ( 1449 episodes = episodes, 1450 encounter = self._payload[self._idx['pk_encounter']] 1451 ) 1452 if len(tests) > 0: 1453 lines.append('') 1454 lines.append(_('Measurements and Results:')) 1455 1456 for t in tests: 1457 lines.extend(t.format()) 1458 1459 del tests 1460 1461 # vaccinations 1462 if with_vaccinations: 1463 vaccs = emr.get_vaccinations ( 1464 episodes = episodes, 1465 encounters = [ self._payload[self._idx['pk_encounter']] ] 1466 ) 1467 1468 if len(vaccs) > 0: 1469 lines.append(u'') 1470 lines.append(_('Vaccinations:')) 1471 1472 for vacc in vaccs: 1473 lines.extend(vacc.format ( 1474 with_indications = True, 1475 with_comment = True, 1476 with_reaction = True, 1477 date_format = '%Y-%m-%d' 1478 )) 1479 del vaccs 1480 1481 # documents 1482 if with_docs: 1483 doc_folder = patient.get_document_folder() 1484 docs = doc_folder.get_documents ( 1485 episodes = episodes, 1486 encounter = self._payload[self._idx['pk_encounter']] 1487 ) 1488 1489 if len(docs) > 0: 1490 lines.append(u'') 1491 lines.append(_('Documents:')) 1492 1493 for d in docs: 1494 lines.append(u' %s %s:%s%s' % ( 1495 d['clin_when'].strftime('%Y-%m-%d'), 1496 d['l10n_type'], 1497 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1498 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1499 )) 1500 1501 del docs 1502 1503 # co-encountlets 1504 if with_co_encountlet_hints: 1505 if episodes is not None: 1506 other_epis = self.get_episodes(exclude = episodes) 1507 if len(other_epis) > 0: 1508 lines.append(u'') 1509 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis)) 1510 for epi in other_epis: 1511 lines.append(u' %s%s%s%s' % ( 1512 gmTools.u_left_double_angle_quote, 1513 epi['description'], 1514 gmTools.u_right_double_angle_quote, 1515 gmTools.coalesce(epi['health_issue'], u'', u' (%s)') 1516 )) 1517 1518 eol_w_margin = u'\n%s' % (u' ' * left_margin) 1519 return u'%s\n' % eol_w_margin.join(lines)
1520 #-----------------------------------------------------------
1521 -def create_encounter(fk_patient=None, fk_location=-1, enc_type=None):
1522 """Creates a new encounter for a patient. 1523 1524 fk_patient - patient PK 1525 fk_location - encounter location 1526 enc_type - type of encounter 1527 1528 FIXME: we don't deal with location yet 1529 """ 1530 if enc_type is None: 1531 enc_type = u'in surgery' 1532 # insert new encounter 1533 queries = [] 1534 try: 1535 enc_type = int(enc_type) 1536 cmd = u""" 1537 INSERT INTO clin.encounter ( 1538 fk_patient, fk_location, fk_type 1539 ) VALUES ( 1540 %(pat)s, 1541 -1, 1542 %(typ)s 1543 ) RETURNING pk""" 1544 except ValueError: 1545 enc_type = enc_type 1546 cmd = u""" 1547 insert into clin.encounter ( 1548 fk_patient, fk_location, fk_type 1549 ) values ( 1550 %(pat)s, 1551 -1, 1552 coalesce((select pk from clin.encounter_type where description = %(typ)s), 0) 1553 ) RETURNING pk""" 1554 args = {'pat': fk_patient, 'typ': enc_type} 1555 queries.append({'cmd': cmd, 'args': args}) 1556 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False) 1557 encounter = cEncounter(aPK_obj = rows[0]['pk']) 1558 1559 return encounter
1560 #-----------------------------------------------------------
1561 -def update_encounter_type(description=None, l10n_description=None):
1562 1563 rows, idx = gmPG2.run_rw_queries( 1564 queries = [{ 1565 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 1566 'args': {'desc': description, 'l10n_desc': l10n_description} 1567 }], 1568 return_data = True 1569 ) 1570 1571 success = rows[0][0] 1572 if not success: 1573 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 1574 1575 return {'description': description, 'l10n_description': l10n_description}
1576 #-----------------------------------------------------------
1577 -def create_encounter_type(description=None, l10n_description=None):
1578 """This will attempt to create a NEW encounter type.""" 1579 1580 # need a system name, so derive one if necessary 1581 if description is None: 1582 description = l10n_description 1583 1584 args = { 1585 'desc': description, 1586 'l10n_desc': l10n_description 1587 } 1588 1589 _log.debug('creating encounter type: %s, %s', description, l10n_description) 1590 1591 # does it exist already ? 1592 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 1593 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1594 1595 # yes 1596 if len(rows) > 0: 1597 # both system and l10n name are the same so all is well 1598 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 1599 _log.info('encounter type [%s] already exists with the proper translation') 1600 return {'description': description, 'l10n_description': l10n_description} 1601 1602 # or maybe there just wasn't a translation to 1603 # the current language for this type yet ? 1604 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 1605 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1606 1607 # there was, so fail 1608 if rows[0][0]: 1609 _log.error('encounter type [%s] already exists but with another translation') 1610 return None 1611 1612 # else set it 1613 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 1614 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1615 return {'description': description, 'l10n_description': l10n_description} 1616 1617 # no 1618 queries = [ 1619 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 1620 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 1621 ] 1622 rows, idx = gmPG2.run_rw_queries(queries = queries) 1623 1624 return {'description': description, 'l10n_description': l10n_description}
1625 #-----------------------------------------------------------
1626 -def get_encounter_types():
1627 cmd = u"select _(description) as l10n_description, description from clin.encounter_type" 1628 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 1629 return rows
1630 #-----------------------------------------------------------
1631 -def get_encounter_type(description=None):
1632 cmd = u"SELECT * from clin.encounter_type where description = %s" 1633 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 1634 return rows
1635 #-----------------------------------------------------------
1636 -def delete_encounter_type(description=None):
1637 cmd = u"delete from clin.encounter_type where description = %(desc)s" 1638 args = {'desc': description} 1639 try: 1640 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1641 except gmPG2.dbapi.IntegrityError, e: 1642 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 1643 return False 1644 raise 1645 1646 return True
1647 #============================================================
1648 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
1649 """Represents one problem. 1650 1651 problems are the aggregation of 1652 .clinically_relevant=True issues and 1653 .is_open=True episodes 1654 """ 1655 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 1656 _cmds_store_payload = [u"select 1"] 1657 _updatable_fields = [] 1658 1659 #--------------------------------------------------------
1660 - def __init__(self, aPK_obj=None, try_potential_problems=False):
1661 """Initialize. 1662 1663 aPK_obj must contain the keys 1664 pk_patient 1665 pk_episode 1666 pk_health_issue 1667 """ 1668 if aPK_obj is None: 1669 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 1670 1671 # As problems are rows from a view of different emr struct items, 1672 # the PK can't be a single field and, as some of the values of the 1673 # composed PK may be None, they must be queried using 'is null', 1674 # so we must programmatically construct the SQL query 1675 where_parts = [] 1676 pk = {} 1677 for col_name in aPK_obj.keys(): 1678 val = aPK_obj[col_name] 1679 if val is None: 1680 where_parts.append('%s IS NULL' % col_name) 1681 else: 1682 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 1683 pk[col_name] = val 1684 1685 # try to instantiate from true problem view 1686 cProblem._cmd_fetch_payload = u""" 1687 SELECT *, False as is_potential_problem 1688 FROM clin.v_problem_list 1689 WHERE %s""" % u' AND '.join(where_parts) 1690 1691 try: 1692 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 1693 return 1694 except gmExceptions.ConstructorError: 1695 _log.exception('actual problem not found, trying "potential" problems') 1696 if try_potential_problems is False: 1697 raise 1698 1699 # try to instantiate from potential-problems view 1700 cProblem._cmd_fetch_payload = u""" 1701 SELECT *, True as is_potential_problem 1702 FROM clin.v_potential_problem_list 1703 WHERE %s""" % u' AND '.join(where_parts) 1704 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1705 #--------------------------------------------------------
1706 - def get_as_episode(self):
1707 """ 1708 Retrieve the cEpisode instance equivalent to this problem. 1709 The problem's type attribute must be 'episode' 1710 """ 1711 if self._payload[self._idx['type']] != 'episode': 1712 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 1713 return None 1714 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
1715 #--------------------------------------------------------
1716 - def get_visual_progress_notes(self, encounter_id=None):
1717 1718 if self._payload[self._idx['type']] == u'issue': 1719 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ] 1720 #xxxxxxxxxxxxx 1721 1722 emr = patient.get_emr() 1723 1724 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID) 1725 return doc_folder.get_visual_progress_notes ( 1726 health_issue = self._payload[self._idx['pk_health_issue']], 1727 episode = self._payload[self._idx['pk_episode']] 1728 )
1729 #-------------------------------------------------------- 1730 # properties 1731 #-------------------------------------------------------- 1732 # doubles as 'diagnostic_certainty_description' getter:
1734 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1735 1736 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
1737 #-----------------------------------------------------------
1738 -def problem2episode(problem=None):
1739 """Retrieve the cEpisode instance equivalent to the given problem. 1740 1741 The problem's type attribute must be 'episode' 1742 1743 @param problem: The problem to retrieve its related episode for 1744 @type problem: A gmEMRStructItems.cProblem instance 1745 """ 1746 if isinstance(problem, cEpisode): 1747 return problem 1748 1749 exc = TypeError('cannot convert [%s] to episode' % problem) 1750 1751 if not isinstance(problem, cProblem): 1752 raise exc 1753 1754 if problem['type'] != 'episode': 1755 raise exc 1756 1757 return cEpisode(aPK_obj = problem['pk_episode'])
1758 #-----------------------------------------------------------
1759 -def problem2issue(problem=None):
1760 """Retrieve the cIssue instance equivalent to the given problem. 1761 1762 The problem's type attribute must be 'issue'. 1763 1764 @param problem: The problem to retrieve the corresponding issue for 1765 @type problem: A gmEMRStructItems.cProblem instance 1766 """ 1767 if isinstance(problem, cHealthIssue): 1768 return problem 1769 1770 exc = TypeError('cannot convert [%s] to health issue' % problem) 1771 1772 if not isinstance(problem, cProblem): 1773 raise exc 1774 1775 if problem['type'] != 'issue': 1776 raise exc 1777 1778 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
1779 #-----------------------------------------------------------
1780 -def reclass_problem(self, problem=None):
1781 """Transform given problem into either episode or health issue instance. 1782 """ 1783 if isinstance(problem, (cEpisode, cHealthIssue)): 1784 return problem 1785 1786 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem)) 1787 1788 if not isinstance(problem, cProblem): 1789 _log.debug(u'%s' % problem) 1790 raise exc 1791 1792 if problem['type'] == 'episode': 1793 return cEpisode(aPK_obj = problem['pk_episode']) 1794 1795 if problem['type'] == 'issue': 1796 return cHealthIssue(aPK_obj = problem['pk_health_issue']) 1797 1798 raise exc
1799 #============================================================
1800 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
1801 1802 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 1803 _cmds_store_payload = [ 1804 u"""update clin.hospital_stay set 1805 clin_when = %(admission)s, 1806 discharge = %(discharge)s, 1807 narrative = gm.nullify_empty_string(%(hospital)s), 1808 fk_episode = %(pk_episode)s, 1809 fk_encounter = %(pk_encounter)s 1810 where 1811 pk = %(pk_hospital_stay)s and 1812 xmin = %(xmin_hospital_stay)s""", 1813 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 1814 ] 1815 _updatable_fields = [ 1816 'admission', 1817 'discharge', 1818 'hospital', 1819 'pk_episode', 1820 'pk_encounter' 1821 ] 1822 #-------------------------------------------------------
1823 - def format(self, left_margin=0, include_procedures=False, include_docs=False):
1824 1825 if self._payload[self._idx['discharge']] is not None: 1826 dis = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 1827 else: 1828 dis = u'' 1829 1830 line = u'%s%s%s%s: %s%s%s' % ( 1831 u' ' * left_margin, 1832 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 1833 dis, 1834 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 1835 gmTools.u_left_double_angle_quote, 1836 self._payload[self._idx['episode']], 1837 gmTools.u_right_double_angle_quote 1838 ) 1839 1840 return line
1841 #-----------------------------------------------------------
1842 -def get_patient_hospital_stays(patient=None):
1843 1844 queries = [{ 1845 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission', 1846 'args': {'pat': patient} 1847 }] 1848 1849 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 1850 1851 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
1852 #-----------------------------------------------------------
1853 -def create_hospital_stay(encounter=None, episode=None):
1854 1855 queries = [{ 1856 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk', 1857 'args': {'enc': encounter, 'epi': episode} 1858 }] 1859 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1860 1861 return cHospitalStay(aPK_obj = rows[0][0])
1862 #-----------------------------------------------------------
1863 -def delete_hospital_stay(stay=None):
1864 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s' 1865 args = {'pk': stay} 1866 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1867 return True
1868 #============================================================
1869 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
1870 1871 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 1872 _cmds_store_payload = [ 1873 u"""UPDATE clin.procedure SET 1874 soap_cat = 'p', 1875 clin_when = %(clin_when)s, 1876 clin_end = %(clin_end)s, 1877 is_ongoing = %(is_ongoing)s, 1878 clin_where = NULLIF ( 1879 COALESCE ( 1880 %(pk_hospital_stay)s::TEXT, 1881 gm.nullify_empty_string(%(clin_where)s) 1882 ), 1883 %(pk_hospital_stay)s::TEXT 1884 ), 1885 narrative = gm.nullify_empty_string(%(performed_procedure)s), 1886 fk_hospital_stay = %(pk_hospital_stay)s, 1887 fk_episode = %(pk_episode)s, 1888 fk_encounter = %(pk_encounter)s 1889 WHERE 1890 pk = %(pk_procedure)s AND 1891 xmin = %(xmin_procedure)s 1892 RETURNING xmin as xmin_procedure""" 1893 ] 1894 _updatable_fields = [ 1895 'clin_when', 1896 'clin_end', 1897 'is_ongoing', 1898 'clin_where', 1899 'performed_procedure', 1900 'pk_hospital_stay', 1901 'pk_episode', 1902 'pk_encounter' 1903 ] 1904 #-------------------------------------------------------
1905 - def __setitem__(self, attribute, value):
1906 1907 if (attribute == 'pk_hospital_stay') and (value is not None): 1908 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 1909 1910 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 1911 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 1912 1913 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
1914 #-------------------------------------------------------
1915 - def format(self, left_margin=0, include_episode=True):
1916 1917 if self._payload[self._idx['is_ongoing']]: 1918 end = _(' (ongoing)') 1919 else: 1920 end = self._payload[self._idx['clin_end']] 1921 if end is None: 1922 end = u'' 1923 else: 1924 end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding()) 1925 1926 line = u'%s%s%s, %s: %s' % ( 1927 (u' ' * left_margin), 1928 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 1929 end, 1930 self._payload[self._idx['clin_where']], 1931 self._payload[self._idx['performed_procedure']] 1932 ) 1933 if include_episode: 1934 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 1935 1936 return line
1937 #-----------------------------------------------------------
1938 -def get_performed_procedures(patient=None):
1939 1940 queries = [ 1941 { 1942 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 1943 'args': {'pat': patient} 1944 } 1945 ] 1946 1947 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 1948 1949 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
1950 #-----------------------------------------------------------
1951 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
1952 1953 queries = [{ 1954 'cmd': u""" 1955 INSERT INTO clin.procedure ( 1956 fk_encounter, 1957 fk_episode, 1958 soap_cat, 1959 clin_where, 1960 fk_hospital_stay, 1961 narrative 1962 ) VALUES ( 1963 %(enc)s, 1964 %(epi)s, 1965 'p', 1966 gm.nullify_empty_string(%(loc)s), 1967 %(stay)s, 1968 gm.nullify_empty_string(%(proc)s) 1969 ) 1970 RETURNING pk""", 1971 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 1972 }] 1973 1974 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1975 1976 return cPerformedProcedure(aPK_obj = rows[0][0])
1977 #-----------------------------------------------------------
1978 -def delete_performed_procedure(procedure=None):
1979 cmd = u'delete from clin.procedure where pk = %(pk)s' 1980 args = {'pk': procedure} 1981 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1982 return True
1983 #============================================================ 1984 # main - unit testing 1985 #------------------------------------------------------------ 1986 if __name__ == '__main__': 1987 1988 if len(sys.argv) < 2: 1989 sys.exit() 1990 1991 if sys.argv[1] != 'test': 1992 sys.exit() 1993 1994 #-------------------------------------------------------- 1995 # define tests 1996 #--------------------------------------------------------
1997 - def test_problem():
1998 print "\nProblem test" 1999 print "------------" 2000 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 2001 print prob 2002 fields = prob.get_fields() 2003 for field in fields: 2004 print field, ':', prob[field] 2005 print '\nupdatable:', prob.get_updatable_fields() 2006 epi = prob.get_as_episode() 2007 print '\nas episode:' 2008 if epi is not None: 2009 for field in epi.get_fields(): 2010 print ' .%s : %s' % (field, epi[field])
2011 #--------------------------------------------------------
2012 - def test_health_issue():
2013 print "\nhealth issue test" 2014 print "-----------------" 2015 h_issue = cHealthIssue(aPK_obj=2) 2016 print h_issue 2017 fields = h_issue.get_fields() 2018 for field in fields: 2019 print field, ':', h_issue[field] 2020 print "has open episode:", h_issue.has_open_episode() 2021 print "open episode:", h_issue.get_open_episode() 2022 print "updateable:", h_issue.get_updatable_fields() 2023 h_issue.close_expired_episode(ttl=7300) 2024 h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 2025 print h_issue 2026 print h_issue.format_as_journal()
2027 #--------------------------------------------------------
2028 - def test_episode():
2029 print "\nepisode test" 2030 print "------------" 2031 episode = cEpisode(aPK_obj=1) 2032 print episode 2033 fields = episode.get_fields() 2034 for field in fields: 2035 print field, ':', episode[field] 2036 print "updatable:", episode.get_updatable_fields() 2037 raw_input('ENTER to continue') 2038 2039 old_description = episode['description'] 2040 old_enc = cEncounter(aPK_obj = 1) 2041 2042 desc = '1-%s' % episode['description'] 2043 print "==> renaming to", desc 2044 successful = episode.rename ( 2045 description = desc 2046 ) 2047 if not successful: 2048 print "error" 2049 else: 2050 print "success" 2051 for field in fields: 2052 print field, ':', episode[field] 2053 2054 print "episode range:", episode.get_access_range() 2055 2056 raw_input('ENTER to continue')
2057 2058 #--------------------------------------------------------
2059 - def test_encounter():
2060 print "\nencounter test" 2061 print "--------------" 2062 encounter = cEncounter(aPK_obj=1) 2063 print encounter 2064 fields = encounter.get_fields() 2065 for field in fields: 2066 print field, ':', encounter[field] 2067 print "updatable:", encounter.get_updatable_fields()
2068 #--------------------------------------------------------
2069 - def test_encounter2latex():
2070 encounter = cEncounter(aPK_obj=1) 2071 print encounter 2072 print "" 2073 print encounter.format_latex()
2074 #--------------------------------------------------------
2075 - def test_performed_procedure():
2076 procs = get_performed_procedures(patient = 12) 2077 for proc in procs: 2078 print proc.format(left_margin=2)
2079 #--------------------------------------------------------
2080 - def test_hospital_stay():
2081 stay = create_hospital_stay(encounter = 1, episode = 2) 2082 stay['hospital'] = u'Starfleet Galaxy General Hospital' 2083 stay.save_payload() 2084 print stay 2085 for s in get_patient_hospital_stays(12): 2086 print s 2087 delete_hospital_stay(stay['pk_hospital_stay']) 2088 stay = create_hospital_stay(encounter = 1, episode = 4)
2089 #--------------------------------------------------------
2090 - def test_diagnostic_certainty_classification_map():
2091 tests = [None, 'A', 'B', 'C', 'D', 'E'] 2092 2093 for t in tests: 2094 print type(t), t 2095 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
2096 2097 #-------------------------------------------------------- 2098 # run them 2099 #test_episode() 2100 #test_problem() 2101 #test_encounter() 2102 #test_health_issue() 2103 #test_hospital_stay() 2104 #test_performed_procedure() 2105 #test_diagnostic_certainty_classification_map() 2106 test_encounter2latex() 2107 #============================================================ 2108