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