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