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

Source Code for Module Gnumed.business.gmPathLab

   1  """GNUmed measurements related business objects.""" 
   2   
   3  # FIXME: use UCUM from Regenstrief Institute 
   4  #============================================================ 
   5  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
   6  __license__ = "GPL" 
   7   
   8   
   9  import sys 
  10  import logging 
  11  import io 
  12  import decimal 
  13  import re as regex 
  14   
  15   
  16  if __name__ == '__main__': 
  17          sys.path.insert(0, '../../') 
  18   
  19  from Gnumed.pycommon import gmDateTime 
  20  if __name__ == '__main__': 
  21          from Gnumed.pycommon import gmLog2 
  22          from Gnumed.pycommon import gmI18N 
  23          gmI18N.activate_locale() 
  24          gmI18N.install_domain('gnumed') 
  25          gmDateTime.init() 
  26  from Gnumed.pycommon import gmExceptions 
  27  from Gnumed.pycommon import gmBusinessDBObject 
  28  from Gnumed.pycommon import gmPG2 
  29  from Gnumed.pycommon import gmTools 
  30  from Gnumed.pycommon import gmDispatcher 
  31  from Gnumed.pycommon import gmHooks 
  32   
  33  from Gnumed.business import gmOrganization 
  34  from Gnumed.business import gmCoding 
  35   
  36  _log = logging.getLogger('gm.lab') 
  37   
  38  #============================================================ 
  39  HL7_RESULT_STATI = { 
  40          None: _('unknown'), 
  41          '': _('empty status'), 
  42          'C': _('C (HL7: Correction, replaces previous final)'), 
  43          'D': _('D (HL7: Deletion)'), 
  44          'F': _('F (HL7: Final)'), 
  45          'I': _('I (HL7: pending, specimen In lab)'), 
  46          'P': _('P (HL7: Preliminary)'), 
  47          'R': _('R (HL7: result entered, not yet verified)'), 
  48          'S': _('S (HL7: partial)'), 
  49          'X': _('X (HL7: cannot obtain results for this observation)'), 
  50          'U': _('U (HL7: mark as final (I/P/R/S -> F, value Unchanged)'), 
  51          'W': _('W (HL7: original Wrong (say, wrong patient))') 
  52  } 
  53   
  54  URL_test_result_information = 'http://www.laborlexikon.de' 
  55  URL_test_result_information_search = "http://www.google.de/search?as_oq=%(search_term)s&num=10&as_sitesearch=laborlexikon.de" 
  56   
  57  #============================================================ 
