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 192 # # seemingly silly but convinces PG to "nicely" 193 # # format the interval for us 194 # cmd = u"""select 195 #age ( 196 # (select dob from dem.identity where pk = %(pat)s) + %(issue_age)s, 197 # (select dob from dem.identity where pk = %(pat)s) 198 #)::text 199 #|| ' (' || age ( 200 # (select dob from dem.identity where pk = %(pat)s) + %(issue_age)s 201 #)::text || ' ago)' 202 #""" 203 # args = { 204 # 'pat': self._payload[self._idx['pk_patient']], 205 # 'issue_age': self._payload[self._idx['age_noted']] 206 # } 207 # rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 208 # return rows[0][0] 209 #--------------------------------------------------------
211 return laterality2str[self._payload[self._idx['laterality']]]
212 213 laterality_description = property(_get_laterality_description, lambda x:x) 214 #--------------------------------------------------------
216 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
217 218 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 219 #--------------------------------------------------------
220 - def format(self, left_margin=0, patient=None):
221 222 if patient.ID != self._payload[self._idx['pk_patient']]: 223 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % ( 224 patient.ID, 225 self._payload[self._idx['pk_health_issue']], 226 self._payload[self._idx['pk_patient']] 227 ) 228 raise ValueError(msg) 229 230 lines = [] 231 232 lines.append(_('Health Issue %s%s%s%s [#%s]') % ( 233 u'\u00BB', 234 self._payload[self._idx['description']], 235 u'\u00AB', 236 gmTools.coalesce ( 237 initial = laterality2str[self._payload[self._idx['laterality']]], 238 instead = u'', 239 template_initial = u' (%s)', 240 none_equivalents = [None, u'', u'?'] 241 ), 242 self._payload[self._idx['pk_health_issue']] 243 )) 244 245 if self._payload[self._idx['is_confidential']]: 246 lines.append('') 247 lines.append(_(' ***** CONFIDENTIAL *****')) 248 lines.append('') 249 250 if self._payload[self._idx['is_cause_of_death']]: 251 lines.append('') 252 lines.append(_(' contributed to death of patient')) 253 lines.append('') 254 255 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 256 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % ( 257 enc['l10n_type'], 258 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 259 enc['last_affirmed_original_tz'].strftime('%H:%M'), 260 self._payload[self._idx['pk_encounter']] 261 )) 262 263 if self._payload[self._idx['age_noted']] is not None: 264 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable()) 265 266 lines.append(_(' Status: %s, %s%s') % ( 267 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')), 268 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')), 269 gmTools.coalesce ( 270 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 271 instead = u'', 272 template_initial = u', %s', 273 none_equivalents = [None, u''] 274 ), 275 )) 276 lines.append('') 277 278 emr = patient.get_emr() 279 280 # episodes 281 epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]]) 282 if epis is None: 283 lines.append(_('Error retrieving episodes for this health issue.')) 284 elif len(epis) == 0: 285 lines.append(_('There are no episodes for this health issue.')) 286 else: 287 lines.append ( 288 _('Episodes: %s (most recent: %s%s%s)') % ( 289 len(epis), 290 gmTools.u_left_double_angle_quote, 291 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'], 292 gmTools.u_right_double_angle_quote 293 ) 294 ) 295 lines.append('') 296 for epi in epis: 297 lines.append(u' \u00BB%s\u00AB (%s)' % ( 298 epi['description'], 299 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed')) 300 )) 301 302 lines.append('') 303 304 # encounters 305 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 306 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']]) 307 308 if first_encounter is None or last_encounter is None: 309 lines.append(_('No encounters found for this health issue.')) 310 else: 311 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]]) 312 lines.append(_('Encounters: %s (%s - %s):') % ( 313 len(encs), 314 first_encounter['started_original_tz'].strftime('%m/%Y'), 315 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y') 316 )) 317 lines.append(_(' Most recent: %s - %s') % ( 318 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 319 last_encounter['last_affirmed_original_tz'].strftime('%H:%M') 320 )) 321 322 # medications 323 meds = emr.get_current_substance_intake ( 324 issues = [ self._payload[self._idx['pk_health_issue']] ], 325 order_by = u'is_currently_active, started, substance' 326 ) 327 328 if len(meds) > 0: 329 lines.append(u'') 330 lines.append(_('Active medications: %s') % len(meds)) 331 for m in meds: 332 lines.append(m.format(left_margin = (left_margin + 1))) 333 del meds 334 335 # hospital stays 336 stays = emr.get_hospital_stays ( 337 issues = [ self._payload[self._idx['pk_health_issue']] ] 338 ) 339 340 if len(stays) > 0: 341 lines.append(u'') 342 lines.append(_('Hospital stays: %s') % len(stays)) 343 for s in stays: 344 lines.append(s.format(left_margin = (left_margin + 1))) 345 del stays 346 347 # procedures 348 procs = emr.get_performed_procedures ( 349 issues = [ self._payload[self._idx['pk_health_issue']] ] 350 ) 351 352 if len(procs) > 0: 353 lines.append(u'') 354 lines.append(_('Procedures performed: %s') % len(procs)) 355 for p in procs: 356 lines.append(p.format(left_margin = (left_margin + 1))) 357 del procs 358 359 epis = self.get_episodes() 360 # documents 361 doc_folder = patient.get_document_folder() 362 docs = doc_folder.get_documents(episodes = [ e['pk_episode'] for e in epis ]) 363 364 if len(docs) > 0: 365 lines.append(u'') 366 lines.append(_('Documents: %s') % len(docs)) 367 del docs 368 369 # rest results 370 tests = emr.get_test_results_by_date(episodes = [ e['pk_episode'] for e in epis ]) 371 if len(tests) > 0: 372 lines.append(u'') 373 lines.append(_('Measurements and Results: %s') % len(tests)) 374 del tests 375 376 del epis 377 378 left_margin = u' ' * left_margin 379 eol_w_margin = u'\n%s' % left_margin 380 return left_margin + eol_w_margin.join(lines) + u'\n'
381 #============================================================
382 -def create_health_issue(description=None, encounter=None, patient=None):
383 """Creates a new health issue for a given patient. 384 385 description - health issue name 386 """ 387 try: 388 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient) 389 return h_issue 390 except gmExceptions.NoSuchBusinessObjectError: 391 pass 392 393 queries = [] 394 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)" 395 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}}) 396 397 cmd = u"select currval('clin.health_issue_pk_seq')" 398 queries.append({'cmd': cmd}) 399 400 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 401 h_issue = cHealthIssue(aPK_obj = rows[0][0]) 402 403 return h_issue
404 #-----------------------------------------------------------
405 -def delete_health_issue(health_issue=None):
406 if isinstance(health_issue, cHealthIssue): 407 pk = health_issue['pk_health_issue'] 408 else: 409 pk = int(health_issue) 410 411 try: 412 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}]) 413 except gmPG2.dbapi.IntegrityError: 414 # should be parsing pgcode/and or error message 415 _log.exception('cannot delete health issue') 416 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
417 #------------------------------------------------------------ 418 # use as dummy for unassociated episodes
419 -def get_dummy_health_issue():
420 issue = { 421 'pk_health_issue': None, 422 'description': _('Unattributed episodes'), 423 'age_noted': None, 424 'laterality': u'na', 425 'is_active': True, 426 'clinically_relevant': True, 427 'is_confidential': None, 428 'is_cause_of_death': False, 429 'is_dummy': True 430 } 431 return issue
432 #-----------------------------------------------------------
433 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
434 return cProblem ( 435 aPK_obj = { 436 'pk_patient': health_issue['pk_patient'], 437 'pk_health_issue': health_issue['pk_health_issue'], 438 'pk_episode': None 439 }, 440 try_potential_problems = allow_irrelevant 441 )
442 #============================================================ 443 # episodes API 444 #============================================================
445 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
446 """Represents one clinical episode. 447 """ 448 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s" 449 _cmds_store_payload = [ 450 u"""update clin.episode set 451 fk_health_issue = %(pk_health_issue)s, 452 is_open = %(episode_open)s::boolean, 453 description = %(description)s, 454 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s) 455 where 456 pk = %(pk_episode)s and 457 xmin = %(xmin_episode)s""", 458 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s""" 459 ] 460 _updatable_fields = [ 461 'pk_health_issue', 462 'episode_open', 463 'description', 464 'diagnostic_certainty_classification' 465 ] 466 #--------------------------------------------------------
467 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
468 pk = aPK_obj 469 if pk is None and row is None: 470 471 where_parts = [u'description = %(desc)s'] 472 473 if id_patient is not None: 474 where_parts.append(u'pk_patient = %(pat)s') 475 476 if health_issue is not None: 477 where_parts.append(u'pk_health_issue = %(issue)s') 478 479 if encounter is not None: 480 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)') 481 482 args = { 483 'pat': id_patient, 484 'issue': health_issue, 485 'enc': encounter, 486 'desc': name 487 } 488 489 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts) 490 491 rows, idx = gmPG2.run_ro_queries( 492 queries = [{'cmd': cmd, 'args': args}], 493 get_col_idx=True 494 ) 495 496 if len(rows) == 0: 497 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter) 498 499 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'} 500 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r) 501 502 else: 503 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
504 #--------------------------------------------------------
505 - def get_access_range(self):
506 """Get earliest and latest access to this episode. 507 508 Returns a tuple(earliest, latest). 509 """ 510 cmd = u""" 511 select 512 min(earliest), 513 max(latest) 514 from ( 515 (select 516 (case when clin_when < modified_when 517 then clin_when 518 else modified_when 519 end) as earliest, 520 (case when clin_when > modified_when 521 then clin_when 522 else modified_when 523 end) as latest 524 from 525 clin.clin_root_item 526 where 527 fk_episode = %(pk)s 528 529 ) union all ( 530 531 select 532 modified_when as earliest, 533 modified_when as latest 534 from 535 clin.episode 536 where 537 pk = %(pk)s 538 ) 539 ) as ranges""" 540 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}]) 541 if len(rows) == 0: 542 return (gmNull.cNull(warn=False), gmNull.cNull(warn=False)) 543 return (rows[0][0], rows[0][1])
544 #--------------------------------------------------------
545 - def get_patient(self):
546 return self._payload[self._idx['pk_patient']]
547 #--------------------------------------------------------
548 - def rename(self, description=None):
549 """Method for episode editing, that is, episode renaming. 550 551 @param description 552 - the new descriptive name for the encounter 553 @type description 554 - a string instance 555 """ 556 # sanity check 557 if description.strip() == '': 558 _log.error('<description> must be a non-empty string instance') 559 return False 560 # update the episode description 561 old_description = self._payload[self._idx['description']] 562 self._payload[self._idx['description']] = description.strip() 563 self._is_modified = True 564 successful, data = self.save_payload() 565 if not successful: 566 _log.error('cannot rename episode [%s] to [%s]' % (self, description)) 567 self._payload[self._idx['description']] = old_description 568 return False 569 return True
570 #--------------------------------------------------------
572 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
573 574 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x) 575 #--------------------------------------------------------
576 - def format(self, left_margin=0, patient=None):
577 578 if patient.ID != self._payload[self._idx['pk_patient']]: 579 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % ( 580 patient.ID, 581 self._payload[self._idx['pk_episode']], 582 self._payload[self._idx['pk_patient']] 583 ) 584 raise ValueError(msg) 585 586 lines = [] 587 588 # episode details 589 lines.append (_('Episode %s%s%s (%s%s) [#%s]\n') % ( 590 gmTools.u_left_double_angle_quote, 591 self._payload[self._idx['description']], 592 gmTools.u_right_double_angle_quote, 593 gmTools.coalesce ( 594 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]), 595 instead = u'', 596 template_initial = u'%s, ', 597 none_equivalents = [None, u''] 598 ), 599 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')), 600 self._payload[self._idx['pk_episode']] 601 )) 602 603 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']]) 604 lines.append (_('Created during encounter: %s (%s - %s) [#%s]\n') % ( 605 enc['l10n_type'], 606 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 607 enc['last_affirmed_original_tz'].strftime('%H:%M'), 608 self._payload[self._idx['pk_encounter']] 609 )) 610 611 # encounters 612 emr = patient.get_emr() 613 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]]) 614 first_encounter = None 615 last_encounter = None 616 if encs is None: 617 lines.append(_('Error retrieving encounters for this episode.')) 618 elif len(encs) == 0: 619 lines.append(_('There are no encounters for this episode.')) 620 else: 621 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']]) 622 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']]) 623 624 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M')) 625 626 lines.append(_('1st and (up to 3) most recent (of %s) encounters (%s - %s):') % ( 627 len(encs), 628 first_encounter['started'].strftime('%m/%Y'), 629 last_encounter['last_affirmed'].strftime('%m/%Y') 630 )) 631 632 lines.append(u' %s - %s (%s):%s' % ( 633 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 634 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'), 635 first_encounter['l10n_type'], 636 gmTools.coalesce ( 637 first_encounter['assessment_of_encounter'], 638 gmTools.coalesce ( 639 first_encounter['reason_for_encounter'], 640 u'', 641 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 642 ), 643 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 644 ) 645 )) 646 647 if len(encs) > 4: 648 lines.append(_(' ... %s skipped ...') % (len(encs) - 4)) 649 650 for enc in encs[1:][-3:]: 651 lines.append(u' %s - %s (%s):%s' % ( 652 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'), 653 enc['last_affirmed_original_tz'].strftime('%H:%M'), 654 enc['l10n_type'], 655 gmTools.coalesce ( 656 enc['assessment_of_encounter'], 657 gmTools.coalesce ( 658 enc['reason_for_encounter'], 659 u'', 660 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE')) 661 ), 662 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE')) 663 ) 664 )) 665 del encs 666 667 # spell out last encounter 668 if last_encounter is not None: 669 lines.append('') 670 lines.append(_('Progress notes in most recent encounter:')) 671 lines.extend(last_encounter.format_soap ( 672 episodes = [ self._payload[self._idx['pk_episode']] ], 673 left_margin = left_margin, 674 soap_cats = 'soap', 675 emr = emr 676 )) 677 678 # documents 679 doc_folder = patient.get_document_folder() 680 docs = doc_folder.get_documents ( 681 episodes = [ self._payload[self._idx['pk_episode']] ] 682 ) 683 684 if len(docs) > 0: 685 lines.append('') 686 lines.append(_('Documents: %s') % len(docs)) 687 688 for d in docs: 689 lines.append(u' %s %s:%s%s' % ( 690 d['clin_when'].strftime('%Y-%m-%d'), 691 d['l10n_type'], 692 gmTools.coalesce(d['comment'], u'', u' "%s"'), 693 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 694 )) 695 del docs 696 697 # hospital stays 698 stays = emr.get_hospital_stays ( 699 episodes = [ self._payload[self._idx['pk_episode']] ] 700 ) 701 702 if len(stays) > 0: 703 lines.append('') 704 lines.append(_('Hospital stays: %s') % len(stays)) 705 706 for s in stays: 707 lines.append(s.format(left_margin = (left_margin + 1))) 708 del stays 709 710 # procedures 711 procs = emr.get_performed_procedures ( 712 episodes = [ self._payload[self._idx['pk_episode']] ] 713 ) 714 715 if len(procs) > 0: 716 lines.append(u'') 717 lines.append(_('Procedures performed: %s') % len(procs)) 718 for p in procs: 719 lines.append(p.format(left_margin = (left_margin + 1), include_episode = False)) 720 del procs 721 722 # test results 723 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ]) 724 725 if len(tests) > 0: 726 lines.append('') 727 lines.append(_('Measurements and Results:')) 728 729 for t in tests: 730 lines.extend(t.format ( 731 with_review = False, 732 with_comments = False, 733 date_format = '%Y-%m-%d' 734 )) 735 del tests 736 737 left_margin = u' ' * left_margin 738 eol_w_margin = u'\n%s' % left_margin 739 return left_margin + eol_w_margin.join(lines) + u'\n'
740 #============================================================
741 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
742 """Creates a new episode for a given patient's health issue. 743 744 pk_health_issue - given health issue PK 745 episode_name - name of episode 746 """ 747 if not allow_dupes: 748 try: 749 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter) 750 if episode['episode_open'] != is_open: 751 episode['episode_open'] = is_open 752 episode.save_payload() 753 return episode 754 except gmExceptions.ConstructorError: 755 pass 756 757 queries = [] 758 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)" 759 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]}) 760 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"}) 761 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True) 762 763 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'}) 764 return episode
765 #-----------------------------------------------------------
766 -def delete_episode(episode=None):
767 if isinstance(episode, cEpisode): 768 pk = episode['pk_episode'] 769 else: 770 pk = int(episode) 771 772 try: 773 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.episode where pk=%(pk)s', 'args': {'pk': pk}}]) 774 except gmPG2.dbapi.IntegrityError: 775 # should be parsing pgcode/and or error message 776 _log.exception('cannot delete episode') 777 raise gmExceptions.DatabaseObjectInUseError('cannot delete episode, it is in use')
778 #-----------------------------------------------------------
779 -def episode2problem(episode=None, allow_closed=False):
780 return cProblem ( 781 aPK_obj = { 782 'pk_patient': episode['pk_patient'], 783 'pk_episode': episode['pk_episode'], 784 'pk_health_issue': episode['pk_health_issue'] 785 }, 786 try_potential_problems = allow_closed 787 )
788 #============================================================ 789 # encounter API 790 #============================================================
791 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
792 """Represents one encounter.""" 793 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s" 794 _cmds_store_payload = [ 795 u"""update clin.encounter set 796 started = %(started)s, 797 last_affirmed = %(last_affirmed)s, 798 fk_location = %(pk_location)s, 799 fk_type = %(pk_type)s, 800 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s), 801 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s) 802 where 803 pk = %(pk_encounter)s and 804 xmin = %(xmin_encounter)s""", 805 u"""select xmin_encounter from clin.v_pat_encounters where pk_encounter=%(pk_encounter)s""" 806 ] 807 _updatable_fields = [ 808 'started', 809 'last_affirmed', 810 'pk_location', 811 'pk_type', 812 'reason_for_encounter', 813 'assessment_of_encounter' 814 ] 815 #--------------------------------------------------------
816 - def set_active(self, staff_id=None):
817 """Set the enconter as the active one. 818 819 "Setting active" means making sure the encounter 820 row has the youngest "last_affirmed" timestamp of 821 all encounter rows for this patient. 822 823 staff_id - Provider's primary key 824 """ 825 self._payload[self._idx['last_affirmed']] = datetime.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone) 826 self.save_payload()
827 #--------------------------------------------------------
828 - def transfer_clinical_data(self, source_episode=None, target_episode=None):
829 """ 830 Moves every element currently linked to the current encounter 831 and the source_episode onto target_episode. 832 833 @param source_episode The episode the elements are currently linked to. 834 @type target_episode A cEpisode intance. 835 @param target_episode The episode the elements will be relinked to. 836 @type target_episode A cEpisode intance. 837 """ 838 if source_episode['pk_episode'] == target_episode['pk_episode']: 839 return True 840 841 queries = [] 842 cmd = u""" 843 UPDATE clin.clin_root_item 844 SET fk_episode = %(trg)s 845 WHERE 846 fk_encounter = %(enc)s AND 847 fk_episode = %(src)s 848 """ 849 rows, idx = gmPG2.run_rw_queries(queries = [{ 850 'cmd': cmd, 851 'args': { 852 'trg': target_episode['pk_episode'], 853 'enc': self.pk_obj, 854 'src': source_episode['pk_episode'] 855 } 856 }]) 857 self.refetch_payload() 858 return True
859 #--------------------------------------------------------
860 - def same_payload(self, another_object=None):
861 862 relevant_fields = [ 863 'pk_location', 864 'pk_type', 865 'reason_for_encounter', 866 'assessment_of_encounter' 867 ] 868 for field in relevant_fields: 869 if self._payload[self._idx[field]] != another_object[field]: 870 return False 871 872 relevant_fields = [ 873 'started', 874 'last_affirmed', 875 ] 876 for field in relevant_fields: 877 if self._payload[self._idx[field]] is None: 878 if another_object[field] is None: 879 continue 880 return False 881 882 if another_object[field] is None: 883 return False 884 885 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'): 886 return False 887 888 return True
889 #--------------------------------------------------------
890 - def has_clinical_data(self):
891 cmd = u""" 892 select exists ( 893 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s 894 union all 895 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 896 )""" 897 args = { 898 'pat': self._payload[self._idx['pk_patient']], 899 'enc': self.pk_obj 900 } 901 rows, idx = gmPG2.run_ro_queries ( 902 queries = [{ 903 'cmd': cmd, 904 'args': args 905 }] 906 ) 907 return rows[0][0]
908 #--------------------------------------------------------
909 - def has_narrative(self):
910 cmd = u""" 911 select exists ( 912 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s 913 )""" 914 args = { 915 'pat': self._payload[self._idx['pk_patient']], 916 'enc': self.pk_obj 917 } 918 rows, idx = gmPG2.run_ro_queries ( 919 queries = [{ 920 'cmd': cmd, 921 'args': args 922 }] 923 ) 924 return rows[0][0]
925 #--------------------------------------------------------
926 - def has_documents(self):
927 cmd = u""" 928 select exists ( 929 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s 930 )""" 931 args = { 932 'pat': self._payload[self._idx['pk_patient']], 933 'enc': self.pk_obj 934 } 935 rows, idx = gmPG2.run_ro_queries ( 936 queries = [{ 937 'cmd': cmd, 938 'args': args 939 }] 940 ) 941 return rows[0][0]
942 #--------------------------------------------------------
943 - def get_latest_soap(self, soap_cat=None, episode=None):
944 945 if soap_cat is not None: 946 soap_cat = soap_cat.lower() 947 948 if episode is None: 949 epi_part = u'fk_episode is null' 950 else: 951 epi_part = u'fk_episode = %(epi)s' 952 953 cmd = u""" 954 select narrative 955 from clin.clin_narrative 956 where 957 fk_encounter = %%(enc)s 958 and 959 soap_cat = %%(cat)s 960 and 961 %s 962 order by clin_when desc 963 limit 1 964 """ % epi_part 965 966 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode} 967 968 rows, idx = gmPG2.run_ro_queries ( 969 queries = [{ 970 'cmd': cmd, 971 'args': args 972 }] 973 ) 974 if len(rows) == 0: 975 return None 976 977 return rows[0][0]
978 #--------------------------------------------------------
979 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soap', emr=None, issues=None):
980 981 lines = [] 982 for soap_cat in soap_cats: 983 soap_cat_narratives = emr.get_clin_narrative ( 984 episodes = episodes, 985 issues = issues, 986 encounters = [self._payload[self._idx['pk_encounter']]], 987 soap_cats = [soap_cat] 988 ) 989 if soap_cat_narratives is None: 990 continue 991 if len(soap_cat_narratives) == 0: 992 continue 993 994 lines.append(u'-- %s ----------' % gmClinNarrative.soap_cat2l10n_str[soap_cat]) 995 for soap_entry in soap_cat_narratives: 996 txt = gmTools.wrap ( 997 text = u'%s\n (%.8s %s)' % ( 998 soap_entry['narrative'], 999 soap_entry['provider'], 1000 soap_entry['date'].strftime('%Y-%m-%d %H:%M') 1001 ), 1002 width = 75, 1003 initial_indent = u'', 1004 subsequent_indent = (u' ' * left_margin) 1005 ) 1006 lines.append(txt) 1007 lines.append('') 1008 1009 return lines
1010 #--------------------------------------------------------
1011 - def format(self, episodes=None, with_soap=False, left_margin=0, patient=None, issues=None, with_docs=True, with_tests=True, fancy_header=True):
1012 1013 lines = [] 1014 1015 if fancy_header: 1016 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % ( 1017 u' ' * left_margin, 1018 self._payload[self._idx['l10n_type']], 1019 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1020 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1021 self._payload[self._idx['source_time_zone']], 1022 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'), 1023 self._payload[self._idx['pk_encounter']] 1024 )) 1025 1026 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % ( 1027 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'), 1028 self._payload[self._idx['last_affirmed']].strftime('%H:%M'), 1029 gmDateTime.current_local_iso_numeric_timezone_string, 1030 gmTools.bool2subst ( 1031 gmDateTime.dst_currently_in_effect, 1032 gmDateTime.py_dst_timezone_name, 1033 gmDateTime.py_timezone_name 1034 ), 1035 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'') 1036 )) 1037 1038 lines.append(u'%s: %s' % ( 1039 _('RFE'), 1040 gmTools.coalesce(self._payload[self._idx['reason_for_encounter']], u'') 1041 )) 1042 lines.append(u'%s: %s' % ( 1043 _('AOE'), 1044 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'') 1045 )) 1046 1047 else: 1048 lines.append(u'%s%s: %s - %s%s' % ( 1049 u' ' * left_margin, 1050 self._payload[self._idx['l10n_type']], 1051 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'), 1052 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'), 1053 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB') 1054 )) 1055 1056 if with_soap: 1057 lines.append(u'') 1058 1059 if patient.ID != self._payload[self._idx['pk_patient']]: 1060 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % ( 1061 patient.ID, 1062 self._payload[self._idx['pk_encounter']], 1063 self._payload[self._idx['pk_patient']] 1064 ) 1065 raise ValueError(msg) 1066 1067 emr = patient.get_emr() 1068 1069 lines.extend(self.format_soap ( 1070 episodes = episodes, 1071 left_margin = left_margin, 1072 soap_cats = 'soap', 1073 emr = emr, 1074 issues = issues 1075 )) 1076 1077 # test results 1078 if with_tests: 1079 tests = emr.get_test_results_by_date ( 1080 episodes = episodes, 1081 encounter = self._payload[self._idx['pk_encounter']] 1082 ) 1083 if len(tests) > 0: 1084 lines.append('') 1085 lines.append(_('Measurements and Results:')) 1086 1087 for t in tests: 1088 lines.extend(t.format()) 1089 1090 del tests 1091 1092 if with_docs: 1093 doc_folder = patient.get_document_folder() 1094 docs = doc_folder.get_documents ( 1095 episodes = episodes, 1096 encounter = self._payload[self._idx['pk_encounter']] 1097 ) 1098 1099 if len(docs) > 0: 1100 lines.append('') 1101 lines.append(_('Documents:')) 1102 1103 for d in docs: 1104 lines.append(u' %s %s:%s%s' % ( 1105 d['clin_when'].strftime('%Y-%m-%d'), 1106 d['l10n_type'], 1107 gmTools.coalesce(d['comment'], u'', u' "%s"'), 1108 gmTools.coalesce(d['ext_ref'], u'', u' (%s)') 1109 )) 1110 1111 del docs 1112 1113 eol_w_margin = u'\n%s' % (u' ' * left_margin) 1114 return u'%s\n' % eol_w_margin.join(lines)
1115 1116 # special items (vaccinations, ...) 1117 1118 # try: 1119 # filtered_items.extend(emr.get_vaccinations( 1120 # since=self.__constraints['since'], 1121 # until=self.__constraints['until'], 1122 # encounters=self.__constraints['encounters'], 1123 # episodes=self.__constraints['episodes'], 1124 # issues=self.__constraints['issues'])) 1125 # except: 1126 # _log.error("vaccination error? outside regime") 1127 1128 #-----------------------------------------------------------
1129 -def create_encounter(fk_patient=None, fk_location=-1, enc_type=None):
1130 """Creates a new encounter for a patient. 1131 1132 fk_patient - patient PK 1133 fk_location - encounter location 1134 enc_type - type of encounter 1135 1136 FIXME: we don't deal with location yet 1137 """ 1138 if enc_type is None: 1139 enc_type = u'in surgery' 1140 # insert new encounter 1141 queries = [] 1142 try: 1143 enc_type = int(enc_type) 1144 cmd = u""" 1145 insert into clin.encounter ( 1146 fk_patient, fk_location, fk_type 1147 ) values ( 1148 %s, -1, %s 1149 )""" 1150 except ValueError: 1151 enc_type = enc_type 1152 cmd = u""" 1153 insert into clin.encounter ( 1154 fk_patient, fk_location, fk_type 1155 ) values ( 1156 %s, -1, coalesce((select pk from clin.encounter_type where description=%s), 0) 1157 )""" 1158 queries.append({'cmd': cmd, 'args': [fk_patient, enc_type]}) 1159 queries.append({'cmd': cEncounter._cmd_fetch_payload % u"currval('clin.encounter_pk_seq')"}) 1160 rows, idx = gmPG2.run_rw_queries(queries=queries, return_data=True, get_col_idx=True) 1161 encounter = cEncounter(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'}) 1162 1163 return encounter
1164 #-----------------------------------------------------------
1165 -def update_encounter_type(description=None, l10n_description=None):
1166 1167 rows, idx = gmPG2.run_rw_queries( 1168 queries = [{ 1169 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 1170 'args': {'desc': description, 'l10n_desc': l10n_description} 1171 }], 1172 return_data = True 1173 ) 1174 1175 success = rows[0][0] 1176 if not success: 1177 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description) 1178 1179 return {'description': description, 'l10n_description': l10n_description}
1180 #-----------------------------------------------------------
1181 -def create_encounter_type(description=None, l10n_description=None):
1182 """This will attempt to create a NEW encounter type.""" 1183 1184 # need a system name, so derive one if necessary 1185 if description is None: 1186 description = l10n_description 1187 1188 args = { 1189 'desc': description, 1190 'l10n_desc': l10n_description 1191 } 1192 1193 _log.debug('creating encounter type: %s, %s', description, l10n_description) 1194 1195 # does it exist already ? 1196 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s" 1197 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1198 1199 # yes 1200 if len(rows) > 0: 1201 # both system and l10n name are the same so all is well 1202 if (rows[0][0] == description) and (rows[0][1] == l10n_description): 1203 _log.info('encounter type [%s] already exists with the proper translation') 1204 return {'description': description, 'l10n_description': l10n_description} 1205 1206 # or maybe there just wasn't a translation to 1207 # the current language for this type yet ? 1208 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())" 1209 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 1210 1211 # there was, so fail 1212 if rows[0][0]: 1213 _log.error('encounter type [%s] already exists but with another translation') 1214 return None 1215 1216 # else set it 1217 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)" 1218 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1219 return {'description': description, 'l10n_description': l10n_description} 1220 1221 # no 1222 queries = [ 1223 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args}, 1224 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args} 1225 ] 1226 rows, idx = gmPG2.run_rw_queries(queries = queries) 1227 1228 return {'description': description, 'l10n_description': l10n_description}
1229 #-----------------------------------------------------------
1230 -def get_encounter_types():
1231 cmd = u"select _(description) as l10n_description, description from clin.encounter_type" 1232 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 1233 return rows
1234 #-----------------------------------------------------------
1235 -def get_encounter_type(description=None):
1236 cmd = u"SELECT * from clin.encounter_type where description = %s" 1237 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}]) 1238 return rows
1239 #-----------------------------------------------------------
1240 -def delete_encounter_type(description=None):
1241 cmd = u"delete from clin.encounter_type where description = %(desc)s" 1242 args = {'desc': description} 1243 try: 1244 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1245 except gmPG2.dbapi.IntegrityError, e: 1246 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION: 1247 return False 1248 raise 1249 1250 return True
1251 #============================================================
1252 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
1253 """Represents one problem. 1254 1255 problems are the aggregation of 1256 .clinically_relevant=True issues and 1257 .is_open=True episodes 1258 """ 1259 _cmd_fetch_payload = u'' # will get programmatically defined in __init__ 1260 _cmds_store_payload = [u"select 1"] 1261 _updatable_fields = [] 1262 1263 #--------------------------------------------------------
1264 - def __init__(self, aPK_obj=None, try_potential_problems=False):
1265 """Initialize. 1266 1267 aPK_obj must contain the keys 1268 pk_patient 1269 pk_episode 1270 pk_health_issue 1271 """ 1272 if aPK_obj is None: 1273 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj) 1274 1275 # As problems are rows from a view of different emr struct items, 1276 # the PK can't be a single field and, as some of the values of the 1277 # composed PK may be None, they must be queried using 'is null', 1278 # so we must programmatically construct the SQL query 1279 where_parts = [] 1280 pk = {} 1281 for col_name in aPK_obj.keys(): 1282 val = aPK_obj[col_name] 1283 if val is None: 1284 where_parts.append('%s IS NULL' % col_name) 1285 else: 1286 where_parts.append('%s = %%(%s)s' % (col_name, col_name)) 1287 pk[col_name] = val 1288 1289 # try to instantiate from true problem view 1290 cProblem._cmd_fetch_payload = u""" 1291 SELECT *, False as is_potential_problem 1292 FROM clin.v_problem_list 1293 WHERE %s""" % u' AND '.join(where_parts) 1294 1295 try: 1296 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk) 1297 return 1298 except gmExceptions.ConstructorError: 1299 _log.exception('problem not found, trying potential problems') 1300 if try_potential_problems is False: 1301 raise 1302 1303 # try to instantiate from non-problem view 1304 cProblem._cmd_fetch_payload = u""" 1305 SELECT *, True as is_potential_problem 1306 FROM clin.v_potential_problem_list 1307 WHERE %s""" % u' AND '.join(where_parts) 1308 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1309 #--------------------------------------------------------
1310 - def get_as_episode(self):
1311 """ 1312 Retrieve the cEpisode instance equivalent to this problem. 1313 The problem's type attribute must be 'episode' 1314 """ 1315 if self._payload[self._idx['type']] != 'episode': 1316 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']])) 1317 return None 1318 return cEpisode(aPK_obj=self._payload[self._idx['pk_episode']])
1319 #-------------------------------------------------------- 1320 # doubles as 'diagnostic_certainty_description' getter:
1322 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1323 1324 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
1325 #============================================================
1326 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
1327 1328 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s" 1329 _cmds_store_payload = [ 1330 u"""update clin.hospital_stay set 1331 clin_when = %(admission)s, 1332 discharge = %(discharge)s, 1333 narrative = gm.nullify_empty_string(%(hospital)s), 1334 fk_episode = %(pk_episode)s, 1335 fk_encounter = %(pk_encounter)s 1336 where 1337 pk = %(pk_hospital_stay)s and 1338 xmin = %(xmin_hospital_stay)s""", 1339 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s""" 1340 ] 1341 _updatable_fields = [ 1342 'admission', 1343 'discharge', 1344 'hospital', 1345 'pk_episode', 1346 'pk_encounter' 1347 ] 1348 #-------------------------------------------------------
1349 - def format(self, left_margin=0, include_procedures=False, include_docs=False):
1350 1351 if self._payload[self._idx['discharge']] is not None: 1352 dis = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding()) 1353 else: 1354 dis = u'' 1355 1356 line = u'%s%s%s%s: %s%s%s' % ( 1357 u' ' * left_margin, 1358 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 1359 dis, 1360 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'), 1361 gmTools.u_left_double_angle_quote, 1362 self._payload[self._idx['episode']], 1363 gmTools.u_right_double_angle_quote 1364 ) 1365 1366 return line
1367 #-----------------------------------------------------------
1368 -def get_patient_hospital_stays(patient=None):
1369 1370 queries = [ 1371 { 1372 'cmd': u'select * from clin.v_pat_hospital_stays where pk_patient = %(pat)s order by admission', 1373 'args': {'pat': patient} 1374 } 1375 ] 1376 1377 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 1378 1379 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
1380 #-----------------------------------------------------------
1381 -def create_hospital_stay(encounter=None, episode=None):
1382 1383 queries = [ 1384 { 1385 'cmd': u'insert into clin.hospital_stay (fk_encounter, fk_episode) values (%(enc)s, %(epi)s)', 1386 'args': {'enc': encounter, 'epi': episode} 1387 }, 1388 {'cmd': u"select currval('clin.hospital_stay_pk_seq')"} 1389 ] 1390 1391 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1392 1393 return cHospitalStay(aPK_obj = rows[0][0])
1394 #-----------------------------------------------------------
1395 -def delete_hospital_stay(stay=None):
1396 cmd = u'delete from clin.hospital_stay where pk = %(pk)s' 1397 args = {'pk': stay} 1398 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1399 return True
1400 #============================================================
1401 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
1402 1403 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s" 1404 _cmds_store_payload = [ 1405 u"""update clin.procedure set 1406 clin_when = %(clin_when)s, 1407 clin_where = gm.nullify_empty_string(%(clin_where)s), 1408 narrative = gm.nullify_empty_string(%(performed_procedure)s), 1409 fk_hospital_stay = %(pk_hospital_stay)s, 1410 fk_episode = %(pk_episode)s, 1411 fk_encounter = %(pk_encounter)s 1412 where 1413 pk = %(pk_procedure)s and 1414 xmin = %(xmin_procedure)s 1415 """, 1416 u"""select xmin_procedure from clin.v_pat_procedures where pk_procedure = %(pk_procedure)s""" 1417 ] 1418 _updatable_fields = [ 1419 'clin_when', 1420 'clin_where', 1421 'performed_procedure', 1422 'pk_hospital_stay', 1423 'pk_episode', 1424 'pk_encounter' 1425 ] 1426 #-------------------------------------------------------
1427 - def __setitem__(self, attribute, value):
1428 1429 if (attribute == 'pk_hospital_stay') and (value is not None): 1430 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None) 1431 1432 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''): 1433 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None) 1434 1435 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
1436 #-------------------------------------------------------
1437 - def format(self, left_margin=0, include_episode=True):
1438 1439 line = u'%s%s (%s): %s' % ( 1440 (u' ' * left_margin), 1441 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()), 1442 self._payload[self._idx['clin_where']], 1443 self._payload[self._idx['performed_procedure']] 1444 ) 1445 if include_episode: 1446 line = u'%s (%s)' % (line, self._payload[self._idx['episode']]) 1447 1448 return line
1449 #-----------------------------------------------------------
1450 -def get_performed_procedures(patient=None):
1451 1452 queries = [ 1453 { 1454 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when', 1455 'args': {'pat': patient} 1456 } 1457 ] 1458 1459 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True) 1460 1461 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
1462 #-----------------------------------------------------------
1463 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
1464 1465 queries = [{ 1466 'cmd': u""" 1467 insert into clin.procedure ( 1468 fk_encounter, 1469 fk_episode, 1470 clin_where, 1471 fk_hospital_stay, 1472 narrative 1473 ) values ( 1474 %(enc)s, 1475 %(epi)s, 1476 gm.nullify_empty_string(%(loc)s), 1477 %(stay)s, 1478 %(proc)s 1479 ) 1480 returning pk""", 1481 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure} 1482 }] 1483 1484 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True) 1485 1486 return cPerformedProcedure(aPK_obj = rows[0][0])
1487 #-----------------------------------------------------------
1488 -def delete_performed_procedure(procedure=None):
1489 cmd = u'delete from clin.procedure where pk = %(pk)s' 1490 args = {'pk': procedure} 1491 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 1492 return True
1493 #============================================================ 1494 # main - unit testing 1495 #------------------------------------------------------------ 1496 if __name__ == '__main__': 1497 1498 #-------------------------------------------------------- 1499 # define tests 1500 #--------------------------------------------------------
1501 - def test_problem():
1502 print "\nProblem test" 1503 print "------------" 1504 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None}) 1505 print prob 1506 fields = prob.get_fields() 1507 for field in fields: 1508 print field, ':', prob[field] 1509 print '\nupdatable:', prob.get_updatable_fields() 1510 epi = prob.get_as_episode() 1511 print '\nas episode:' 1512 if epi is not None: 1513 for field in epi.get_fields(): 1514 print ' .%s : %s' % (field, epi[field])
1515 #--------------------------------------------------------
1516 - def test_health_issue():
1517 print "\nhealth issue test" 1518 print "-----------------" 1519 h_issue = cHealthIssue(aPK_obj=2) 1520 print h_issue 1521 fields = h_issue.get_fields() 1522 for field in fields: 1523 print field, ':', h_issue[field] 1524 print "has open episode:", h_issue.has_open_episode() 1525 print "open episode:", h_issue.get_open_episode() 1526 print "updateable:", h_issue.get_updatable_fields() 1527 h_issue.close_expired_episode(ttl=7300) 1528 h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis') 1529 print h_issue
1530 #--------------------------------------------------------
1531 - def test_episode():
1532 print "\nepisode test" 1533 print "------------" 1534 episode = cEpisode(aPK_obj=1) 1535 print episode 1536 fields = episode.get_fields() 1537 for field in fields: 1538 print field, ':', episode[field] 1539 print "updatable:", episode.get_updatable_fields() 1540 raw_input('ENTER to continue') 1541 1542 old_description = episode['description'] 1543 old_enc = cEncounter(aPK_obj = 1) 1544 1545 desc = '1-%s' % episode['description'] 1546 print "==> renaming to", desc 1547 successful = episode.rename ( 1548 description = desc 1549 ) 1550 if not successful: 1551 print "error" 1552 else: 1553 print "success" 1554 for field in fields: 1555 print field, ':', episode[field] 1556 1557 print "episode range:", episode.get_access_range() 1558 1559 raw_input('ENTER to continue')
1560 1561 #--------------------------------------------------------
1562 - def test_encounter():
1563 print "\nencounter test" 1564 print "--------------" 1565 encounter = cEncounter(aPK_obj=1) 1566 print encounter 1567 fields = encounter.get_fields() 1568 for field in fields: 1569 print field, ':', encounter[field] 1570 print "updatable:", encounter.get_updatable_fields()
1571 #--------------------------------------------------------
1572 - def test_performed_procedure():
1573 procs = get_performed_procedures(patient = 12) 1574 for proc in procs: 1575 print proc.format(left_margin=2)
1576 #--------------------------------------------------------
1577 - def test_hospital_stay():
1578 stay = create_hospital_stay(encounter = 1, episode = 2) 1579 stay['hospital'] = u'Starfleet Galaxy General Hospital' 1580 stay.save_payload() 1581 print stay 1582 for s in get_patient_hospital_stays(12): 1583 print s 1584 delete_hospital_stay(stay['pk_hospital_stay']) 1585 stay = create_hospital_stay(encounter = 1, episode = 4)
1586 #--------------------------------------------------------
1587 - def test_diagnostic_certainty_classification_map():
1588 tests = [None, 'A', 'B', 'C', 'D', 'E'] 1589 1590 for t in tests: 1591 print type(t), t 1592 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
1593 1594 #-------------------------------------------------------- 1595 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 1596 # run them 1597 #test_episode() 1598 #test_problem() 1599 #test_encounter() 1600 #test_health_issue() 1601 #test_hospital_stay() 1602 #test_performed_procedure() 1603 test_diagnostic_certainty_classification_map() 1604 #============================================================ 1605 # $Log: gmEMRStructItems.py,v $ 1606 # Revision 1.157 2010/01/21 08:40:15 ncq 1607 # - make diagnostic certainty conversion robust against when _() is imported 1608 # - enable problem to return certainty description 1609 # - test certainty mapper 1610 # 1611 # Revision 1.156 2009/12/21 14:58:19 ncq 1612 # - for issue/episode show creation encounter 1613 # - %x -> %Y-%m-%d timestamp formatting 1614 # 1615 # Revision 1.155 2009/11/28 18:35:23 ncq 1616 # - fix health_issue2problem/episode2problem 1617 # - add is_dummy to dummy health issue 1618 # - enhance cProblem to allow for potential problems, too 1619 # 1620 # Revision 1.154 2009/11/13 21:01:45 ncq 1621 # - create-performed-proc 1622 # 1623 # Revision 1.153 2009/11/06 15:03:15 ncq 1624 # - better known-since formatting for health issue 1625 # - include meds in class formatting 1626 # 1627 # Revision 1.152 2009/10/20 10:24:03 ncq 1628 # - laterality/certainty properties 1629 # 1630 # Revision 1.151 2009/09/29 13:14:06 ncq 1631 # - fix faulty param in create_health_issue 1632 # 1633 # Revision 1.150 2009/09/23 14:31:25 ncq 1634 # - better ABCD long desc 1635 # - health issue 1636 # - teach init/create-* to take advantage of patient pk 1637 # - better formatting 1638 # - cProcedure etc 1639 # 1640 # Revision 1.149 2009/09/17 21:51:27 ncq 1641 # - add performed procedures support 1642 # 1643 # Revision 1.148 2009/09/15 15:24:48 ncq 1644 # - add format() to hospital stays and use it 1645 # 1646 # Revision 1.147 2009/09/13 18:22:55 ncq 1647 # - ignore return of save_payload() - it's a dummy 1648 # 1649 # Revision 1.146 2009/09/01 23:03:57 ncq 1650 # - better classification 1651 # 1652 # Revision 1.145 2009/09/01 22:14:15 ncq 1653 # - support diagnostic certainty on issues and episodes 1654 # 1655 # Revision 1.144 2009/07/16 09:51:53 ncq 1656 # - properly update enc type and check success 1657 # 1658 # Revision 1.143 2009/07/06 14:56:04 ncq 1659 # - consolidate date formatting 1660 # - use improved test results formatting 1661 # 1662 # Revision 1.142 2009/07/02 20:46:17 ncq 1663 # - get-episodes in issue 1664 # - include docs/tests in issue/episode formatting 1665 # 1666 # Revision 1.141 2009/07/01 17:05:56 ncq 1667 # - cleanup 1668 # 1669 # Revision 1.140 2009/06/29 14:59:18 ncq 1670 # - add get-latest-soap 1671 # 1672 # Revision 1.139 2009/06/20 12:33:52 ncq 1673 # - improved episode formatting as per list 1674 # 1675 # Revision 1.138 2009/06/04 14:32:16 ncq 1676 # - reimport lost comment 1677 # 1678 # Revision 1.138 2009/05/28 10:45:33 ncq 1679 # - comment added 1680 # 1681 # Revision 1.137 2009/04/13 10:52:14 ncq 1682 # - support same_payload on encounter 1683 # 1684 # Revision 1.136 2009/04/05 17:48:52 ncq 1685 # - support grouping 1686 # 1687 # Revision 1.135 2009/04/03 11:07:25 ncq 1688 # - include stays in issue formatting 1689 # 1690 # Revision 1.134 2009/04/03 10:39:40 ncq 1691 # - include hospital stays into episode formatting 1692 # 1693 # Revision 1.133 2009/04/03 09:31:13 ncq 1694 # - add hospital stay API 1695 # 1696 # Revision 1.132 2009/02/27 12:38:03 ncq 1697 # - improved SOAP formatting 1698 # 1699 # Revision 1.131 2009/02/23 08:46:01 ncq 1700 # - fix faulty column label 1701 # 1702 # Revision 1.130 2009/02/17 17:45:53 ncq 1703 # - fix create_health_issue 1704 # 1705 # Revision 1.129 2008/12/18 21:26:20 ncq 1706 # - u''ify some strings 1707 # 1708 # Revision 1.128 2008/12/09 23:21:00 ncq 1709 # - .date -> .clin_when in documents 1710 # - no more fk_patient in episode 1711 # 1712 # Revision 1.127 2008/12/01 12:36:13 ncq 1713 # - much improved formatting 1714 # 1715 # Revision 1.126 2008/11/24 11:09:01 ncq 1716 # - health issues now stem from a view 1717 # - no more fk_patient in clin.health_issue 1718 # - no more patient id in create_episode 1719 # 1720 # Revision 1.125 2008/11/20 18:40:53 ncq 1721 # - health_issue/episode2problem 1722 # - improved formatting 1723 # 1724 # Revision 1.124 2008/10/22 12:04:55 ncq 1725 # - use %x in strftime 1726 # 1727 # Revision 1.123 2008/10/12 15:13:30 ncq 1728 # - no more "foundational" in health issue 1729 # 1730 # Revision 1.122 2008/09/09 19:55:07 ncq 1731 # - Jerzy found a misspelling 1732 # 1733 # Revision 1.121 2008/09/04 11:52:17 ncq 1734 # - append an empty line per soap category 1735 # 1736 # Revision 1.120 2008/09/02 18:58:27 ncq 1737 # - fk_patient dropped from clin.health_issue 1738 # 1739 # Revision 1.119 2008/08/17 18:13:39 ncq 1740 # - add CRLF after date/time/provider in soap formatting as 1741 # suggested by Rogerio on the list 1742 # 1743 # Revision 1.118 2008/07/24 13:57:51 ncq 1744 # - update/create/delete_encounter_type 1745 # 1746 # Revision 1.117 2008/07/22 13:53:12 ncq 1747 # - cleanup 1748 # 1749 # Revision 1.116 2008/07/14 13:44:38 ncq 1750 # - add .format to episode 1751 # - factor out .format_soap from .format on encounter 1752 # - better visualize soap sections in output as per user request 1753 # 1754 # Revision 1.115 2008/07/12 15:20:30 ncq 1755 # - add format to health issue 1756 # 1757 # Revision 1.114 2008/06/26 21:17:59 ncq 1758 # - episode formatting: include docs 1759 # - encounter formatting: include results and docs 1760 # 1761 # Revision 1.113 2008/06/24 16:53:58 ncq 1762 # - include test results in encounter formatting such 1763 # as to be included in the EMR tree browser :-) 1764 # 1765 # Revision 1.112 2008/05/19 15:43:45 ncq 1766 # - adapt to TZ code changes 1767 # 1768 # Revision 1.111 2008/05/13 14:06:17 ncq 1769 # - remove superfluous \n 1770 # - add missing . 1771 # 1772 # Revision 1.110 2008/04/11 12:20:52 ncq 1773 # - format() on episode and encounter 1774 # 1775 # Revision 1.109 2008/03/05 22:24:31 ncq 1776 # - support fk_encounter in issue and episode creation 1777 # 1778 # Revision 1.108 2008/02/25 17:29:59 ncq 1779 # - logging cleanup 1780 # 1781 # Revision 1.107 2008/01/30 13:34:49 ncq 1782 # - switch to std lib logging 1783 # 1784 # Revision 1.106 2008/01/22 11:49:14 ncq 1785 # - cleanup 1786 # - free-standing -> Unattributed as per list 1787 # 1788 # Revision 1.105 2008/01/16 19:36:17 ncq 1789 # - improve get_encounter_types() 1790 # 1791 # Revision 1.104 2008/01/13 01:12:53 ncq 1792 # - age_noted_human_readable() 1793 # 1794 # Revision 1.103 2007/10/29 11:04:11 ncq 1795 # - properly handle NULL pk_health_issue in create_apisode() thereby 1796 # finding dupes in free-standing episodes, too 1797 # - this then asks for an explicit allow_dupes (defaulted to False) 1798 # in create_episode() as we may, indeed, wish to allow dupes 1799 # sometimes 1800 # 1801 # Revision 1.102 2007/10/11 12:00:17 ncq 1802 # - add has_narrative() and has_documents() 1803 # 1804 # Revision 1.101 2007/09/07 10:55:55 ncq 1805 # - get_dummy_health_issue() 1806 # 1807 # Revision 1.100 2007/08/15 14:56:30 ncq 1808 # - delete_health_issue() 1809 # 1810 # Revision 1.99 2007/05/18 13:25:56 ncq 1811 # - fix cEncounter.transfer_clinical_data() 1812 # 1813 # Revision 1.98 2007/05/14 10:32:50 ncq 1814 # - raise DatabaseObjectInUseError on psycopg2 integrity error 1815 # 1816 # Revision 1.97 2007/04/27 22:54:13 ncq 1817 # - when checking for existing episodes need to check 1818 # associated health issue, too, of course 1819 # 1820 # Revision 1.96 2007/04/02 18:35:20 ncq 1821 # - create_encounter now more exception-y 1822 # 1823 # Revision 1.95 2007/03/18 13:01:55 ncq 1824 # - a bit of cleanup 1825 # 1826 # Revision 1.94 2007/01/09 18:01:32 ncq 1827 # - let exceptions report errors 1828 # 1829 # Revision 1.93 2007/01/09 12:56:02 ncq 1830 # - create_episode() now always takes patient fk 1831 # 1832 # Revision 1.92 2007/01/04 22:50:04 ncq 1833 # - allow changing fk_patient in cEpisode 1834 # 1835 # Revision 1.91 2007/01/02 16:14:41 ncq 1836 # - fix close_expired_episode() 1837 # 1838 # Revision 1.90 2006/12/25 22:48:52 ncq 1839 # - add cEncounter.has_clinical_data() 1840 # 1841 # Revision 1.89 2006/12/22 16:53:31 ncq 1842 # - use timezone definition in gmDateTime 1843 # 1844 # Revision 1.88 2006/11/24 09:30:33 ncq 1845 # - make cHealthIssue save more of its members 1846 # - if_patient -> fk_patient 1847 # - do not log i18n()ed message so failure there doesn't stop us from creating a health issue or episode 1848 # 1849 # Revision 1.87 2006/11/05 17:02:25 ncq 1850 # - enable health issue and episode to be inited from row 1851 # 1852 # Revision 1.86 2006/10/28 14:59:38 ncq 1853 # - __ -> _ 1854 # 1855 # Revision 1.85 2006/10/28 14:59:20 ncq 1856 # - when reading from views no need to explicitely load xmin_*, it's part of the view anyways 1857 # - reduce query duplication by reuse of _cmd_fetch_payload 1858 # 1859 # Revision 1.84 2006/10/10 07:26:37 ncq 1860 # - no more clinitem exceptions 1861 # 1862 # Revision 1.83 2006/10/09 12:18:18 ncq 1863 # - convert to gmPG2 1864 # - convert to cBusinessDBObject 1865 # - unicode queries 1866 # - robustified test suite 1867 # 1868 # Revision 1.82 2006/09/03 11:27:24 ncq 1869 # - use gmNull.cNull 1870 # - add cHealthIssue.get_open_episode() 1871 # - add cEpisode.get_access_range() 1872 # 1873 # Revision 1.81 2006/07/19 20:25:00 ncq 1874 # - gmPyCompat.py is history 1875 # 1876 # Revision 1.80 2006/06/26 12:27:53 ncq 1877 # - cleanup 1878 # - add close_episode() and has_open_episode() to cHealthIssue 1879 # 1880 # Revision 1.79 2006/06/05 22:00:53 ncq 1881 # - must be "episode_open", not "is_open" 1882 # 1883 # Revision 1.78 2006/05/06 18:53:56 ncq 1884 # - select age(...) <> ...; -> select ... <> now() - ...; as per Syan 1885 # 1886 # Revision 1.77 2006/03/09 21:11:49 ncq 1887 # - spell out rfe/aoe 1888 # 1889 # Revision 1.76 2006/02/27 22:38:36 ncq 1890 # - spell out rfe/aoe as per Richard's request 1891 # 1892 # Revision 1.75 2005/12/26 05:26:38 sjtan 1893 # 1894 # match schema 1895 # 1896 # Revision 1.74 2005/12/25 13:24:30 sjtan 1897 # 1898 # schema changes in names . 1899 # 1900 # Revision 1.73 2005/12/06 17:57:13 ncq 1901 # - more id->pk fixes 1902 # 1903 # Revision 1.72 2005/12/06 14:24:14 ncq 1904 # - clin.clin_health_issue/episode -> clin.health_issue/episode 1905 # 1906 # Revision 1.71 2005/11/27 12:56:19 ncq 1907 # - add get_encounter_types() 1908 # 1909 # Revision 1.70 2005/11/27 12:44:57 ncq 1910 # - clinical tables are in schema "clin" now 1911 # 1912 # Revision 1.69 2005/10/15 18:15:37 ncq 1913 # - cleanup 1914 # - clin_encounter has fk_*, not pk_* 1915 # - remove clin_encounter.pk_provider support 1916 # - fix cEncounter.set_active() 1917 # - comment on that transfer_clinical_data will work but will not 1918 # notify others about its changes 1919 # 1920 # Revision 1.68 2005/10/12 22:31:13 ncq 1921 # - encounter['rfe'] not mandatory anymore, hence don't need default 1922 # - encounters don't have a provider 1923 # 1924 # Revision 1.67 2005/10/11 21:49:36 ncq 1925 # - make create_encounter oblivious of emr object again 1926 # 1927 # Revision 1.66 2005/10/08 12:33:09 sjtan 1928 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch. 1929 # 1930 # Revision 1.65 2005/10/04 19:21:31 sjtan 1931 # unicode and str are different but printable types. 1932 # 1933 # Revision 1.64 2005/09/25 01:00:47 ihaywood 1934 # bugfixes 1935 # 1936 # remember 2.6 uses "import wx" not "from wxPython import wx" 1937 # removed not null constraint on clin_encounter.rfe as has no value on instantiation 1938 # client doesn't try to set clin_encounter.description as it doesn't exist anymore 1939 # 1940 # Revision 1.63 2005/09/22 15:45:11 ncq 1941 # - clin_encounter.fk_provider removed 1942 # 1943 # Revision 1.62 2005/09/19 16:32:42 ncq 1944 # - add rfe/aoe support to clin_encounter 1945 # - remove get_aoes()/get_rfes() 1946 # 1947 # Revision 1.61 2005/09/12 15:05:58 ncq 1948 # - improve close_expired_episodes() 1949 # 1950 # Revision 1.60 2005/09/11 17:23:53 ncq 1951 # - close_expired_episodes() 1952 # - support is_open when adding episode 1953 # 1954 # Revision 1.59 2005/08/22 13:02:46 ncq 1955 # - prepare for 0.2 1956 # 1957 # Revision 1.58 2005/07/02 18:16:58 ncq 1958 # - default encounter type "in surgery" for 0.1, to become 1959 # "in surgery"-on-write later on 1960 # 1961 # Revision 1.57 2005/06/23 14:58:51 ncq 1962 # - clean up transfer_clinical_data() 1963 # 1964 # Revision 1.56 2005/06/20 18:48:51 ncq 1965 # - a little cleanup in transfer_data 1966 # 1967 # Revision 1.55 2005/06/20 13:03:38 cfmoro 1968 # Relink encounter to another episode 1969 # 1970 # Revision 1.54 2005/06/15 22:25:29 ncq 1971 # - issue.rename() 1972 # 1973 # Revision 1.53 2005/06/14 18:53:37 ncq 1974 # - really do rename in rename(), needs to set _is_modified to work 1975 # 1976 # Revision 1.52 2005/06/12 21:40:42 ncq 1977 # - cleanup 1978 # 1979 # Revision 1.51 2005/05/14 15:05:40 ncq 1980 # - show HH:MM in auto-created encounters 1981 # 1982 # Revision 1.50 2005/04/25 08:28:28 ncq 1983 # - episode now has .description 1984 # 1985 # Revision 1.49 2005/04/24 14:42:22 ncq 1986 # - add age_noted as changable 1987 # 1988 # Revision 1.48 2005/04/03 20:05:38 ncq 1989 # - cEpisode.set_active() doesn't make sense no more 1990 # 1991 # Revision 1.47 2005/03/29 07:22:38 ncq 1992 # - improve text for auto generated encounters 1993 # 1994 # Revision 1.46 2005/03/23 18:31:19 ncq 1995 # - v_patient_items -> v_pat_items 1996 # 1997 # Revision 1.45 2005/03/20 16:47:26 ncq 1998 # - cleanup 1999 # 2000 # Revision 1.44 2005/03/17 21:59:35 cfmoro 2001 # Fixed log comment 2002 # 2003 # Revision 1.43 2005/03/17 21:46:23 cfmoro 2004 # Simplified cEpisode.rename function 2005 # 2006 # Revision 1.42 2005/03/17 21:14:45 cfmoro 2007 # Improved exception handling in get_as_episode. 2008 # 2009 # Revision 1.41 2005/03/17 13:35:52 ncq 2010 # - cleanup and streamlining 2011 # 2012 # Revision 1.40 2005/03/16 19:10:06 cfmoro 2013 # Added cProblem.get_as_episode method 2014 # 2015 # Revision 1.39 2005/03/14 14:28:54 ncq 2016 # - id_patient -> pk_patient 2017 # - properly handle simplified episode naming in create_episode() 2018 # 2019 # Revision 1.38 2005/03/08 16:42:47 ncq 2020 # - there are episodes w/ and w/o fk_patient IS NULL so handle that 2021 # properly in set_active() 2022 # 2023 # Revision 1.37 2005/02/28 18:15:36 ncq 2024 # - proper fix for not being able to fetch unnamed episodes 2025 # is to require a name in the first place ... 2026 # 2027 # Revision 1.36 2005/02/20 10:30:49 sjtan 2028 # 2029 # unnamed episodes cannot be refetched. 2030 # 2031 # Revision 1.35 2005/01/31 12:58:24 ncq 2032 # - episode.rename() finally works 2033 # 2034 # Revision 1.34 2005/01/31 09:35:28 ncq 2035 # - add episode.get_description() to return clin_narrative row 2036 # - improve episode.rename() - works for adding new narrative now 2037 # - improve create_episode() - revise later 2038 # - improve unit testing 2039 # 2040 # Revision 1.33 2005/01/29 17:53:57 ncq 2041 # - debug/enhance create_episode 2042 # 2043 # Revision 1.32 2005/01/25 17:24:57 ncq 2044 # - streamlined cEpisode.rename() 2045 # 2046 # Revision 1.31 2005/01/25 01:36:19 cfmoro 2047 # Added cEpisode.rename method 2048 # 2049 # Revision 1.30 2005/01/15 20:24:35 ncq 2050 # - streamlined cProblem 2051 # 2052 # Revision 1.29 2005/01/15 19:55:55 cfmoro 2053 # Added problem support to emr 2054 # 2055 # Revision 1.28 2005/01/02 19:55:30 ncq 2056 # - don't need _xmins_refetch_col_pos anymore 2057 # 2058 # Revision 1.27 2004/12/20 16:45:49 ncq 2059 # - gmBusinessDBObject now requires refetching of XMIN after save_payload 2060 # 2061 # Revision 1.26 2004/12/15 10:42:09 ncq 2062 # - cClinEpisode not handles the fields properly 2063 # 2064 # Revision 1.25 2004/12/15 10:28:11 ncq 2065 # - fix create_episode() aka add_episode() 2066 # 2067 # Revision 1.24 2004/11/03 22:32:34 ncq 2068 # - support _cmds_lock_rows_for_update in business object base class 2069 # 2070 # Revision 1.23 2004/09/19 15:02:29 ncq 2071 # - episode: id -> pk, support fk_patient 2072 # - no default name in create_health_issue 2073 # 2074 # Revision 1.22 2004/07/05 10:24:46 ncq 2075 # - use v_pat_rfe/aoe, by Carlos 2076 # 2077 # Revision 1.21 2004/07/04 15:09:40 ncq 2078 # - when refactoring need to fix imports, too 2079 # 2080 # Revision 1.20 2004/07/04 13:24:31 ncq 2081 # - add cRFE/cAOE 2082 # - use in get_rfes(), get_aoes() 2083 # 2084 # Revision 1.19 2004/06/30 20:34:37 ncq 2085 # - cEncounter.get_RFEs() 2086 # 2087 # Revision 1.18 2004/06/26 23:45:50 ncq 2088 # - cleanup, id_* -> fk/pk_* 2089 # 2090 # Revision 1.17 2004/06/26 07:33:55 ncq 2091 # - id_episode -> fk/pk_episode 2092 # 2093 # Revision 1.16 2004/06/08 00:44:41 ncq 2094 # - v_pat_episodes now has description, not episode for name of episode 2095 # 2096 # Revision 1.15 2004/06/02 22:12:48 ncq 2097 # - cleanup 2098 # 2099 # Revision 1.14 2004/06/02 13:45:19 sjtan 2100 # 2101 # episode->description for update statement as well. 2102 # 2103 # Revision 1.13 2004/06/02 13:18:48 sjtan 2104 # 2105 # revert, as backend view definition was changed yesterday to be more consistent. 2106 # 2107 # Revision 1.12 2004/06/02 12:48:56 sjtan 2108 # 2109 # map episode to description in cursor.description, so can find as episode['description'] 2110 # and also save. 2111 # 2112 # Revision 1.11 2004/06/01 23:53:56 ncq 2113 # - v_pat_episodes.episode -> *.description 2114 # 2115 # Revision 1.10 2004/06/01 08:20:14 ncq 2116 # - limit in get_lab_results 2117 # 2118 # Revision 1.9 2004/05/30 20:10:31 ncq 2119 # - cleanup 2120 # 2121 # Revision 1.8 2004/05/22 12:42:54 ncq 2122 # - add create_episode() 2123 # - cleanup add_episode() 2124 # 2125 # Revision 1.7 2004/05/18 22:36:52 ncq 2126 # - need mx.DateTime 2127 # - fix fields updatable in episode 2128 # - fix delete action in episode.set_active() 2129 # 2130 # Revision 1.6 2004/05/18 20:35:42 ncq 2131 # - cleanup 2132 # 2133 # Revision 1.5 2004/05/17 19:02:26 ncq 2134 # - encounter.set_active() 2135 # - improve create_encounter() 2136 # 2137 # Revision 1.4 2004/05/16 15:47:51 ncq 2138 # - add episode.set_active() 2139 # 2140 # Revision 1.3 2004/05/16 14:31:27 ncq 2141 # - cleanup 2142 # - allow health issue to be instantiated by name/patient 2143 # - create_health_issue()/create_encounter 2144 # - based on Carlos' work 2145 # 2146 # Revision 1.2 2004/05/12 14:28:53 ncq 2147 # - allow dict style pk definition in __init__ for multicolum primary keys (think views) 2148 # - self.pk -> self.pk_obj 2149 # - __init__(aPKey) -> __init__(aPK_obj) 2150 # 2151 # Revision 1.1 2004/04/17 12:18:50 ncq 2152 # - health issue, episode, encounter classes 2153 # 2154