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, gmExceptions, gmNull, gmBusinessDBObject, gmDateTime, gmTools, gmI18N 
  16  from Gnumed.business import gmClinNarrative 
  17   
  18   
  19  _log = logging.getLogger('gm.emr') 
  20  _log.info(__version__) 
  21   
  22  try: _ 
  23  except NameError: _ = lambda x:x 
  24  #============================================================ 
  25  # diagnostic certainty classification 
  26  #============================================================ 
  27  __diagnostic_certainty_classification_map = None 
  28   
29 -def diagnostic_certainty_classification2str(classification):
30 31 global __diagnostic_certainty_classification_map 32 33 if __diagnostic_certainty_classification_map is None: 34 __diagnostic_certainty_classification_map = { 35 None: u'', 36 u'A': _(u'A: Sign'), 37 u'B': _(u'B: Cluster of signs'), 38 u'C': _(u'C: Syndromic diagnosis'), 39 u'D': _(u'D: Scientific diagnosis') 40 } 41 42 try: 43 return __diagnostic_certainty_classification_map[classification] 44 except KeyError: 45 return _(u'%s: unknown diagnostic certainty classification') % classification
46 #============================================================ 47 # Health Issues API 48 #============================================================ 49 laterality2str = { 50 None: u'?', 51 u'na': u'', 52 u'sd': _('bilateral'), 53 u'ds': _('bilateral'), 54 u's': _('left'), 55 u'd': _('right') 56 } 57 58 #============================================================
59 -class cHealthIssue(gmBusinessDBObject.cBusinessDBObject):
60 """Represents one health issue.""" 61 62 _cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s" 63 _cmds_store_payload = [ 64 u"""update clin.health_issue set 65 description = %(description)s, 66 age_noted = %(age_noted)s, 67 laterality = gm.nullify_empty_string(%(laterality)s), 68 grouping = gm.nullify_empty_string(%(grouping)s), 69 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s), 70 is_active = %(is_active)s, 71 clinically_relevant = %(clinically_relevant)s, 72 is_confidential = %(is_confidential)s, 73 is_cause_of_death = %(is_cause_of_death)s 74 where 75 pk = %(pk_health_issue)s and 76 xmin = %(xmin_health_issue)s""", 77 u"select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s" 78 ] 79 _updatable_fields = [ 80 'description', 81 'grouping', 82 'age_noted', 83 'laterality', 84 'is_active', 85 'clinically_relevant', 86 'is_confidential', 87 'is_cause_of_death', 88 'diagnostic_certainty_classification' 89 ] 90 #--------------------------------------------------------
91 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
92 pk = aPK_obj 93 94 if (pk is not None) or (row is not None): 95 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row) 96 return 97 98 if patient is None: 99 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 100 where 101 description = %(desc)s 102 and 103 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)""" 104 else: 105 cmd = u"""select *, xmin_health_issue from clin.v_health_issues 106 where 107 description = %(desc)s 108 and 109 pk_patient = %(pat)s""" 110 111 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}] 112 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 113 114 if len(rows) == 0: 115 raise gmExceptions.NoSuchBusinessObjectError, 'no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient) 116 117 pk = rows[0][0] 118 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'} 119 120 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
121 #--------------------------------------------------------
122 - def rename(self, description=None):
123 """Method for issue renaming. 124 125 @param description 126 - the new descriptive name for the issue 127 @type description 128 - a string instance 129 """ 130 # sanity check 131 if not type(description) in [str, unicode] or description.strip() == '': 132 _log.error('<description> must be a non-empty string') 133 return False 134 # update the issue description 135 old_description = self._payload[self._idx['description']] 136 self._payload[self._idx['description']] = description.strip() 137 self._is_modified = True 138 successful, data = self.save_payload() 139 if not successful: 140 _log.error('cannot rename health issue [%s] with [%s]' % (self, description)) 141 self._payload[self._idx['description']] = old_description 142 return False 143 return True
144 #--------------------------------------------------------
145 - def get_episodes(self):
146 cmd = u"select * from clin.v_pat_episodes where pk_health_issue = %(pk)s" 147 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True) 148 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
149 #--------------------------------------------------------
150 - def close_expired_episode(self, ttl=180):
151 """ttl in days""" 152 open_episode = self.get_open_episode() 153 if open_episode is None: 154 return True 155 earliest, latest = open_episode.get_access_range() 156 ttl = datetime.timedelta(ttl) 157 now = datetime.datetime.now(tz=latest.tzinfo) 158 if (latest + ttl) > now: 159 return False 160 open_episode['episode_open'] = False 161 success, data = open_episode.save_payload() 162 if success: 163 return True 164 return False # should be an exception
165 #--------------------------------------------------------
166 - def close_episode(self):
167 open_episode = self.get_open_episode() 168 open_episode['episode_open'] = False 169 success, data = open_episode.save_payload() 170 if success: 171 return True 172 return False
173 #--------------------------------------------------------
174 - def has_open_episode(self):
175 cmd = u"select exists (select 1 from clin.episode where fk_health_issue = %s and is_open is True)" 176 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 177 return rows[0][0]
178 #--------------------------------------------------------
179 - def get_open_episode(self):
180 cmd = u"select pk from clin.episode where fk_health_issue = %s and is_open is True" 181 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}]) 182 if len(rows) == 0: 183 return None 184 return cEpisode(aPK_obj=rows[0][0])
185 #--------------------------------------------------------
186 - def age_noted_human_readable(self):
187 if self._payload[self._idx['age_noted']] is None: 188 return u'<???>' 189 190 # since we've already got an interval we are bound to use it, 191 # further transformation will only introduce more errors, 192 # later we can improve this deeper inside 193 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
194 #--------------------------------------------------------
196 try: 197 return laterality2str[self._payload[self._idx['laterality']]] 198 except KeyError: 199 return u'<???>'
200 201 laterality_description = property(_get_laterality_description, lambda x:x) 202 #--------------------------------------------------------
204 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
205 206 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 207 #--------------------------------------------------------
208 - def format(self, left_margin=0, patient=None):
209 210 if patient.ID != self._payload[self._idx['pk_patient']]: 211 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % ( 212 patient.ID, 213 self._payload[self._idx['pk_health_issue']], 214 self._payload[self._idx['pk_patient']] 215 ) 216 raise ValueError(msg) 217 218 lines = [] 219 220 lines.append(_('Health Issue %s%s%s%s [#%s]') % ( 221 u'\u00BB', 222 self._payload[self._idx['description']], 223 u'\u00AB', 224 gmTools.coalesce ( 225 initial = self.laterality_description, 226 instead = u'', 227 template_initial = u' (%s)', 228 none_equivalents = [None, u'', u'?'] 229 ), 230 self._payload[self._idx['pk_health_issue']] 231 )) 232 233 if self._payload[self._idx['is_confidential']]: 234 lines.append('') 235 lines.append(_(' ***** CONFIDENTIAL *****')) 236 lines.append('') 237 238 if self._payload[self._idx['is_cause_of_death']]: 239 lines.append('') 240 lines.append(_(' contributed to death of patient')) 241 lines.append('') 242 243 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 244 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % ( 245 enc['l10n_type'], 246 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 247 enc['last_affirmed_original_tz'].strftime('%H:%M'), 248 self._payload[self._idx['pk_encounter']] 249 )) 250 251 if self._payload[self._idx['age_noted']] is not None: 252 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable()) 253 254 lines.append(_(' Status: %s, %s%s') % ( 255 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')), 256 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')), 257 gmTools.coalesce ( 258 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 259 instead = u'', 260 template_initial = u', %s', 261 none_equivalents = [None, u''] 262 ), 263 )) 264 lines.append('') 265 266 emr = patient.get_emr() 267 268 # episodes 269 epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]]) 270 if epis is None: 271 lines.append(_('Error retrieving episodes for this health issue.')) 272 elif len(epis) == 0: 273 lines.append(_('There are no episodes for this health issue.')) 274 else: 275 lines.append ( 276 _('Episodes: %s (most recent: %s%s%s)') % ( 277 len(epis), 278 gmTools.u_left_double_angle_quote, 279 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'], 280 gmTools.u_right_double_angle_quote 281 ) 282 ) 283 lines.append('') 284 for epi in epis: 285 lines.append(u' \u00BB%s\u00AB (%s)' % ( 286 epi['description'], 287 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed')) 288 )) 289 290 lines.append('') 291 292 # encounters 293 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 294 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 295 296 if first_encounter is None or last_encounter is None: 297 lines.append(_('No encounters found for this health issue.')) 298 else: 299 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]]) 300 lines.append(_('Encounters: %s (%s - %s):') % ( 301 len(encs), 302 first_encounter['started_original_tz'].strftime('%m/%Y'), 303 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y') 304 )) 305 lines.append(_(' Most recent: %s - %s') % ( 306 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 307 last_encounter['last_affirmed_original_tz'].strftime('%H:%M') 308 )) 309 310 # medications 311 meds = emr.get_current_substance_intake ( 312 issues = [ self._payload[self._idx['pk_health_issue']] ], 313 order_by = u'is_currently_active, started, substance' 314 ) 315 316 if len(meds) > 0: 317 lines.append(u'') 318 lines.append(_('Active medications: %s') % len(meds)) 319 for m in meds: 320 lines.append(m.format(left_margin = (left_margin + 1))) 321 del meds 322 323 # hospital stays 324 stays = emr.get_hospital_stays ( 325 issues = [ self._payload[self._idx['pk_health_issue']] ] 326 ) 327 328 if len(stays) > 0: 329 lines.append(u'') 330 lines.append(_('Hospital stays: %s') % len(stays)) 331 for s in stays: 332 lines.append(s.format(left_margin = (left_margin + 1))) 333 del stays 334 335 # procedures 336 procs = emr.get_performed_procedures ( 337 issues = [ self._payload[self._idx['pk_health_issue']] ] 338 ) 339 340 if len(procs) > 0: 341 lines.append(u'') 342 lines.append(_('Procedures performed: %s') % len(procs)) 343 for p in procs: 344 lines.append(p.format(left_margin = (left_margin + 1))) 345 del procs 346 347 epis = self.get_episodes() 348 if len(epis) > 0: 349 epi_pks = [ e['pk_episode'] for e in epis ] 350 351 # documents 352 doc_folder = patient.get_document_folder() 353 docs = doc_folder.get_documents(episodes = epi_pks) 354 if len(docs) > 0: 355 lines.append(u'') 356 lines.append(_('Documents: %s') % len(docs)) 357 del docs 358 359 # test results 360 tests = emr.get_test_results_by_date(episodes = epi_pks) 361 if len(tests) > 0: 362 lines.append(u'') 363 lines.append(_('Measurements and Results: %s') % len(tests)) 364 del tests 365 366 # vaccinations 367 vaccs = emr.get_vaccinations(episodes = epi_pks) 368 if len(vaccs) > 0: 369 lines.append(u'') 370 lines.append(_('Vaccinations:')) 371 for vacc in vaccs: 372 lines.extend(vacc.format(with_reaction = True)) 373 del vaccs 374 375 del epis 376 377 left_margin = u' ' * left_margin 378 eol_w_margin = u'\n%s' % left_margin 379 return left_margin + eol_w_margin.join(lines) + u'\n'
380 #============================================================
381 -def create_health_issue(description=None, encounter=None, patient=None):
382 """Creates a new health issue for a given patient. 383 384 description - health issue name 385 """ 386 try: 387 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 388 return h_issue 389 except gmExceptions.NoSuchBusinessObjectError: 390 pass 391 392 queries = [] 393 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 394 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 395 396 cmd = u"select currval('clin.health_issue_pk_seq')" 397 queries.append({'cmd': cmd}) 398 399 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 400 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 401 402 return h_issue
403 #-----------------------------------------------------------
404 -def delete_health_issue(health_issue=None):
405 if isinstance(health_issue, cHealthIssue): 406 pk = health_issue['pk_health_issue'] 407 else: 408 pk = int(health_issue) 409 410 try: 411 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}]) 412 except gmPG2.dbapi.IntegrityError: 413 # should be parsing pgcode/and or error message 414 _log.exception('cannot delete health issue') 415 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
416 #------------------------------------------------------------ 417 # use as dummy for unassociated episodes
418 -def get_dummy_health_issue():
419 issue = { 420 'pk_health_issue': None, 421 'description': _('Unattributed episodes'), 422 'age_noted': None, 423 'laterality': u'na', 424 'is_active': True, 425 'clinically_relevant': True, 426 'is_confidential': None, 427 'is_cause_of_death': False, 428 'is_dummy': True 429 } 430 return issue
431 #-----------------------------------------------------------
432 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
433 return cProblem ( 434 aPK_obj = { 435 'pk_patient': health_issue['pk_patient'], 436 'pk_health_issue': health_issue['pk_health_issue'], 437 'pk_episode': None 438 }, 439 try_potential_problems = allow_irrelevant 440 )
441 #============================================================ 442 # episodes API 443 #============================================================
444 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
445 """Represents one clinical episode. 446 """ 447 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s" 448 _cmds_store_payload = [ 449 u"""update clin.episode set 450 fk_health_issue = %(pk_health_issue)s, 451 is_open = %(episode_open)s::boolean, 452 description = %(description)s, 453 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 454 where 455 pk = %(pk_episode)s and 456 xmin = %(xmin_episode)s""", 457 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 458 ] 459 _updatable_fields = [ 460 'pk_health_issue', 461 'episode_open', 462 'description', 463 'diagnostic_certainty_classification' 464 ] 465 #--------------------------------------------------------
466 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
467 pk = aPK_obj 468 if pk is None and row is None: 469 470 where_parts = [u'description = %(desc)s'] 471 472 if id_patient is not None: 473 where_parts.append(u'pk_patient = %(pat)s') 474 475 if health_issue is not None: 476 where_parts.append(u'pk_health_issue = %(issue)s') 477 478 if encounter is not None: 479 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)') 480 481 args = { 482 'pat': id_patient, 483 'issue': health_issue, 484 'enc': encounter, 485 'desc': name 486 } 487 488 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts) 489 490 rows, idx = gmPG2.run_ro_queries( 491 queries = [{'cmd': cmd, 'args': args}], 492 get_col_idx=True 493 ) 494 495 if len(rows) == 0: 496 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter) 497 498 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 499 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 500 501 else: 502 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
503 #--------------------------------------------------------
504 - def get_access_range(self):
505 """Get earliest and latest access to this episode. 506 507 Returns a tuple(earliest, latest). 508 """ 509 cmd = u""" 510 select 511 min(earliest), 512 max(latest) 513 from ( 514 (select 515 (case when clin_when < modified_when 516 then clin_when 517 else modified_when 518 end) as earliest, 519 (case when clin_when > modified_when 520 then clin_when 521 else modified_when 522 end) as latest 523 from 524 clin.clin_root_item 525 where 526 fk_episode = %(pk)s 527 528 ) union all ( 529 530 select 531 modified_when as earliest, 532 modified_when as latest 533 from 534 clin.episode 535 where 536 pk = %(pk)s 537 ) 538 ) as ranges""" 539 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 540 if len(rows) == 0: 541 return (gmNull.cNull(warn=False), gmNull.cNull(warn=False)) 542 return (rows[0][0], rows[0][1])
543 #--------------------------------------------------------
544 - def get_patient(self):
545 return self._payload[self._idx['pk_patient']]
546 #--------------------------------------------------------
547 - def rename(self, description=None):
548 """Method for episode editing, that is, episode renaming. 549 550 @param description 551 - the new descriptive name for the encounter 552 @type description 553 - a string instance 554 """ 555 # sanity check 556 if description.strip() == '': 557 _log.error('<description> must be a non-empty string instance') 558 return False 559 # update the episode description 560 old_description = self._payload[self._idx['description']] 561 self._payload[self._idx['description']] = description.strip() 562 self._is_modified = True 563 successful, data = self.save_payload() 564 if not successful: 565 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 566 self._payload[self._idx['description']] = old_description 567 return False 568 return True
569 #--------------------------------------------------------
571 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
572 573 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 574 #--------------------------------------------------------
575 - def format(self, left_margin=0, patient=None):
576 577 if patient.ID != self._payload[self._idx['pk_patient']]: 578 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 579 patient.ID, 580 self._payload[self._idx['pk_episode']], 581 self._payload[self._idx['pk_patient']] 582 ) 583 raise ValueError(msg) 584 585 lines = [] 586 587 # episode details 588 lines.append (_('Episode %s%s%s (%s%s) [#%s]\n') % ( 589 gmTools.u_left_double_angle_quote, 590 self._payload[self._idx['description']], 591 gmTools.u_right_double_angle_quote, 592 gmTools.coalesce ( 593 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 594 instead = u'', 595 template_initial = u'%s, ', 596 none_equivalents = [None, u''] 597 ), 598 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 599 self._payload[self._idx['pk_episode']] 600 )) 601 602 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 603 lines.append (_('Created during encounter: %s (%s - %s) [#%s]\n') % ( 604 enc['l10n_type'], 605 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 606 enc['last_affirmed_original_tz'].strftime('%H:%M'), 607 self._payload[self._idx['pk_encounter']] 608 )) 609 610 # encounters 611 emr = patient.get_emr() 612 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 613 first_encounter = None 614 last_encounter = None 615 if encs is None: 616 lines.append(_('Error retrieving encounters for this episode.')) 617 elif len(encs) == 0: 618 lines.append(_('There are no encounters for this episode.')) 619 else: 620 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 621 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 622 623 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 624 625 lines.append(_('1st and (up to 3) most recent (of %s) encounters (%s - %s):') % ( 626 len(encs), 627 first_encounter['started'].strftime('%m/%Y'), 628 last_encounter['last_affirmed'].strftime('%m/%Y') 629 )) 630 631 lines.append(u' %s - %s (%s):%s' % ( 632 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 633 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 634 first_encounter['l10n_type'], 635 gmTools.coalesce ( 636 first_encounter['assessment_of_encounter'], 637 gmTools.coalesce ( 638 first_encounter['reason_for_encounter'], 639 u'', 640 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 641 ), 642 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 643 ) 644 )) 645 646 if len(encs) > 4: 647 lines.append(_(' ... %s skipped ...') % (len(encs) - 4)) 648 649 for enc in encs[1:][-3:]: 650 lines.append(u' %s - %s (%s):%s' % ( 651 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 652 enc['last_affirmed_original_tz'].strftime('%H:%M'), 653 enc['l10n_type'], 654 gmTools.coalesce ( 655 enc['assessment_of_encounter'], 656 gmTools.coalesce ( 657 enc['reason_for_encounter'], 658 u'', 659 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 660 ), 661 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 662 ) 663 )) 664 del encs 665 666 # spell out last encounter 667 if last_encounter is not None: 668 lines.append('') 669 lines.append(_('Progress notes in most recent encounter:')) 670 lines.extend(last_encounter.format_soap ( 671 episodes = [ self._payload[self._idx['pk_episode']] ], 672 left_margin = left_margin, 673 soap_cats = 'soap', 674 emr = emr 675 )) 676 677 # documents 678 doc_folder = patient.get_document_folder() 679 docs = doc_folder.get_documents ( 680 episodes = [ self._payload[self._idx['pk_episode']] ] 681 ) 682 683 if len(docs) > 0: 684 lines.append('') 685 lines.append(_('Documents: %s') % len(docs)) 686 687 for d in docs: 688 lines.append(u' %s %s:%s%s' % ( 689 d['clin_when'].strftime('%Y-%m-%d'), 690 d['l10n_type'], 691 gmTools.coalesce(d['comment'], u'', u' "%s"'), 692 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 693 )) 694 del docs 695 696 # hospital stays 697 stays = emr.get_hospital_stays ( 698 episodes = [ self._payload[self._idx['pk_episode']] ] 699 ) 700 701 if len(stays) > 0: 702 lines.append('') 703 lines.append(_('Hospital stays: %s') % len(stays)) 704 705 for s in stays: 706 lines.append(s.format(left_margin = (left_margin + 1))) 707 del stays 708 709 # procedures 710 procs = emr.get_performed_procedures ( 711 episodes = [ self._payload[self._idx['pk_episode']] ] 712 ) 713 714 if len(procs) > 0: 715 lines.append(u'') 716 lines.append(_('Procedures performed: %s') % len(procs)) 717 for p in procs: 718 lines.append(p.format(left_margin = (left_margin + 1), include_episode = False)) 719 del procs 720 721 # test results 722 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 723 724 if len(tests) > 0: 725 lines.append('') 726 lines.append(_('Measurements and Results:')) 727 728 for t in tests: 729 lines.extend(t.format ( 730 with_review = False, 731 with_comments = False, 732 date_format = '%Y-%m-%d' 733 )) 734 del tests 735 736 # vaccinations 737 vaccs = emr.get_vaccinations(episodes = [ self._payload[self._idx['pk_episode']] ]) 738 739 if len(vaccs) > 0: 740 lines.append(u'') 741 lines.append(_('Vaccinations:')) 742 743 for vacc in vaccs: 744 lines.extend(vacc.format ( 745 with_indications = True, 746 with_comment = True, 747 with_reaction = True, 748 date_format = '%Y-%m-%d' 749 )) 750 del vaccs 751 752 left_margin = u' ' * left_margin 753 eol_w_margin = u'\n%s' % left_margin 754 return left_margin + eol_w_margin.join(lines) + u'\n'
755 #============================================================
756 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
757 """Creates a new episode for a given patient's health issue. 758 759 pk_health_issue - given health issue PK 760 episode_name - name of episode 761 """ 762 if not allow_dupes: 763 try: 764 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 765 if episode['episode_open'] != is_open: 766 episode['episode_open'] = is_open 767 episode.save_payload() 768 return episode 769 except gmExceptions.ConstructorError: 770 pass 771 772 queries = [] 773 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 774 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 775 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 776 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 777 778 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 779 return episode
780 #-----------------------------------------------------------
781 -def delete_episode(episode=None):
782 if isinstance(episode, cEpisode): 783 pk = episode['pk_episode'] 784 else: 785 pk = int(episode) 786 787 try: 788 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.episode where pk=%(pk)s', 'args': {'pk': pk}}]) 789 except gmPG2.dbapi.IntegrityError: 790 # should be parsing pgcode/and or error message 791 _log.exception('cannot delete episode') 792 raise gmExceptions.DatabaseObjectInUseError('cannot delete episode, it is in use')
793 #-----------------------------------------------------------
794 -def episode2problem(episode=None, allow_closed=False):
795 return cProblem ( 796 aPK_obj = { 797 'pk_patient': episode['pk_patient'], 798 'pk_episode': episode['pk_episode'], 799 'pk_health_issue': episode['pk_health_issue'] 800 }, 801 try_potential_problems = allow_closed 802 )
803 #============================================================ 804 # encounter API 805 #============================================================
806 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
807 """Represents one encounter.""" 808 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 809 _cmds_store_payload = [ 810 u"""update clin.encounter set 811 started = %(started)s, 812 last_affirmed = %(last_affirmed)s, 813 fk_location = %(pk_location)s, 814 fk_type = %(pk_type)s, 815 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 816 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 817 where 818 pk = %(pk_encounter)s and 819 xmin = %(xmin_encounter)s""", 820 u"""select xmin_encounter from clin.v_pat_encounters where pk_encounter=%(pk_encounter)s""" 821 ] 822 _updatable_fields = [ 823 'started', 824 'last_affirmed', 825 'pk_location', 826 'pk_type', 827 'reason_for_encounter', 828 'assessment_of_encounter' 829 ] 830 #--------------------------------------------------------
831 - def set_active(self):
832 """Set the enconter as the active one. 833 834 "Setting active" means making sure the encounter 835 row has the youngest "last_affirmed" timestamp of 836 all encounter rows for this patient. 837 """ 838 self['last_affirmed'] = gmDateTime.pydt_now_here() 839 self.save()
840 #--------------------------------------------------------
841 - def transfer_clinical_data(self, source_episode=None, target_episode=None):
842 """ 843 Moves every element currently linked to the current encounter 844 and the source_episode onto target_episode. 845 846 @param source_episode The episode the elements are currently linked to. 847 @type target_episode A cEpisode intance. 848 @param target_episode The episode the elements will be relinked to. 849 @type target_episode A cEpisode intance. 850 """ 851 if source_episode['pk_episode'] == target_episode['pk_episode']: 852 return True 853 854 queries = [] 855 cmd = u""" 856 UPDATE clin.clin_root_item 857 SET fk_episode = %(trg)s 858 WHERE 859 fk_encounter = %(enc)s AND 860 fk_episode = %(src)s 861 """ 862 rows, idx = gmPG2.run_rw_queries(queries = [{ 863 'cmd': cmd, 864 'args': { 865 'trg': target_episode['pk_episode'], 866 'enc': self.pk_obj, 867 'src': source_episode['pk_episode'] 868 } 869 }]) 870 self.refetch_payload() 871 return True
872 #--------------------------------------------------------
873 - def same_payload(self, another_object=None):
874 875 relevant_fields = [ 876 'pk_location', 877 'pk_type', 878 'pk_patient', 879 'reason_for_encounter', 880 'assessment_of_encounter' 881 ] 882 for field in relevant_fields: 883 if self._payload[self._idx[field]] != another_object[field]: 884 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 885 return False 886 887 relevant_fields = [ 888 'started', 889 'last_affirmed', 890 ] 891 for field in relevant_fields: 892 if self._payload[self._idx[field]] is None: 893 if another_object[field] is None: 894 continue 895 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 896 return False 897 898 if another_object[field] is None: 899 return False 900 901 #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'): 902 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'): 903 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field]) 904 return False 905 906 return True
907 #--------------------------------------------------------
908 - def has_clinical_data(self):
909 cmd = u""" 910 select exists ( 911 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 912 union all 913 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 914 )""" 915 args = { 916 'pat': self._payload[self._idx['pk_patient']], 917 'enc': self.pk_obj 918 } 919 rows, idx = gmPG2.run_ro_queries ( 920 queries = [{ 921 'cmd': cmd, 922 'args': args 923 }] 924 ) 925 return rows[0][0]
926 #--------------------------------------------------------
927 - def has_narrative(self):
928 cmd = u""" 929 select exists ( 930 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 931 )""" 932 args = { 933 'pat': self._payload[self._idx['pk_patient']], 934 'enc': self.pk_obj 935 } 936 rows, idx = gmPG2.run_ro_queries ( 937 queries = [{ 938 'cmd': cmd, 939 'args': args 940 }] 941 ) 942 return rows[0][0]
943 #--------------------------------------------------------
944 - def has_documents(self):
945 cmd = u""" 946 select exists ( 947 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 948 )""" 949 args = { 950 'pat': self._payload[self._idx['pk_patient']], 951 'enc': self.pk_obj 952 } 953 rows, idx = gmPG2.run_ro_queries ( 954 queries = [{ 955 'cmd': cmd, 956 'args': args 957 }] 958 ) 959 return rows[0][0]
960 #--------------------------------------------------------
961 - def get_latest_soap(self, soap_cat=None, episode=None):
962 963 if soap_cat is not None: 964 soap_cat = soap_cat.lower() 965 966 if episode is None: 967 epi_part = u'fk_episode is null' 968 else: 969 epi_part = u'fk_episode = %(epi)s' 970 971 cmd = u""" 972 select narrative 973 from clin.clin_narrative 974 where 975 fk_encounter = %%(enc)s 976 and 977 soap_cat = %%(cat)s 978 and 979 %s 980 order by clin_when desc 981 limit 1 982 """ % epi_part 983 984 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 985 986 rows, idx = gmPG2.run_ro_queries ( 987 queries = [{ 988 'cmd': cmd, 989 'args': args 990 }] 991 ) 992 if len(rows) == 0: 993 return None 994 995 return rows[0][0]
996 #--------------------------------------------------------
997 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soap', emr=None, issues=None):
998 999 lines = [] 1000 for soap_cat in soap_cats: 1001 soap_cat_narratives = emr.get_clin_narrative ( 1002 episodes = episodes, 1003 issues = issues, 1004 encounters = [self._payload[self._idx['pk_encounter']]], 1005 soap_cats = [soap_cat] 1006 ) 1007 if soap_cat_narratives is None: 1008 continue 1009 if len(soap_cat_narratives) == 0: 1010 continue 1011 1012 lines.append(u'-- %s ----------' % gmClinNarrative.soap_cat2l10n_str[soap_cat]) 1013 for soap_entry in soap_cat_narratives: 1014 txt = gmTools.wrap ( 1015 text = u'%s\n (%.8s %s)' % ( 1016 soap_entry['narrative'], 1017 soap_entry['provider'], 1018 soap_entry['date'].strftime('%Y-%m-%d %H:%M') 1019 ), 1020 width = 75, 1021 initial_indent = u'', 1022 subsequent_indent = (u' ' * left_margin) 1023 ) 1024 lines.append(txt) 1025 lines.append('') 1026 1027 return lines
1028 #--------------------------------------------------------
1029 - 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):
1030 1031 lines = [] 1032 1033 if fancy_header: 1034 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1035 u' ' * left_margin, 1036 self._payload[self._idx['l10n_type']], 1037 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1038 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1039 self._payload[self._idx['source_time_zone']], 1040 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 1041 self._payload[self._idx['pk_encounter']] 1042 )) 1043 1044 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1045 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1046 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1047 gmDateTime.current_local_iso_numeric_timezone_string, 1048 gmTools.bool2subst ( 1049 gmDateTime.dst_currently_in_effect, 1050 gmDateTime.py_dst_timezone_name, 1051 gmDateTime.py_timezone_name 1052 ), 1053 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1054 )) 1055 1056 lines.append(u'%s: %s' % ( 1057 _('RFE'), 1058 gmTools.coalesce(self._payload[self._idx['reason_for_encounter']], u'') 1059 )) 1060 lines.append(u'%s: %s' % ( 1061 _('AOE'), 1062 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'') 1063 )) 1064 1065 else: 1066 lines.append(u'%s%s: %s - %s%s' % ( 1067 u' ' * left_margin, 1068 self._payload[self._idx['l10n_type']], 1069 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1070 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1071 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1072 )) 1073 1074 if with_soap: 1075 lines.append(u'') 1076 1077 if patient.ID != self._payload[self._idx['pk_patient']]: 1078 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 1079 patient.ID, 1080 self._payload[self._idx['pk_encounter']], 1081 self._payload[self._idx['pk_patient']] 1082 ) 1083 raise ValueError(msg) 1084 1085 emr = patient.get_emr() 1086 1087 lines.extend(self.format_soap ( 1088 episodes = episodes, 1089 left_margin = left_margin, 1090 soap_cats = 'soap', 1091 emr = emr, 1092 issues = issues 1093 )) 1094 1095 # test results 1096 if with_tests: 1097 tests = emr.get_test_results_by_date ( 1098 episodes = episodes, 1099 encounter = self._payload[self._idx['pk_encounter']] 1100 ) 1101 if len(tests) > 0: 1102 lines.append('') 1103 lines.append(_('Measurements and Results:')) 1104 1105 for t in tests: 1106 lines.extend(t.format()) 1107 1108 del tests 1109 1110 # vaccinations 1111 if with_vaccinations: 1112 vaccs = emr.get_vaccinations ( 1113 episodes = episodes, 1114 encounters = [ self._payload[self._idx['pk_encounter']] ] 1115 ) 1116 1117 if len(vaccs) > 0: 1118 lines.append(u'') 1119 lines.append(_('Vaccinations:')) 1120 1121 for vacc in vaccs: 1122 lines.extend(vacc.format ( 1123 with_indications = True, 1124 with_comment = True, 1125 with_reaction = True, 1126 date_format = '%Y-%m-%d' 1127 )) 1128 del vaccs 1129 1130 # documents 1131 if with_docs: 1132 doc_folder = patient.get_document_folder() 1133 docs = doc_folder.get_documents ( 1134 episodes = episodes, 1135 encounter = self._payload[self._idx['pk_encounter']] 1136 ) 1137 1138 if len(docs) > 0: 1139 lines.append('') 1140 lines.append(_('Documents:')) 1141 1142 for d in docs: 1143 lines.append(u' %s %s:%s%s' % ( 1144 d['clin_when'].strftime('%Y-%m-%d'), 1145 d['l10n_type'], 1146 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1147 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1148 )) 1149 1150 del docs 1151 1152 eol_w_margin = u'\n%s' % (u' ' * left_margin) 1153 return u'%s\n' % eol_w_margin.join(lines)
1154 #-----------------------------------------------------------
1155 -def create_encounter(fk_patient=None, fk_location=-1, enc_type=None):
1156 """Creates a new encounter for a patient. 1157 1158 fk_patient - patient PK 1159 fk_location - encounter location 1160 enc_type - type of encounter 1161 1162 FIXME: we don't deal with location yet 1163 """ 1164 if enc_type is None: 1165 enc_type = u'in surgery' 1166 # insert new encounter 1167 queries = [] 1168 try: 1169 enc_type = int(enc_type) 1170 cmd = u""" 1171 insert into clin.encounter ( 1172 fk_patient, fk_location, fk_type 1173 ) values ( 1174 %s, -1, %s 1175 )""" 1176 except ValueError: 1177 enc_type = enc_type 1178 cmd = u""" 1179 insert into clin.encounter ( 1180 fk_patient, fk_location, fk_type 1181 ) values ( 1182 %s, -1, coalesce((select pk from clin.encounter_type where description=%s), 0) 1183 )""" 1184 queries.append({'cmd': cmd, 'args': [fk_patient, enc_type]}) 1185 queries.append({'cmd': cEncounter._cmd_fetch_payload % u"currval('clin.encounter_pk_seq')"}) 1186 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True, get_col_idx=True) 1187 encounter = cEncounter(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'}) 1188 1189 return encounter
1190 #-----------------------------------------------------------
1191 -def update_encounter_type(description=None, l10n_description=None):
1192 1193 rows, idx = gmPG2.run_rw_queries( 1194 queries = [{ 1195 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 1196 'args': {'desc': description, 'l10n_desc': l10n_description} 1197 }], 1198 return_data = True 1199 ) 1200 1201 success = rows[0][0] 1202 if not success: 1203 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 1204 1205 return {'description': description, 'l10n_description': l10n_description}
1206 #-----------------------------------------------------------
1207 -def create_encounter_type(description=None, l10n_description=None):
1208 """This will attempt to create a NEW encounter type.""" 1209 1210 # need a system name, so derive one if necessary 1211 if description is None: 1212 description = l10n_description 1213 1214 args = { 1215 'desc': description, 1216 'l10n_desc': l10n_description 1217 } 1218 1219 _log.debug('creating encounter type: %s, %s', description, l10n_description) 1220 1221 # does it exist already ? 1222 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 1223 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1224 1225 # yes 1226 if len(rows) > 0: 1227 # both system and l10n name are the same so all is well 1228 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 1229 _log.info('encounter type [%s] already exists with the proper translation') 1230 return {'description': description, 'l10n_description': l10n_description} 1231 1232 # or maybe there just wasn't a translation to 1233 # the current language for this type yet ? 1234 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 1235 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1236 1237 # there was, so fail 1238 if rows[0][0]: 1239 _log.error('encounter type [%s] already exists but with another translation') 1240 return None 1241 1242 # else set it 1243 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 1244 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1245 return {'description': description, 'l10n_description': l10n_description} 1246 1247 # no 1248 queries = [ 1249 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 1250 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 1251 ] 1252 rows, idx = gmPG2.run_rw_queries(queries = queries) 1253 1254 return {'description': description, 'l10n_description': l10n_description}
1255 #-----------------------------------------------------------
1256 -def get_encounter_types():
1257 cmd = u"select _(description) as l10n_description, description from clin.encounter_type" 1258 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 1259 return rows
1260 #-----------------------------------------------------------
1261 -def get_encounter_type(description=None):
1262 cmd = u"SELECT * from clin.encounter_type where description = %s" 1263 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 1264 return rows
1265 #-----------------------------------------------------------
1266 -def delete_encounter_type(description=None):
1267 cmd = u"delete from clin.encounter_type where description = %(desc)s" 1268 args = {'desc': description} 1269 try: 1270 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1271 except gmPG2.dbapi.IntegrityError, e: 1272 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 1273 return False 1274 raise 1275 1276 return True
1277 #============================================================
1278 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
1279 """Represents one problem. 1280 1281 problems are the aggregation of 1282 .clinically_relevant=True issues and 1283 .is_open=True episodes 1284 """ 1285 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 1286 _cmds_store_payload = [u"select 1"] 1287 _updatable_fields = [] 1288 1289 #--------------------------------------------------------
1290 - def __init__(self, aPK_obj=None, try_potential_problems=False):
1291 """Initialize. 1292 1293 aPK_obj must contain the keys 1294 pk_patient 1295 pk_episode 1296 pk_health_issue 1297 """ 1298 if aPK_obj is None: 1299 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 1300 1301 # As problems are rows from a view of different emr struct items, 1302 # the PK can't be a single field and, as some of the values of the 1303 # composed PK may be None, they must be queried using 'is null', 1304 # so we must programmatically construct the SQL query 1305 where_parts = [] 1306 pk = {} 1307 for col_name in aPK_obj.keys(): 1308 val = aPK_obj[col_name] 1309 if val is None: 1310 where_parts.append('%s IS NULL' % col_name) 1311 else: 1312 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 1313 pk[col_name] = val 1314 1315 # try to instantiate from true problem view 1316 cProblem._cmd_fetch_payload = u""" 1317 SELECT *, False as is_potential_problem 1318 FROM clin.v_problem_list 1319 WHERE %s""" % u' AND '.join(where_parts) 1320 1321 try: 1322 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 1323 return 1324 except gmExceptions.ConstructorError: 1325 _log.exception('problem not found, trying potential problems') 1326 if try_potential_problems is False: 1327 raise 1328 1329 # try to instantiate from non-problem view 1330 cProblem._cmd_fetch_payload = u""" 1331 SELECT *, True as is_potential_problem 1332 FROM clin.v_potential_problem_list 1333 WHERE %s""" % u' AND '.join(where_parts) 1334 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1335 #--------------------------------------------------------
1336 - def get_as_episode(self):
1337 """ 1338 Retrieve the cEpisode instance equivalent to this problem. 1339 The problem's type attribute must be 'episode' 1340 """ 1341 if self._payload[self._idx['type']] != 'episode': 1342 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 1343 return None 1344 return cEpisode(aPK_obj=self._payload[self._idx['pk_episode']])
1345 #-------------------------------------------------------- 1346 # doubles as 'diagnostic_certainty_description' getter:
1348 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1349 1350 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
1351 #============================================================
1352 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
1353 1354 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 1355 _cmds_store_payload = [ 1356 u"""update clin.hospital_stay set 1357 clin_when = %(admission)s, 1358 discharge = %(discharge)s, 1359 narrative = gm.nullify_empty_string(%(hospital)s), 1360 fk_episode = %(pk_episode)s, 1361 fk_encounter = %(pk_encounter)s 1362 where 1363 pk = %(pk_hospital_stay)s and 1364 xmin = %(xmin_hospital_stay)s""", 1365 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 1366 ] 1367 _updatable_fields = [ 1368 'admission', 1369 'discharge', 1370 'hospital', 1371 'pk_episode', 1372 'pk_encounter' 1373 ] 1374 #-------------------------------------------------------
1375 - def format(self, left_margin=0, include_procedures=False, include_docs=False):
1376 1377 if self._payload[self._idx['discharge']] is not None: 1378 dis = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 1379 else: 1380 dis = u'' 1381 1382 line = u'%s%s%s%s: %s%s%s' % ( 1383 u' ' * left_margin, 1384 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 1385 dis, 1386 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 1387 gmTools.u_left_double_angle_quote, 1388 self._payload[self._idx['episode']], 1389 gmTools.u_right_double_angle_quote 1390 ) 1391 1392 return line
1393 #-----------------------------------------------------------
1394 -def get_patient_hospital_stays(patient=None):
1395 1396 queries = [ 1397 { 1398 'cmd': u'select * from clin.v_pat_hospital_stays where pk_patient = %(pat)s order by admission', 1399 'args': {'pat': patient} 1400 } 1401 ] 1402 1403 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 1404 1405 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
1406 #-----------------------------------------------------------
1407 -def create_hospital_stay(encounter=None, episode=None):
1408 1409 queries = [ 1410 { 1411 'cmd': u'insert into clin.hospital_stay (fk_encounter, fk_episode) values (%(enc)s, %(epi)s)', 1412 'args': {'enc': encounter, 'epi': episode} 1413 }, 1414 {'cmd': u"select currval('clin.hospital_stay_pk_seq')"} 1415 ] 1416 1417 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1418 1419 return cHospitalStay(aPK_obj = rows[0][0])
1420 #-----------------------------------------------------------
1421 -def delete_hospital_stay(stay=None):
1422 cmd = u'delete from clin.hospital_stay where pk = %(pk)s' 1423 args = {'pk': stay} 1424 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1425 return True
1426 #============================================================
1427 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
1428 1429 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 1430 _cmds_store_payload = [ 1431 u"""UPDATE clin.procedure SET 1432 clin_when = %(clin_when)s, 1433 clin_where = NULLIF ( 1434 COALESCE ( 1435 %(pk_hospital_stay)s::TEXT, 1436 gm.nullify_empty_string(%(clin_where)s) 1437 ), 1438 %(pk_hospital_stay)s::TEXT 1439 ), 1440 narrative = gm.nullify_empty_string(%(performed_procedure)s), 1441 fk_hospital_stay = %(pk_hospital_stay)s, 1442 fk_episode = %(pk_episode)s, 1443 fk_encounter = %(pk_encounter)s 1444 WHERE 1445 pk = %(pk_procedure)s AND 1446 xmin = %(xmin_procedure)s 1447 RETURNING xmin as xmin_procedure""" 1448 # ,u"""select xmin_procedure from clin.v_pat_procedures where pk_procedure = %(pk_procedure)s""" 1449 ] 1450 _updatable_fields = [ 1451 'clin_when', 1452 'clin_where', 1453 'performed_procedure', 1454 'pk_hospital_stay', 1455 'pk_episode', 1456 'pk_encounter' 1457 ] 1458 #-------------------------------------------------------
1459 - def __setitem__(self, attribute, value):
1460 1461 if (attribute == 'pk_hospital_stay') and (value is not None): 1462 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 1463 1464 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 1465 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 1466 1467 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
1468 #-------------------------------------------------------
1469 - def format(self, left_margin=0, include_episode=True):
1470 1471 line = u'%s%s (%s): %s' % ( 1472 (u' ' * left_margin), 1473 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 1474 self._payload[self._idx['clin_where']], 1475 self._payload[self._idx['performed_procedure']] 1476 ) 1477 if include_episode: 1478 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 1479 1480 return line
1481 #-----------------------------------------------------------
1482 -def get_performed_procedures(patient=None):
1483 1484 queries = [ 1485 { 1486 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 1487 'args': {'pat': patient} 1488 } 1489 ] 1490 1491 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 1492 1493 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
1494 #-----------------------------------------------------------
1495 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
1496 1497 queries = [{ 1498 'cmd': u""" 1499 INSERT INTO clin.procedure ( 1500 fk_encounter, 1501 fk_episode, 1502 clin_where, 1503 fk_hospital_stay, 1504 narrative 1505 ) VALUES ( 1506 %(enc)s, 1507 %(epi)s, 1508 gm.nullify_empty_string(%(loc)s), 1509 %(stay)s, 1510 gm.nullify_empty_string(%(proc)s) 1511 ) 1512 RETURNING pk""", 1513 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 1514 }] 1515 1516 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1517 1518 return cPerformedProcedure(aPK_obj = rows[0][0])
1519 #-----------------------------------------------------------
1520 -def delete_performed_procedure(procedure=None):
1521 cmd = u'delete from clin.procedure where pk = %(pk)s' 1522 args = {'pk': procedure} 1523 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1524 return True
1525 #============================================================ 1526 # main - unit testing 1527 #------------------------------------------------------------ 1528 if __name__ == '__main__': 1529 1530 if len(sys.argv) < 2: 1531 sys.exit() 1532 1533 if sys.argv[1] != 'test': 1534 sys.exit() 1535 1536 #-------------------------------------------------------- 1537 # define tests 1538 #--------------------------------------------------------
1539 - def test_problem():
1540 print "\nProblem test" 1541 print "------------" 1542 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 1543 print prob 1544 fields = prob.get_fields() 1545 for field in fields: 1546 print field, ':', prob[field] 1547 print '\nupdatable:', prob.get_updatable_fields() 1548 epi = prob.get_as_episode() 1549 print '\nas episode:' 1550 if epi is not None: 1551 for field in epi.get_fields(): 1552 print ' .%s : %s' % (field, epi[field])
1553 #--------------------------------------------------------
1554 - def test_health_issue():
1555 print "\nhealth issue test" 1556 print "-----------------" 1557 h_issue = cHealthIssue(aPK_obj=2) 1558 print h_issue 1559 fields = h_issue.get_fields() 1560 for field in fields: 1561 print field, ':', h_issue[field] 1562 print "has open episode:", h_issue.has_open_episode() 1563 print "open episode:", h_issue.get_open_episode() 1564 print "updateable:", h_issue.get_updatable_fields() 1565 h_issue.close_expired_episode(ttl=7300) 1566 h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 1567 print h_issue
1568 #--------------------------------------------------------
1569 - def test_episode():
1570 print "\nepisode test" 1571 print "------------" 1572 episode = cEpisode(aPK_obj=1) 1573 print episode 1574 fields = episode.get_fields() 1575 for field in fields: 1576 print field, ':', episode[field] 1577 print "updatable:", episode.get_updatable_fields() 1578 raw_input('ENTER to continue') 1579 1580 old_description = episode['description'] 1581 old_enc = cEncounter(aPK_obj = 1) 1582 1583 desc = '1-%s' % episode['description'] 1584 print "==> renaming to", desc 1585 successful = episode.rename ( 1586 description = desc 1587 ) 1588 if not successful: 1589 print "error" 1590 else: 1591 print "success" 1592 for field in fields: 1593 print field, ':', episode[field] 1594 1595 print "episode range:", episode.get_access_range() 1596 1597 raw_input('ENTER to continue')
1598 1599 #--------------------------------------------------------
1600 - def test_encounter():
1601 print "\nencounter test" 1602 print "--------------" 1603 encounter = cEncounter(aPK_obj=1) 1604 print encounter 1605 fields = encounter.get_fields() 1606 for field in fields: 1607 print field, ':', encounter[field] 1608 print "updatable:", encounter.get_updatable_fields()
1609 #--------------------------------------------------------
1610 - def test_performed_procedure():
1611 procs = get_performed_procedures(patient = 12) 1612 for proc in procs: 1613 print proc.format(left_margin=2)
1614 #--------------------------------------------------------
1615 - def test_hospital_stay():
1616 stay = create_hospital_stay(encounter = 1, episode = 2) 1617 stay['hospital'] = u'Starfleet Galaxy General Hospital' 1618 stay.save_payload() 1619 print stay 1620 for s in get_patient_hospital_stays(12): 1621 print s 1622 delete_hospital_stay(stay['pk_hospital_stay']) 1623 stay = create_hospital_stay(encounter = 1, episode = 4)
1624 #--------------------------------------------------------
1625 - def test_diagnostic_certainty_classification_map():
1626 tests = [None, 'A', 'B', 'C', 'D', 'E'] 1627 1628 for t in tests: 1629 print type(t), t 1630 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
1631 1632 #-------------------------------------------------------- 1633 # run them 1634 #test_episode() 1635 #test_problem() 1636 #test_encounter() 1637 #test_health_issue() 1638 #test_hospital_stay() 1639 #test_performed_procedure() 1640 test_diagnostic_certainty_classification_map() 1641 #============================================================ 1642