58 -def _on_test_result_modified():
59 """Always relates to the active patient.""" 60 gmHooks.run_hook_script(hook = 'after_test_result_modified')
61 62 gmDispatcher.connect(_on_test_result_modified, 'clin.test_result_mod_db') 63 64 #============================================================ 65 _SQL_get_test_orgs = "SELECT * FROM clin.v_test_orgs WHERE %s" 66
67 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
68 """Represents one test org/lab.""" 69 _cmd_fetch_payload = _SQL_get_test_orgs % 'pk_test_org = %s' 70 _cmds_store_payload = [ 71 """UPDATE clin.test_org SET 72 fk_org_unit = %(pk_org_unit)s, 73 contact = gm.nullify_empty_string(%(test_org_contact)s), 74 comment = gm.nullify_empty_string(%(comment)s) 75 WHERE 76 pk = %(pk_test_org)s 77 AND 78 xmin = %(xmin_test_org)s 79 RETURNING 80 xmin AS xmin_test_org 81 """ 82 ] 83 _updatable_fields = [ 84 'pk_org_unit', 85 'test_org_contact', 86 'comment' 87 ]
88 #------------------------------------------------------------
89 -def create_test_org(name=None, comment=None, pk_org_unit=None, link_obj=None):
90 91 _log.debug('creating test org [%s:%s:%s]', name, comment, pk_org_unit) 92 93 if name is None: 94 name = 'unassigned lab' 95 96 # get org unit 97 if pk_org_unit is None: 98 org = gmOrganization.create_org ( 99 link_obj = link_obj, 100 organization = name, 101 category = 'Laboratory' 102 ) 103 org_unit = gmOrganization.create_org_unit ( 104 link_obj = link_obj, 105 pk_organization = org['pk_org'], 106 unit = name 107 ) 108 pk_org_unit = org_unit['pk_org_unit'] 109 110 # test org exists ? 111 args = {'pk_unit': pk_org_unit} 112 cmd = 'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s' 113 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}]) 114 115 if len(rows) == 0: 116 cmd = 'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk' 117 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True) 118 119 test_org = cTestOrg(link_obj = link_obj, aPK_obj = rows[0][0]) 120 if comment is not None: 121 comment = comment.strip() 122 test_org['comment'] = comment 123 test_org.save(conn = link_obj) 124 125 return test_org
126 #------------------------------------------------------------
127 -def delete_test_org(test_org=None):
128 args = {'pk': test_org} 129 cmd = """ 130 DELETE FROM clin.test_org 131 WHERE 132 pk = %(pk)s 133 AND 134 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1) 135 AND 136 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1) 137 """ 138 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
139 #------------------------------------------------------------
140 -def get_test_orgs(order_by='unit'):
141 cmd = 'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by 142 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 143 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
144 145 #============================================================ 146 # test panels / profiles 147 #------------------------------------------------------------ 148 _SQL_get_test_panels = "SELECT * FROM clin.v_test_panels WHERE %s" 149
150 -class cTestPanel(gmBusinessDBObject.cBusinessDBObject):
151 """Represents a grouping/listing of tests into a panel.""" 152 153 _cmd_fetch_payload = _SQL_get_test_panels % "pk_test_panel = %s" 154 _cmds_store_payload = [ 155 """ 156 UPDATE clin.test_panel SET 157 description = gm.nullify_empty_string(%(description)s), 158 comment = gm.nullify_empty_string(%(comment)s) 159 WHERE 160 pk = %(pk_test_panel)s 161 AND 162 xmin = %(xmin_test_panel)s 163 RETURNING 164 xmin AS xmin_test_panel 165 """ 166 ] 167 _updatable_fields = [ 168 'description', 169 'comment' 170 ] 171 #--------------------------------------------------------
172 - def format(self):
173 txt = _('Test panel "%s" [#%s]\n') % ( 174 self._payload[self._idx['description']], 175 self._payload[self._idx['pk_test_panel']] 176 ) 177 178 if self._payload[self._idx['comment']] is not None: 179 txt += '\n' 180 txt += gmTools.wrap ( 181 text = self._payload[self._idx['comment']], 182 width = 50, 183 initial_indent = ' ', 184 subsequent_indent = ' ' 185 ) 186 txt += '\n' 187 188 txt += '\n' 189 txt += _('Includes:\n') 190 if len(self.included_loincs) == 0: 191 txt += _('no tests') 192 else: 193 tts_by_loinc = {} 194 for loinc in self._payload[self._idx['loincs']]: 195 tts_by_loinc[loinc] = [] 196 for ttype in self.test_types: 197 tts_by_loinc[ttype['loinc']].append(ttype) 198 for loinc, ttypes in tts_by_loinc.items(): 199 # maybe resolve LOINC, too 200 txt += _(' %s: %s\n') % ( 201 loinc, 202 '; '.join([ '%(abbrev)s@%(name_org)s [#%(pk_test_type)s]' % tt for tt in ttypes ]) 203 ) 204 205 codes = self.generic_codes 206 if len(codes) > 0: 207 txt += '\n' 208 for c in codes: 209 txt += '%s %s: %s (%s - %s)\n' % ( 210 (' ' * left_margin), 211 c['code'], 212 c['term'], 213 c['name_short'], 214 c['version'] 215 ) 216 217 return txt
218 219 #--------------------------------------------------------
220 - def add_code(self, pk_code=None):
221 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 222 cmd = "INSERT INTO clin.lnk_code2tst_pnl (fk_item, fk_generic_code) values (%(tp)s, %(code)s)" 223 args = { 224 'tp': self._payload[self._idx['pk_test_panel']], 225 'code': pk_code 226 } 227 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 228 return True
229 230 #--------------------------------------------------------
231 - def remove_code(self, pk_code=None):
232 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 233 cmd = "DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code = %(code)s" 234 args = { 235 'tp': self._payload[self._idx['pk_test_panel']], 236 'code': pk_code 237 } 238 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 239 return True
240 241 #--------------------------------------------------------
242 - def get_test_types_for_results(self, pk_patient, order_by=None, unique_meta_types=False):
243 """Retrieve data about test types on this panel (for which this patient has results).""" 244 245 if order_by is None: 246 order_by = '' 247 else: 248 order_by = 'ORDER BY %s' % order_by 249 250 if unique_meta_types: 251 cmd = """ 252 SELECT * FROM clin.v_test_types c_vtt 253 WHERE c_vtt.pk_test_type IN ( 254 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type 255 FROM clin.v_test_results c_vtr1 256 WHERE 257 c_vtr1.pk_test_type IN %%(pks)s 258 AND 259 c_vtr1.pk_patient = %%(pat)s 260 AND 261 c_vtr1.pk_meta_test_type IS NOT NULL 262 UNION ALL 263 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type 264 FROM clin.v_test_results c_vtr2 265 WHERE 266 c_vtr2.pk_test_type IN %%(pks)s 267 AND 268 c_vtr2.pk_patient = %%(pat)s 269 AND 270 c_vtr2.pk_meta_test_type IS NULL 271 ) 272 %s""" % order_by 273 else: 274 cmd = """ 275 SELECT * FROM clin.v_test_types c_vtt 276 WHERE c_vtt.pk_test_type IN ( 277 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type 278 FROM clin.v_test_results c_vtr 279 WHERE 280 c_vtr.pk_test_type IN %%(pks)s 281 AND 282 c_vtr.pk_patient = %%(pat)s 283 ) 284 %s""" % order_by 285 286 args = { 287 'pat': pk_patient, 288 'pks': tuple([ tt['pk_test_type'] for tt in self._payload[self._idx['test_types']] ]) 289 } 290 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 291 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
292 293 #--------------------------------------------------------
294 - def add_loinc(self, loinc):
295 if self._payload[self._idx['loincs']] is not None: 296 if loinc in self._payload[self._idx['loincs']]: 297 return 298 gmPG2.run_rw_queries(queries = [{ 299 'cmd': 'INSERT INTO clin.lnk_loinc2test_panel (fk_test_panel, loinc) VALUES (%(pk_pnl)s, %(loinc)s)', 300 'args': {'loinc': loinc, 'pk_pnl': self._payload[self._idx['pk_test_panel']]} 301 }]) 302 return
303 304 #--------------------------------------------------------
305 - def remove_loinc(self, loinc):
306 if self._payload[self._idx['loincs']] is None: 307 return 308 if loinc not in self._payload[self._idx['loincs']]: 309 return 310 gmPG2.run_rw_queries(queries = [{ 311 'cmd': 'DELETE FROM clin.lnk_loinc2test_panel WHERE fk_test_panel = %(pk_pnl)s AND loinc = %(loinc)s', 312 'args': {'loinc': loinc, 'pk_pnl': self._payload[self._idx['pk_test_panel']]} 313 }]) 314 return
315 316 #-------------------------------------------------------- 317 # properties 318 #--------------------------------------------------------
319 - def _get_included_loincs(self):
320 return self._payload[self._idx['loincs']]
321
322 - def _set_included_loincs(self, loincs):
323 queries = [] 324 # remove those which don't belong 325 if len(loincs) == 0: 326 cmd = 'DELETE FROM clin.lnk_loinc2test_panel WHERE fk_test_panel = %(pk_pnl)s' 327 else: 328 cmd = 'DELETE FROM clin.lnk_loinc2test_panel WHERE fk_test_panel = %(pk_pnl)s AND loinc NOT IN %(loincs)s' 329 queries.append({'cmd': cmd, 'args': {'loincs': tuple(loincs), 'pk_pnl': self._payload[self._idx['pk_test_panel']]}}) 330 # add those not there yet 331 if len(loincs) > 0: 332 for loinc in loincs: 333 cmd = """INSERT INTO clin.lnk_loinc2test_panel (fk_test_panel, loinc) 334 SELECT %(pk_pnl)s, %(loinc)s WHERE NOT EXISTS ( 335 SELECT 1 FROM clin.lnk_loinc2test_panel WHERE 336 fk_test_panel = %(pk_pnl)s 337 AND 338 loinc = %(loinc)s 339 )""" 340 queries.append({'cmd': cmd, 'args': {'loinc': loinc, 'pk_pnl': self._payload[self._idx['pk_test_panel']]}}) 341 return gmPG2.run_rw_queries(queries = queries)
342 343 included_loincs = property(_get_included_loincs, _set_included_loincs) 344 345 #--------------------------------------------------------
346 - def _get_test_types(self):
347 if len(self._payload[self._idx['test_types']]) == 0: 348 return [] 349 350 rows, idx = gmPG2.run_ro_queries ( 351 queries = [{ 352 'cmd': _SQL_get_test_types % 'pk_test_type IN %(pks)s ORDER BY unified_abbrev', 353 'args': {'pks': tuple([ tt['pk_test_type'] for tt in self._payload[self._idx['test_types']] ])} 354 }], 355 get_col_idx = True 356 ) 357 return [ cMeasurementType(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_type'}) for r in rows ]
358 359 test_types = property(_get_test_types, lambda x:x) 360 361 #--------------------------------------------------------
362 - def _get_generic_codes(self):
363 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 364 return [] 365 366 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 367 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 368 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 369 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
370
371 - def _set_generic_codes(self, pk_codes):
372 queries = [] 373 # remove all codes 374 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 375 queries.append ({ 376 'cmd': 'DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code IN %(codes)s', 377 'args': { 378 'tp': self._payload[self._idx['pk_test_panel']], 379 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 380 } 381 }) 382 # add new codes 383 for pk_code in pk_codes: 384 queries.append ({ 385 'cmd': 'INSERT INTO clin.lnk_code2test_panel (fk_item, fk_generic_code) VALUES (%(tp)s, %(pk_code)s)', 386 'args': { 387 'tp': self._payload[self._idx['pk_test_panel']], 388 'pk_code': pk_code 389 } 390 }) 391 if len(queries) == 0: 392 return 393 # run it all in one transaction 394 rows, idx = gmPG2.run_rw_queries(queries = queries) 395 return
396 397 generic_codes = property(_get_generic_codes, _set_generic_codes) 398 399 #--------------------------------------------------------
400 - def get_most_recent_results(self, pk_patient=None, order_by=None, group_by_meta_type=False, include_missing=False):
401 402 if len(self._payload[self._idx['test_types']]) == 0: 403 return [] 404 405 pnl_results = get_most_recent_results_for_panel ( 406 pk_patient = pk_patient, 407 pk_panel = self._payload[self._idx['pk_test_panel']], 408 order_by = order_by, 409 group_by_meta_type = group_by_meta_type 410 ) 411 if not include_missing: 412 return pnl_results 413 414 loincs_found = [ r['loinc_tt'] for r in pnl_results ] 415 loincs_found.extend([ r['loinc_meta'] for r in pnl_results if r['loinc_meta'] not in loincs_found ]) 416 loincs2consider = set([ tt['loinc'] for tt in self._payload[self._idx['test_types']] ]) 417 loincs_missing = loincs2consider - set(loincs_found) 418 pnl_results.extend(loincs_missing) 419 return pnl_results
420 421 #------------------------------------------------------------
422 -def get_test_panels(order_by=None, loincs=None):
423 where_args = {} 424 if loincs is None: 425 where_parts = ['true'] 426 else: 427 where_parts = ['loincs @> %(loincs)s'] 428 where_args['loincs'] = list(loincs) 429 430 if order_by is None: 431 order_by = u'' 432 else: 433 order_by = ' ORDER BY %s' % order_by 434 435 cmd = (_SQL_get_test_panels % ' AND '.join(where_parts)) + order_by 436 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': where_args}], get_col_idx = True) 437 return [ cTestPanel(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_panel'}) for r in rows ]
438 439 #------------------------------------------------------------
440 -def create_test_panel(description=None):
441 442 args = {'desc': description.strip()} 443 cmd = """ 444 INSERT INTO clin.test_panel (description) 445 VALUES (gm.nullify_empty_string(%(desc)s)) 446 RETURNING pk 447 """ 448 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 449 450 return cTestPanel(aPK_obj = rows[0]['pk'])
451 452 #------------------------------------------------------------
453 -def delete_test_panel(pk=None):
454 args = {'pk': pk} 455 cmd = "DELETE FROM clin.test_panel WHERE pk = %(pk)s" 456 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 457 return True
458 459 #============================================================
460 -class cMetaTestType(gmBusinessDBObject.cBusinessDBObject):
461 """Represents one meta test type under which actual test types can be aggregated.""" 462 463 _cmd_fetch_payload = "SELECT *, xmin FROM clin.meta_test_type WHERE pk = %s" 464 _cmds_store_payload = [""" 465 UPDATE clin.meta_test_type SET 466 abbrev = %(abbrev)s, 467 name = %(name)s, 468 loinc = gm.nullify_empty_string(%(loinc)s), 469 comment = gm.nullify_empty_string(%(comment)s) 470 WHERE 471 pk = %(pk)s 472 AND 473 xmin = %(xmin)s 474 RETURNING 475 xmin 476 """] 477 _updatable_fields = [ 478 'abbrev', 479 'name', 480 'loinc', 481 'comment' 482 ] 483 #--------------------------------------------------------
484 - def format(self, with_tests=False, patient=None):
485 txt = _('Meta (%s=aggregate) test type [#%s]\n\n') % (gmTools.u_sum, self._payload[self._idx['pk']]) 486 txt += _(' Name: %s (%s)\n') % ( 487 self._payload[self._idx['abbrev']], 488 self._payload[self._idx['name']] 489 ) 490 if self._payload[self._idx['loinc']] is not None: 491 txt += ' LOINC: %s\n' % self._payload[self._idx['loinc']] 492 if self._payload[self._idx['comment']] is not None: 493 txt += _(' Comment: %s\n') % self._payload[self._idx['comment']] 494 if with_tests: 495 ttypes = self.included_test_types 496 if len(ttypes) > 0: 497 txt += _(' Aggregates the following test types:\n') 498 for ttype in ttypes: 499 txt += ' - %s (%s)%s%s%s [#%s]\n' % ( 500 ttype['name'], 501 ttype['abbrev'], 502 gmTools.coalesce(ttype['reference_unit'], '', ', %s'), 503 gmTools.coalesce(ttype['name_org'], '', ' (%s)'), 504 gmTools.coalesce(ttype['loinc'], '', ', LOINC: %s'), 505 ttype['pk_test_type'] 506 ) 507 if patient is not None: 508 txt += '\n' 509 most_recent = self.get_most_recent_result(patient = patient) 510 if most_recent is not None: 511 txt += _(' Most recent (%s): %s%s%s') % ( 512 gmDateTime.pydt_strftime(most_recent['clin_when'], '%Y %b %d'), 513 most_recent['unified_val'], 514 gmTools.coalesce(most_recent['val_unit'], '', ' %s'), 515 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)') 516 ) 517 oldest = self.get_oldest_result(patient = patient) 518 if oldest is not None: 519 txt += '\n' 520 txt += _(' Oldest (%s): %s%s%s') % ( 521 gmDateTime.pydt_strftime(oldest['clin_when'], '%Y %b %d'), 522 oldest['unified_val'], 523 gmTools.coalesce(oldest['val_unit'], '', ' %s'), 524 gmTools.coalesce(oldest['abnormality_indicator'], '', ' (%s)') 525 ) 526 return txt
527 528 #--------------------------------------------------------
529 - def get_most_recent_result(self, patient=None):
530 args = { 531 'pat': patient, 532 'mttyp': self._payload[self._idx['pk']] 533 } 534 cmd = """ 535 SELECT * FROM clin.v_test_results 536 WHERE 537 pk_patient = %(pat)s 538 AND 539 pk_meta_test_type = %(mttyp)s 540 ORDER BY clin_when DESC 541 LIMIT 1""" 542 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 543 if len(rows) == 0: 544 return None 545 546 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
547 548 #--------------------------------------------------------
549 - def get_oldest_result(self, patient=None):
550 args = { 551 'pat': patient, 552 'mttyp': self._payload[self._idx['pk']] 553 } 554 cmd = """ 555 SELECT * FROM clin.v_test_results 556 WHERE 557 pk_patient = %(pat)s 558 AND 559 pk_meta_test_type = %(mttyp)s 560 ORDER BY clin_when 561 LIMIT 1""" 562 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 563 if len(rows) == 0: 564 return None 565 566 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
567 568 #--------------------------------------------------------
569 - def get_temporally_closest_result(self, date, pk_patient):
570 571 args = { 572 'pat': pk_patient, 573 'mtyp': self._payload[self._idx['pk']], 574 'mloinc': self._payload[self._idx['loinc']], 575 'when': date 576 } 577 WHERE_meta = '' 578 579 SQL = """ 580 SELECT * FROM clin.v_test_results 581 WHERE 582 pk_patient = %%(pat)s 583 AND 584 clin_when %s %%(when)s 585 AND 586 ((pk_meta_test_type = %%(mtyp)s) OR (loinc_meta = %%(mloinc)s)) 587 ORDER BY clin_when 588 LIMIT 1""" 589 590 # get earlier results by meta type 591 earlier_result = None 592 cmd = SQL % '<' 593 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 594 if len(rows) > 0: 595 earlier_result = cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]}) 596 597 # get later results by meta type ? 598 later_result = None 599 cmd = SQL % '>' 600 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 601 if len(rows) > 0: 602 later_result = cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]}) 603 604 if earlier_result is None: 605 return later_result 606 if later_result is None: 607 return earlier_result 608 609 earlier_ago = date - earlier_result['clin_when'] 610 later_ago = later_result['clin_when'] - date 611 if earlier_ago < later_ago: 612 return earlier_result 613 return later_result
614 615 #-------------------------------------------------------- 616 # properties 617 #--------------------------------------------------------
618 - def _get_included_test_types(self):
619 cmd = _SQL_get_test_types % 'pk_meta_test_type = %(pk_meta)s' 620 args = {'pk_meta': self._payload[self._idx['pk']]} 621 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 622 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
623 624 included_test_types = property(_get_included_test_types, lambda x:x)
625 626 #------------------------------------------------------------
627 -def create_meta_type(name=None, abbreviation=None, return_existing=False):
628 cmd = """ 629 INSERT INTO clin.meta_test_type (name, abbrev) 630 SELECT 631 %(name)s, 632 %(abbr)s 633 WHERE NOT EXISTS ( 634 SELECT 1 FROM clin.meta_test_type 635 WHERE 636 name = %(name)s 637 AND 638 abbrev = %(abbr)s 639 ) 640 RETURNING *, xmin 641 """ 642 args = { 643 'name': name.strip(), 644 'abbr': abbreviation.strip() 645 } 646 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True, return_data = True) 647 if len(rows) == 0: 648 if not return_existing: 649 return None 650 cmd = "SELECT *, xmin FROM clin.meta_test_type WHERE name = %(name)s and %(abbr)s" 651 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 652 653 return cMetaTestType(row = {'pk_field': 'pk', 'idx': idx, 'data': rows[0]})
654 655 #------------------------------------------------------------
656 -def delete_meta_type(meta_type=None):
657 cmd = """ 658 DELETE FROM clin.meta_test_type 659 WHERE 660 pk = %(pk)s 661 AND 662 NOT EXISTS ( 663 SELECT 1 FROM clin.test_type 664 WHERE fk_meta_test_type = %(pk)s 665 )""" 666 args = {'pk': meta_type} 667 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
668 669 #------------------------------------------------------------
670 -def get_meta_test_types():
671 cmd = 'SELECT *, xmin FROM clin.meta_test_type' 672 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 673 return [ cMetaTestType(row = {'pk_field': 'pk', 'data': r, 'idx': idx}) for r in rows ]
674 675 #============================================================ 676 _SQL_get_test_types = "SELECT * FROM clin.v_test_types WHERE %s" 677
678 -class cMeasurementType(gmBusinessDBObject.cBusinessDBObject):
679 """Represents one test result type.""" 680 681 _cmd_fetch_payload = _SQL_get_test_types % "pk_test_type = %s" 682 683 _cmds_store_payload = [ 684 """UPDATE clin.test_type SET 685 abbrev = gm.nullify_empty_string(%(abbrev)s), 686 name = gm.nullify_empty_string(%(name)s), 687 loinc = gm.nullify_empty_string(%(loinc)s), 688 comment = gm.nullify_empty_string(%(comment_type)s), 689 reference_unit = gm.nullify_empty_string(%(reference_unit)s), 690 fk_test_org = %(pk_test_org)s, 691 fk_meta_test_type = %(pk_meta_test_type)s 692 WHERE 693 pk = %(pk_test_type)s 694 AND 695 xmin = %(xmin_test_type)s 696 RETURNING 697 xmin AS xmin_test_type""" 698 ] 699 700 _updatable_fields = [ 701 'abbrev', 702 'name', 703 'loinc', 704 'comment_type', 705 'reference_unit', 706 'pk_test_org', 707 'pk_meta_test_type' 708 ] 709 #-------------------------------------------------------- 710 # properties 711 #--------------------------------------------------------
712 - def _get_in_use(self):
713 cmd = 'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)' 714 args = {'pk_type': self._payload[self._idx['pk_test_type']]} 715 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 716 return rows[0][0]
717 718 in_use = property(_get_in_use, lambda x:x) 719 720 #--------------------------------------------------------
721 - def get_most_recent_results(self, patient=None, no_of_results=1):
722 results = get_most_recent_results_for_test_type ( 723 test_type = self._payload[self._idx['pk_test_type']], 724 no_of_results = no_of_results, 725 patient = patient 726 ) 727 if results is not None: 728 if no_of_results == 1: 729 return results 730 if len(results) > 1: 731 return results 732 733 if self._payload[self._idx['loinc']] is None: 734 return None 735 736 return get_most_recent_results_in_loinc_group ( 737 loincs = tuple(self._payload[self._idx['loinc']]), 738 no_of_results = no_of_results, 739 patient = patient 740 )
741 742 #--------------------------------------------------------
743 - def get_oldest_result(self, patient=None):
744 result = get_oldest_result ( 745 test_type = self._payload[self._idx['pk_test_type']], 746 loinc = None, 747 patient = patient 748 ) 749 if result is None: 750 if self._payload[self._idx['loinc']] is not None: 751 result = get_oldest_result ( 752 test_type = None, 753 loinc = self._payload[self._idx['loinc']], 754 patient = patient 755 ) 756 return result
757 758 #--------------------------------------------------------
759 - def _get_test_panels(self):
760 if self._payload[self._idx['pk_test_panels']] is None: 761 return None 762 763 return [ cTestPanel(aPK_obj = pk) for pk in self._payload[self._idx['pk_test_panels']] ]
764 765 test_panels = property(_get_test_panels, lambda x:x) 766 767 #--------------------------------------------------------
768 - def get_meta_test_type(self, real_one_only=True):
769 if real_one_only is False: 770 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']]) 771 if self._payload[self._idx['is_fake_meta_type']]: 772 return None 773 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']])
774 775 meta_test_type = property(get_meta_test_type, lambda x:x) 776 777 #--------------------------------------------------------
778 - def get_temporally_closest_normal_range(self, unit, timestamp=None):
779 """Returns the closest test result which does have normal range information. 780 781 - needs <unit> 782 - if <timestamp> is None it will assume now() and thus return the most recent 783 """ 784 if timestamp is None: 785 timestamp = gmDateTime.pydt_now_here() 786 cmd = """ 787 SELECT * FROM clin.v_test_results 788 WHERE 789 pk_test_type = %(pk_type)s 790 AND 791 val_unit = %(unit)s 792 AND 793 ( 794 (val_normal_min IS NOT NULL) 795 OR 796 (val_normal_max IS NOT NULL) 797 OR 798 (val_normal_range IS NOT NULL) 799 ) 800 ORDER BY 801 CASE 802 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 803 ELSE %(clin_when)s - clin_when 804 END 805 LIMIT 1""" 806 args = { 807 'pk_type': self._payload[self._idx['pk_test_type']], 808 'unit': unit, 809 'clin_when': timestamp 810 } 811 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 812 if len(rows) == 0: 813 return None 814 r = rows[0] 815 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
816 817 #--------------------------------------------------------
818 - def get_temporally_closest_target_range(self, unit, patient, timestamp=None):
819 """Returns the closest test result which does have target range information. 820 821 - needs <unit> 822 - needs <patient> (as target will be per-patient) 823 - if <timestamp> is None it will assume now() and thus return the most recent 824 """ 825 if timestamp is None: 826 timestamp = gmDateTime.pydt_now_here() 827 cmd = """ 828 SELECT * FROM clin.v_test_results 829 WHERE 830 pk_test_type = %(pk_type)s 831 AND 832 val_unit = %(unit)s 833 AND 834 pk_patient = %(pat)s 835 AND 836 ( 837 (val_target_min IS NOT NULL) 838 OR 839 (val_target_max IS NOT NULL) 840 OR 841 (val_target_range IS NOT NULL) 842 ) 843 ORDER BY 844 CASE 845 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 846 ELSE %(clin_when)s - clin_when 847 END 848 LIMIT 1""" 849 args = { 850 'pk_type': self._payload[self._idx['pk_test_type']], 851 'unit': unit, 852 'pat': patient, 853 'clin_when': timestamp 854 } 855 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 856 if len(rows) == 0: 857 return None 858 r = rows[0] 859 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
860 861 #--------------------------------------------------------
862 - def get_temporally_closest_unit(self, timestamp=None):
863 """Returns the unit of the closest test result. 864 865 - if <timestamp> is None it will assume now() and thus return the most recent 866 """ 867 if timestamp is None: 868 timestamp = gmDateTime.pydt_now_here() 869 cmd = """ 870 SELECT val_unit FROM clin.v_test_results 871 WHERE 872 pk_test_type = %(pk_type)s 873 AND 874 val_unit IS NOT NULL 875 ORDER BY 876 CASE 877 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 878 ELSE %(clin_when)s - clin_when 879 END 880 LIMIT 1""" 881 args = { 882 'pk_type': self._payload[self._idx['pk_test_type']], 883 'clin_when': timestamp 884 } 885 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 886 if len(rows) == 0: 887 return None 888 return rows[0]['val_unit']
889 890 temporally_closest_unit = property(get_temporally_closest_unit, lambda x:x) 891 892 #--------------------------------------------------------
893 - def format(self, patient=None):
894 tt = '' 895 tt += _('Test type "%s" (%s) [#%s]\n') % ( 896 self._payload[self._idx['name']], 897 self._payload[self._idx['abbrev']], 898 self._payload[self._idx['pk_test_type']] 899 ) 900 tt += '\n' 901 tt += gmTools.coalesce(self._payload[self._idx['loinc']], '', ' LOINC: %s\n') 902 tt += gmTools.coalesce(self._payload[self._idx['reference_unit']], '', _(' Reference unit: %s\n')) 903 tt += gmTools.coalesce(self._payload[self._idx['comment_type']], '', _(' Comment: %s\n')) 904 905 tt += '\n' 906 tt += _('Lab details:\n') 907 tt += _(' Name: %s\n') % gmTools.coalesce(self._payload[self._idx['name_org']], '') 908 tt += gmTools.coalesce(self._payload[self._idx['contact_org']], '', _(' Contact: %s\n')) 909 tt += gmTools.coalesce(self._payload[self._idx['comment_org']], '', _(' Comment: %s\n')) 910 911 if self._payload[self._idx['is_fake_meta_type']] is False: 912 tt += '\n' 913 tt += _('Aggregated under meta type:\n') 914 tt += _(' Name: %s - %s [#%s]\n') % ( 915 self._payload[self._idx['abbrev_meta']], 916 self._payload[self._idx['name_meta']], 917 self._payload[self._idx['pk_meta_test_type']] 918 ) 919 tt += gmTools.coalesce(self._payload[self._idx['loinc_meta']], '', ' LOINC: %s\n') 920 tt += gmTools.coalesce(self._payload[self._idx['comment_meta']], '', _(' Comment: %s\n')) 921 922 panels = self.test_panels 923 if panels is not None: 924 tt += '\n' 925 tt += _('Listed in test panels:\n') 926 for panel in panels: 927 tt += _(' Panel "%s" [#%s]\n') % ( 928 panel['description'], 929 panel['pk_test_panel'] 930 ) 931 932 if patient is not None: 933 tt += '\n' 934 result = self.get_most_recent_results(patient = patient, no_of_results = 1) 935 if result is not None: 936 tt += _(' Most recent (%s): %s%s%s') % ( 937 gmDateTime.pydt_strftime(result['clin_when'], '%Y %b %d'), 938 result['unified_val'], 939 gmTools.coalesce(result['val_unit'], '', ' %s'), 940 gmTools.coalesce(result['abnormality_indicator'], '', ' (%s)') 941 ) 942 result = self.get_oldest_result(patient = patient) 943 if result is not None: 944 tt += '\n' 945 tt += _(' Oldest (%s): %s%s%s') % ( 946 gmDateTime.pydt_strftime(result['clin_when'], '%Y %b %d'), 947 result['unified_val'], 948 gmTools.coalesce(result['val_unit'], '', ' %s'), 949 gmTools.coalesce(result['abnormality_indicator'], '', ' (%s)') 950 ) 951 952 return tt
953 954 #------------------------------------------------------------
955 -def get_measurement_types(order_by=None, loincs=None):
956 args = {} 957 where_parts = [] 958 if loincs is not None: 959 if len(loincs) > 0: 960 where_parts.append('loinc IN %(loincs)s') 961 args['loincs'] = tuple(loincs) 962 if len(where_parts) == 0: 963 where_parts.append('TRUE') 964 WHERE_clause = ' AND '.join(where_parts) 965 cmd = (_SQL_get_test_types % WHERE_clause) + gmTools.coalesce(order_by, '', ' ORDER BY %s') 966 #cmd = 'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, '', 'order by %s') 967 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 968 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
969 970 #------------------------------------------------------------
971 -def find_measurement_type(lab=None, abbrev=None, name=None, link_obj=None):
972 973 if (abbrev is None) and (name is None): 974 raise ValueError('must have <abbrev> and/or <name> set') 975 976 where_snippets = [] 977 978 if lab is None: 979 where_snippets.append('pk_test_org IS NULL') 980 else: 981 try: 982 int(lab) 983 where_snippets.append('pk_test_org = %(lab)s') 984 except (TypeError, ValueError): 985 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)') 986 987 if abbrev is not None: 988 where_snippets.append('abbrev = %(abbrev)s') 989 990 if name is not None: 991 where_snippets.append('name = %(name)s') 992 993 where_clause = ' and '.join(where_snippets) 994 cmd = "select * from clin.v_test_types where %s" % where_clause 995 args = {'lab': lab, 'abbrev': abbrev, 'name': name} 996 997 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 998 999 if len(rows) == 0: 1000 return None 1001 1002 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx}) 1003 return tt
1004 1005 #------------------------------------------------------------
1006 -def delete_measurement_type(measurement_type=None):
1007 cmd = 'delete from clin.test_type where pk = %(pk)s' 1008 args = {'pk': measurement_type} 1009 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1010 1011 #------------------------------------------------------------
1012 -def create_measurement_type(lab=None, abbrev=None, unit=None, name=None, link_obj=None):
1013 """Create or get test type.""" 1014 1015 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name, link_obj = link_obj) 1016 # found ? 1017 if ttype is not None: 1018 return ttype 1019 1020 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit) 1021 1022 # not found, so create it 1023 # if unit is None: 1024 # _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit)) 1025 # raise ValueError('need <unit> to create test type') 1026 1027 # make query 1028 cols = [] 1029 val_snippets = [] 1030 vals = {} 1031 1032 # lab 1033 if lab is None: 1034 lab = create_test_org(link_obj = link_obj)['pk_test_org'] 1035 1036 cols.append('fk_test_org') 1037 try: 1038 vals['lab'] = int(lab) 1039 val_snippets.append('%(lab)s') 1040 except: 1041 vals['lab'] = lab 1042 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)') 1043 1044 # code 1045 cols.append('abbrev') 1046 val_snippets.append('%(abbrev)s') 1047 vals['abbrev'] = abbrev 1048 1049 # unit 1050 if unit is not None: 1051 cols.append('reference_unit') 1052 val_snippets.append('%(unit)s') 1053 vals['unit'] = unit 1054 1055 # name 1056 if name is not None: 1057 cols.append('name') 1058 val_snippets.append('%(name)s') 1059 vals['name'] = name 1060 1061 col_clause = ', '.join(cols) 1062 val_clause = ', '.join(val_snippets) 1063 queries = [ 1064 {'cmd': 'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals}, 1065 {'cmd': "select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"} 1066 ] 1067 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, get_col_idx = True, return_data = True) 1068 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx}) 1069 1070 return ttype
1071 1072 #============================================================
1073 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
1074 """Represents one test result.""" 1075 1076 _cmd_fetch_payload = "select * from clin.v_test_results where pk_test_result = %s" 1077 1078 _cmds_store_payload = [ 1079 """UPDATE clin.test_result SET 1080 clin_when = %(clin_when)s, 1081 narrative = nullif(trim(%(comment)s), ''), 1082 val_num = %(val_num)s, 1083 val_alpha = nullif(trim(%(val_alpha)s), ''), 1084 val_unit = nullif(trim(%(val_unit)s), ''), 1085 val_normal_min = %(val_normal_min)s, 1086 val_normal_max = %(val_normal_max)s, 1087 val_normal_range = nullif(trim(%(val_normal_range)s), ''), 1088 val_target_min = %(val_target_min)s, 1089 val_target_max = %(val_target_max)s, 1090 val_target_range = nullif(trim(%(val_target_range)s), ''), 1091 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''), 1092 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''), 1093 note_test_org = nullif(trim(%(note_test_org)s), ''), 1094 material = nullif(trim(%(material)s), ''), 1095 material_detail = nullif(trim(%(material_detail)s), ''), 1096 status = gm.nullify_empty_string(%(status)s), 1097 val_grouping = gm.nullify_empty_string(%(val_grouping)s), 1098 source_data = gm.nullify_empty_string(%(source_data)s), 1099 fk_intended_reviewer = %(pk_intended_reviewer)s, 1100 fk_encounter = %(pk_encounter)s, 1101 fk_episode = %(pk_episode)s, 1102 fk_type = %(pk_test_type)s, 1103 fk_request = %(pk_request)s 1104 WHERE 1105 pk = %(pk_test_result)s AND 1106 xmin = %(xmin_test_result)s 1107 RETURNING 1108 xmin AS xmin_test_result 1109 """ 1110 # , u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s""" 1111 ] 1112 1113 _updatable_fields = [ 1114 'clin_when', 1115 'comment', 1116 'val_num', 1117 'val_alpha', 1118 'val_unit', 1119 'val_normal_min', 1120 'val_normal_max', 1121 'val_normal_range', 1122 'val_target_min', 1123 'val_target_max', 1124 'val_target_range', 1125 'abnormality_indicator', 1126 'norm_ref_group', 1127 'note_test_org', 1128 'material', 1129 'material_detail', 1130 'status', 1131 'val_grouping', 1132 'source_data', 1133 'pk_intended_reviewer', 1134 'pk_encounter', 1135 'pk_episode', 1136 'pk_test_type', 1137 'pk_request' 1138 ] 1139 1140 #--------------------------------------------------------
1141 - def format_concisely(self, date_format='%Y %b %d', with_notes=True):
1142 range_info = gmTools.coalesce ( 1143 self.formatted_clinical_range, 1144 self.formatted_normal_range 1145 ) 1146 review = gmTools.bool2subst ( 1147 self._payload[self._idx['reviewed']], 1148 '', 1149 ' ' + gmTools.u_writing_hand, 1150 ' ' + gmTools.u_writing_hand 1151 ) 1152 txt = '%s %s: %s%s%s%s%s%s' % ( 1153 gmDateTime.pydt_strftime ( 1154 self._payload[self._idx['clin_when']], 1155 date_format 1156 ), 1157 self._payload[self._idx['name_tt']], 1158 self._payload[self._idx['unified_val']], 1159 gmTools.coalesce(self._payload[self._idx['val_unit']], '', ' %s'), 1160 gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], '', ' %s'), 1161 gmTools.coalesce(range_info, '', ' (%s)'), 1162 gmTools.coalesce(self._payload[self._idx['status']], '', ' [%s]')[:2], 1163 review 1164 ) 1165 if with_notes: 1166 txt += '\n' 1167 if self._payload[self._idx['note_test_org']] is not None: 1168 txt += ' ' + _('Lab comment: %s\n') % _('\n Lab comment: ').join(self._payload[self._idx['note_test_org']].split('\n')) 1169 if self._payload[self._idx['comment']] is not None: 1170 txt += ' ' + _('Praxis comment: %s\n') % _('\n Praxis comment: ').join(self._payload[self._idx['comment']].split('\n')) 1171 1172 return txt.strip('\n')
1173 1174 #--------------------------------------------------------
1175 - def format(self, with_review=True, with_evaluation=True, with_ranges=True, with_episode=True, with_type_details=True, with_source_data=False, date_format='%Y %b %d %H:%M'):
1176 1177 # FIXME: add battery, request details 1178 1179 # header 1180 tt = _('Result from %s \n') % gmDateTime.pydt_strftime ( 1181 self._payload[self._idx['clin_when']], 1182 date_format 1183 ) 1184 1185 # basics 1186 tt += ' ' + _('Type: "%(name)s" (%(abbr)s) [#%(pk_type)s]\n') % ({ 1187 'name': self._payload[self._idx['name_tt']], 1188 'abbr': self._payload[self._idx['abbrev_tt']], 1189 'pk_type': self._payload[self._idx['pk_test_type']] 1190 }) 1191 if self.is_long_text: 1192 sso = gmTools.u_superscript_one 1193 else: 1194 sso = '' 1195 tt += ' ' + _('%(sso)sResult: %(val)s%(unit)s%(ind)s [#%(pk_result)s]\n') % ({ 1196 'sso': sso, 1197 'val': self._payload[self._idx['unified_val']], 1198 'unit': gmTools.coalesce(self._payload[self._idx['val_unit']], '', ' %s'), 1199 'ind': gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], '', ' (%s)'), 1200 'pk_result': self._payload[self._idx['pk_test_result']] 1201 }) 1202 1203 if self._payload[self._idx['status']] is not None: 1204 try: 1205 stat = HL7_RESULT_STATI[self._payload[self._idx['status']]] 1206 except KeyError: 1207 stat = self._payload[self._idx['status']] 1208 tt += ' ' + _('Status: %s\n') % stat 1209 if self._payload[self._idx['val_grouping']] is not None: 1210 tt += ' ' + _('Grouping: %s\n') % self._payload[self._idx['val_grouping']] 1211 1212 if with_evaluation: 1213 norm_eval = None 1214 if self._payload[self._idx['val_num']] is not None: 1215 # 1) normal range 1216 # lowered ? 1217 if (self._payload[self._idx['val_normal_min']] is not None) and (self._payload[self._idx['val_num']] < self._payload[self._idx['val_normal_min']]): 1218 try: 1219 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_normal_min']] 1220 except ZeroDivisionError: 1221 percent = None 1222 if percent is not None: 1223 if percent < 6: 1224 norm_eval = _('%.1f %% of the normal lower limit') % percent 1225 else: 1226 norm_eval = _('%.0f %% of the normal lower limit') % percent 1227 # raised ? 1228 if (self._payload[self._idx['val_normal_max']] is not None) and (self._payload[self._idx['val_num']] > self._payload[self._idx['val_normal_max']]): 1229 try: 1230 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_normal_max']] 1231 except ZeroDivisionError: 1232 x_times = None 1233 if x_times is not None: 1234 if x_times < 10: 1235 norm_eval = _('%.1f times the normal upper limit') % x_times 1236 else: 1237 norm_eval = _('%.0f times the normal upper limit') % x_times 1238 if norm_eval is not None: 1239 tt += ' = %s\n' % norm_eval 1240 # #------------------------------------- 1241 # # this idea was shot down on the list 1242 # #------------------------------------- 1243 # # bandwidth of deviation 1244 # if None not in [self._payload[self._idx['val_normal_min']], self._payload[self._idx['val_normal_max']]]: 1245 # normal_width = self._payload[self._idx['val_normal_max']] - self._payload[self._idx['val_normal_min']] 1246 # deviation_from_normal_range = None 1247 # # below ? 1248 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_normal_min']]: 1249 # deviation_from_normal_range = self._payload[self._idx['val_normal_min']] - self._payload[self._idx['val_num']] 1250 # # above ? 1251 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_normal_max']]: 1252 # deviation_from_normal_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_normal_max']] 1253 # if deviation_from_normal_range is None: 1254 # try: 1255 # times_deviation = deviation_from_normal_range / normal_width 1256 # except ZeroDivisionError: 1257 # times_deviation = None 1258 # if times_deviation is not None: 1259 # if times_deviation < 10: 1260 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation 1261 # else: 1262 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation 1263 # #------------------------------------- 1264 1265 # 2) clinical target range 1266 norm_eval = None 1267 # lowered ? 1268 if (self._payload[self._idx['val_target_min']] is not None) and (self._payload[self._idx['val_num']] < self._payload[self._idx['val_target_min']]): 1269 try: 1270 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_target_min']] 1271 except ZeroDivisionError: 1272 percent = None 1273 if percent is not None: 1274 if percent < 6: 1275 norm_eval = _('%.1f %% of the target lower limit') % percent 1276 else: 1277 norm_eval = _('%.0f %% of the target lower limit') % percent 1278 # raised ? 1279 if (self._payload[self._idx['val_target_max']] is not None) and (self._payload[self._idx['val_num']] > self._payload[self._idx['val_target_max']]): 1280 try: 1281 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_target_max']] 1282 except ZeroDivisionError: 1283 x_times = None 1284 if x_times is not None: 1285 if x_times < 10: 1286 norm_eval = _('%.1f times the target upper limit') % x_times 1287 else: 1288 norm_eval = _('%.0f times the target upper limit') % x_times 1289 if norm_eval is not None: 1290 tt += ' = %s\n' % norm_eval 1291 # #------------------------------------- 1292 # # this idea was shot down on the list 1293 # #------------------------------------- 1294 # # bandwidth of deviation 1295 # if None not in [self._payload[self._idx['val_target_min']], self._payload[self._idx['val_target_max']]]: 1296 # normal_width = self._payload[self._idx['val_target_max']] - self._payload[self._idx['val_target_min']] 1297 # deviation_from_target_range = None 1298 # # below ? 1299 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_target_min']]: 1300 # deviation_from_target_range = self._payload[self._idx['val_target_min']] - self._payload[self._idx['val_num']] 1301 # # above ? 1302 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_target_max']]: 1303 # deviation_from_target_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_target_max']] 1304 # if deviation_from_target_range is None: 1305 # try: 1306 # times_deviation = deviation_from_target_range / normal_width 1307 # except ZeroDivisionError: 1308 # times_deviation = None 1309 # if times_deviation is not None: 1310 # if times_deviation < 10: 1311 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation 1312 # else: 1313 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation 1314 # #------------------------------------- 1315 1316 tmp = ('%s%s' % ( 1317 gmTools.coalesce(self._payload[self._idx['name_test_org']], ''), 1318 gmTools.coalesce(self._payload[self._idx['contact_test_org']], '', ' (%s)'), 1319 )).strip() 1320 if tmp != '': 1321 tt += ' ' + _('Source: %s\n') % tmp 1322 tt += '\n' 1323 if self._payload[self._idx['note_test_org']] is not None: 1324 tt += ' ' + gmTools.u_superscript_one + _('Lab comment: %s\n') % _('\n Lab comment: ').join(self._payload[self._idx['note_test_org']].split('\n')) 1325 if self._payload[self._idx['comment']] is not None: 1326 tt += ' ' + gmTools.u_superscript_one + _('Praxis comment: %s\n') % _('\n Praxis comment: ').join(self._payload[self._idx['comment']].split('\n')) 1327 1328 if with_ranges: 1329 tt += gmTools.coalesce(self.formatted_normal_range, '', ' ' + _('Standard normal range: %s\n')) 1330 tt += gmTools.coalesce(self.formatted_clinical_range, '', ' ' + _('Clinical target range: %s\n')) 1331 tt += gmTools.coalesce(self._payload[self._idx['norm_ref_group']], '', ' ' + _('Reference group: %s\n')) 1332 1333 # metadata 1334 if with_episode: 1335 tt += ' ' + _('Episode: %s\n') % self._payload[self._idx['episode']] 1336 if self._payload[self._idx['health_issue']] is not None: 1337 tt += ' ' + _('Issue: %s\n') % self._payload[self._idx['health_issue']] 1338 if self._payload[self._idx['material']] is not None: 1339 tt += ' ' + _('Material: %s\n') % self._payload[self._idx['material']] 1340 if self._payload[self._idx['material_detail']] is not None: 1341 tt += ' ' + _('Details: %s\n') % self._payload[self._idx['material_detail']] 1342 tt += '\n' 1343 1344 if with_review: 1345 if self._payload[self._idx['reviewed']]: 1346 review = gmDateTime.pydt_strftime ( 1347 self._payload[self._idx['last_reviewed']], 1348 date_format 1349 ) 1350 else: 1351 review = _('not yet') 1352 tt += _('Signed (%(sig_hand)s): %(reviewed)s\n') % ({ 1353 'sig_hand': gmTools.u_writing_hand, 1354 'reviewed': review 1355 }) 1356 tt += ' ' + _('Responsible clinician: %s\n') % gmTools.bool2subst ( 1357 self._payload[self._idx['you_are_responsible']], 1358 _('you'), 1359 self._payload[self._idx['responsible_reviewer']] 1360 ) 1361 if self._payload[self._idx['reviewed']]: 1362 tt += ' ' + _('Last reviewer: %(reviewer)s\n') % ({ 1363 'reviewer': gmTools.bool2subst ( 1364 self._payload[self._idx['review_by_you']], 1365 _('you'), 1366 gmTools.coalesce(self._payload[self._idx['last_reviewer']], '?') 1367 ) 1368 }) 1369 tt += ' ' + _(' Technically abnormal: %(abnormal)s\n') % ({ 1370 'abnormal': gmTools.bool2subst ( 1371 self._payload[self._idx['is_technically_abnormal']], 1372 _('yes'), 1373 _('no'), 1374 '?' 1375 ) 1376 }) 1377 tt += ' ' + _(' Clinically relevant: %(relevant)s\n') % ({ 1378 'relevant': gmTools.bool2subst ( 1379 self._payload[self._idx['is_clinically_relevant']], 1380 _('yes'), 1381 _('no'), 1382 '?' 1383 ) 1384 }) 1385 if self._payload[self._idx['review_comment']] is not None: 1386 tt += ' ' + _(' Comment: %s\n') % self._payload[self._idx['review_comment']].strip() 1387 tt += '\n' 1388 1389 # type 1390 if with_type_details: 1391 has_details = None not in [self._payload[self._idx['comment_tt']], self._payload[self._idx['pk_meta_test_type']], self._payload[self._idx['comment_meta']]] 1392 if has_details: 1393 tt += _('Test type details:\n') 1394 if self._payload[self._idx['comment_tt']] is not None: 1395 tt += ' ' + _('Type comment: %s\n') % _('\n Type comment:').join(self._payload[self._idx['comment_tt']].split('\n')) 1396 if self._payload[self._idx['pk_meta_test_type']] is not None: 1397 tt += ' ' + _('Aggregated (%s) under: %s (%s) [#%s]\n') % ( 1398 gmTools.u_sum, 1399 self._payload[self._idx['name_meta']], 1400 self._payload[self._idx['abbrev_meta']], 1401 self._payload[self._idx['pk_meta_test_type']] 1402 ) 1403 if self._payload[self._idx['comment_meta']] is not None: 1404 tt += ' ' + _('Group comment: %s\n') % _('\n Group comment: ').join(self._payload[self._idx['comment_meta']].split('\n')) 1405 if has_details: 1406 tt += '\n' 1407 1408 if with_source_data: 1409 if self._payload[self._idx['source_data']] is not None: 1410 tt += _('Source data:\n') 1411 tt += ' ' + self._payload[self._idx['source_data']] 1412 tt += '\n\n' 1413 1414 if with_review: 1415 tt += _('Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({ 1416 'row_ver': self._payload[self._idx['row_version']], 1417 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']],date_format), 1418 'mod_by': self._payload[self._idx['modified_by']] 1419 }) 1420 1421 return tt
1422 1423 #--------------------------------------------------------
1424 - def _get_has_normal_min_or_max(self):
1425 return ( 1426 self._payload[self._idx['val_normal_min']] is not None 1427 ) or ( 1428 self._payload[self._idx['val_normal_max']] is not None 1429 )
1430 1431 has_normal_min_or_max = property(_get_has_normal_min_or_max, lambda x:x) 1432 1433 #--------------------------------------------------------
1434 - def _get_normal_min_max(self):
1435 has_range_info = ( 1436 self._payload[self._idx['val_normal_min']] is not None 1437 ) or ( 1438 self._payload[self._idx['val_normal_max']] is not None 1439 ) 1440 if has_range_info is False: 1441 return None 1442 1443 return '%s - %s' % ( 1444 gmTools.coalesce(self._payload[self._idx['val_normal_min']], '?'), 1445 gmTools.coalesce(self._payload[self._idx['val_normal_max']], '?') 1446 )
1447 1448 normal_min_max = property(_get_normal_min_max, lambda x:x) 1449 1450 #--------------------------------------------------------
1452 has_numerical_range = ( 1453 self._payload[self._idx['val_normal_min']] is not None 1454 ) or ( 1455 self._payload[self._idx['val_normal_max']] is not None 1456 ) 1457 if has_numerical_range: 1458 numerical_range = '%s - %s' % ( 1459 gmTools.coalesce(self._payload[self._idx['val_normal_min']], '?'), 1460 gmTools.coalesce(self._payload[self._idx['val_normal_max']], '?') 1461 ) 1462 else: 1463 numerical_range = '' 1464 textual_range = gmTools.coalesce ( 1465 self._payload[self._idx['val_normal_range']], 1466 '', 1467 gmTools.bool2subst ( 1468 has_numerical_range, 1469 ' / %s', 1470 '%s' 1471 ) 1472 ) 1473 range_info = '%s%s' % (numerical_range, textual_range) 1474 if range_info == '': 1475 return None 1476 return range_info
1477 1478 formatted_normal_range = property(_get_formatted_normal_range, lambda x:x) 1479 1480 #--------------------------------------------------------
1482 return ( 1483 self._payload[self._idx['val_target_min']] is not None 1484 ) or ( 1485 self._payload[self._idx['val_target_max']] is not None 1486 )
1487 1488 has_clinical_min_or_max = property(_get_has_clinical_min_or_max, lambda x:x) 1489 1490 #--------------------------------------------------------
1491 - def _get_clinical_min_max(self):
1492 has_range_info = ( 1493 self._payload[self._idx['val_target_min']] is not None 1494 ) or ( 1495 self._payload[self._idx['val_target_max']] is not None 1496 ) 1497 if has_range_info is False: 1498 return None 1499 1500 return '%s - %s' % ( 1501 gmTools.coalesce(self._payload[self._idx['val_target_min']], '?'), 1502 gmTools.coalesce(self._payload[self._idx['val_target_max']], '?') 1503 )
1504 1505 clinical_min_max = property(_get_clinical_min_max, lambda x:x) 1506 1507 #--------------------------------------------------------
1509 has_numerical_range = ( 1510 self._payload[self._idx['val_target_min']] is not None 1511 ) or ( 1512 self._payload[self._idx['val_target_max']] is not None 1513 ) 1514 if has_numerical_range: 1515 numerical_range = '%s - %s' % ( 1516 gmTools.coalesce(self._payload[self._idx['val_target_min']], '?'), 1517 gmTools.coalesce(self._payload[self._idx['val_target_max']], '?') 1518 ) 1519 else: 1520 numerical_range = '' 1521 textual_range = gmTools.coalesce ( 1522 self._payload[self._idx['val_target_range']], 1523 '', 1524 gmTools.bool2subst ( 1525 has_numerical_range, 1526 ' / %s', 1527 '%s' 1528 ) 1529 ) 1530 range_info = '%s%s' % (numerical_range, textual_range) 1531 if range_info == '': 1532 return None 1533 return range_info
1534 1535 formatted_clinical_range = property(_get_formatted_clinical_range, lambda x:x) 1536 1537 #--------------------------------------------------------
1539 """Returns the closest test result which does have normal range information.""" 1540 if self._payload[self._idx['val_normal_min']] is not None: 1541 return self 1542 if self._payload[self._idx['val_normal_max']] is not None: 1543 return self 1544 if self._payload[self._idx['val_normal_range']] is not None: 1545 return self 1546 cmd = """ 1547 SELECT * from clin.v_test_results 1548 WHERE 1549 pk_type = %(pk_type)s 1550 AND 1551 val_unit = %(unit)s 1552 AND 1553 ( 1554 (val_normal_min IS NOT NULL) 1555 OR 1556 (val_normal_max IS NOT NULL) 1557 OR 1558 (val_normal_range IS NOT NULL) 1559 ) 1560 ORDER BY 1561 CASE 1562 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 1563 ELSE %(clin_when)s - clin_when 1564 END 1565 LIMIT 1""" 1566 args = { 1567 'pk_type': self._payload[self._idx['pk_test_type']], 1568 'unit': self._payload[self._idx['val_unit']], 1569 'clin_when': self._payload[self._idx['clin_when']] 1570 } 1571 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1572 if len(rows) == 0: 1573 return None 1574 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1575 1576 temporally_closest_normal_range = property(_get_temporally_closest_normal_range, lambda x:x) 1577 1578 #--------------------------------------------------------
1579 - def _get_formatted_range(self):
1580 1581 has_normal_min_or_max = ( 1582 self._payload[self._idx['val_normal_min']] is not None 1583 ) or ( 1584 self._payload[self._idx['val_normal_max']] is not None 1585 ) 1586 if has_normal_min_or_max: 1587 normal_min_max = '%s - %s' % ( 1588 gmTools.coalesce(self._payload[self._idx['val_normal_min']], '?'), 1589 gmTools.coalesce(self._payload[self._idx['val_normal_max']], '?') 1590 ) 1591 1592 has_clinical_min_or_max = ( 1593 self._payload[self._idx['val_target_min']] is not None 1594 ) or ( 1595 self._payload[self._idx['val_target_max']] is not None 1596 ) 1597 if has_clinical_min_or_max: 1598 clinical_min_max = '%s - %s' % ( 1599 gmTools.coalesce(self._payload[self._idx['val_target_min']], '?'), 1600 gmTools.coalesce(self._payload[self._idx['val_target_max']], '?') 1601 ) 1602 1603 if has_clinical_min_or_max: 1604 return _('Target: %(clin_min_max)s%(clin_range)s') % ({ 1605 'clin_min_max': clinical_min_max, 1606 'clin_range': gmTools.coalesce ( 1607 self._payload[self._idx['val_target_range']], 1608 '', 1609 gmTools.bool2subst ( 1610 has_clinical_min_or_max, 1611 ' / %s', 1612 '%s' 1613 ) 1614 ) 1615 }) 1616 1617 if has_normal_min_or_max: 1618 return _('Norm: %(norm_min_max)s%(norm_range)s') % ({ 1619 'norm_min_max': normal_min_max, 1620 'norm_range': gmTools.coalesce ( 1621 self._payload[self._idx['val_normal_range']], 1622 '', 1623 gmTools.bool2subst ( 1624 has_normal_min_or_max, 1625 ' / %s', 1626 '%s' 1627 ) 1628 ) 1629 }) 1630 1631 if self._payload[self._idx['val_target_range']] is not None: 1632 return _('Target: %s') % self._payload[self._idx['val_target_range']], 1633 1634 if self._payload[self._idx['val_normal_range']] is not None: 1635 return _('Norm: %s') % self._payload[self._idx['val_normal_range']] 1636 1637 return None
1638 1639 formatted_range = property(_get_formatted_range, lambda x:x) 1640 1641 #--------------------------------------------------------
1642 - def _get_test_type(self):
1643 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
1644 1645 test_type = property(_get_test_type, lambda x:x) 1646 1647 #--------------------------------------------------------
1649 # 1) the user is right (review) 1650 if self._payload[self._idx['is_technically_abnormal']] is False: 1651 return False 1652 # 2) the lab is right (result.abnormality_indicator) 1653 indicator = self._payload[self._idx['abnormality_indicator']] 1654 if indicator is not None: 1655 indicator = indicator.strip() 1656 if indicator != '': 1657 if indicator.strip('+') == '': 1658 return True 1659 if indicator.strip('-') == '': 1660 return False 1661 # 3) non-numerical value ? 1662 if self._payload[self._idx['val_num']] is None: 1663 return None 1664 # 4) the target range is right 1665 target_max = self._payload[self._idx['val_target_max']] 1666 if target_max is not None: 1667 if target_max < self._payload[self._idx['val_num']]: 1668 return True 1669 # 4) the normal range is right 1670 normal_max = self._payload[self._idx['val_normal_max']] 1671 if normal_max is not None: 1672 if normal_max < self._payload[self._idx['val_num']]: 1673 return True 1674 return None
1675 1676 is_considered_elevated = property(_get_is_considered_elevated, lambda x:x) 1677 1678 #--------------------------------------------------------
1679 - def _get_is_considered_lowered(self):
1680 # 1) the user is right (review) 1681 if self._payload[self._idx['is_technically_abnormal']] is False: 1682 return False 1683 # 2) the lab is right (result.abnormality_indicator) 1684 indicator = self._payload[self._idx['abnormality_indicator']] 1685 if indicator is not None: 1686 indicator = indicator.strip() 1687 if indicator != '': 1688 if indicator.strip('+') == '': 1689 return False 1690 if indicator.strip('-') == '': 1691 return True 1692 # 3) non-numerical value ? 1693 if self._payload[self._idx['val_num']] is None: 1694 return None 1695 # 4) the target range is right 1696 target_min = self._payload[self._idx['val_target_min']] 1697 if target_min is not None: 1698 if target_min > self._payload[self._idx['val_num']]: 1699 return True 1700 # 4) the normal range is right 1701 normal_min = self._payload[self._idx['val_normal_min']] 1702 if normal_min is not None: 1703 if normal_min > self._payload[self._idx['val_num']]: 1704 return True 1705 return None
1706 1707 is_considered_lowered = property(_get_is_considered_lowered, lambda x:x) 1708 1709 #--------------------------------------------------------
1711 if self.is_considered_lowered is True: 1712 return True 1713 if self.is_considered_elevated is True: 1714 return True 1715 if (self.is_considered_lowered is False) and (self.is_considered_elevated is False): 1716 return False 1717 return self._payload[self._idx['is_technically_abnormal']]
1718 1719 is_considered_abnormal = property(_get_is_considered_abnormal, lambda x:x) 1720 1721 #--------------------------------------------------------
1722 - def _set_reference_range(self, ref_range):
1723 """Parse reference range from string. 1724 1725 Note: does NOT save the result. 1726 """ 1727 ref_range = ref_range.strip().replace(' ', '') 1728 1729 is_range = regex.match('-{0,1}\d+[.,]{0,1}\d*--{0,1}\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE) 1730 if is_range is not None: 1731 min_val = regex.match('-{0,1}\d+[.,]{0,1}\d*-', ref_range, regex.UNICODE).group(0).rstrip('-') 1732 success, min_val = gmTools.input2decimal(min_val) 1733 max_val = (regex.search('--{0,1}\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE).group(0))[1:] 1734 success, max_val = gmTools.input2decimal(max_val) 1735 self['val_normal_min'] = min_val 1736 self['val_normal_max'] = max_val 1737 return 1738 1739 if ref_range.startswith('<'): 1740 is_range = regex.match('<\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE) 1741 if is_range is not None: 1742 max_val = ref_range[1:] 1743 success, max_val = gmTools.input2decimal(max_val) 1744 self['val_normal_min'] = 0 1745 self['val_normal_max'] = max_val 1746 return 1747 1748 if ref_range.startswith('<-'): 1749 is_range = regex.match('<-\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE) 1750 if is_range is not None: 1751 max_val = ref_range[1:] 1752 success, max_val = gmTools.input2decimal(max_val) 1753 self['val_normal_min'] = None 1754 self['val_normal_max'] = max_val 1755 return 1756 1757 if ref_range.startswith('>'): 1758 is_range = regex.match('>\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE) 1759 if is_range is not None: 1760 min_val = ref_range[1:] 1761 success, min_val = gmTools.input2decimal(min_val) 1762 self['val_normal_min'] = min_val 1763 self['val_normal_max'] = None 1764 return 1765 1766 if ref_range.startswith('>-'): 1767 is_range = regex.match('>-\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE) 1768 if is_range is not None: 1769 min_val = ref_range[1:] 1770 success, min_val = gmTools.input2decimal(min_val) 1771 self['val_normal_min'] = min_val 1772 self['val_normal_max'] = 0 1773 return 1774 1775 self['val_normal_range'] = ref_range 1776 return
1777 1778 reference_range = property(lambda x:x, _set_reference_range) 1779 1780 #--------------------------------------------------------
1782 # 1) the user is right 1783 if self._payload[self._idx['is_technically_abnormal']] is False: 1784 return '' 1785 # 2) the lab is right (result.abnormality_indicator) 1786 indicator = self._payload[self._idx['abnormality_indicator']] 1787 if indicator is not None: 1788 indicator = indicator.strip() 1789 if indicator != '': 1790 return indicator 1791 # 3) non-numerical value ? then we can't know more 1792 if self._payload[self._idx['val_num']] is None: 1793 return None 1794 # 4) the target range is right 1795 target_min = self._payload[self._idx['val_target_min']] 1796 if target_min is not None: 1797 if target_min > self._payload[self._idx['val_num']]: 1798 return '-' 1799 target_max = self._payload[self._idx['val_target_max']] 1800 if target_max is not None: 1801 if target_max < self._payload[self._idx['val_num']]: 1802 return '+' 1803 # 4) the normal range is right 1804 normal_min = self._payload[self._idx['val_normal_min']] 1805 if normal_min is not None: 1806 if normal_min > self._payload[self._idx['val_num']]: 1807 return '-' 1808 normal_max = self._payload[self._idx['val_normal_max']] 1809 if normal_max is not None: 1810 if normal_max < self._payload[self._idx['val_num']]: 1811 return '+' 1812 # reviewed, abnormal, but no indicator available 1813 if self._payload[self._idx['is_technically_abnormal']] is True: 1814 return gmTools.u_plus_minus 1815 1816 return None
1817 1818 formatted_abnormality_indicator = property(_get_formatted_abnormality_indicator, lambda x:x) 1819 1820 #--------------------------------------------------------
1821 - def _get_is_long_text(self):
1822 if self._payload[self._idx['val_alpha']] is None: 1823 return False 1824 lines = gmTools.strip_empty_lines(text = self._payload[self._idx['val_alpha']], eol = '\n', return_list = True) 1825 if len(lines) > 4: 1826 return True 1827 return False
1828 1829 is_long_text = property(_get_is_long_text, lambda x:x) 1830 1831 #--------------------------------------------------------
1833 if self._payload[self._idx['val_alpha']] is None: 1834 return None 1835 val = self._payload[self._idx['val_alpha']].lstrip() 1836 if val[0] == '<': 1837 factor = decimal.Decimal(0.5) 1838 val = val[1:] 1839 elif val[0] == '>': 1840 factor = 2 1841 val = val[1:] 1842 else: 1843 return None 1844 success, val = gmTools.input2decimal(initial = val) 1845 if not success: 1846 return None 1847 return val * factor
1848 1849 estimate_numeric_value_from_alpha = property(_get_estimate_numeric_value_from_alpha, lambda x:x) 1850 1851 #--------------------------------------------------------
1852 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
1853 1854 # FIXME: this is not concurrency safe 1855 if self._payload[self._idx['reviewed']]: 1856 self.__change_existing_review ( 1857 technically_abnormal = technically_abnormal, 1858 clinically_relevant = clinically_relevant, 1859 comment = comment 1860 ) 1861 else: 1862 # do not sign off unreviewed results if 1863 # NOTHING AT ALL is known about them 1864 if technically_abnormal is None: 1865 if clinically_relevant is None: 1866 comment = gmTools.none_if(comment, '', strip_string = True) 1867 if comment is None: 1868 if make_me_responsible is False: 1869 return True 1870 self.__set_new_review ( 1871 technically_abnormal = technically_abnormal, 1872 clinically_relevant = clinically_relevant, 1873 comment = comment 1874 ) 1875 1876 if make_me_responsible is True: 1877 cmd = "SELECT pk FROM dem.staff WHERE db_user = current_user" 1878 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 1879 self['pk_intended_reviewer'] = rows[0][0] 1880 self.save_payload() 1881 return 1882 1883 self.refetch_payload()
1884 1885 #--------------------------------------------------------
1886 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
1887 1888 if desired_earlier_results < 1: 1889 raise ValueError('<desired_earlier_results> must be > 0') 1890 1891 if desired_later_results < 1: 1892 raise ValueError('<desired_later_results> must be > 0') 1893 1894 args = { 1895 'pat': self._payload[self._idx['pk_patient']], 1896 'ttyp': self._payload[self._idx['pk_test_type']], 1897 'tloinc': self._payload[self._idx['loinc_tt']], 1898 'mtyp': self._payload[self._idx['pk_meta_test_type']], 1899 'mloinc': self._payload[self._idx['loinc_meta']], 1900 'when': self._payload[self._idx['clin_when']], 1901 'offset': max_offset 1902 } 1903 WHERE = '((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))' 1904 WHERE_meta = '((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))' 1905 if max_offset is not None: 1906 WHERE = WHERE + ' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 1907 WHERE_meta = WHERE_meta + ' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 1908 1909 SQL = """ 1910 SELECT * FROM clin.v_test_results 1911 WHERE 1912 pk_patient = %%(pat)s 1913 AND 1914 clin_when %s %%(when)s 1915 AND 1916 %s 1917 ORDER BY clin_when 1918 LIMIT %s""" 1919 1920 # get earlier results 1921 earlier_results = [] 1922 # by type 1923 cmd = SQL % ('<', WHERE, desired_earlier_results) 1924 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1925 if len(rows) > 0: 1926 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1927 # by meta type ? 1928 missing_results = desired_earlier_results - len(earlier_results) 1929 if missing_results > 0: 1930 cmd = SQL % ('<', WHERE_meta, missing_results) 1931 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1932 if len(rows) > 0: 1933 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1934 1935 # get later results 1936 later_results = [] 1937 # by type 1938 cmd = SQL % ('>', WHERE, desired_later_results) 1939 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1940 if len(rows) > 0: 1941 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1942 # by meta type ? 1943 missing_results = desired_later_results - len(later_results) 1944 if missing_results > 0: 1945 cmd = SQL % ('>', WHERE_meta, missing_results) 1946 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1947 if len(rows) > 0: 1948 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1949 1950 return earlier_results, later_results
1951 1952 #-------------------------------------------------------- 1953 # internal API 1954 #--------------------------------------------------------
1955 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1956 """Add a review to a row. 1957 1958 - if technically abnormal is not provided/None it will be set 1959 to True if the lab's indicator has a meaningful value 1960 - if clinically relevant is not provided/None it is set to 1961 whatever technically abnormal is 1962 """ 1963 if technically_abnormal is None: 1964 technically_abnormal = False 1965 if self._payload[self._idx['abnormality_indicator']] is not None: 1966 if self._payload[self._idx['abnormality_indicator']].strip() != '': 1967 technically_abnormal = True 1968 1969 if clinically_relevant is None: 1970 clinically_relevant = technically_abnormal 1971 1972 cmd = """ 1973 INSERT INTO clin.reviewed_test_results ( 1974 fk_reviewed_row, 1975 is_technically_abnormal, 1976 clinically_relevant, 1977 comment 1978 ) VALUES ( 1979 %(pk)s, 1980 %(abnormal)s, 1981 %(relevant)s, 1982 gm.nullify_empty_string(%(cmt)s) 1983 )""" 1984 args = { 1985 'pk': self._payload[self._idx['pk_test_result']], 1986 'abnormal': technically_abnormal, 1987 'relevant': clinically_relevant, 1988 'cmt': comment 1989 } 1990 1991 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1992 1993 #--------------------------------------------------------
1994 - def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1995 """Change a review on a row. 1996 1997 - if technically abnormal/clinically relevant are 1998 None they are not set 1999 """ 2000 args = { 2001 'pk_result': self._payload[self._idx['pk_test_result']], 2002 'abnormal': technically_abnormal, 2003 'relevant': clinically_relevant, 2004 'cmt': comment 2005 } 2006 2007 set_parts = [ 2008 'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)', 2009 'comment = gm.nullify_empty_string(%(cmt)s)' 2010 ] 2011 2012 if technically_abnormal is not None: 2013 set_parts.append('is_technically_abnormal = %(abnormal)s') 2014 2015 if clinically_relevant is not None: 2016 set_parts.append('clinically_relevant = %(relevant)s') 2017 2018 cmd = """ 2019 UPDATE clin.reviewed_test_results SET 2020 %s 2021 WHERE 2022 fk_reviewed_row = %%(pk_result)s 2023 """ % ',\n '.join(set_parts) 2024 2025 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2026 2027 #------------------------------------------------------------
2028 -def get_test_results(pk_patient=None, encounters=None, episodes=None, order_by=None):
2029 2030 where_parts = [] 2031 2032 if pk_patient is not None: 2033 where_parts.append('pk_patient = %(pat)s') 2034 args = {'pat': pk_patient} 2035 2036 # if tests is not None: 2037 # where_parts.append(u'pk_test_type IN %(tests)s') 2038 # args['tests'] = tuple(tests) 2039 2040 if encounters is not None: 2041 where_parts.append('pk_encounter IN %(encs)s') 2042 args['encs'] = tuple(encounters) 2043 2044 if episodes is not None: 2045 where_parts.append('pk_episode IN %(epis)s') 2046 args['epis'] = tuple(episodes) 2047 2048 if order_by is None: 2049 order_by = '' 2050 else: 2051 order_by = 'ORDER BY %s' % order_by 2052 2053 cmd = """ 2054 SELECT * FROM clin.v_test_results 2055 WHERE %s 2056 %s 2057 """ % ( 2058 ' AND '.join(where_parts), 2059 order_by 2060 ) 2061 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2062 2063 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ] 2064 return tests
2065 2066 #------------------------------------------------------------
2067 -def get_most_recent_results_for_panel(pk_patient=None, pk_panel=None, order_by=None, group_by_meta_type=False):
2068 2069 if order_by is None: 2070 order_by = '' 2071 else: 2072 order_by = 'ORDER BY %s' % order_by 2073 2074 args = { 2075 'pat': pk_patient, 2076 'pk_pnl': pk_panel 2077 } 2078 2079 if group_by_meta_type: 2080 # return most recent results in panel grouped by 2081 # meta test type if any, non-grouped results are 2082 # returned ungrouped :-) 2083 cmd = """ 2084 SELECT c_vtr.* 2085 FROM ( 2086 -- max(clin_when) per test_type-in-panel for patient 2087 SELECT 2088 pk_meta_test_type, 2089 MAX(clin_when) AS max_clin_when 2090 FROM clin.v_test_results 2091 WHERE 2092 pk_patient = %(pat)s 2093 AND 2094 pk_meta_test_type IS DISTINCT FROM NULL 2095 AND 2096 pk_test_type IN ( 2097 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s) 2098 ) 2099 GROUP BY pk_meta_test_type 2100 ) AS latest_results 2101 INNER JOIN clin.v_test_results c_vtr ON 2102 c_vtr.pk_meta_test_type = latest_results.pk_meta_test_type 2103 AND 2104 c_vtr.clin_when = latest_results.max_clin_when 2105 2106 UNION ALL 2107 2108 SELECT c_vtr.* 2109 FROM ( 2110 -- max(clin_when) per test_type-in-panel for patient 2111 SELECT 2112 pk_test_type, 2113 MAX(clin_when) AS max_clin_when 2114 FROM clin.v_test_results 2115 WHERE 2116 pk_patient = %(pat)s 2117 AND 2118 pk_meta_test_type IS NULL 2119 AND 2120 pk_test_type IN ( 2121 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s) 2122 ) 2123 GROUP BY pk_test_type 2124 ) AS latest_results 2125 INNER JOIN clin.v_test_results c_vtr ON 2126 c_vtr.pk_test_type = latest_results.pk_test_type 2127 AND 2128 c_vtr.clin_when = latest_results.max_clin_when 2129 """ 2130 else: 2131 # return most recent results in panel regardless of whether 2132 # distinct test types in this panel are grouped under the 2133 # same meta test type 2134 cmd = """ 2135 SELECT c_vtr.* 2136 FROM ( 2137 -- max(clin_when) per test_type-in-panel for patient 2138 SELECT 2139 pk_test_type, 2140 MAX(clin_when) AS max_clin_when 2141 FROM clin.v_test_results 2142 WHERE 2143 pk_patient = %(pat)s 2144 AND 2145 pk_test_type IN ( 2146 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s) 2147 ) 2148 GROUP BY pk_test_type 2149 ) AS latest_results 2150 -- this INNER join makes certain we do not expand 2151 -- the row selection beyond the patient's rows 2152 -- which we constrained to inside the SELECT 2153 -- producing "latest_results" 2154 INNER JOIN clin.v_test_results c_vtr ON 2155 c_vtr.pk_test_type = latest_results.pk_test_type 2156 AND 2157 c_vtr.clin_when = latest_results.max_clin_when 2158 """ 2159 cmd += order_by 2160 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2161 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2162 2163 #------------------------------------------------------------
2164 -def get_result_at_timestamp(timestamp=None, test_type=None, loinc=None, tolerance_interval=None, patient=None):
2165 2166 if None not in [test_type, loinc]: 2167 raise ValueError('either <test_type> or <loinc> must be None') 2168 2169 args = { 2170 'pat': patient, 2171 'ttyp': test_type, 2172 'loinc': loinc, 2173 'ts': timestamp, 2174 'intv': tolerance_interval 2175 } 2176 2177 where_parts = ['pk_patient = %(pat)s'] 2178 if test_type is not None: 2179 where_parts.append('pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 2180 elif loinc is not None: 2181 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))') 2182 args['loinc'] = tuple(loinc) 2183 2184 if tolerance_interval is None: 2185 where_parts.append('clin_when = %(ts)s') 2186 else: 2187 where_parts.append('clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)') 2188 2189 cmd = """ 2190 SELECT * FROM clin.v_test_results 2191 WHERE 2192 %s 2193 ORDER BY 2194 abs(extract(epoch from age(clin_when, %%(ts)s))) 2195 LIMIT 1""" % ' AND '.join(where_parts) 2196 2197 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2198 if len(rows) == 0: 2199 return None 2200 2201 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2202 2203 #------------------------------------------------------------
2204 -def get_results_for_day(timestamp=None, patient=None, order_by=None):
2205 2206 args = { 2207 'pat': patient, 2208 'ts': timestamp 2209 } 2210 2211 where_parts = [ 2212 'pk_patient = %(pat)s', 2213 "date_trunc('day'::text, clin_when) = date_trunc('day'::text, %(ts)s)" 2214 ] 2215 2216 cmd = """ 2217 SELECT * FROM clin.v_test_results 2218 WHERE 2219 %s 2220 ORDER BY 2221 val_grouping, 2222 abbrev_tt, 2223 clin_when DESC 2224 """ % ' AND '.join(where_parts) 2225 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2226 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2227 2228 #------------------------------------------------------------
2229 -def get_results_for_issue(pk_health_issue=None, order_by=None):
2230 args = {'pk_issue': pk_health_issue} 2231 where_parts = ['pk_health_issue = %(pk_issue)s'] 2232 cmd = """ 2233 SELECT * FROM clin.v_test_results 2234 WHERE %s 2235 ORDER BY 2236 val_grouping, 2237 abbrev_tt, 2238 clin_when DESC 2239 """ % ' AND '.join(where_parts) 2240 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2241 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2242 2243 #------------------------------------------------------------
2244 -def get_results_for_episode(pk_episode=None):
2245 args = {'pk_epi': pk_episode} 2246 where_parts = ['pk_episode = %(pk_epi)s'] 2247 cmd = """ 2248 SELECT * FROM clin.v_test_results 2249 WHERE %s 2250 ORDER BY 2251 val_grouping, 2252 abbrev_tt, 2253 clin_when DESC 2254 """ % ' AND '.join(where_parts) 2255 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2256 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2257 2258 #------------------------------------------------------------
2259 -def get_most_recent_results_in_loinc_group(loincs=None, no_of_results=1, patient=None, consider_meta_type=False, max_age=None):
2260 """Get N most recent results *among* a list of tests selected by LOINC.""" 2261 2262 # <loinc> must be a list or tuple or set, NOT a single string 2263 # <max_age> must be a string holding a PG interval or else a pydt interval 2264 2265 if no_of_results < 1: 2266 raise ValueError('<no_of_results> must be > 0') 2267 2268 # if not consider_meta_type: 2269 # return get_most_recent_results ( 2270 # loinc = loinc, 2271 # no_of_results = no_of_results, 2272 # patient = patient 2273 # ) 2274 2275 args = {'pat': patient, 'loincs': tuple(loincs)} 2276 if max_age is None: 2277 max_age_cond = '' 2278 else: 2279 max_age_cond = 'AND clin_when > (now() - %(max_age)s::interval)' 2280 args['max_age'] = max_age 2281 2282 if consider_meta_type: 2283 rank_order = '_rank ASC' 2284 else: 2285 rank_order = '_rank DESC' 2286 2287 cmd = """ 2288 SELECT DISTINCT ON (pk_test_type) * FROM ( 2289 ( -- get results for meta type loincs 2290 SELECT *, 1 AS _rank 2291 FROM clin.v_test_results 2292 WHERE 2293 pk_patient = %%(pat)s 2294 AND 2295 loinc_meta IN %%(loincs)s 2296 %s 2297 -- no use weeding out duplicates by UNION-only, because _rank will make them unique anyway 2298 ) UNION ALL ( 2299 -- get results for direct loincs 2300 SELECT *, 2 AS _rank 2301 FROM clin.v_test_results 2302 WHERE 2303 pk_patient = %%(pat)s 2304 AND 2305 loinc_tt IN %%(loincs)s 2306 %s 2307 ) 2308 ORDER BY 2309 -- all of them by most-recent 2310 clin_when DESC, 2311 -- then by rank-of meta vs direct 2312 %s 2313 ) AS ordered_results 2314 -- then return only what's needed 2315 LIMIT %s""" % ( 2316 max_age_cond, 2317 max_age_cond, 2318 rank_order, 2319 no_of_results 2320 ) 2321 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2322 if no_of_results == 1: 2323 if len(rows) == 0: 2324 return None 2325 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]}) 2326 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2327 2328 #------------------------------------------------------------
2329 -def get_most_recent_results_for_test_type(test_type=None, no_of_results=1, patient=None):
2330 """Get N most recent results for *a* (one) given test type.""" 2331 2332 if test_type is None: 2333 raise ValueError('<test_type> must not be None') 2334 2335 if no_of_results < 1: 2336 raise ValueError('<no_of_results> must be > 0') 2337 2338 args = { 2339 'pat': patient, 2340 'ttyp': test_type 2341 } 2342 where_parts = ['pk_patient = %(pat)s'] 2343 where_parts.append('pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 2344 cmd = """ 2345 SELECT * FROM clin.v_test_results 2346 WHERE 2347 %s 2348 ORDER BY clin_when DESC 2349 LIMIT %s""" % ( 2350 ' AND '.join(where_parts), 2351 no_of_results 2352 ) 2353 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2354 if no_of_results == 1: 2355 if len(rows) == 0: 2356 return None 2357 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]}) 2358 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2359 2360 #------------------------------------------------------------
2361 -def get_most_recent_result_for_test_types(pk_test_types=None, pk_patient=None):
2362 """Return the one most recent result for *each* of a list of test types.""" 2363 2364 where_parts = ['pk_patient = %(pat)s'] 2365 args = {'pat': pk_patient} 2366 2367 if pk_test_types is not None: 2368 where_parts.append('pk_test_type IN %(ttyps)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 2369 args['ttyps'] = tuple(pk_test_types) 2370 2371 cmd = """ 2372 SELECT * FROM ( 2373 SELECT 2374 *, 2375 MIN(clin_when) OVER relevant_tests AS min_clin_when 2376 FROM 2377 clin.v_test_results 2378 WHERE 2379 %s 2380 WINDOW relevant_tests AS (PARTITION BY pk_patient, pk_test_type) 2381 ) AS windowed_tests 2382 WHERE 2383 clin_when = min_clin_when 2384 """ % ' AND '.join(where_parts) 2385 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2386 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2387 2388 #------------------------------------------------------------
2389 -def get_most_recent_results_for_patient(no_of_results=1, patient=None):
2390 """Get N most recent results for a given patient.""" 2391 2392 if no_of_results < 1: 2393 raise ValueError('<no_of_results> must be > 0') 2394 2395 args = {'pat': patient} 2396 cmd = """ 2397 SELECT * FROM clin.v_test_results 2398 WHERE 2399 pk_patient = %%(pat)s 2400 ORDER BY clin_when DESC 2401 LIMIT %s""" % no_of_results 2402 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2403 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2404 2405 #------------------------------------------------------------
2406 -def get_oldest_result(test_type=None, loinc=None, patient=None):
2407 2408 if None not in [test_type, loinc]: 2409 raise ValueError('either <test_type> or <loinc> must be None') 2410 2411 args = { 2412 'pat': patient, 2413 'ttyp': test_type, 2414 'loinc': loinc 2415 } 2416 2417 where_parts = ['pk_patient = %(pat)s'] 2418 if test_type is not None: 2419 where_parts.append('pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 2420 elif loinc is not None: 2421 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))') 2422 args['loinc'] = tuple(loinc) 2423 2424 cmd = """ 2425 SELECT * FROM clin.v_test_results 2426 WHERE 2427 %s 2428 ORDER BY clin_when 2429 LIMIT 1""" % ' AND '.join(where_parts) 2430 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 2431 if len(rows) == 0: 2432 return None 2433 2434 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2435 2436 #------------------------------------------------------------
2437 -def delete_test_result(result=None):
2438 try: 2439 pk = int(result) 2440 except (TypeError, AttributeError): 2441 pk = result['pk_test_result'] 2442 2443 cmd = 'DELETE FROM clin.test_result WHERE pk = %(pk)s' 2444 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
2445 2446 #------------------------------------------------------------
2447 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
2448 2449 cmd1 = """ 2450 INSERT INTO clin.test_result ( 2451 fk_encounter, 2452 fk_episode, 2453 fk_type, 2454 fk_intended_reviewer, 2455 val_num, 2456 val_alpha, 2457 val_unit 2458 ) VALUES ( 2459 %(enc)s, 2460 %(epi)s, 2461 %(type)s, 2462 %(rev)s, 2463 %(v_num)s, 2464 %(v_alpha)s, 2465 %(unit)s 2466 ) 2467 """ 2468 cmd2 = "SELECT * from clin.v_test_results WHERE pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))" 2469 args = { 2470 'enc': encounter, 2471 'epi': episode, 2472 'type': type, 2473 'rev': intended_reviewer, 2474 'v_num': val_num, 2475 'v_alpha': val_alpha, 2476 'unit': unit 2477 } 2478 rows, idx = gmPG2.run_rw_queries ( 2479 link_obj = link_obj, 2480 queries = [ 2481 {'cmd': cmd1, 'args': args}, 2482 {'cmd': cmd2} 2483 ], 2484 return_data = True, 2485 get_col_idx = True 2486 ) 2487 tr = cTestResult(row = { 2488 'pk_field': 'pk_test_result', 2489 'idx': idx, 2490 'data': rows[0] 2491 }) 2492 return tr
2493 2494 #------------------------------------------------------------
2495 -def format_test_results(results=None, output_format='latex'):
2496 2497 _log.debug('formatting test results into [%s]', output_format) 2498 2499 if output_format == 'latex': 2500 return __format_test_results_latex(results = results) 2501 2502 msg = _('unknown test results output format [%s]') % output_format 2503 _log.error(msg) 2504 return msg
2505 2506 #------------------------------------------------------------
2507 -def __tests2latex_minipage(results=None, width='1.5cm', show_time=False, show_range=True):
2508 2509 if len(results) == 0: 2510 return '\\begin{minipage}{%s} \\end{minipage}' % width 2511 2512 lines = [] 2513 for t in results: 2514 2515 tmp = '' 2516 2517 if show_time: 2518 tmp += '{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M') 2519 2520 tmp += '%.8s' % t['unified_val'] 2521 2522 lines.append(tmp) 2523 tmp = '' 2524 2525 if show_range: 2526 has_range = ( 2527 t['unified_target_range'] is not None 2528 or 2529 t['unified_target_min'] is not None 2530 or 2531 t['unified_target_max'] is not None 2532 ) 2533 if has_range: 2534 if t['unified_target_range'] is not None: 2535 tmp += '{\\tiny %s}' % t['unified_target_range'] 2536 else: 2537 tmp += '{\\tiny %s}' % ( 2538 gmTools.coalesce(t['unified_target_min'], '- ', '%s - '), 2539 gmTools.coalesce(t['unified_target_max'], '', '%s') 2540 ) 2541 lines.append(tmp) 2542 2543 return '\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, ' \\\\ '.join(lines))
2544 2545 #------------------------------------------------------------
2546 -def __tests2latex_cell(results=None, show_time=False, show_range=True):
2547 2548 if len(results) == 0: 2549 return '' 2550 2551 lines = [] 2552 for t in results: 2553 2554 tmp = '' 2555 2556 if show_time: 2557 tmp += '\\tiny %s ' % t['clin_when'].strftime('%H:%M') 2558 2559 tmp += '\\normalsize %.8s' % t['unified_val'] 2560 2561 lines.append(tmp) 2562 tmp = '\\tiny %s' % gmTools.coalesce(t['val_unit'], '', '%s ') 2563 2564 if not show_range: 2565 lines.append(tmp) 2566 continue 2567 2568 has_range = ( 2569 t['unified_target_range'] is not None 2570 or 2571 t['unified_target_min'] is not None 2572 or 2573 t['unified_target_max'] is not None 2574 ) 2575 2576 if not has_range: 2577 lines.append(tmp) 2578 continue 2579 2580 if t['unified_target_range'] is not None: 2581 tmp += '[%s]' % t['unified_target_range'] 2582 else: 2583 tmp += '[%s%s]' % ( 2584 gmTools.coalesce(t['unified_target_min'], '--', '%s--'), 2585 gmTools.coalesce(t['unified_target_max'], '', '%s') 2586 ) 2587 lines.append(tmp) 2588 2589 return ' \\\\ '.join(lines)
2590 2591 #------------------------------------------------------------
2592 -def __format_test_results_latex(results=None):
2593 2594 if len(results) == 0: 2595 return '\\noindent %s' % _('No test results to format.') 2596 2597 # discover the columns and rows 2598 dates = {} 2599 tests = {} 2600 grid = {} 2601 for result in results: 2602 # row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name']) 2603 row_label = result['unified_abbrev'] 2604 tests[row_label] = None 2605 col_label = '{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d') 2606 dates[col_label] = None 2607 try: 2608 grid[row_label] 2609 except KeyError: 2610 grid[row_label] = {} 2611 try: 2612 grid[row_label][col_label].append(result) 2613 except KeyError: 2614 grid[row_label][col_label] = [result] 2615 2616 col_labels = sorted(dates.keys(), reverse = True) 2617 del dates 2618 row_labels = sorted(tests.keys()) 2619 del tests 2620 2621 col_def = len(col_labels) * '>{\\raggedleft}p{1.7cm}|' 2622 2623 # format them 2624 tex = """\\noindent %s 2625 2626 \\noindent \\begin{tabular}{|l|%s} 2627 \\hline 2628 & %s \\tabularnewline 2629 \\hline 2630 2631 %%s \\tabularnewline 2632 2633 \\hline 2634 2635 \\end{tabular}""" % ( 2636 _('Test results'), 2637 col_def, 2638 ' & '.join(col_labels) 2639 ) 2640 2641 rows = [] 2642 2643 # loop over rows 2644 for rl in row_labels: 2645 cells = [rl] 2646 # loop over cols per row 2647 for cl in col_labels: 2648 try: 2649 # get tests for this (row/col) position 2650 tests = grid[rl][cl] 2651 except KeyError: 2652 # none there, so insert empty cell 2653 cells.append(' ') 2654 continue 2655 2656 cells.append ( 2657 __tests2latex_cell ( 2658 results = tests, 2659 show_time = (len(tests) > 1), 2660 show_range = True 2661 ) 2662 ) 2663 2664 rows.append(' & '.join(cells)) 2665 2666 return tex % ' \\tabularnewline\n \\hline\n'.join(rows)
2667 2668 #============================================================
2669 -def export_results_for_gnuplot(results=None, filename=None, show_year=True):
2670 2671 if filename is None: 2672 filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.dat') 2673 2674 # sort results into series by test type 2675 series = {} 2676 for r in results: 2677 try: 2678 series[r['unified_name']].append(r) 2679 except KeyError: 2680 series[r['unified_name']] = [r] 2681 2682 gp_data = io.open(filename, mode = 'wt', encoding = 'utf8') 2683 2684 gp_data.write('# %s\n' % _('GNUmed test results export for Gnuplot plotting')) 2685 gp_data.write('# -------------------------------------------------------------\n') 2686 gp_data.write('# first line of index: test type abbreviation & name\n') 2687 gp_data.write('#\n') 2688 gp_data.write('# clin_when at full precision\n') 2689 gp_data.write('# value\n') 2690 gp_data.write('# unit\n') 2691 gp_data.write('# unified (target or normal) range: lower bound\n') 2692 gp_data.write('# unified (target or normal) range: upper bound\n') 2693 gp_data.write('# normal range: lower bound\n') 2694 gp_data.write('# normal range: upper bound\n') 2695 gp_data.write('# target range: lower bound\n') 2696 gp_data.write('# target range: upper bound\n') 2697 gp_data.write('# clin_when formatted into string as x-axis tic label\n') 2698 gp_data.write('# -------------------------------------------------------------\n') 2699 2700 for test_type in series.keys(): 2701 if len(series[test_type]) == 0: 2702 continue 2703 2704 r = series[test_type][0] 2705 title = '%s (%s)' % ( 2706 r['unified_abbrev'], 2707 r['unified_name'] 2708 ) 2709 gp_data.write('\n\n"%s" "%s"\n' % (title, title)) 2710 2711 prev_date = None 2712 prev_year = None 2713 for r in series[test_type]: 2714 curr_date = gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d', 'utf8', gmDateTime.acc_days) 2715 if curr_date == prev_date: 2716 gp_data.write('\n# %s\n' % _('blank line inserted to allow for discontinued line drawing of same-day values')) 2717 if show_year: 2718 if r['clin_when'].year == prev_year: 2719 when_template = '%b %d %H:%M' 2720 else: 2721 when_template = '%b %d %H:%M (%Y)' 2722 prev_year = r['clin_when'].year 2723 else: 2724 when_template = '%b %d' 2725 val = r['val_num'] 2726 if val is None: 2727 val = r.estimate_numeric_value_from_alpha 2728 if val is None: 2729 continue # skip distinctly non-numericable values 2730 gp_data.write ('%s %s "%s" %s %s %s %s %s %s "%s"\n' % ( 2731 #r['clin_when'].strftime('%Y-%m-%d_%H:%M'), 2732 gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d_%H:%M', 'utf8', gmDateTime.acc_minutes), 2733 val, 2734 gmTools.coalesce(r['val_unit'], '"<?>"'), 2735 gmTools.coalesce(r['unified_target_min'], '"<?>"'), 2736 gmTools.coalesce(r['unified_target_max'], '"<?>"'), 2737 gmTools.coalesce(r['val_normal_min'], '"<?>"'), 2738 gmTools.coalesce(r['val_normal_max'], '"<?>"'), 2739 gmTools.coalesce(r['val_target_min'], '"<?>"'), 2740 gmTools.coalesce(r['val_target_max'], '"<?>"'), 2741 gmDateTime.pydt_strftime ( 2742 r['clin_when'], 2743 format = when_template, 2744 accuracy = gmDateTime.acc_minutes 2745 ) 2746 )) 2747 prev_date = curr_date 2748 2749 gp_data.close() 2750 2751 return filename
2752 2753 #============================================================
2754 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
2755 """Represents one lab result.""" 2756 2757 _cmd_fetch_payload = """ 2758 select *, xmin_test_result from v_results4lab_req 2759 where pk_result=%s""" 2760 _cmds_lock_rows_for_update = [ 2761 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update""" 2762 ] 2763 _cmds_store_payload = [ 2764 """update test_result set 2765 clin_when = %(val_when)s, 2766 narrative = %(progress_note_result)s, 2767 fk_type = %(pk_test_type)s, 2768 val_num = %(val_num)s::numeric, 2769 val_alpha = %(val_alpha)s, 2770 val_unit = %(val_unit)s, 2771 val_normal_min = %(val_normal_min)s, 2772 val_normal_max = %(val_normal_max)s, 2773 val_normal_range = %(val_normal_range)s, 2774 val_target_min = %(val_target_min)s, 2775 val_target_max = %(val_target_max)s, 2776 val_target_range = %(val_target_range)s, 2777 abnormality_indicator = %(abnormal)s, 2778 norm_ref_group = %(ref_group)s, 2779 note_provider = %(note_provider)s, 2780 material = %(material)s, 2781 material_detail = %(material_detail)s 2782 where pk = %(pk_result)s""", 2783 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s""" 2784 ] 2785 2786 _updatable_fields = [ 2787 'val_when', 2788 'progress_note_result', 2789 'val_num', 2790 'val_alpha', 2791 'val_unit', 2792 'val_normal_min', 2793 'val_normal_max', 2794 'val_normal_range', 2795 'val_target_min', 2796 'val_target_max', 2797 'val_target_range', 2798 'abnormal', 2799 'ref_group', 2800 'note_provider', 2801 'material', 2802 'material_detail' 2803 ] 2804 #--------------------------------------------------------
2805 - def __init__(self, aPK_obj=None, row=None):
2806 """Instantiate. 2807 2808 aPK_obj as dict: 2809 - patient_id 2810 - when_field (see view definition) 2811 - when 2812 - test_type 2813 - val_num 2814 - val_alpha 2815 - unit 2816 """ 2817 # instantiate from row data ? 2818 if aPK_obj is None: 2819 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 2820 return 2821 pk = aPK_obj 2822 # find PK from row data ? 2823 if type(aPK_obj) == dict: 2824 # sanity checks 2825 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]: 2826 raise gmExceptions.ConstructorError('parameter error: %s' % aPK_obj) 2827 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None): 2828 raise gmExceptions.ConstructorError('parameter error: val_num and val_alpha cannot both be None') 2829 # get PK 2830 where_snippets = [ 2831 'pk_patient=%(patient_id)s', 2832 'pk_test_type=%(test_type)s', 2833 '%s=%%(when)s' % aPK_obj['when_field'], 2834 'val_unit=%(unit)s' 2835 ] 2836 if aPK_obj['val_num'] is not None: 2837 where_snippets.append('val_num=%(val_num)s::numeric') 2838 if aPK_obj['val_alpha'] is not None: 2839 where_snippets.append('val_alpha=%(val_alpha)s') 2840 2841 where_clause = ' and '.join(where_snippets) 2842 cmd = "select pk_result from v_results4lab_req where %s" % where_clause 2843 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 2844 if data is None: 2845 raise gmExceptions.ConstructorError('error getting lab result for: %s' % aPK_obj) 2846 if len(data) == 0: 2847 raise gmExceptions.NoSuchClinItemError('no lab result for: %s' % aPK_obj) 2848 pk = data[0][0] 2849 # instantiate class 2850 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2851 #--------------------------------------------------------
2852 - def get_patient(self):
2853 cmd = """ 2854 select 2855 %s, 2856 vbp.title, 2857 vbp.firstnames, 2858 vbp.lastnames, 2859 vbp.dob 2860 from v_active_persons vbp 2861 where vbp.pk_identity = %%s""" % self._payload[self._idx['pk_patient']] 2862 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']]) 2863 return pat[0]
2864 2865 #============================================================
2866 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
2867 """Represents one lab request.""" 2868 2869 _cmd_fetch_payload = """ 2870 select *, xmin_lab_request from v_lab_requests 2871 where pk_request=%s""" 2872 _cmds_lock_rows_for_update = [ 2873 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update""" 2874 ] 2875 _cmds_store_payload = [ 2876 """update lab_request set 2877 request_id=%(request_id)s, 2878 lab_request_id=%(lab_request_id)s, 2879 clin_when=%(sampled_when)s, 2880 lab_rxd_when=%(lab_rxd_when)s, 2881 results_reported_when=%(results_reported_when)s, 2882 request_status=%(request_status)s, 2883 is_pending=%(is_pending)s::bool, 2884 narrative=%(progress_note)s 2885 where pk=%(pk_request)s""", 2886 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s""" 2887 ] 2888 _updatable_fields = [ 2889 'request_id', 2890 'lab_request_id', 2891 'sampled_when', 2892 'lab_rxd_when', 2893 'results_reported_when', 2894 'request_status', 2895 'is_pending', 2896 'progress_note' 2897 ] 2898 #--------------------------------------------------------
2899 - def __init__(self, aPK_obj=None, row=None):
2900 """Instantiate lab request. 2901 2902 The aPK_obj can be either a dict with the keys "req_id" 2903 and "lab" or a simple primary key. 2904 """ 2905 # instantiate from row data ? 2906 if aPK_obj is None: 2907 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 2908 return 2909 pk = aPK_obj 2910 # instantiate from "req_id" and "lab" ? 2911 if type(aPK_obj) == dict: 2912 # sanity check 2913 try: 2914 aPK_obj['req_id'] 2915 aPK_obj['lab'] 2916 except: 2917 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info()) 2918 raise gmExceptions.ConstructorError('[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj)) 2919 # generate query 2920 where_snippets = [] 2921 vals = {} 2922 where_snippets.append('request_id=%(req_id)s') 2923 if type(aPK_obj['lab']) == int: 2924 where_snippets.append('pk_test_org=%(lab)s') 2925 else: 2926 where_snippets.append('lab_name=%(lab)s') 2927 where_clause = ' and '.join(where_snippets) 2928 cmd = "select pk_request from v_lab_requests where %s" % where_clause 2929 # get pk 2930 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 2931 if data is None: 2932 raise gmExceptions.ConstructorError('[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj)) 2933 if len(data) == 0: 2934 raise gmExceptions.NoSuchClinItemError('[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj)) 2935 pk = data[0][0] 2936 # instantiate class 2937 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2938 #--------------------------------------------------------
2939 - def get_patient(self):
2940 cmd = """ 2941 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob 2942 from v_pat_items vpi, v_active_persons vbp 2943 where 2944 vpi.pk_item=%s 2945 and 2946 vbp.pk_identity=vpi.pk_patient""" 2947 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']]) 2948 if pat is None: 2949 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']]) 2950 return None 2951 if len(pat) == 0: 2952 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']]) 2953 return None 2954 return pat[0]
2955 2956 #============================================================ 2957 # convenience functions 2958 #------------------------------------------------------------
2959 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
2960 """Create or get lab request. 2961 2962 returns tuple (status, value): 2963 (True, lab request instance) 2964 (False, error message) 2965 (None, housekeeping_todo primary key) 2966 """ 2967 req = None 2968 aPK_obj = { 2969 'lab': lab, 2970 'req_id': req_id 2971 } 2972 try: 2973 req = cLabRequest (aPK_obj) 2974 except gmExceptions.NoSuchClinItemError as msg: 2975 _log.info('%s: will try to create lab request' % str(msg)) 2976 except gmExceptions.ConstructorError as msg: 2977 _log.exception(str(msg), sys.exc_info(), verbose=0) 2978 return (False, msg) 2979 # found 2980 if req is not None: 2981 db_pat = req.get_patient() 2982 if db_pat is None: 2983 _log.error('cannot cross-check patient on lab request') 2984 return (None, '') 2985 # yes but ambigous 2986 if pat_id != db_pat[0]: 2987 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat)) 2988 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $' 2989 to = 'user' 2990 prob = _('The lab request already exists but belongs to a different patient.') 2991 sol = _('Verify which patient this lab request really belongs to.') 2992 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat) 2993 cat = 'lab' 2994 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat) 2995 return (None, data) 2996 return (True, req) 2997 # not found 2998 queries = [] 2999 if type(lab) is int: 3000 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)" 3001 else: 3002 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, (select pk from test_org where internal_OBSOLETE_name=%s), %s)" 3003 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id])) 3004 cmd = "select currval('lab_request_pk_seq')" 3005 queries.append((cmd, [])) 3006 # insert new 3007 result, err = gmPG.run_commit('historica', queries, True) 3008 if result is None: 3009 return (False, err) 3010 try: 3011 req = cLabRequest(aPK_obj=result[0][0]) 3012 except gmExceptions.ConstructorError as msg: 3013 _log.exception(str(msg), sys.exc_info(), verbose=0) 3014 return (False, msg) 3015 return (True, req)
3016 #------------------------------------------------------------
3017 -def create_lab_result(patient_id=None, when_field=None, when=None, test_type=None, val_num=None, val_alpha=None, unit=None, encounter_id=None, request=None):
3018 tres = None 3019 data = { 3020 'patient_id': patient_id, 3021 'when_field': when_field, 3022 'when': when, 3023 'test_type': test_type, 3024 'val_num': val_num, 3025 'val_alpha': val_alpha, 3026 'unit': unit 3027 } 3028 try: 3029 tres = cLabResult(aPK_obj=data) 3030 # exists already, so fail 3031 _log.error('will not overwrite existing test result') 3032 _log.debug(str(tres)) 3033 return (None, tres) 3034 except gmExceptions.NoSuchClinItemError: 3035 _log.debug('test result not found - as expected, will create it') 3036 except gmExceptions.ConstructorError as msg: 3037 _log.exception(str(msg), sys.exc_info(), verbose=0) 3038 return (False, msg) 3039 if request is None: 3040 return (False, _('need lab request when inserting lab result')) 3041 # not found 3042 if encounter_id is None: 3043 encounter_id = request['pk_encounter'] 3044 queries = [] 3045 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)" 3046 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit])) 3047 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)" 3048 queries.append((cmd, [request['pk_request']])) 3049 cmd = "select currval('test_result_pk_seq')" 3050 queries.append((cmd, [])) 3051 # insert new 3052 result, err = gmPG.run_commit('historica', queries, True) 3053 if result is None: 3054 return (False, err) 3055 try: 3056 tres = cLabResult(aPK_obj=result[0][0]) 3057 except gmExceptions.ConstructorError as msg: 3058 _log.exception(str(msg), sys.exc_info(), verbose=0) 3059 return (False, msg) 3060 return (True, tres)
3061 #------------------------------------------------------------
3062 -def get_unreviewed_results(limit=50):
3063 # sanity check 3064 if limit < 1: 3065 limit = 1 3066 # retrieve one more row than needed so we know there's more available ;-) 3067 lim = limit + 1 3068 cmd = """ 3069 select pk_result 3070 from v_results4lab_req 3071 where reviewed is false 3072 order by pk_patient 3073 limit %s""" % lim 3074 rows = gmPG.run_ro_query('historica', cmd) 3075 if rows is None: 3076 _log.error('error retrieving unreviewed lab results') 3077 return (None, _('error retrieving unreviewed lab results')) 3078 if len(rows) == 0: 3079 return (False, []) 3080 # more than LIMIT rows ? 3081 if len(rows) == lim: 3082 more_avail = True 3083 # but deliver only LIMIT rows so that our assumption holds true... 3084 del rows[limit] 3085 else: 3086 more_avail = False 3087 results = [] 3088 for row in rows: 3089 try: 3090 results.append(cLabResult(aPK_obj=row[0])) 3091 except gmExceptions.ConstructorError: 3092 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0) 3093 return (more_avail, results)
3094 3095 #------------------------------------------------------------
3096 -def get_pending_requests(limit=250):
3097 lim = limit + 1 3098 cmd = "select pk from lab_request where is_pending is true limit %s" % lim 3099 rows = gmPG.run_ro_query('historica', cmd) 3100 if rows is None: 3101 _log.error('error retrieving pending lab requests') 3102 return (None, None) 3103 if len(rows) == 0: 3104 return (False, []) 3105 results = [] 3106 # more than LIMIT rows ? 3107 if len(rows) == lim: 3108 too_many = True 3109 # but deliver only LIMIT rows so that our assumption holds true... 3110 del rows[limit] 3111 else: 3112 too_many = False 3113 requests = [] 3114 for row in rows: 3115 try: 3116 requests.append(cLabRequest(aPK_obj=row[0])) 3117 except gmExceptions.ConstructorError: 3118 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0) 3119 return (too_many, requests)
3120 3121 #------------------------------------------------------------
3122 -def get_next_request_ID(lab=None, incrementor_func=None):
3123 """Get logically next request ID for given lab. 3124 3125 - incrementor_func: 3126 - if not supplied the next ID is guessed 3127 - if supplied it is applied to the most recently used ID 3128 """ 3129 if type(lab) == int: 3130 lab_snippet = 'vlr.fk_test_org=%s' 3131 else: 3132 lab_snippet = 'vlr.lab_name=%s' 3133 lab = str(lab) 3134 cmd = """ 3135 select request_id 3136 from lab_request lr0 3137 where lr0.clin_when = ( 3138 select max(vlr.sampled_when) 3139 from v_lab_requests vlr 3140 where %s 3141 )""" % lab_snippet 3142 rows = gmPG.run_ro_query('historica', cmd, None, lab) 3143 if rows is None: 3144 _log.warning('error getting most recently used request ID for lab [%s]' % lab) 3145 return '' 3146 if len(rows) == 0: 3147 return '' 3148 most_recent = rows[0][0] 3149 # apply supplied incrementor 3150 if incrementor_func is not None: 3151 try: 3152 next = incrementor_func(most_recent) 3153 except TypeError: 3154 _log.error('cannot call incrementor function [%s]' % str(incrementor_func)) 3155 return most_recent 3156 return next 3157 # try to be smart ourselves 3158 for pos in range(len(most_recent)): 3159 header = most_recent[:pos] 3160 trailer = most_recent[pos:] 3161 try: 3162 return '%s%s' % (header, str(int(trailer) + 1)) 3163 except ValueError: 3164 header = most_recent[:-1] 3165 trailer = most_recent[-1:] 3166 return '%s%s' % (header, chr(ord(trailer) + 1))
3167 3168 #============================================================
3169 -def calculate_bmi(mass=None, height=None, age=None):
3170 """Calculate BMI. 3171 3172 mass: kg 3173 height: cm 3174 age: not yet used 3175 3176 returns: 3177 (True/False, data) 3178 True: data = (bmi, lower_normal, upper_normal) 3179 False: data = error message 3180 """ 3181 converted, mass = gmTools.input2decimal(mass) 3182 if not converted: 3183 return False, 'mass: cannot convert <%s> to Decimal' % mass 3184 3185 converted, height = gmTools.input2decimal(height) 3186 if not converted: 3187 return False, 'height: cannot convert <%s> to Decimal' % height 3188 3189 approx_surface = (height / decimal.Decimal(100))**2 3190 bmi = mass / approx_surface 3191 3192 print(mass, height, '->', approx_surface, '->', bmi) 3193 3194 lower_normal_mass = 20.0 * approx_surface 3195 upper_normal_mass = 25.0 * approx_surface 3196 3197 return True, (bmi, lower_normal_mass, upper_normal_mass)
3198 3199 #============================================================ 3200 # main - unit testing 3201 #------------------------------------------------------------ 3202 if __name__ == '__main__': 3203 3204 if len(sys.argv) < 2: 3205 sys.exit() 3206 3207 if sys.argv[1] != 'test': 3208 sys.exit() 3209 3210 import time 3211 3212 gmI18N.activate_locale() 3213 gmI18N.install_domain() 3214 3215 #------------------------------------------
3216 - def test_create_test_result():
3217 tr = create_test_result ( 3218 encounter = 1, 3219 episode = 1, 3220 type = 1, 3221 intended_reviewer = 1, 3222 val_num = '12', 3223 val_alpha=None, 3224 unit = 'mg/dl' 3225 ) 3226 print(tr) 3227 return tr
3228 #------------------------------------------
3229 - def test_delete_test_result():
3230 tr = test_create_test_result() 3231 delete_test_result(tr)
3232 #------------------------------------------
3233 - def test_result():
3234 r = cTestResult(aPK_obj=6) 3235 #print r 3236 #print r.reference_ranges 3237 #print r.formatted_range 3238 #print r.temporally_closest_normal_range 3239 print(r.estimate_numeric_value_from_alpha)
3240 #------------------------------------------
3241 - def test_lab_result():
3242 print("test_result()") 3243 # lab_result = cLabResult(aPK_obj=4) 3244 data = { 3245 'patient_id': 12, 3246 'when_field': 'val_when', 3247 'when': '2000-09-17 18:23:00+02', 3248 'test_type': 9, 3249 'val_num': 17.3, 3250 'val_alpha': None, 3251 'unit': 'mg/l' 3252 } 3253 lab_result = cLabResult(aPK_obj=data) 3254 print(lab_result) 3255 fields = lab_result.get_fields() 3256 for field in fields: 3257 print(field, ':', lab_result[field]) 3258 print("updatable:", lab_result.get_updatable_fields()) 3259 print(time.time()) 3260 print(lab_result.get_patient()) 3261 print(time.time())
3262 #------------------------------------------
3263 - def test_request():
3264 print("test_request()") 3265 try: 3266 # lab_req = cLabRequest(aPK_obj=1) 3267 # lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2) 3268 data = { 3269 'req_id': 'EML#SC937-0176-CEC#11', 3270 'lab': 'Enterprise Main Lab' 3271 } 3272 lab_req = cLabRequest(aPK_obj=data) 3273 except gmExceptions.ConstructorError as msg: 3274 print("no such lab request:", msg) 3275 return 3276 print(lab_req) 3277 fields = lab_req.get_fields() 3278 for field in fields: 3279 print(field, ':', lab_req[field]) 3280 print("updatable:", lab_req.get_updatable_fields()) 3281 print(time.time()) 3282 print(lab_req.get_patient()) 3283 print(time.time())
3284 #--------------------------------------------------------
3285 - def test_unreviewed():
3286 data = get_unreviewed_results() 3287 for result in data: 3288 print(result)
3289 #--------------------------------------------------------
3290 - def test_pending():
3291 data = get_pending_requests() 3292 for result in data: 3293 print(result)
3294 #--------------------------------------------------------
3295 - def test_create_measurement_type():
3296 print(create_measurement_type ( 3297 lab = None, 3298 abbrev = 'tBZ2', 3299 unit = 'mg%', 3300 name = 'BZ (test 2)' 3301 ))
3302 #--------------------------------------------------------
3303 - def test_meta_test_type():
3304 mtt = cMetaTestType(aPK_obj = 1) 3305 print(mtt) 3306 print(get_meta_test_types())
3307 #--------------------------------------------------------
3308 - def test_test_type():
3309 tt = cMeasurementType(aPK_obj = 1) 3310 print(tt) 3311 print(get_measurement_types())
3312 #--------------------------------------------------------
3313 - def test_format_test_results():
3314 results = [ 3315 cTestResult(aPK_obj=1), 3316 cTestResult(aPK_obj=2), 3317 cTestResult(aPK_obj=3) 3318 # cTestResult(aPK_obj=4) 3319 ] 3320 print(format_test_results(results = results))
3321 #--------------------------------------------------------
3322 - def test_calculate_bmi():
3323 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3]) 3324 bmi, low, high = data 3325 print("BMI:", bmi) 3326 print("low:", low, "kg") 3327 print("hi :", high, "kg")
3328 3329 #--------------------------------------------------------
3330 - def test_test_panel():
3331 tp = cTestPanel(aPK_obj = 2) 3332 print(tp) 3333 print(tp.test_types) 3334 print(tp.format())
3335 3336 #--------------------------------------------------------
3337 - def test_get_most_recent_results_for_panel():
3338 tp = cTestPanel(aPK_obj = 1) 3339 #print tp.included_loincs 3340 #tp = cTestPanel(aPK_obj = 3) 3341 print(tp.format()) 3342 #most_recent = tp.get_most_recent_results(pk_patient = 12, group_by_meta_type = False) 3343 #most_recent = tp.get_most_recent_results(pk_patient = 138, group_by_meta_type = False) 3344 #print len(most_recent) 3345 most_recent = tp.get_most_recent_results(pk_patient = 12, group_by_meta_type = True, include_missing = True) 3346 #most_recent = tp.get_most_recent_results(pk_patient = 138, group_by_meta_type = True) 3347 print('found:', len(most_recent)) 3348 3349 for t in most_recent: 3350 print('--------------') 3351 if t['pk_meta_test_type'] is None: 3352 print("standalone") 3353 else: 3354 print("meta") 3355 print(t.format())
3356 3357 #--------------------------------------------------------
3358 - def test_get_most_recent_results_in_loinc_group():
3359 most_recent = get_most_recent_results_in_loinc_group ( 3360 #loincs = [u'pseudo LOINC [C-reactive protein (EML)::9] (v21->v22 test panel conversion)'], 3361 loincs = ['8867-4'], 3362 no_of_results = 2, 3363 patient = 12, 3364 consider_meta_type = True 3365 #consider_meta_type = False 3366 ) 3367 for t in most_recent: 3368 if t['pk_meta_test_type'] is None: 3369 print("---- standalone ----") 3370 else: 3371 print("---- meta ----") 3372 print(t.format())
3373 3374 #-------------------------------------------------------- 3375 3376 #test_result() 3377 #test_create_test_result() 3378 #test_delete_test_result() 3379 #test_create_measurement_type() 3380 #test_lab_result() 3381 #test_request() 3382 #test_create_result() 3383 #test_unreviewed() 3384 #test_pending() 3385 #test_meta_test_type() 3386 #test_test_type() 3387 #test_format_test_results() 3388 #test_calculate_bmi() 3389 #test_test_panel() 3390 test_get_most_recent_results_for_panel() 3391 #test_get_most_recent_results_in_loinc_group() 3392 3393 #============================================================ 3394