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