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 types 
  10  import sys 
  11  import logging 
  12  import codecs 
  13  import decimal 
  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          gmDateTime.init() 
  24  from Gnumed.pycommon import gmExceptions 
  25  from Gnumed.pycommon import gmBusinessDBObject 
  26  from Gnumed.pycommon import gmPG2 
  27  from Gnumed.pycommon import gmTools 
  28  from Gnumed.pycommon import gmDispatcher 
  29  from Gnumed.pycommon import gmHooks 
  30  from Gnumed.business import gmOrganization 
  31  from Gnumed.business import gmCoding 
  32   
  33   
  34  _log = logging.getLogger('gm.lab') 
  35   
  36  #============================================================ 
37 -def _on_test_result_modified():
38 """Always relates to the active patient.""" 39 gmHooks.run_hook_script(hook = u'after_test_result_modified')
40 41 gmDispatcher.connect(_on_test_result_modified, u'clin.test_result_mod_db') 42 43 #============================================================
44 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
45 """Represents one test org/lab.""" 46 _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s""" 47 _cmds_store_payload = [ 48 u"""UPDATE clin.test_org SET 49 fk_org_unit = %(pk_org_unit)s, 50 contact = gm.nullify_empty_string(%(test_org_contact)s), 51 comment = gm.nullify_empty_string(%(comment)s) 52 WHERE 53 pk = %(pk_test_org)s 54 AND 55 xmin = %(xmin_test_org)s 56 RETURNING 57 xmin AS xmin_test_org 58 """ 59 ] 60 _updatable_fields = [ 61 u'pk_org_unit', 62 u'test_org_contact', 63 u'comment' 64 ]
65 #------------------------------------------------------------
66 -def create_test_org(name=None, comment=None, pk_org_unit=None):
67 68 if name is None: 69 name = u'unassigned lab' 70 71 # get org unit 72 if pk_org_unit is None: 73 org = gmOrganization.org_exists(organization = name) 74 if org is None: 75 org = gmOrganization.create_org ( 76 organization = name, 77 category = u'Laboratory' 78 ) 79 org_unit = gmOrganization.create_org_unit ( 80 pk_organization = org['pk_org'], 81 unit = name 82 ) 83 pk_org_unit = org_unit['pk_org_unit'] 84 85 # test org exists ? 86 args = {'pk_unit': pk_org_unit} 87 cmd = u'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s' 88 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 89 90 if len(rows) == 0: 91 cmd = u'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk' 92 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 93 94 test_org = cTestOrg(aPK_obj = rows[0][0]) 95 if comment is not None: 96 comment = comment.strip() 97 test_org['comment'] = comment 98 test_org.save() 99 100 return test_org
101 #------------------------------------------------------------
102 -def delete_test_org(test_org=None):
103 args = {'pk': test_org} 104 cmd = u""" 105 DELETE FROM clin.test_org 106 WHERE 107 pk = %(pk)s 108 AND 109 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1) 110 AND 111 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1) 112 """ 113 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
114 #------------------------------------------------------------
115 -def get_test_orgs(order_by=u'unit'):
116 cmd = u'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by 117 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 118 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
119 120 #============================================================ 121 # test panels / profiles 122 #------------------------------------------------------------ 123 _SQL_get_test_panels = u"SELECT * FROM clin.v_test_panels WHERE %s" 124
125 -class cTestPanel(gmBusinessDBObject.cBusinessDBObject):
126 """Represents a grouping/listing of tests into a panel.""" 127 128 _cmd_fetch_payload = _SQL_get_test_panels % u"pk_test_panel = %s" 129 _cmds_store_payload = [ 130 u""" 131 UPDATE clin.test_panel SET 132 description = gm.nullify_empty_string(%(description)s), 133 comment = gm.nullify_empty_string(%(comment)s), 134 fk_test_types = %(pk_test_types)s 135 WHERE 136 pk = %(pk_test_panel)s 137 AND 138 xmin = %(xmin_test_panel)s 139 RETURNING 140 xmin AS xmin_test_panel 141 """ 142 ] 143 _updatable_fields = [ 144 u'description', 145 u'comment', 146 u'pk_test_types' 147 ] 148 #--------------------------------------------------------
149 - def format(self):
150 txt = _('Test panel "%s" [#%s]\n') % ( 151 self._payload[self._idx['description']], 152 self._payload[self._idx['pk_test_panel']] 153 ) 154 155 if self._payload[self._idx['comment']] is not None: 156 txt += u'\n' 157 txt += gmTools.wrap ( 158 text = self._payload[self._idx['comment']], 159 width = 50, 160 initial_indent = u' ', 161 subsequent_indent = u' ' 162 ) 163 txt += u'\n' 164 165 tts = self.test_types 166 if tts is not None: 167 txt += u'\n' 168 txt += _('Included test types:\n') 169 for tt in tts: 170 txt += u' %s: %s\n' % ( 171 tt['abbrev'], 172 tt['name'] 173 ) 174 175 codes = self.generic_codes 176 if len(codes) > 0: 177 txt += u'\n' 178 for c in codes: 179 txt += u'%s %s: %s (%s - %s)\n' % ( 180 (u' ' * left_margin), 181 c['code'], 182 c['term'], 183 c['name_short'], 184 c['version'] 185 ) 186 187 return txt
188 #--------------------------------------------------------
189 - def add_code(self, pk_code=None):
190 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 191 cmd = u"INSERT INTO clin.lnk_code2tst_pnl (fk_item, fk_generic_code) values (%(tp)s, %(code)s)" 192 args = { 193 'tp': self._payload[self._idx['pk_test_panel']], 194 'code': pk_code 195 } 196 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 197 return True
198 #--------------------------------------------------------
199 - def remove_code(self, pk_code=None):
200 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 201 cmd = u"DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code = %(code)s" 202 args = { 203 'tp': self._payload[self._idx['pk_test_panel']], 204 'code': pk_code 205 } 206 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 207 return True
208 #-------------------------------------------------------- 209 # properties 210 #--------------------------------------------------------
211 - def _get_test_types(self):
212 if self._payload[self._idx['pk_test_types']] is None: 213 return None 214 215 rows, idx = gmPG2.run_ro_queries ( 216 queries = [{ 217 'cmd': _SQL_get_test_types % u'pk_test_type IN %(pks)s ORDER BY unified_abbrev', 218 'args': {'pks': tuple(self._payload[self._idx['pk_test_types']])} 219 }], 220 get_col_idx = True 221 ) 222 return [ cMeasurementType(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_type'}) for r in rows ]
223 224 test_types = property(_get_test_types, lambda x:x) 225 #--------------------------------------------------------
226 - def _get_generic_codes(self):
227 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 228 return [] 229 230 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 231 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 232 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 233 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
234
235 - def _set_generic_codes(self, pk_codes):
236 queries = [] 237 # remove all codes 238 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 239 queries.append ({ 240 'cmd': u'DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code IN %(codes)s', 241 'args': { 242 'tp': self._payload[self._idx['pk_test_panel']], 243 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 244 } 245 }) 246 # add new codes 247 for pk_code in pk_codes: 248 queries.append ({ 249 'cmd': u'INSERT INTO clin.lnk_code2test_panel (fk_item, fk_generic_code) VALUES (%(tp)s, %(pk_code)s)', 250 'args': { 251 'tp': self._payload[self._idx['pk_test_panel']], 252 'pk_code': pk_code 253 } 254 }) 255 if len(queries) == 0: 256 return 257 # run it all in one transaction 258 rows, idx = gmPG2.run_rw_queries(queries = queries) 259 return
260 261 generic_codes = property(_get_generic_codes, _set_generic_codes) 262 #--------------------------------------------------------
263 - def get_most_recent_results(self, pk_patient=None, order_by=None):
264 return get_most_recent_results_for_panel ( 265 pk_patient = pk_patient, 266 pk_panel = self._payload[self._idx['pk_test_panel']], 267 order_by = order_by 268 )
269 #------------------------------------------------------------
270 -def get_test_panels(order_by=None):
271 if order_by is None: 272 order_by = u'true' 273 else: 274 order_by = u'true ORDER BY %s' % order_by 275 276 cmd = _SQL_get_test_panels % order_by 277 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 278 return [ cTestPanel(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_panel'}) for r in rows ]
279 280 #------------------------------------------------------------
281 -def create_test_panel(description=None):
282 283 args = {u'desc': description.strip()} 284 cmd = u""" 285 INSERT INTO clin.test_panel (description) 286 VALUES (gm.nullify_empty_string(%(desc)s)) 287 RETURNING pk 288 """ 289 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 290 291 return cTestPanel(aPK_obj = rows[0]['pk'])
292 293 #------------------------------------------------------------
294 -def delete_test_panel(pk=None):
295 args = {'pk': pk} 296 cmd = u"DELETE FROM clin.test_panel WHERE pk = %(pk)s" 297 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 298 return True
299 300 #============================================================
301 -class cMetaTestType(gmBusinessDBObject.cBusinessDBObject):
302 """Represents one meta test type under which actual test types can be aggregated.""" 303 304 _cmd_fetch_payload = u"SELECT *, xmin FROM clin.meta_test_type WHERE pk = %s" 305 _cmds_store_payload = [u""" 306 UPDATE clin.meta_test_type SET 307 abbrev = %(abbrev)s, 308 name = %(name)s, 309 loinc = gm.nullify_empty_string(%(loinc)s), 310 comment = gm.nullify_empty_string(%(comment)s) 311 WHERE 312 pk = %(pk)s 313 AND 314 xmin = %(xmin)s 315 RETURNING 316 xmin 317 """] 318 _updatable_fields = [ 319 u'abbrev', 320 u'name', 321 u'loinc', 322 u'comment' 323 ] 324 #--------------------------------------------------------
325 - def format(self, with_tests=False, patient=None):
326 txt = _('Meta (%s=aggregate) test type [#%s]\n\n') % (gmTools.u_sum, self._payload[self._idx['pk']]) 327 txt += _(' Name: %s (%s)\n') % ( 328 self._payload[self._idx['abbrev']], 329 self._payload[self._idx['name']] 330 ) 331 if self._payload[self._idx['loinc']] is not None: 332 txt += u' LOINC: %s (%s)\n' % self._payload[self._idx['loinc']] 333 if self._payload[self._idx['loinc']] is not None: 334 txt += _(' Comment: %s\n') % self._payload[self._idx['comment']] 335 if with_tests: 336 ttypes = self.included_test_types 337 if len(ttypes) > 0: 338 txt += _(' Aggregates the following test types:\n') 339 for ttype in ttypes: 340 txt += u' %s (%s)%s%s [#%s]\n' % ( 341 ttype['abbrev'], 342 ttype['name'], 343 gmTools.coalesce(ttype['conversion_unit'], u'', ', %s'), 344 gmTools.coalesce(ttype['loinc'], u'', u', LOINC: %s'), 345 ttype['pk_test_type'] 346 ) 347 if patient is not None: 348 txt += u'\n' 349 most_recent = self.get_most_recent_result(patient = patient) 350 if most_recent is not None: 351 txt += _(' Most recent (%s): %s%s%s') % ( 352 most_recent['clin_when'].strftime('%Y %b %d'), 353 most_recent['unified_val'], 354 gmTools.coalesce(most_recent['val_unit'], u'', u' %s'), 355 gmTools.coalesce(most_recent['abnormality_indicator'], u'', u' (%s)') 356 ) 357 oldest = self.get_oldest_result(patient = patient) 358 if oldest is not None: 359 txt += u'\n' 360 txt += _(' Oldest (%s): %s%s%s') % ( 361 oldest['clin_when'].strftime('%Y %b %d'), 362 oldest['unified_val'], 363 gmTools.coalesce(oldest['val_unit'], u'', u' %s'), 364 gmTools.coalesce(oldest['abnormality_indicator'], u'', u' (%s)') 365 ) 366 return txt
367 368 #--------------------------------------------------------
369 - def get_most_recent_result(self, patient=None):
370 args = { 371 'pat': patient, 372 'mttyp': self._payload[self._idx['pk']] 373 } 374 cmd = u""" 375 SELECT * FROM clin.v_test_results 376 WHERE 377 pk_patient = %(pat)s 378 AND 379 pk_meta_test_type = %(mttyp)s 380 ORDER BY clin_when DESC 381 LIMIT 1""" 382 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 383 if len(rows) == 0: 384 return None 385 386 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
387 388 #--------------------------------------------------------
389 - def get_oldest_result(self, patient=None):
390 args = { 391 'pat': patient, 392 'mttyp': self._payload[self._idx['pk']] 393 } 394 cmd = u""" 395 SELECT * FROM clin.v_test_results 396 WHERE 397 pk_patient = %(pat)s 398 AND 399 pk_meta_test_type = %(mttyp)s 400 ORDER BY clin_when 401 LIMIT 1""" 402 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 403 if len(rows) == 0: 404 return None 405 406 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
407 408 #-------------------------------------------------------- 409 # properties 410 #--------------------------------------------------------
411 - def _get_included_test_types(self):
412 cmd = _SQL_get_test_types % u'pk_meta_test_type = %(pk_meta)s' 413 args = {u'pk_meta': self._payload[self._idx['pk']]} 414 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 415 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
416 417 included_test_types = property(_get_included_test_types, lambda x:x)
418 419 #------------------------------------------------------------
420 -def create_meta_type(name=None, abbreviation=None, return_existing=False):
421 cmd = u""" 422 INSERT INTO clin.meta_test_type (name, abbrev) 423 SELECT 424 %(name)s, 425 %(abbr)s 426 WHERE NOT EXISTS ( 427 SELECT 1 FROM clin.meta_test_type 428 WHERE 429 name = %(name)s 430 AND 431 abbrev = %(abbr)s 432 ) 433 RETURNING *, xmin 434 """ 435 args = { 436 'name': name.strip(), 437 'abbr': abbreviation.strip() 438 } 439 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True, return_data = True) 440 if len(rows) == 0: 441 if not return_existing: 442 return None 443 cmd = u"SELECT *, xmin FROM clin.meta_test_type WHERE name = %(name)s and %(abbr)s" 444 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 445 446 return cMetaTestType(row = {'pk_field': 'pk', 'idx': idx, 'data': rows[0]})
447 448 #------------------------------------------------------------
449 -def delete_meta_type(meta_type=None):
450 cmd = u""" 451 DELETE FROM clin.meta_test_type 452 WHERE 453 pk = %(pk)s 454 AND 455 NOT EXISTS ( 456 SELECT 1 FROM clin.test_type 457 WHERE fk_meta_test_type = %(pk)s 458 )""" 459 args = {'pk': meta_type} 460 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
461 462 #------------------------------------------------------------
463 -def get_meta_test_types():
464 cmd = u'SELECT *, xmin FROM clin.meta_test_type' 465 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 466 return [ cMetaTestType(row = {'pk_field': 'pk', 'data': r, 'idx': idx}) for r in rows ]
467 468 #============================================================ 469 _SQL_get_test_types = u"SELECT * FROM clin.v_test_types WHERE %s" 470
471 -class cMeasurementType(gmBusinessDBObject.cBusinessDBObject):
472 """Represents one test result type.""" 473 474 _cmd_fetch_payload = _SQL_get_test_types % u"pk_test_type = %s" 475 476 _cmds_store_payload = [ 477 u"""UPDATE clin.test_type SET 478 abbrev = gm.nullify_empty_string(%(abbrev)s), 479 name = gm.nullify_empty_string(%(name)s), 480 loinc = gm.nullify_empty_string(%(loinc)s), 481 comment = gm.nullify_empty_string(%(comment_type)s), 482 conversion_unit = gm.nullify_empty_string(%(conversion_unit)s), 483 fk_test_org = %(pk_test_org)s, 484 fk_meta_test_type = %(pk_meta_test_type)s 485 WHERE 486 pk = %(pk_test_type)s 487 AND 488 xmin = %(xmin_test_type)s 489 RETURNING 490 xmin AS xmin_test_type""" 491 ] 492 493 _updatable_fields = [ 494 'abbrev', 495 'name', 496 'loinc', 497 'comment_type', 498 'conversion_unit', 499 'pk_test_org', 500 'pk_meta_test_type' 501 ] 502 #-------------------------------------------------------- 503 # properties 504 #--------------------------------------------------------
505 - def _get_in_use(self):
506 cmd = u'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)' 507 args = {'pk_type': self._payload[self._idx['pk_test_type']]} 508 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 509 return rows[0][0]
510 511 in_use = property(_get_in_use, lambda x:x) 512 #--------------------------------------------------------
513 - def get_most_recent_results(self, patient=None, no_of_results=1):
514 results = get_most_recent_results ( 515 test_type = self._payload[self._idx['pk_test_type']], 516 loinc = None, 517 no_of_results = no_of_results, 518 patient = patient 519 ) 520 if results is None: 521 if self._payload[self._idx['loinc']] is not None: 522 results = get_most_recent_results ( 523 test_type = None, 524 loinc = self._payload[self._idx['loinc']], 525 no_of_results = no_of_results, 526 patient = patient 527 ) 528 return results
529 530 #--------------------------------------------------------
531 - def get_oldest_result(self, patient=None):
532 result = get_oldest_result ( 533 test_type = self._payload[self._idx['pk_test_type']], 534 loinc = None, 535 patient = patient 536 ) 537 if result is None: 538 if self._payload[self._idx['loinc']] is not None: 539 result = get_oldest_result ( 540 test_type = None, 541 loinc = self._payload[self._idx['loinc']], 542 patient = patient 543 ) 544 return result
545 546 #--------------------------------------------------------
547 - def _get_test_panels(self):
548 if self._payload[self._idx['pk_test_panels']] is None: 549 return None 550 551 return [ cTestPanel(aPK_obj = pk) for pk in self._payload[self._idx['pk_test_panels']] ]
552 553 test_panels = property(_get_test_panels, lambda x:x) 554 555 #--------------------------------------------------------
556 - def get_meta_test_type(self, real_one_only=True):
557 if real_one_only is False: 558 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']]) 559 if self._payload[self._idx['is_fake_meta_type']]: 560 return None 561 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']])
562 563 meta_test_type = property(get_meta_test_type, lambda x:x) 564 #--------------------------------------------------------
565 - def get_temporally_closest_normal_range(self, unit, timestamp=None):
566 """Returns the closest test result which does have normal range information. 567 568 - needs <unit> 569 - if <timestamp> is None it will assume now() and thus return the most recent 570 """ 571 if timestamp is None: 572 timestamp = gmDateTime.pydt_now_here() 573 cmd = u""" 574 SELECT * FROM clin.v_test_results 575 WHERE 576 pk_test_type = %(pk_type)s 577 AND 578 val_unit = %(unit)s 579 AND 580 ( 581 (val_normal_min IS NOT NULL) 582 OR 583 (val_normal_max IS NOT NULL) 584 OR 585 (val_normal_range IS NOT NULL) 586 ) 587 ORDER BY 588 CASE 589 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 590 ELSE %(clin_when)s - clin_when 591 END 592 LIMIT 1""" 593 args = { 594 u'pk_type': self._payload[self._idx['pk_test_type']], 595 u'unit': unit, 596 u'clin_when': timestamp 597 } 598 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 599 if len(rows) == 0: 600 return None 601 r = rows[0] 602 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
603 604 #--------------------------------------------------------
605 - def get_temporally_closest_target_range(self, unit, patient, timestamp=None):
606 """Returns the closest test result which does have target range information. 607 608 - needs <unit> 609 - needs <patient> (as target will be per-patient) 610 - if <timestamp> is None it will assume now() and thus return the most recent 611 """ 612 if timestamp is None: 613 timestamp = gmDateTime.pydt_now_here() 614 cmd = u""" 615 SELECT * FROM clin.v_test_results 616 WHERE 617 pk_test_type = %(pk_type)s 618 AND 619 val_unit = %(unit)s 620 AND 621 pk_patient = %(pat)s 622 AND 623 ( 624 (val_target_min IS NOT NULL) 625 OR 626 (val_target_max IS NOT NULL) 627 OR 628 (val_target_range IS NOT NULL) 629 ) 630 ORDER BY 631 CASE 632 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 633 ELSE %(clin_when)s - clin_when 634 END 635 LIMIT 1""" 636 args = { 637 u'pk_type': self._payload[self._idx['pk_test_type']], 638 u'unit': unit, 639 u'pat': patient, 640 u'clin_when': timestamp 641 } 642 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 643 if len(rows) == 0: 644 return None 645 r = rows[0] 646 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
647 648 #--------------------------------------------------------
649 - def get_temporally_closest_unit(self, timestamp=None):
650 """Returns the unit of the closest test result. 651 652 - if <timestamp> is None it will assume now() and thus return the most recent 653 """ 654 if timestamp is None: 655 timestamp = gmDateTime.pydt_now_here() 656 cmd = u""" 657 SELECT val_unit FROM clin.v_test_results 658 WHERE 659 pk_test_type = %(pk_type)s 660 AND 661 val_unit IS NOT NULL 662 ORDER BY 663 CASE 664 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 665 ELSE %(clin_when)s - clin_when 666 END 667 LIMIT 1""" 668 args = { 669 u'pk_type': self._payload[self._idx['pk_test_type']], 670 u'clin_when': timestamp 671 } 672 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 673 if len(rows) == 0: 674 return None 675 return rows[0]['val_unit']
676 677 temporally_closest_unit = property(get_temporally_closest_unit, lambda x:x) 678 679 #--------------------------------------------------------
680 - def format(self, patient=None):
681 tt = u'' 682 tt += _('Test type "%s" (%s) [#%s]\n') % ( 683 self._payload[self._idx['name']], 684 self._payload[self._idx['abbrev']], 685 self._payload[self._idx['pk_test_type']] 686 ) 687 tt += u'\n' 688 tt += gmTools.coalesce(self._payload[self._idx['loinc']], u'', u' LOINC: %s\n') 689 tt += gmTools.coalesce(self._payload[self._idx['conversion_unit']], u'', _(' Conversion unit: %s\n')) 690 tt += gmTools.coalesce(self._payload[self._idx['comment_type']], u'', _(' Comment: %s\n')) 691 692 tt += u'\n' 693 tt += _('Lab details:\n') 694 tt += _(' Name: %s\n') % self._payload[self._idx['name_org']] 695 tt += gmTools.coalesce(self._payload[self._idx['contact_org']], u'', _(' Contact: %s\n')) 696 tt += gmTools.coalesce(self._payload[self._idx['comment_org']], u'', _(' Comment: %s\n')) 697 698 if self._payload[self._idx['is_fake_meta_type']] is False: 699 tt += u'\n' 700 tt += _('Aggregated under meta type:\n') 701 tt += _(' Name: %s - %s [#%s]\n') % ( 702 self._payload[self._idx['abbrev_meta']], 703 self._payload[self._idx['name_meta']], 704 self._payload[self._idx['pk_meta_test_type']] 705 ) 706 tt += gmTools.coalesce(self._payload[self._idx['loinc_meta']], u'', u' LOINC: %s\n') 707 tt += gmTools.coalesce(self._payload[self._idx['comment_meta']], u'', _(' Comment: %s\n')) 708 709 panels = self.test_panels 710 if panels is not None: 711 tt += u'\n' 712 tt += _('Listed in test panels:\n') 713 for panel in panels: 714 tt += _(' Panel "%s" [#%s]\n') % ( 715 panel['description'], 716 panel['pk_test_panel'] 717 ) 718 719 if patient is not None: 720 tt += u'\n' 721 result = self.get_most_recent_results(patient = patient, no_of_results = 1) 722 if result is not None: 723 tt += _(' Most recent (%s): %s%s%s') % ( 724 result['clin_when'].strftime('%Y-%m-%d'), 725 result['unified_val'], 726 gmTools.coalesce(result['val_unit'], u'', u' %s'), 727 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)') 728 ) 729 result = self.get_oldest_result(patient = patient) 730 if result is not None: 731 tt += u'\n' 732 tt += _(' Oldest (%s): %s%s%s') % ( 733 result['clin_when'].strftime('%Y-%m-%d'), 734 result['unified_val'], 735 gmTools.coalesce(result['val_unit'], u'', u' %s'), 736 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)') 737 ) 738 739 return tt
740 741 #------------------------------------------------------------
742 -def get_measurement_types(order_by=None):
743 cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s') 744 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 745 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
746 747 #------------------------------------------------------------
748 -def find_measurement_type(lab=None, abbrev=None, name=None):
749 750 if (abbrev is None) and (name is None): 751 raise ValueError('must have <abbrev> and/or <name> set') 752 753 where_snippets = [] 754 755 if lab is None: 756 where_snippets.append('pk_test_org IS NULL') 757 else: 758 try: 759 int(lab) 760 where_snippets.append('pk_test_org = %(lab)s') 761 except (TypeError, ValueError): 762 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)') 763 764 if abbrev is not None: 765 where_snippets.append('abbrev = %(abbrev)s') 766 767 if name is not None: 768 where_snippets.append('name = %(name)s') 769 770 where_clause = u' and '.join(where_snippets) 771 cmd = u"select * from clin.v_test_types where %s" % where_clause 772 args = {'lab': lab, 'abbrev': abbrev, 'name': name} 773 774 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 775 776 if len(rows) == 0: 777 return None 778 779 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx}) 780 return tt
781 782 #------------------------------------------------------------
783 -def delete_measurement_type(measurement_type=None):
784 cmd = u'delete from clin.test_type where pk = %(pk)s' 785 args = {'pk': measurement_type} 786 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
787 788 #------------------------------------------------------------
789 -def create_measurement_type(lab=None, abbrev=None, unit=None, name=None):
790 """Create or get test type.""" 791 792 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name) 793 # found ? 794 if ttype is not None: 795 return ttype 796 797 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit) 798 799 # not found, so create it 800 if unit is None: 801 _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit)) 802 raise ValueError('need <unit> to create test type') 803 804 # make query 805 cols = [] 806 val_snippets = [] 807 vals = {} 808 809 # lab 810 if lab is None: 811 lab = create_test_org()['pk_test_org'] 812 813 cols.append('fk_test_org') 814 try: 815 vals['lab'] = int(lab) 816 val_snippets.append('%(lab)s') 817 except: 818 vals['lab'] = lab 819 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)') 820 821 # code 822 cols.append('abbrev') 823 val_snippets.append('%(abbrev)s') 824 vals['abbrev'] = abbrev 825 826 # unit 827 cols.append('conversion_unit') 828 val_snippets.append('%(unit)s') 829 vals['unit'] = unit 830 831 # name 832 if name is not None: 833 cols.append('name') 834 val_snippets.append('%(name)s') 835 vals['name'] = name 836 837 col_clause = u', '.join(cols) 838 val_clause = u', '.join(val_snippets) 839 queries = [ 840 {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals}, 841 {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"} 842 ] 843 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True) 844 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx}) 845 846 return ttype
847 848 #============================================================
849 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
850 """Represents one test result.""" 851 852 _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s" 853 854 _cmds_store_payload = [ 855 u"""update clin.test_result set 856 clin_when = %(clin_when)s, 857 narrative = nullif(trim(%(comment)s), ''), 858 val_num = %(val_num)s, 859 val_alpha = nullif(trim(%(val_alpha)s), ''), 860 val_unit = nullif(trim(%(val_unit)s), ''), 861 val_normal_min = %(val_normal_min)s, 862 val_normal_max = %(val_normal_max)s, 863 val_normal_range = nullif(trim(%(val_normal_range)s), ''), 864 val_target_min = %(val_target_min)s, 865 val_target_max = %(val_target_max)s, 866 val_target_range = nullif(trim(%(val_target_range)s), ''), 867 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''), 868 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''), 869 note_test_org = nullif(trim(%(note_test_org)s), ''), 870 material = nullif(trim(%(material)s), ''), 871 material_detail = nullif(trim(%(material_detail)s), ''), 872 fk_intended_reviewer = %(pk_intended_reviewer)s, 873 fk_encounter = %(pk_encounter)s, 874 fk_episode = %(pk_episode)s, 875 fk_type = %(pk_test_type)s, 876 fk_request = %(pk_request)s 877 where 878 pk = %(pk_test_result)s and 879 xmin = %(xmin_test_result)s""", 880 u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s""" 881 ] 882 883 _updatable_fields = [ 884 'clin_when', 885 'comment', 886 'val_num', 887 'val_alpha', 888 'val_unit', 889 'val_normal_min', 890 'val_normal_max', 891 'val_normal_range', 892 'val_target_min', 893 'val_target_max', 894 'val_target_range', 895 'abnormality_indicator', 896 'norm_ref_group', 897 'note_test_org', 898 'material', 899 'material_detail', 900 'pk_intended_reviewer', 901 'pk_encounter', 902 'pk_episode', 903 'pk_test_type', 904 'pk_request' 905 ] 906 #--------------------------------------------------------
907 - def format(self, with_review=True, with_evaluation=True, with_ranges=True, with_episode=True, with_type_details=True, date_format='%Y %b %d %H:%M'):
908 909 # FIXME: add battery, request details 910 911 has_normal_min_or_max = ( 912 self._payload[self._idx['val_normal_min']] is not None 913 ) or ( 914 self._payload[self._idx['val_normal_max']] is not None 915 ) 916 if has_normal_min_or_max: 917 normal_min_max = u'%s - %s' % ( 918 gmTools.coalesce(self._payload[self._idx['val_normal_min']], u'?'), 919 gmTools.coalesce(self._payload[self._idx['val_normal_max']], u'?') 920 ) 921 else: 922 normal_min_max = u'' 923 924 has_clinical_min_or_max = ( 925 self._payload[self._idx['val_target_min']] is not None 926 ) or ( 927 self._payload[self._idx['val_target_max']] is not None 928 ) 929 if has_clinical_min_or_max: 930 clinical_min_max = u'%s - %s' % ( 931 gmTools.coalesce(self._payload[self._idx['val_target_min']], u'?'), 932 gmTools.coalesce(self._payload[self._idx['val_target_max']], u'?') 933 ) 934 else: 935 clinical_min_max = u'' 936 937 # header 938 tt = _(u'Result from %s \n') % gmDateTime.pydt_strftime ( 939 self._payload[self._idx['clin_when']], 940 date_format 941 ) 942 943 # basics 944 tt += u' ' + _(u'Type: "%(name)s" (%(abbr)s) [#%(pk_type)s]\n') % ({ 945 'name': self._payload[self._idx['name_tt']], 946 'abbr': self._payload[self._idx['abbrev_tt']], 947 'pk_type': self._payload[self._idx['pk_test_type']] 948 }) 949 tt += u' ' + _(u'Result: %(val)s%(unit)s%(ind)s [#%(pk_result)s]\n') % ({ 950 'val': self._payload[self._idx['unified_val']], 951 'unit': gmTools.coalesce(self._payload[self._idx['val_unit']], u'', u' %s'), 952 'ind': gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], u'', u' (%s)'), 953 'pk_result': self._payload[self._idx['pk_test_result']] 954 }) 955 tmp = (u'%s%s' % ( 956 gmTools.coalesce(self._payload[self._idx['name_test_org']], u''), 957 gmTools.coalesce(self._payload[self._idx['contact_test_org']], u'', u' (%s)'), 958 )).strip() 959 if tmp != u'': 960 tt += u' ' + _(u'Source: %s\n') % tmp 961 tt += u'\n' 962 963 if with_evaluation: 964 norm_eval = None 965 if self._payload[self._idx['val_num']] is not None: 966 # 1) normal range 967 # lowered ? 968 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']]): 969 try: 970 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_normal_min']] 971 except ZeroDivisionError: 972 percent = None 973 if percent is not None: 974 if percent < 6: 975 norm_eval = _(u'%.1f %% of the normal lower limit') % percent 976 else: 977 norm_eval = _(u'%.0f %% of the normal lower limit') % percent 978 # raised ? 979 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']]): 980 try: 981 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_normal_max']] 982 except ZeroDivisionError: 983 x_times = None 984 if x_times is not None: 985 if x_times < 10: 986 norm_eval = _(u'%.1f times the normal upper limit') % x_times 987 else: 988 norm_eval = _(u'%.0f times the normal upper limit') % x_times 989 if norm_eval is not None: 990 tt += u' (%s)\n' % norm_eval 991 # #------------------------------------- 992 # # this idea was shot down on the list 993 # #------------------------------------- 994 # # bandwidth of deviation 995 # if None not in [self._payload[self._idx['val_normal_min']], self._payload[self._idx['val_normal_max']]]: 996 # normal_width = self._payload[self._idx['val_normal_max']] - self._payload[self._idx['val_normal_min']] 997 # deviation_from_normal_range = None 998 # # below ? 999 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_normal_min']]: 1000 # deviation_from_normal_range = self._payload[self._idx['val_normal_min']] - self._payload[self._idx['val_num']] 1001 # # above ? 1002 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_normal_max']]: 1003 # deviation_from_normal_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_normal_max']] 1004 # if deviation_from_normal_range is None: 1005 # try: 1006 # times_deviation = deviation_from_normal_range / normal_width 1007 # except ZeroDivisionError: 1008 # times_deviation = None 1009 # if times_deviation is not None: 1010 # if times_deviation < 10: 1011 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation 1012 # else: 1013 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation 1014 # #------------------------------------- 1015 1016 # 2) clinical target range 1017 norm_eval = None 1018 # lowered ? 1019 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']]): 1020 try: 1021 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_target_min']] 1022 except ZeroDivisionError: 1023 percent = None 1024 if percent is not None: 1025 if percent < 6: 1026 norm_eval = _(u'%.1f %% of the target lower limit') % percent 1027 else: 1028 norm_eval = _(u'%.0f %% of the target lower limit') % percent 1029 # raised ? 1030 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']]): 1031 try: 1032 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_target_max']] 1033 except ZeroDivisionError: 1034 x_times = None 1035 if x_times is not None: 1036 if x_times < 10: 1037 norm_eval = _(u'%.1f times the target upper limit') % x_times 1038 else: 1039 norm_eval = _(u'%.0f times the target upper limit') % x_times 1040 if norm_eval is not None: 1041 tt += u' (%s)\n' % norm_eval 1042 # #------------------------------------- 1043 # # this idea was shot down on the list 1044 # #------------------------------------- 1045 # # bandwidth of deviation 1046 # if None not in [self._payload[self._idx['val_target_min']], self._payload[self._idx['val_target_max']]]: 1047 # normal_width = self._payload[self._idx['val_target_max']] - self._payload[self._idx['val_target_min']] 1048 # deviation_from_target_range = None 1049 # # below ? 1050 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_target_min']]: 1051 # deviation_from_target_range = self._payload[self._idx['val_target_min']] - self._payload[self._idx['val_num']] 1052 # # above ? 1053 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_target_max']]: 1054 # deviation_from_target_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_target_max']] 1055 # if deviation_from_target_range is None: 1056 # try: 1057 # times_deviation = deviation_from_target_range / normal_width 1058 # except ZeroDivisionError: 1059 # times_deviation = None 1060 # if times_deviation is not None: 1061 # if times_deviation < 10: 1062 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation 1063 # else: 1064 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation 1065 # #------------------------------------- 1066 1067 if with_ranges: 1068 tt += u' ' + _(u'Standard normal range: %(norm_min_max)s%(norm_range)s \n') % ({ 1069 'norm_min_max': normal_min_max, 1070 'norm_range': gmTools.coalesce ( 1071 self._payload[self._idx['val_normal_range']], 1072 u'', 1073 gmTools.bool2subst ( 1074 has_normal_min_or_max, 1075 u' / %s', 1076 u'%s' 1077 ) 1078 ) 1079 }) 1080 if self._payload[self._idx['norm_ref_group']] is not None: 1081 tt += u' ' + _(u'Reference group: %s\n') % self._payload[self._idx['norm_ref_group']] 1082 tt += u' ' + _(u'Clinical target range: %(clin_min_max)s%(clin_range)s \n') % ({ 1083 'clin_min_max': clinical_min_max, 1084 'clin_range': gmTools.coalesce ( 1085 self._payload[self._idx['val_target_range']], 1086 u'', 1087 gmTools.bool2subst ( 1088 has_clinical_min_or_max, 1089 u' / %s', 1090 u'%s' 1091 ) 1092 ) 1093 }) 1094 1095 # metadata 1096 if self._payload[self._idx['comment']] is not None: 1097 tt += u' ' + _(u'Doc: %s\n') % _(u'\n Doc: ').join(self._payload[self._idx['comment']].split(u'\n')) 1098 if self._payload[self._idx['note_test_org']] is not None: 1099 tt += u' ' + _(u'Lab: %s\n') % _(u'\n Lab: ').join(self._payload[self._idx['note_test_org']].split(u'\n')) 1100 if with_episode: 1101 tt += u' ' + _(u'Episode: %s\n') % self._payload[self._idx['episode']] 1102 if self._payload[self._idx['health_issue']] is not None: 1103 tt += u' ' + _(u'Issue: %s\n') % self._payload[self._idx['health_issue']] 1104 if self._payload[self._idx['material']] is not None: 1105 tt += u' ' + _(u'Material: %s\n') % self._payload[self._idx['material']] 1106 if self._payload[self._idx['material_detail']] is not None: 1107 tt += u' ' + _(u'Details: %s\n') % self._payload[self._idx['material_detail']] 1108 tt += u'\n' 1109 1110 if with_review: 1111 if self._payload[self._idx['reviewed']]: 1112 review = gmDateTime.pydt_strftime ( 1113 self._payload[self._idx['last_reviewed']], 1114 date_format 1115 ) 1116 else: 1117 review = _('not yet') 1118 tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({ 1119 'sig_hand': gmTools.u_writing_hand, 1120 'reviewed': review 1121 }) 1122 tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst ( 1123 self._payload[self._idx['you_are_responsible']], 1124 _('you'), 1125 self._payload[self._idx['responsible_reviewer']] 1126 ) 1127 if self._payload[self._idx['reviewed']]: 1128 tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({ 1129 'reviewer': gmTools.bool2subst ( 1130 self._payload[self._idx['review_by_you']], 1131 _('you'), 1132 gmTools.coalesce(self._payload[self._idx['last_reviewer']], u'?') 1133 ) 1134 }) 1135 tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({ 1136 'abnormal': gmTools.bool2subst ( 1137 self._payload[self._idx['is_technically_abnormal']], 1138 _('yes'), 1139 _('no'), 1140 u'?' 1141 ) 1142 }) 1143 tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({ 1144 'relevant': gmTools.bool2subst ( 1145 self._payload[self._idx['is_clinically_relevant']], 1146 _('yes'), 1147 _('no'), 1148 u'?' 1149 ) 1150 }) 1151 if self._payload[self._idx['review_comment']] is not None: 1152 tt += u' ' + _(u' Comment: %s\n') % self._payload[self._idx['review_comment']].strip() 1153 tt += u'\n' 1154 1155 # type 1156 if with_type_details: 1157 tt += _(u'Test type details:\n') 1158 tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s) [#%(pk_u_type)s]\n') % ({ 1159 'name_meta': gmTools.coalesce(self._payload[self._idx['name_meta']], u''), 1160 'abbrev_meta': gmTools.coalesce(self._payload[self._idx['abbrev_meta']], u''), 1161 'pk_u_type': self._payload[self._idx['pk_meta_test_type']] 1162 }) 1163 if self._payload[self._idx['comment_tt']] is not None: 1164 tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(self._payload[self._idx['comment_tt']].split(u'\n')) 1165 if self._payload[self._idx['comment_meta']] is not None: 1166 tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(self._payload[self._idx['comment_meta']].split(u'\n')) 1167 tt += u'\n' 1168 1169 if with_review: 1170 tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({ 1171 'row_ver': self._payload[self._idx['row_version']], 1172 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']],date_format), 1173 'mod_by': self._payload[self._idx['modified_by']] 1174 }) 1175 1176 return tt
1177 #--------------------------------------------------------
1179 """Returns the closest test result which does have normal range information.""" 1180 if self._payload[self._idx['val_normal_min']] is not None: 1181 return self 1182 if self._payload[self._idx['val_normal_max']] is not None: 1183 return self 1184 if self._payload[self._idx['val_normal_range']] is not None: 1185 return self 1186 cmd = u""" 1187 SELECT * from clin.v_test_results 1188 WHERE 1189 pk_type = %(pk_type)s 1190 AND 1191 val_unit = %(unit)s 1192 AND 1193 ( 1194 (val_normal_min IS NOT NULL) 1195 OR 1196 (val_normal_max IS NOT NULL) 1197 OR 1198 (val_normal_range IS NOT NULL) 1199 ) 1200 ORDER BY 1201 CASE 1202 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s 1203 ELSE %(clin_when)s - clin_when 1204 END 1205 LIMIT 1""" 1206 args = { 1207 u'pk_type': self._payload[self._idx['pk_test_type']], 1208 u'unit': self._payload[self._idx['val_unit']], 1209 u'clin_when': self._payload[self._idx['clin_when']] 1210 } 1211 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1212 if len(rows) == 0: 1213 return None 1214 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1215 1216 temporally_closest_normal_range = property(_get_temporally_closest_normal_range, lambda x:x) 1217 #--------------------------------------------------------
1218 - def _get_formatted_range(self):
1219 1220 has_normal_min_or_max = ( 1221 self._payload[self._idx['val_normal_min']] is not None 1222 ) or ( 1223 self._payload[self._idx['val_normal_max']] is not None 1224 ) 1225 if has_normal_min_or_max: 1226 normal_min_max = u'%s - %s' % ( 1227 gmTools.coalesce(self._payload[self._idx['val_normal_min']], u'?'), 1228 gmTools.coalesce(self._payload[self._idx['val_normal_max']], u'?') 1229 ) 1230 1231 has_clinical_min_or_max = ( 1232 self._payload[self._idx['val_target_min']] is not None 1233 ) or ( 1234 self._payload[self._idx['val_target_max']] is not None 1235 ) 1236 if has_clinical_min_or_max: 1237 clinical_min_max = u'%s - %s' % ( 1238 gmTools.coalesce(self._payload[self._idx['val_target_min']], u'?'), 1239 gmTools.coalesce(self._payload[self._idx['val_target_max']], u'?') 1240 ) 1241 1242 if has_clinical_min_or_max: 1243 return _('Target: %(clin_min_max)s%(clin_range)s') % ({ 1244 'clin_min_max': clinical_min_max, 1245 'clin_range': gmTools.coalesce ( 1246 self._payload[self._idx['val_target_range']], 1247 u'', 1248 gmTools.bool2subst ( 1249 has_clinical_min_or_max, 1250 u' / %s', 1251 u'%s' 1252 ) 1253 ) 1254 }) 1255 1256 if has_normal_min_or_max: 1257 return _('Norm: %(norm_min_max)s%(norm_range)s') % ({ 1258 'norm_min_max': normal_min_max, 1259 'norm_range': gmTools.coalesce ( 1260 self._payload[self._idx['val_normal_range']], 1261 u'', 1262 gmTools.bool2subst ( 1263 has_normal_min_or_max, 1264 u' / %s', 1265 u'%s' 1266 ) 1267 ) 1268 }) 1269 1270 if self._payload[self._idx['val_target_range']] is not None: 1271 return _('Target: %s') % self._payload[self._idx['val_target_range']], 1272 1273 if self._payload[self._idx['val_normal_range']] is not None: 1274 return _('Norm: %s') % self._payload[self._idx['val_normal_range']] 1275 1276 return None
1277 1278 formatted_range = property(_get_formatted_range, lambda x:x) 1279 #--------------------------------------------------------
1280 - def _get_test_type(self):
1281 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
1282 1283 test_type = property(_get_test_type, lambda x:x) 1284 #--------------------------------------------------------
1286 # 1) the user is right (review) 1287 if self._payload[self._idx['is_technically_abnormal']] is False: 1288 return False 1289 # 2) the lab is right (result.abnormality_indicator) 1290 indicator = self._payload[self._idx['abnormality_indicator']] 1291 if indicator is not None: 1292 indicator = indicator.strip() 1293 if indicator != u'': 1294 if indicator.strip(u'+') == u'': 1295 return True 1296 if indicator.strip(u'-') == u'': 1297 return False 1298 # 3) non-numerical value ? 1299 if self._payload[self._idx['val_num']] is None: 1300 return None 1301 # 4) the target range is right 1302 target_max = self._payload[self._idx['val_target_max']] 1303 if target_max is not None: 1304 if target_max < self._payload[self._idx['val_num']]: 1305 return True 1306 # 4) the normal range is right 1307 normal_max = self._payload[self._idx['val_normal_max']] 1308 if normal_max is not None: 1309 if normal_max < self._payload[self._idx['val_num']]: 1310 return True 1311 return None
1312 1313 is_considered_elevated = property(_get_is_considered_elevated, lambda x:x) 1314 #--------------------------------------------------------
1315 - def _get_is_considered_lowered(self):
1316 # 1) the user is right (review) 1317 if self._payload[self._idx['is_technically_abnormal']] is False: 1318 return False 1319 # 2) the lab is right (result.abnormality_indicator) 1320 indicator = self._payload[self._idx['abnormality_indicator']] 1321 if indicator is not None: 1322 indicator = indicator.strip() 1323 if indicator != u'': 1324 if indicator.strip(u'+') == u'': 1325 return False 1326 if indicator.strip(u'-') == u'': 1327 return True 1328 # 3) non-numerical value ? 1329 if self._payload[self._idx['val_num']] is None: 1330 return None 1331 # 4) the target range is right 1332 target_min = self._payload[self._idx['val_target_min']] 1333 if target_min is not None: 1334 if target_min > self._payload[self._idx['val_num']]: 1335 return True 1336 # 4) the normal range is right 1337 normal_min = self._payload[self._idx['val_normal_min']] 1338 if normal_min is not None: 1339 if normal_min > self._payload[self._idx['val_num']]: 1340 return True 1341 return None
1342 1343 is_considered_lowered = property(_get_is_considered_lowered, lambda x:x) 1344 #--------------------------------------------------------
1346 if self.is_considered_lowered is True: 1347 return True 1348 if self.is_considered_elevated is True: 1349 return True 1350 if (self.is_considered_lowered is False) and (self.is_considered_elevated is False): 1351 return False 1352 return self._payload[self._idx['is_technically_abnormal']]
1353 1354 is_considered_abnormal = property(_get_is_considered_abnormal, lambda x:x) 1355 #--------------------------------------------------------
1357 # 1) the user is right 1358 if self._payload[self._idx['is_technically_abnormal']] is False: 1359 return u'' 1360 # 2) the lab is right (result.abnormality_indicator) 1361 indicator = self._payload[self._idx['abnormality_indicator']] 1362 if indicator is not None: 1363 indicator = indicator.strip() 1364 if indicator != u'': 1365 return indicator 1366 # 3) non-numerical value ? then we can't know more 1367 if self._payload[self._idx['val_num']] is None: 1368 return None 1369 # 4) the target range is right 1370 target_min = self._payload[self._idx['val_target_min']] 1371 if target_min is not None: 1372 if target_min > self._payload[self._idx['val_num']]: 1373 return u'-' 1374 target_max = self._payload[self._idx['val_target_max']] 1375 if target_max is not None: 1376 if target_max < self._payload[self._idx['val_num']]: 1377 return u'+' 1378 # 4) the normal range is right 1379 normal_min = self._payload[self._idx['val_normal_min']] 1380 if normal_min is not None: 1381 if normal_min > self._payload[self._idx['val_num']]: 1382 return u'-' 1383 normal_max = self._payload[self._idx['val_normal_max']] 1384 if normal_max is not None: 1385 if normal_max < self._payload[self._idx['val_num']]: 1386 return u'+' 1387 # reviewed, abnormal, but no indicator available 1388 if self._payload[self._idx['is_technically_abnormal']] is True: 1389 return gmTools.u_plus_minus 1390 1391 return None
1392 1393 formatted_abnormality_indicator = property(_get_formatted_abnormality_indicator, lambda x:x) 1394 #--------------------------------------------------------
1395 - def _get_is_long_text(self):
1396 if self._payload[self._idx['val_alpha']] is None: 1397 return False 1398 lines = gmTools.strip_empty_lines(text = self._payload[self._idx['val_alpha']], eol = u'\n', return_list = True) 1399 if len(lines) > 4: 1400 return True 1401 return False
1402 1403 is_long_text = property(_get_is_long_text, lambda x:x) 1404 #--------------------------------------------------------
1406 if self._payload[self._idx['val_alpha']] is None: 1407 return None 1408 val = self._payload[self._idx['val_alpha']].lstrip() 1409 if val[0] == u'<': 1410 factor = decimal.Decimal(0.5) 1411 val = val[1:] 1412 elif val[0] == u'>': 1413 factor = 2 1414 val = val[1:] 1415 else: 1416 return None 1417 success, val = gmTools.input2decimal(initial = val) 1418 if not success: 1419 return None 1420 return val * factor
1421 1422 estimate_numeric_value_from_alpha = property(_get_estimate_numeric_value_from_alpha, lambda x:x) 1423 #--------------------------------------------------------
1424 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
1425 1426 # FIXME: this is not concurrency safe 1427 if self._payload[self._idx['reviewed']]: 1428 self.__change_existing_review ( 1429 technically_abnormal = technically_abnormal, 1430 clinically_relevant = clinically_relevant, 1431 comment = comment 1432 ) 1433 else: 1434 # do not sign off unreviewed results if 1435 # NOTHING AT ALL is known about them 1436 if technically_abnormal is None: 1437 if clinically_relevant is None: 1438 comment = gmTools.none_if(comment, u'', strip_string = True) 1439 if comment is None: 1440 if make_me_responsible is False: 1441 return True 1442 self.__set_new_review ( 1443 technically_abnormal = technically_abnormal, 1444 clinically_relevant = clinically_relevant, 1445 comment = comment 1446 ) 1447 1448 if make_me_responsible is True: 1449 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user" 1450 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 1451 self['pk_intended_reviewer'] = rows[0][0] 1452 self.save_payload() 1453 return 1454 1455 self.refetch_payload()
1456 #--------------------------------------------------------
1457 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
1458 1459 if desired_earlier_results < 1: 1460 raise ValueError('<desired_earlier_results> must be > 0') 1461 1462 if desired_later_results < 1: 1463 raise ValueError('<desired_later_results> must be > 0') 1464 1465 args = { 1466 'pat': self._payload[self._idx['pk_patient']], 1467 'ttyp': self._payload[self._idx['pk_test_type']], 1468 'tloinc': self._payload[self._idx['loinc_tt']], 1469 'mtyp': self._payload[self._idx['pk_meta_test_type']], 1470 'mloinc': self._payload[self._idx['loinc_meta']], 1471 'when': self._payload[self._idx['clin_when']], 1472 'offset': max_offset 1473 } 1474 WHERE = u'((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))' 1475 WHERE_meta = u'((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))' 1476 if max_offset is not None: 1477 WHERE = WHERE + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 1478 WHERE_meta = WHERE_meta + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 1479 1480 SQL = u""" 1481 SELECT * FROM clin.v_test_results 1482 WHERE 1483 pk_patient = %%(pat)s 1484 AND 1485 clin_when %s %%(when)s 1486 AND 1487 %s 1488 ORDER BY clin_when 1489 LIMIT %s""" 1490 1491 # get earlier results 1492 earlier_results = [] 1493 # by type 1494 cmd = SQL % (u'<', WHERE, desired_earlier_results) 1495 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1496 if len(rows) > 0: 1497 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1498 # by meta type ? 1499 missing_results = desired_earlier_results - len(earlier_results) 1500 if missing_results > 0: 1501 cmd = SQL % (u'<', WHERE_meta, missing_results) 1502 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1503 if len(rows) > 0: 1504 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1505 1506 # get later results 1507 later_results = [] 1508 # by type 1509 cmd = SQL % (u'>', WHERE, desired_later_results) 1510 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1511 if len(rows) > 0: 1512 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1513 # by meta type ? 1514 missing_results = desired_later_results - len(later_results) 1515 if missing_results > 0: 1516 cmd = SQL % (u'>', WHERE_meta, missing_results) 1517 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1518 if len(rows) > 0: 1519 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1520 1521 return earlier_results, later_results
1522 #-------------------------------------------------------- 1523 # internal API 1524 #--------------------------------------------------------
1525 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1526 """Add a review to a row. 1527 1528 - if technically abnormal is not provided/None it will be set 1529 to True if the lab's indicator has a meaningful value 1530 - if clinically relevant is not provided/None it is set to 1531 whatever technically abnormal is 1532 """ 1533 if technically_abnormal is None: 1534 technically_abnormal = False 1535 if self._payload[self._idx['abnormality_indicator']] is not None: 1536 if self._payload[self._idx['abnormality_indicator']].strip() != u'': 1537 technically_abnormal = True 1538 1539 if clinically_relevant is None: 1540 clinically_relevant = technically_abnormal 1541 1542 cmd = u""" 1543 INSERT INTO clin.reviewed_test_results ( 1544 fk_reviewed_row, 1545 is_technically_abnormal, 1546 clinically_relevant, 1547 comment 1548 ) VALUES ( 1549 %(pk)s, 1550 %(abnormal)s, 1551 %(relevant)s, 1552 gm.nullify_empty_string(%(cmt)s) 1553 )""" 1554 args = { 1555 'pk': self._payload[self._idx['pk_test_result']], 1556 'abnormal': technically_abnormal, 1557 'relevant': clinically_relevant, 1558 'cmt': comment 1559 } 1560 1561 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1562 #--------------------------------------------------------
1563 - def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1564 """Change a review on a row. 1565 1566 - if technically abnormal/clinically relevant are 1567 None they are not set 1568 """ 1569 args = { 1570 'pk_row': self._payload[self._idx['pk_test_result']], 1571 'abnormal': technically_abnormal, 1572 'relevant': clinically_relevant, 1573 'cmt': comment 1574 } 1575 1576 set_parts = [ 1577 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)', 1578 u'comment = gm.nullify_empty_string(%(cmt)s)' 1579 ] 1580 1581 if technically_abnormal is not None: 1582 set_parts.append(u'is_technically_abnormal = %(abnormal)s') 1583 1584 if clinically_relevant is not None: 1585 set_parts.append(u'clinically_relevant = %(relevant)s') 1586 1587 cmd = u""" 1588 UPDATE clin.reviewed_test_results SET 1589 %s 1590 WHERE 1591 fk_reviewed_row = %%(pk_row)s 1592 """ % u',\n '.join(set_parts) 1593 1594 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1595 1596 #------------------------------------------------------------
1597 -def get_test_results(pk_patient=None, encounters=None, episodes=None, order_by=None):
1598 1599 where_parts = [] 1600 1601 if pk_patient is not None: 1602 where_parts.append(u'pk_patient = %(pat)s') 1603 args = {'pat': pk_patient} 1604 1605 # if tests is not None: 1606 # where_parts.append(u'pk_test_type IN %(tests)s') 1607 # args['tests'] = tuple(tests) 1608 1609 if encounters is not None: 1610 where_parts.append(u'pk_encounter IN %(encs)s') 1611 args['encs'] = tuple(encounters) 1612 1613 if episodes is not None: 1614 where_parts.append(u'pk_episode IN %(epis)s') 1615 args['epis'] = tuple(episodes) 1616 1617 if order_by is None: 1618 order_by = u'' 1619 else: 1620 order_by = u'ORDER BY %s' % order_by 1621 1622 cmd = u""" 1623 SELECT * FROM clin.v_test_results 1624 WHERE %s 1625 %s 1626 """ % ( 1627 u' AND '.join(where_parts), 1628 order_by 1629 ) 1630 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1631 1632 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ] 1633 return tests
1634 1635 #------------------------------------------------------------
1636 -def get_most_recent_results_for_panel(pk_patient=None, pk_panel=None, order_by=None):
1637 1638 if order_by is None: 1639 order_by = u'' 1640 else: 1641 order_by = u'ORDER BY %s' % order_by 1642 1643 args = { 1644 'pat': pk_patient, 1645 'pnl': pk_panel 1646 } 1647 cmd = u""" 1648 SELECT c_vtr.* 1649 FROM ( 1650 -- max(clin_when) per test_type-in-panel for patient 1651 SELECT 1652 pk_test_type, 1653 MAX(clin_when) AS max_clin_when 1654 FROM clin.v_test_results 1655 WHERE 1656 pk_patient = %%(pat)s 1657 AND 1658 pk_test_type = ANY ( 1659 (SELECT fk_test_types FROM clin.test_panel WHERE pk = %%(pnl)s)::int[] 1660 ) 1661 GROUP BY pk_test_type 1662 ) AS latest_results 1663 INNER JOIN clin.v_test_results c_vtr ON c_vtr.pk_test_type = latest_results.pk_test_type AND c_vtr.clin_when = latest_results.max_clin_when 1664 %s 1665 """ % order_by 1666 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1667 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ] 1668 return tests
1669 1670 #------------------------------------------------------------
1671 -def get_result_at_timestamp(timestamp=None, test_type=None, loinc=None, tolerance_interval=None, patient=None):
1672 1673 if None not in [test_type, loinc]: 1674 raise ValueError('either <test_type> or <loinc> must be None') 1675 1676 args = { 1677 'pat': patient, 1678 'ttyp': test_type, 1679 'loinc': loinc, 1680 'ts': timestamp, 1681 'intv': tolerance_interval 1682 } 1683 1684 where_parts = [u'pk_patient = %(pat)s'] 1685 if test_type is not None: 1686 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 1687 elif loinc is not None: 1688 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))') 1689 args['loinc'] = tuple(loinc) 1690 1691 if tolerance_interval is None: 1692 where_parts.append(u'clin_when = %(ts)s') 1693 else: 1694 where_parts.append(u'clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)') 1695 1696 cmd = u""" 1697 SELECT * FROM clin.v_test_results 1698 WHERE 1699 %s 1700 ORDER BY 1701 abs(extract(epoch from age(clin_when, %%(ts)s))) 1702 LIMIT 1""" % u' AND '.join(where_parts) 1703 1704 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1705 if len(rows) == 0: 1706 return None 1707 1708 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1709 1710 #------------------------------------------------------------
1711 -def get_most_recent_results(test_type=None, loinc=None, no_of_results=1, patient=None):
1712 1713 if None not in [test_type, loinc]: 1714 raise ValueError('either <test_type> or <loinc> must be None') 1715 1716 if no_of_results < 1: 1717 raise ValueError('<no_of_results> must be > 0') 1718 1719 args = { 1720 'pat': patient, 1721 'ttyp': test_type, 1722 'loinc': loinc 1723 } 1724 1725 where_parts = [u'pk_patient = %(pat)s'] 1726 if test_type is not None: 1727 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 1728 elif loinc is not None: 1729 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))') 1730 args['loinc'] = tuple(loinc) 1731 1732 cmd = u""" 1733 SELECT * FROM clin.v_test_results 1734 WHERE 1735 %s 1736 ORDER BY clin_when DESC 1737 LIMIT %s""" % ( 1738 u' AND '.join(where_parts), 1739 no_of_results 1740 ) 1741 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1742 if len(rows) == 0: 1743 return None 1744 1745 if no_of_results == 1: 1746 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]}) 1747 1748 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1749 1750 #------------------------------------------------------------
1751 -def get_oldest_result(test_type=None, loinc=None, patient=None):
1752 1753 if None not in [test_type, loinc]: 1754 raise ValueError('either <test_type> or <loinc> must be None') 1755 1756 args = { 1757 'pat': patient, 1758 'ttyp': test_type, 1759 'loinc': loinc 1760 } 1761 1762 where_parts = [u'pk_patient = %(pat)s'] 1763 if test_type is not None: 1764 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 1765 elif loinc is not None: 1766 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))') 1767 args['loinc'] = tuple(loinc) 1768 1769 cmd = u""" 1770 SELECT * FROM clin.v_test_results 1771 WHERE 1772 %s 1773 ORDER BY clin_when 1774 LIMIT 1""" % u' AND '.join(where_parts) 1775 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1776 if len(rows) == 0: 1777 return None 1778 1779 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1780 1781 #------------------------------------------------------------
1782 -def delete_test_result(result=None):
1783 try: 1784 pk = int(result) 1785 except (TypeError, AttributeError): 1786 pk = result['pk_test_result'] 1787 1788 cmd = u'DELETE FROM clin.test_result WHERE pk = %(pk)s' 1789 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1790 1791 #------------------------------------------------------------
1792 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1793 1794 cmd1 = u""" 1795 insert into clin.test_result ( 1796 fk_encounter, 1797 fk_episode, 1798 fk_type, 1799 fk_intended_reviewer, 1800 val_num, 1801 val_alpha, 1802 val_unit 1803 ) values ( 1804 %(enc)s, 1805 %(epi)s, 1806 %(type)s, 1807 %(rev)s, 1808 %(v_num)s, 1809 %(v_alpha)s, 1810 %(unit)s 1811 )""" 1812 1813 cmd2 = u""" 1814 select * 1815 from 1816 clin.v_test_results 1817 where 1818 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))""" 1819 1820 args = { 1821 u'enc': encounter, 1822 u'epi': episode, 1823 u'type': type, 1824 u'rev': intended_reviewer, 1825 u'v_num': val_num, 1826 u'v_alpha': val_alpha, 1827 u'unit': unit 1828 } 1829 1830 rows, idx = gmPG2.run_rw_queries ( 1831 queries = [ 1832 {'cmd': cmd1, 'args': args}, 1833 {'cmd': cmd2} 1834 ], 1835 return_data = True, 1836 get_col_idx = True 1837 ) 1838 1839 tr = cTestResult(row = { 1840 'pk_field': 'pk_test_result', 1841 'idx': idx, 1842 'data': rows[0] 1843 }) 1844 1845 return tr
1846 1847 #------------------------------------------------------------
1848 -def format_test_results(results=None, output_format=u'latex'):
1849 1850 _log.debug(u'formatting test results into [%s]', output_format) 1851 1852 if output_format == u'latex': 1853 return __format_test_results_latex(results = results) 1854 1855 msg = _('unknown test results output format [%s]') % output_format 1856 _log.error(msg) 1857 return msg
1858 1859 #------------------------------------------------------------
1860 -def __tests2latex_minipage(results=None, width=u'1.5cm', show_time=False, show_range=True):
1861 1862 if len(results) == 0: 1863 return u'\\begin{minipage}{%s} \\end{minipage}' % width 1864 1865 lines = [] 1866 for t in results: 1867 1868 tmp = u'' 1869 1870 if show_time: 1871 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M') 1872 1873 tmp += u'%.8s' % t['unified_val'] 1874 1875 lines.append(tmp) 1876 tmp = u'' 1877 1878 if show_range: 1879 has_range = ( 1880 t['unified_target_range'] is not None 1881 or 1882 t['unified_target_min'] is not None 1883 or 1884 t['unified_target_max'] is not None 1885 ) 1886 if has_range: 1887 if t['unified_target_range'] is not None: 1888 tmp += u'{\\tiny %s}' % t['unified_target_range'] 1889 else: 1890 tmp += u'{\\tiny %s}' % ( 1891 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '), 1892 gmTools.coalesce(t['unified_target_max'], u'', u'%s') 1893 ) 1894 lines.append(tmp) 1895 1896 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
1897 1898 #------------------------------------------------------------
1899 -def __tests2latex_cell(results=None, show_time=False, show_range=True):
1900 1901 if len(results) == 0: 1902 return u'' 1903 1904 lines = [] 1905 for t in results: 1906 1907 tmp = u'' 1908 1909 if show_time: 1910 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M') 1911 1912 tmp += u'\\normalsize %.8s' % t['unified_val'] 1913 1914 lines.append(tmp) 1915 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ') 1916 1917 if not show_range: 1918 lines.append(tmp) 1919 continue 1920 1921 has_range = ( 1922 t['unified_target_range'] is not None 1923 or 1924 t['unified_target_min'] is not None 1925 or 1926 t['unified_target_max'] is not None 1927 ) 1928 1929 if not has_range: 1930 lines.append(tmp) 1931 continue 1932 1933 if t['unified_target_range'] is not None: 1934 tmp += u'[%s]' % t['unified_target_range'] 1935 else: 1936 tmp += u'[%s%s]' % ( 1937 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'), 1938 gmTools.coalesce(t['unified_target_max'], u'', u'%s') 1939 ) 1940 lines.append(tmp) 1941 1942 return u' \\\\ '.join(lines)
1943 1944 #------------------------------------------------------------
1945 -def __format_test_results_latex(results=None):
1946 1947 if len(results) == 0: 1948 return u'\\noindent %s' % _('No test results to format.') 1949 1950 # discover the columns and rows 1951 dates = {} 1952 tests = {} 1953 grid = {} 1954 for result in results: 1955 # row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name']) 1956 row_label = result['unified_abbrev'] 1957 tests[row_label] = None 1958 col_label = u'{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d') 1959 dates[col_label] = None 1960 try: 1961 grid[row_label] 1962 except KeyError: 1963 grid[row_label] = {} 1964 try: 1965 grid[row_label][col_label].append(result) 1966 except KeyError: 1967 grid[row_label][col_label] = [result] 1968 1969 col_labels = sorted(dates.keys(), reverse = True) 1970 del dates 1971 row_labels = sorted(tests.keys()) 1972 del tests 1973 1974 col_def = len(col_labels) * u'>{\\raggedleft}p{1.7cm}|' 1975 1976 # format them 1977 tex = u"""\\noindent %s 1978 1979 \\noindent \\begin{tabular}{|l|%s} 1980 \\hline 1981 & %s \\tabularnewline 1982 \\hline 1983 1984 %%s \\tabularnewline 1985 1986 \\hline 1987 1988 \\end{tabular}""" % ( 1989 _('Test results'), 1990 col_def, 1991 u' & '.join(col_labels) 1992 ) 1993 1994 rows = [] 1995 1996 # loop over rows 1997 for rl in row_labels: 1998 cells = [rl] 1999 # loop over cols per row 2000 for cl in col_labels: 2001 try: 2002 # get tests for this (row/col) position 2003 tests = grid[rl][cl] 2004 except KeyError: 2005 # none there, so insert empty cell 2006 cells.append(u' ') 2007 continue 2008 2009 cells.append ( 2010 __tests2latex_cell ( 2011 results = tests, 2012 show_time = (len(tests) > 1), 2013 show_range = True 2014 ) 2015 ) 2016 2017 rows.append(u' & '.join(cells)) 2018 2019 return tex % u' \\tabularnewline\n \\hline\n'.join(rows)
2020 2021 #============================================================
2022 -def export_results_for_gnuplot(results=None, filename=None, show_year=True):
2023 2024 if filename is None: 2025 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat') 2026 2027 # sort results into series by test type 2028 series = {} 2029 for r in results: 2030 try: 2031 series[r['unified_name']].append(r) 2032 except KeyError: 2033 series[r['unified_name']] = [r] 2034 2035 gp_data = codecs.open(filename, 'wb', 'utf8') 2036 2037 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting')) 2038 gp_data.write(u'# -------------------------------------------------------------\n') 2039 gp_data.write(u'# first line of index: test type abbreviation & name\n') 2040 gp_data.write(u'#\n') 2041 gp_data.write(u'# clin_when at full precision\n') 2042 gp_data.write(u'# value\n') 2043 gp_data.write(u'# unit\n') 2044 gp_data.write(u'# unified (target or normal) range: lower bound\n') 2045 gp_data.write(u'# unified (target or normal) range: upper bound\n') 2046 gp_data.write(u'# normal range: lower bound\n') 2047 gp_data.write(u'# normal range: upper bound\n') 2048 gp_data.write(u'# target range: lower bound\n') 2049 gp_data.write(u'# target range: upper bound\n') 2050 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n') 2051 gp_data.write(u'# -------------------------------------------------------------\n') 2052 2053 for test_type in series.keys(): 2054 if len(series[test_type]) == 0: 2055 continue 2056 2057 r = series[test_type][0] 2058 title = u'%s (%s)' % ( 2059 r['unified_abbrev'], 2060 r['unified_name'] 2061 ) 2062 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title)) 2063 2064 prev_date = None 2065 prev_year = None 2066 for r in series[test_type]: 2067 curr_date = gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d', 'utf8', gmDateTime.acc_days) 2068 if curr_date == prev_date: 2069 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing of same-day values')) 2070 if show_year: 2071 if r['clin_when'].year == prev_year: 2072 when_template = '%b %d %H:%M' 2073 else: 2074 when_template = '%b %d %H:%M (%Y)' 2075 prev_year = r['clin_when'].year 2076 else: 2077 when_template = '%b %d' 2078 val = r['val_num'] 2079 if val is None: 2080 val = r.estimate_numeric_value_from_alpha 2081 if val is None: 2082 continue # skip distinctly non-numericable values 2083 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % ( 2084 #r['clin_when'].strftime('%Y-%m-%d_%H:%M'), 2085 gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d_%H:%M', 'utf8', gmDateTime.acc_minutes), 2086 val, 2087 gmTools.coalesce(r['val_unit'], u'"<?>"'), 2088 gmTools.coalesce(r['unified_target_min'], u'"<?>"'), 2089 gmTools.coalesce(r['unified_target_max'], u'"<?>"'), 2090 gmTools.coalesce(r['val_normal_min'], u'"<?>"'), 2091 gmTools.coalesce(r['val_normal_max'], u'"<?>"'), 2092 gmTools.coalesce(r['val_target_min'], u'"<?>"'), 2093 gmTools.coalesce(r['val_target_max'], u'"<?>"'), 2094 gmDateTime.pydt_strftime ( 2095 r['clin_when'], 2096 format = when_template, 2097 accuracy = gmDateTime.acc_minutes 2098 ) 2099 )) 2100 prev_date = curr_date 2101 2102 gp_data.close() 2103 2104 return filename
2105 2106 #============================================================
2107 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
2108 """Represents one lab result.""" 2109 2110 _cmd_fetch_payload = """ 2111 select *, xmin_test_result from v_results4lab_req 2112 where pk_result=%s""" 2113 _cmds_lock_rows_for_update = [ 2114 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update""" 2115 ] 2116 _cmds_store_payload = [ 2117 """update test_result set 2118 clin_when = %(val_when)s, 2119 narrative = %(progress_note_result)s, 2120 fk_type = %(pk_test_type)s, 2121 val_num = %(val_num)s::numeric, 2122 val_alpha = %(val_alpha)s, 2123 val_unit = %(val_unit)s, 2124 val_normal_min = %(val_normal_min)s, 2125 val_normal_max = %(val_normal_max)s, 2126 val_normal_range = %(val_normal_range)s, 2127 val_target_min = %(val_target_min)s, 2128 val_target_max = %(val_target_max)s, 2129 val_target_range = %(val_target_range)s, 2130 abnormality_indicator = %(abnormal)s, 2131 norm_ref_group = %(ref_group)s, 2132 note_provider = %(note_provider)s, 2133 material = %(material)s, 2134 material_detail = %(material_detail)s 2135 where pk = %(pk_result)s""", 2136 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s""" 2137 ] 2138 2139 _updatable_fields = [ 2140 'val_when', 2141 'progress_note_result', 2142 'val_num', 2143 'val_alpha', 2144 'val_unit', 2145 'val_normal_min', 2146 'val_normal_max', 2147 'val_normal_range', 2148 'val_target_min', 2149 'val_target_max', 2150 'val_target_range', 2151 'abnormal', 2152 'ref_group', 2153 'note_provider', 2154 'material', 2155 'material_detail' 2156 ] 2157 #--------------------------------------------------------
2158 - def __init__(self, aPK_obj=None, row=None):
2159 """Instantiate. 2160 2161 aPK_obj as dict: 2162 - patient_id 2163 - when_field (see view definition) 2164 - when 2165 - test_type 2166 - val_num 2167 - val_alpha 2168 - unit 2169 """ 2170 # instantiate from row data ? 2171 if aPK_obj is None: 2172 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 2173 return 2174 pk = aPK_obj 2175 # find PK from row data ? 2176 if type(aPK_obj) == types.DictType: 2177 # sanity checks 2178 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]: 2179 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj 2180 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None): 2181 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None' 2182 # get PK 2183 where_snippets = [ 2184 'pk_patient=%(patient_id)s', 2185 'pk_test_type=%(test_type)s', 2186 '%s=%%(when)s' % aPK_obj['when_field'], 2187 'val_unit=%(unit)s' 2188 ] 2189 if aPK_obj['val_num'] is not None: 2190 where_snippets.append('val_num=%(val_num)s::numeric') 2191 if aPK_obj['val_alpha'] is not None: 2192 where_snippets.append('val_alpha=%(val_alpha)s') 2193 2194 where_clause = ' and '.join(where_snippets) 2195 cmd = "select pk_result from v_results4lab_req where %s" % where_clause 2196 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 2197 if data is None: 2198 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj 2199 if len(data) == 0: 2200 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj 2201 pk = data[0][0] 2202 # instantiate class 2203 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2204 #--------------------------------------------------------
2205 - def get_patient(self):
2206 cmd = """ 2207 select 2208 %s, 2209 vbp.title, 2210 vbp.firstnames, 2211 vbp.lastnames, 2212 vbp.dob 2213 from v_basic_person vbp 2214 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']] 2215 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']]) 2216 return pat[0]
2217 #============================================================
2218 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
2219 """Represents one lab request.""" 2220 2221 _cmd_fetch_payload = """ 2222 select *, xmin_lab_request from v_lab_requests 2223 where pk_request=%s""" 2224 _cmds_lock_rows_for_update = [ 2225 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update""" 2226 ] 2227 _cmds_store_payload = [ 2228 """update lab_request set 2229 request_id=%(request_id)s, 2230 lab_request_id=%(lab_request_id)s, 2231 clin_when=%(sampled_when)s, 2232 lab_rxd_when=%(lab_rxd_when)s, 2233 results_reported_when=%(results_reported_when)s, 2234 request_status=%(request_status)s, 2235 is_pending=%(is_pending)s::bool, 2236 narrative=%(progress_note)s 2237 where pk=%(pk_request)s""", 2238 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s""" 2239 ] 2240 _updatable_fields = [ 2241 'request_id', 2242 'lab_request_id', 2243 'sampled_when', 2244 'lab_rxd_when', 2245 'results_reported_when', 2246 'request_status', 2247 'is_pending', 2248 'progress_note' 2249 ] 2250 #--------------------------------------------------------
2251 - def __init__(self, aPK_obj=None, row=None):
2252 """Instantiate lab request. 2253 2254 The aPK_obj can be either a dict with the keys "req_id" 2255 and "lab" or a simple primary key. 2256 """ 2257 # instantiate from row data ? 2258 if aPK_obj is None: 2259 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 2260 return 2261 pk = aPK_obj 2262 # instantiate from "req_id" and "lab" ? 2263 if type(aPK_obj) == types.DictType: 2264 # sanity check 2265 try: 2266 aPK_obj['req_id'] 2267 aPK_obj['lab'] 2268 except: 2269 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info()) 2270 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj) 2271 # generate query 2272 where_snippets = [] 2273 vals = {} 2274 where_snippets.append('request_id=%(req_id)s') 2275 if type(aPK_obj['lab']) == types.IntType: 2276 where_snippets.append('pk_test_org=%(lab)s') 2277 else: 2278 where_snippets.append('lab_name=%(lab)s') 2279 where_clause = ' and '.join(where_snippets) 2280 cmd = "select pk_request from v_lab_requests where %s" % where_clause 2281 # get pk 2282 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 2283 if data is None: 2284 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj) 2285 if len(data) == 0: 2286 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj) 2287 pk = data[0][0] 2288 # instantiate class 2289 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2290 #--------------------------------------------------------
2291 - def get_patient(self):
2292 cmd = """ 2293 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob 2294 from v_pat_items vpi, v_basic_person vbp 2295 where 2296 vpi.pk_item=%s 2297 and 2298 vbp.pk_identity=vpi.pk_patient""" 2299 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']]) 2300 if pat is None: 2301 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']]) 2302 return None 2303 if len(pat) == 0: 2304 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']]) 2305 return None 2306 return pat[0]
2307 #============================================================ 2308 # convenience functions 2309 #------------------------------------------------------------
2310 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
2311 """Create or get lab request. 2312 2313 returns tuple (status, value): 2314 (True, lab request instance) 2315 (False, error message) 2316 (None, housekeeping_todo primary key) 2317 """ 2318 req = None 2319 aPK_obj = { 2320 'lab': lab, 2321 'req_id': req_id 2322 } 2323 try: 2324 req = cLabRequest (aPK_obj) 2325 except gmExceptions.NoSuchClinItemError, msg: 2326 _log.info('%s: will try to create lab request' % str(msg)) 2327 except gmExceptions.ConstructorError, msg: 2328 _log.exception(str(msg), sys.exc_info(), verbose=0) 2329 return (False, msg) 2330 # found 2331 if req is not None: 2332 db_pat = req.get_patient() 2333 if db_pat is None: 2334 _log.error('cannot cross-check patient on lab request') 2335 return (None, '') 2336 # yes but ambigous 2337 if pat_id != db_pat[0]: 2338 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat)) 2339 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $' 2340 to = 'user' 2341 prob = _('The lab request already exists but belongs to a different patient.') 2342 sol = _('Verify which patient this lab request really belongs to.') 2343 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat) 2344 cat = 'lab' 2345 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat) 2346 return (None, data) 2347 return (True, req) 2348 # not found 2349 queries = [] 2350 if type(lab) is types.IntType: 2351 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)" 2352 else: 2353 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)" 2354 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id])) 2355 cmd = "select currval('lab_request_pk_seq')" 2356 queries.append((cmd, [])) 2357 # insert new 2358 result, err = gmPG.run_commit('historica', queries, True) 2359 if result is None: 2360 return (False, err) 2361 try: 2362 req = cLabRequest(aPK_obj=result[0][0]) 2363 except gmExceptions.ConstructorError, msg: 2364 _log.exception(str(msg), sys.exc_info(), verbose=0) 2365 return (False, msg) 2366 return (True, req)
2367 #------------------------------------------------------------
2368 -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):
2369 tres = None 2370 data = { 2371 'patient_id': patient_id, 2372 'when_field': when_field, 2373 'when': when, 2374 'test_type': test_type, 2375 'val_num': val_num, 2376 'val_alpha': val_alpha, 2377 'unit': unit 2378 } 2379 try: 2380 tres = cLabResult(aPK_obj=data) 2381 # exists already, so fail 2382 _log.error('will not overwrite existing test result') 2383 _log.debug(str(tres)) 2384 return (None, tres) 2385 except gmExceptions.NoSuchClinItemError: 2386 _log.debug('test result not found - as expected, will create it') 2387 except gmExceptions.ConstructorError, msg: 2388 _log.exception(str(msg), sys.exc_info(), verbose=0) 2389 return (False, msg) 2390 if request is None: 2391 return (False, _('need lab request when inserting lab result')) 2392 # not found 2393 if encounter_id is None: 2394 encounter_id = request['pk_encounter'] 2395 queries = [] 2396 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)" 2397 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit])) 2398 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)" 2399 queries.append((cmd, [request['pk_request']])) 2400 cmd = "select currval('test_result_pk_seq')" 2401 queries.append((cmd, [])) 2402 # insert new 2403 result, err = gmPG.run_commit('historica', queries, True) 2404 if result is None: 2405 return (False, err) 2406 try: 2407 tres = cLabResult(aPK_obj=result[0][0]) 2408 except gmExceptions.ConstructorError, msg: 2409 _log.exception(str(msg), sys.exc_info(), verbose=0) 2410 return (False, msg) 2411 return (True, tres)
2412 #------------------------------------------------------------
2413 -def get_unreviewed_results(limit=50):
2414 # sanity check 2415 if limit < 1: 2416 limit = 1 2417 # retrieve one more row than needed so we know there's more available ;-) 2418 lim = limit + 1 2419 cmd = """ 2420 select pk_result 2421 from v_results4lab_req 2422 where reviewed is false 2423 order by pk_patient 2424 limit %s""" % lim 2425 rows = gmPG.run_ro_query('historica', cmd) 2426 if rows is None: 2427 _log.error('error retrieving unreviewed lab results') 2428 return (None, _('error retrieving unreviewed lab results')) 2429 if len(rows) == 0: 2430 return (False, []) 2431 # more than LIMIT rows ? 2432 if len(rows) == lim: 2433 more_avail = True 2434 # but deliver only LIMIT rows so that our assumption holds true... 2435 del rows[limit] 2436 else: 2437 more_avail = False 2438 results = [] 2439 for row in rows: 2440 try: 2441 results.append(cLabResult(aPK_obj=row[0])) 2442 except gmExceptions.ConstructorError: 2443 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0) 2444 return (more_avail, results)
2445 #------------------------------------------------------------
2446 -def get_pending_requests(limit=250):
2447 lim = limit + 1 2448 cmd = "select pk from lab_request where is_pending is true limit %s" % lim 2449 rows = gmPG.run_ro_query('historica', cmd) 2450 if rows is None: 2451 _log.error('error retrieving pending lab requests') 2452 return (None, None) 2453 if len(rows) == 0: 2454 return (False, []) 2455 results = [] 2456 # more than LIMIT rows ? 2457 if len(rows) == lim: 2458 too_many = True 2459 # but deliver only LIMIT rows so that our assumption holds true... 2460 del rows[limit] 2461 else: 2462 too_many = False 2463 requests = [] 2464 for row in rows: 2465 try: 2466 requests.append(cLabRequest(aPK_obj=row[0])) 2467 except gmExceptions.ConstructorError: 2468 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0) 2469 return (too_many, requests)
2470 #------------------------------------------------------------
2471 -def get_next_request_ID(lab=None, incrementor_func=None):
2472 """Get logically next request ID for given lab. 2473 2474 - incrementor_func: 2475 - if not supplied the next ID is guessed 2476 - if supplied it is applied to the most recently used ID 2477 """ 2478 if type(lab) == types.IntType: 2479 lab_snippet = 'vlr.fk_test_org=%s' 2480 else: 2481 lab_snippet = 'vlr.lab_name=%s' 2482 lab = str(lab) 2483 cmd = """ 2484 select request_id 2485 from lab_request lr0 2486 where lr0.clin_when = ( 2487 select max(vlr.sampled_when) 2488 from v_lab_requests vlr 2489 where %s 2490 )""" % lab_snippet 2491 rows = gmPG.run_ro_query('historica', cmd, None, lab) 2492 if rows is None: 2493 _log.warning('error getting most recently used request ID for lab [%s]' % lab) 2494 return '' 2495 if len(rows) == 0: 2496 return '' 2497 most_recent = rows[0][0] 2498 # apply supplied incrementor 2499 if incrementor_func is not None: 2500 try: 2501 next = incrementor_func(most_recent) 2502 except TypeError: 2503 _log.error('cannot call incrementor function [%s]' % str(incrementor_func)) 2504 return most_recent 2505 return next 2506 # try to be smart ourselves 2507 for pos in range(len(most_recent)): 2508 header = most_recent[:pos] 2509 trailer = most_recent[pos:] 2510 try: 2511 return '%s%s' % (header, str(int(trailer) + 1)) 2512 except ValueError: 2513 header = most_recent[:-1] 2514 trailer = most_recent[-1:] 2515 return '%s%s' % (header, chr(ord(trailer) + 1))
2516 #============================================================
2517 -def calculate_bmi(mass=None, height=None, age=None):
2518 """Calculate BMI. 2519 2520 mass: kg 2521 height: cm 2522 age: not yet used 2523 2524 returns: 2525 (True/False, data) 2526 True: data = (bmi, lower_normal, upper_normal) 2527 False: data = error message 2528 """ 2529 converted, mass = gmTools.input2decimal(mass) 2530 if not converted: 2531 return False, u'mass: cannot convert <%s> to Decimal' % mass 2532 2533 converted, height = gmTools.input2decimal(height) 2534 if not converted: 2535 return False, u'height: cannot convert <%s> to Decimal' % height 2536 2537 approx_surface = (height / decimal.Decimal(100))**2 2538 bmi = mass / approx_surface 2539 2540 print mass, height, '->', approx_surface, '->', bmi 2541 2542 lower_normal_mass = 20.0 * approx_surface 2543 upper_normal_mass = 25.0 * approx_surface 2544 2545 return True, (bmi, lower_normal_mass, upper_normal_mass)
2546 #============================================================ 2547 # main - unit testing 2548 #------------------------------------------------------------ 2549 if __name__ == '__main__': 2550 2551 if len(sys.argv) < 2: 2552 sys.exit() 2553 2554 if sys.argv[1] != 'test': 2555 sys.exit() 2556 2557 import time 2558 2559 gmI18N.activate_locale() 2560 gmI18N.install_domain() 2561 2562 #------------------------------------------
2563 - def test_create_test_result():
2564 tr = create_test_result ( 2565 encounter = 1, 2566 episode = 1, 2567 type = 1, 2568 intended_reviewer = 1, 2569 val_num = '12', 2570 val_alpha=None, 2571 unit = 'mg/dl' 2572 ) 2573 print tr 2574 return tr
2575 #------------------------------------------
2576 - def test_delete_test_result():
2577 tr = test_create_test_result() 2578 delete_test_result(tr)
2579 #------------------------------------------
2580 - def test_result():
2581 r = cTestResult(aPK_obj=6) 2582 #print r 2583 #print r.reference_ranges 2584 #print r.formatted_range 2585 #print r.temporally_closest_normal_range 2586 print r.estimate_numeric_value_from_alpha
2587 #------------------------------------------
2588 - def test_lab_result():
2589 print "test_result()" 2590 # lab_result = cLabResult(aPK_obj=4) 2591 data = { 2592 'patient_id': 12, 2593 'when_field': 'val_when', 2594 'when': '2000-09-17 18:23:00+02', 2595 'test_type': 9, 2596 'val_num': 17.3, 2597 'val_alpha': None, 2598 'unit': 'mg/l' 2599 } 2600 lab_result = cLabResult(aPK_obj=data) 2601 print lab_result 2602 fields = lab_result.get_fields() 2603 for field in fields: 2604 print field, ':', lab_result[field] 2605 print "updatable:", lab_result.get_updatable_fields() 2606 print time.time() 2607 print lab_result.get_patient() 2608 print time.time()
2609 #------------------------------------------
2610 - def test_request():
2611 print "test_request()" 2612 try: 2613 # lab_req = cLabRequest(aPK_obj=1) 2614 # lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2) 2615 data = { 2616 'req_id': 'EML#SC937-0176-CEC#11', 2617 'lab': 'Enterprise Main Lab' 2618 } 2619 lab_req = cLabRequest(aPK_obj=data) 2620 except gmExceptions.ConstructorError, msg: 2621 print "no such lab request:", msg 2622 return 2623 print lab_req 2624 fields = lab_req.get_fields() 2625 for field in fields: 2626 print field, ':', lab_req[field] 2627 print "updatable:", lab_req.get_updatable_fields() 2628 print time.time() 2629 print lab_req.get_patient() 2630 print time.time()
2631 #--------------------------------------------------------
2632 - def test_unreviewed():
2633 data = get_unreviewed_results() 2634 for result in data: 2635 print result
2636 #--------------------------------------------------------
2637 - def test_pending():
2638 data = get_pending_requests() 2639 for result in data: 2640 print result
2641 #--------------------------------------------------------
2642 - def test_create_measurement_type():
2643 print create_measurement_type ( 2644 lab = None, 2645 abbrev = u'tBZ2', 2646 unit = u'mg%', 2647 name = 'BZ (test 2)' 2648 )
2649 #--------------------------------------------------------
2650 - def test_meta_test_type():
2651 mtt = cMetaTestType(aPK_obj = 1) 2652 print mtt 2653 print get_meta_test_types()
2654 #--------------------------------------------------------
2655 - def test_test_type():
2656 tt = cMeasurementType(aPK_obj = 1) 2657 print tt 2658 print get_measurement_types()
2659 #--------------------------------------------------------
2660 - def test_format_test_results():
2661 results = [ 2662 cTestResult(aPK_obj=1), 2663 cTestResult(aPK_obj=2), 2664 cTestResult(aPK_obj=3) 2665 # cTestResult(aPK_obj=4) 2666 ] 2667 print format_test_results(results = results)
2668 #--------------------------------------------------------
2669 - def test_calculate_bmi():
2670 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3]) 2671 bmi, low, high = data 2672 2673 print "BMI:", bmi 2674 print "low:", low, "kg" 2675 print "hi :", high, "kg"
2676 #--------------------------------------------------------
2677 - def test_test_panel():
2678 tp = cTestPanel(aPK_obj = 1) 2679 print tp 2680 print tp.format()
2681 #--------------------------------------------------------
2682 - def test_get_most_recent_results_for_panel():
2683 tp = cTestPanel(aPK_obj = 1) 2684 print tp.format() 2685 print len(tp.get_most_recent_results(pk_patient=12))
2686 #-------------------------------------------------------- 2687 2688 test_result() 2689 #test_create_test_result() 2690 #test_delete_test_result() 2691 #test_create_measurement_type() 2692 #test_lab_result() 2693 #test_request() 2694 #test_create_result() 2695 #test_unreviewed() 2696 #test_pending() 2697 #test_meta_test_type() 2698 #test_test_type() 2699 #test_format_test_results() 2700 #test_calculate_bmi() 2701 #test_test_panel() 2702 #test_get_most_recent_results_for_panel() 2703 2704 #============================================================ 2705