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