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, with_episode=True, with_type_details=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 if with_episode: 841 tt += u' ' + _(u'Episode: %s\n') % self._payload[self._idx['episode']] 842 if self._payload[self._idx['health_issue']] is not None: 843 tt += u' ' + _(u'Issue: %s\n') % self._payload[self._idx['health_issue']] 844 if self._payload[self._idx['material']] is not None: 845 tt += u' ' + _(u'Material: %s\n') % self._payload[self._idx['material']] 846 if self._payload[self._idx['material_detail']] is not None: 847 tt += u' ' + _(u'Details: %s\n') % self._payload[self._idx['material_detail']] 848 tt += u'\n' 849 850 if with_review: 851 if self._payload[self._idx['reviewed']]: 852 review = gmDateTime.pydt_strftime ( 853 self._payload[self._idx['last_reviewed']], 854 date_format 855 ) 856 else: 857 review = _('not yet') 858 tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({ 859 'sig_hand': gmTools.u_writing_hand, 860 'reviewed': review 861 }) 862 tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst ( 863 self._payload[self._idx['you_are_responsible']], 864 _('you'), 865 self._payload[self._idx['responsible_reviewer']] 866 ) 867 if self._payload[self._idx['reviewed']]: 868 tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({ 869 'reviewer': gmTools.bool2subst ( 870 self._payload[self._idx['review_by_you']], 871 _('you'), 872 gmTools.coalesce(self._payload[self._idx['last_reviewer']], u'?') 873 ) 874 }) 875 tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({ 876 'abnormal': gmTools.bool2subst ( 877 self._payload[self._idx['is_technically_abnormal']], 878 _('yes'), 879 _('no'), 880 u'?' 881 ) 882 }) 883 tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({ 884 'relevant': gmTools.bool2subst ( 885 self._payload[self._idx['is_clinically_relevant']], 886 _('yes'), 887 _('no'), 888 u'?' 889 ) 890 }) 891 if self._payload[self._idx['review_comment']] is not None: 892 tt += u' ' + _(u' Comment: %s\n') % self._payload[self._idx['review_comment']].strip() 893 tt += u'\n' 894 895 # type 896 if with_type_details: 897 tt += _(u'Test type details:\n') 898 tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s) [#%(pk_u_type)s]\n') % ({ 899 'name_meta': gmTools.coalesce(self._payload[self._idx['name_meta']], u''), 900 'abbrev_meta': gmTools.coalesce(self._payload[self._idx['abbrev_meta']], u''), 901 'pk_u_type': self._payload[self._idx['pk_meta_test_type']] 902 }) 903 if self._payload[self._idx['comment_tt']] is not None: 904 tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(self._payload[self._idx['comment_tt']].split(u'\n')) 905 if self._payload[self._idx['comment_meta']] is not None: 906 tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(self._payload[self._idx['comment_meta']].split(u'\n')) 907 tt += u'\n' 908 909 if with_review: 910 tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({ 911 'row_ver': self._payload[self._idx['row_version']], 912 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']],date_format), 913 'mod_by': self._payload[self._idx['modified_by']] 914 }) 915 916 return tt
917 #--------------------------------------------------------
918 - def _get_reference_ranges(self):
919 920 cmd = u""" 921 select 922 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) 923 pk_patient, 924 val_unit, 925 val_normal_min, val_normal_max, val_normal_range, 926 val_target_min, val_target_max, val_target_range, 927 norm_ref_group, 928 coalesce(norm_ref_group, '') as norm_ref_group_str 929 from 930 clin.v_test_results 931 where 932 pk_test_type = %(pk_type)s 933 """ 934 args = {'pk_type': self._payload[self._idx['pk_test_type']]} 935 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 936 return rows
937
938 - def _set_reference_ranges(self, val):
939 raise AttributeError('[%s]: reference ranges not settable') % self.__class__.__name__
940 941 reference_ranges = property(_get_reference_ranges, _set_reference_ranges) 942 #--------------------------------------------------------
943 - def _get_test_type(self):
944 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
945 946 test_type = property(_get_test_type, lambda x:x) 947 #--------------------------------------------------------
948 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
949 950 # FIXME: this is not concurrency safe 951 if self._payload[self._idx['reviewed']]: 952 self.__change_existing_review ( 953 technically_abnormal = technically_abnormal, 954 clinically_relevant = clinically_relevant, 955 comment = comment 956 ) 957 else: 958 # do not sign off unreviewed results if 959 # NOTHING AT ALL is known about them 960 if technically_abnormal is None: 961 if clinically_relevant is None: 962 comment = gmTools.none_if(comment, u'', strip_string = True) 963 if comment is None: 964 if make_me_responsible is False: 965 return True 966 self.__set_new_review ( 967 technically_abnormal = technically_abnormal, 968 clinically_relevant = clinically_relevant, 969 comment = comment 970 ) 971 972 if make_me_responsible is True: 973 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user" 974 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 975 self['pk_intended_reviewer'] = rows[0][0] 976 self.save_payload() 977 return 978 979 self.refetch_payload()
980 #--------------------------------------------------------
981 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
982 983 if desired_earlier_results < 1: 984 raise ValueError('<desired_earlier_results> must be > 0') 985 986 if desired_later_results < 1: 987 raise ValueError('<desired_later_results> must be > 0') 988 989 args = { 990 'pat': self._payload[self._idx['pk_patient']], 991 'ttyp': self._payload[self._idx['pk_test_type']], 992 'tloinc': self._payload[self._idx['loinc_tt']], 993 'mtyp': self._payload[self._idx['pk_meta_test_type']], 994 'mloinc': self._payload[self._idx['loinc_meta']], 995 'when': self._payload[self._idx['clin_when']], 996 'offset': max_offset 997 } 998 WHERE = u'((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))' 999 WHERE_meta = u'((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))' 1000 if max_offset is not None: 1001 WHERE = WHERE + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 1002 WHERE_meta = WHERE_meta + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 1003 1004 SQL = u""" 1005 SELECT * FROM clin.v_test_results 1006 WHERE 1007 pk_patient = %%(pat)s 1008 AND 1009 clin_when %s %%(when)s 1010 AND 1011 %s 1012 ORDER BY clin_when 1013 LIMIT %s""" 1014 1015 # get earlier results 1016 earlier_results = [] 1017 # by type 1018 cmd = SQL % (u'<', WHERE, desired_earlier_results) 1019 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1020 if len(rows) > 0: 1021 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1022 # by meta type ? 1023 missing_results = desired_earlier_results - len(earlier_results) 1024 if missing_results > 0: 1025 cmd = SQL % (u'<', WHERE_meta, missing_results) 1026 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1027 if len(rows) > 0: 1028 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1029 1030 # get later results 1031 later_results = [] 1032 # by type 1033 cmd = SQL % (u'>', WHERE, desired_later_results) 1034 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1035 if len(rows) > 0: 1036 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1037 # by meta type ? 1038 missing_results = desired_later_results - len(later_results) 1039 if missing_results > 0: 1040 cmd = SQL % (u'>', WHERE_meta, missing_results) 1041 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1042 if len(rows) > 0: 1043 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 1044 1045 return earlier_results, later_results
1046 #-------------------------------------------------------- 1047 # internal API 1048 #--------------------------------------------------------
1049 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1050 """Add a review to a row. 1051 1052 - if technically abnormal is not provided/None it will be set 1053 to True if the lab's indicator has a meaningful value 1054 - if clinically relevant is not provided/None it is set to 1055 whatever technically abnormal is 1056 """ 1057 if technically_abnormal is None: 1058 technically_abnormal = False 1059 if self._payload[self._idx['abnormality_indicator']] is not None: 1060 if self._payload[self._idx['abnormality_indicator']].strip() != u'': 1061 technically_abnormal = True 1062 1063 if clinically_relevant is None: 1064 clinically_relevant = technically_abnormal 1065 1066 cmd = u""" 1067 INSERT INTO clin.reviewed_test_results ( 1068 fk_reviewed_row, 1069 is_technically_abnormal, 1070 clinically_relevant, 1071 comment 1072 ) VALUES ( 1073 %(pk)s, 1074 %(abnormal)s, 1075 %(relevant)s, 1076 gm.nullify_empty_string(%(cmt)s) 1077 )""" 1078 args = { 1079 'pk': self._payload[self._idx['pk_test_result']], 1080 'abnormal': technically_abnormal, 1081 'relevant': clinically_relevant, 1082 'cmt': comment 1083 } 1084 1085 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1086 #--------------------------------------------------------
1087 - def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1088 """Change a review on a row. 1089 1090 - if technically abnormal/clinically relevant are 1091 None they are not set 1092 """ 1093 args = { 1094 'pk_row': self._payload[self._idx['pk_test_result']], 1095 'abnormal': technically_abnormal, 1096 'relevant': clinically_relevant, 1097 'cmt': comment 1098 } 1099 1100 set_parts = [ 1101 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)', 1102 u'comment = gm.nullify_empty_string(%(cmt)s)' 1103 ] 1104 1105 if technically_abnormal is not None: 1106 set_parts.append(u'is_technically_abnormal = %(abnormal)s') 1107 1108 if clinically_relevant is not None: 1109 set_parts.append(u'clinically_relevant = %(relevant)s') 1110 1111 cmd = u""" 1112 UPDATE clin.reviewed_test_results SET 1113 %s 1114 WHERE 1115 fk_reviewed_row = %%(pk_row)s 1116 """ % u',\n '.join(set_parts) 1117 1118 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1119 1120 #------------------------------------------------------------
1121 -def get_test_results(pk_patient=None, encounters=None, episodes=None, order_by=None):
1122 1123 where_parts = [] 1124 1125 if pk_patient is not None: 1126 where_parts.append(u'pk_patient = %(pat)s') 1127 args = {'pat': pk_patient} 1128 1129 # if tests is not None: 1130 # where_parts.append(u'pk_test_type IN %(tests)s') 1131 # args['tests'] = tuple(tests) 1132 1133 if encounters is not None: 1134 where_parts.append(u'pk_encounter IN %(encs)s') 1135 args['encs'] = tuple(encounters) 1136 1137 if episodes is not None: 1138 where_parts.append(u'pk_episode IN %(epis)s') 1139 args['epis'] = tuple(episodes) 1140 1141 if order_by is None: 1142 order_by = u'' 1143 else: 1144 order_by = u'ORDER BY %s' % order_by 1145 1146 cmd = u""" 1147 SELECT * FROM clin.v_test_results 1148 WHERE %s 1149 %s 1150 """ % ( 1151 u' AND '.join(where_parts), 1152 order_by 1153 ) 1154 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1155 1156 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ] 1157 return tests
1158 1159 #------------------------------------------------------------
1160 -def get_result_at_timestamp(timestamp=None, test_type=None, loinc=None, tolerance_interval=None, patient=None):
1161 1162 if None not in [test_type, loinc]: 1163 raise ValueError('either <test_type> or <loinc> must be None') 1164 1165 args = { 1166 'pat': patient, 1167 'ttyp': test_type, 1168 'loinc': loinc, 1169 'ts': timestamp, 1170 'intv': tolerance_interval 1171 } 1172 1173 where_parts = [u'pk_patient = %(pat)s'] 1174 if test_type is not None: 1175 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 1176 elif loinc is not None: 1177 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))') 1178 args['loinc'] = tuple(loinc) 1179 1180 if tolerance_interval is None: 1181 where_parts.append(u'clin_when = %(ts)s') 1182 else: 1183 where_parts.append(u'clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)') 1184 1185 cmd = u""" 1186 SELECT * FROM clin.v_test_results 1187 WHERE 1188 %s 1189 ORDER BY 1190 abs(extract(epoch from age(clin_when, %%(ts)s))) 1191 LIMIT 1""" % u' AND '.join(where_parts) 1192 1193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1194 if len(rows) == 0: 1195 return None 1196 1197 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1198 1199 #------------------------------------------------------------
1200 -def get_most_recent_results(test_type=None, loinc=None, no_of_results=1, patient=None):
1201 1202 if None not in [test_type, loinc]: 1203 raise ValueError('either <test_type> or <loinc> must be None') 1204 1205 if no_of_results < 1: 1206 raise ValueError('<no_of_results> must be > 0') 1207 1208 args = { 1209 'pat': patient, 1210 'ttyp': test_type, 1211 'loinc': loinc 1212 } 1213 1214 where_parts = [u'pk_patient = %(pat)s'] 1215 if test_type is not None: 1216 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 1217 elif loinc is not None: 1218 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))') 1219 args['loinc'] = tuple(loinc) 1220 1221 cmd = u""" 1222 SELECT * FROM clin.v_test_results 1223 WHERE 1224 %s 1225 ORDER BY clin_when DESC 1226 LIMIT %s""" % ( 1227 u' AND '.join(where_parts), 1228 no_of_results 1229 ) 1230 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 1231 if len(rows) == 0: 1232 return None 1233 1234 if no_of_results == 1: 1235 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]}) 1236 1237 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1238 1239 #------------------------------------------------------------
1240 -def delete_test_result(result=None):
1241 try: 1242 pk = int(result) 1243 except (TypeError, AttributeError): 1244 pk = result['pk_test_result'] 1245 1246 cmd = u'DELETE FROM clin.test_result WHERE pk = %(pk)s' 1247 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1248 1249 #------------------------------------------------------------
1250 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1251 1252 cmd1 = u""" 1253 insert into clin.test_result ( 1254 fk_encounter, 1255 fk_episode, 1256 fk_type, 1257 fk_intended_reviewer, 1258 val_num, 1259 val_alpha, 1260 val_unit 1261 ) values ( 1262 %(enc)s, 1263 %(epi)s, 1264 %(type)s, 1265 %(rev)s, 1266 %(v_num)s, 1267 %(v_alpha)s, 1268 %(unit)s 1269 )""" 1270 1271 cmd2 = u""" 1272 select * 1273 from 1274 clin.v_test_results 1275 where 1276 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))""" 1277 1278 args = { 1279 u'enc': encounter, 1280 u'epi': episode, 1281 u'type': type, 1282 u'rev': intended_reviewer, 1283 u'v_num': val_num, 1284 u'v_alpha': val_alpha, 1285 u'unit': unit 1286 } 1287 1288 rows, idx = gmPG2.run_rw_queries ( 1289 queries = [ 1290 {'cmd': cmd1, 'args': args}, 1291 {'cmd': cmd2} 1292 ], 1293 return_data = True, 1294 get_col_idx = True 1295 ) 1296 1297 tr = cTestResult(row = { 1298 'pk_field': 'pk_test_result', 1299 'idx': idx, 1300 'data': rows[0] 1301 }) 1302 1303 return tr
1304 1305 #------------------------------------------------------------
1306 -def format_test_results(results=None, output_format=u'latex'):
1307 1308 _log.debug(u'formatting test results into [%s]', output_format) 1309 1310 if output_format == u'latex': 1311 return __format_test_results_latex(results = results) 1312 1313 msg = _('unknown test results output format [%s]') % output_format 1314 _log.error(msg) 1315 return msg
1316 1317 #------------------------------------------------------------
1318 -def __tests2latex_minipage(results=None, width=u'1.5cm', show_time=False, show_range=True):
1319 1320 if len(results) == 0: 1321 return u'\\begin{minipage}{%s} \\end{minipage}' % width 1322 1323 lines = [] 1324 for t in results: 1325 1326 tmp = u'' 1327 1328 if show_time: 1329 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M') 1330 1331 tmp += u'%.8s' % t['unified_val'] 1332 1333 lines.append(tmp) 1334 tmp = u'' 1335 1336 if show_range: 1337 has_range = ( 1338 t['unified_target_range'] is not None 1339 or 1340 t['unified_target_min'] is not None 1341 or 1342 t['unified_target_max'] is not None 1343 ) 1344 if has_range: 1345 if t['unified_target_range'] is not None: 1346 tmp += u'{\\tiny %s}' % t['unified_target_range'] 1347 else: 1348 tmp += u'{\\tiny %s}' % ( 1349 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '), 1350 gmTools.coalesce(t['unified_target_max'], u'', u'%s') 1351 ) 1352 lines.append(tmp) 1353 1354 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
1355 1356 #------------------------------------------------------------
1357 -def __tests2latex_cell(results=None, show_time=False, show_range=True):
1358 1359 if len(results) == 0: 1360 return u'' 1361 1362 lines = [] 1363 for t in results: 1364 1365 tmp = u'' 1366 1367 if show_time: 1368 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M') 1369 1370 tmp += u'\\normalsize %.8s' % t['unified_val'] 1371 1372 lines.append(tmp) 1373 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ') 1374 1375 if not show_range: 1376 lines.append(tmp) 1377 continue 1378 1379 has_range = ( 1380 t['unified_target_range'] is not None 1381 or 1382 t['unified_target_min'] is not None 1383 or 1384 t['unified_target_max'] is not None 1385 ) 1386 1387 if not has_range: 1388 lines.append(tmp) 1389 continue 1390 1391 if t['unified_target_range'] is not None: 1392 tmp += u'[%s]' % t['unified_target_range'] 1393 else: 1394 tmp += u'[%s%s]' % ( 1395 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'), 1396 gmTools.coalesce(t['unified_target_max'], u'', u'%s') 1397 ) 1398 lines.append(tmp) 1399 1400 return u' \\\\ '.join(lines)
1401 1402 #------------------------------------------------------------
1403 -def __format_test_results_latex(results=None):
1404 1405 if len(results) == 0: 1406 return u'\\noindent %s' % _('No test results to format.') 1407 1408 # discover the columns and rows 1409 dates = {} 1410 tests = {} 1411 grid = {} 1412 for result in results: 1413 # row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name']) 1414 row_label = result['unified_abbrev'] 1415 tests[row_label] = None 1416 col_label = u'{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d') 1417 dates[col_label] = None 1418 try: 1419 grid[row_label] 1420 except KeyError: 1421 grid[row_label] = {} 1422 try: 1423 grid[row_label][col_label].append(result) 1424 except KeyError: 1425 grid[row_label][col_label] = [result] 1426 1427 col_labels = sorted(dates.keys(), reverse = True) 1428 del dates 1429 row_labels = sorted(tests.keys()) 1430 del tests 1431 1432 col_def = len(col_labels) * u'>{\\raggedleft}p{1.7cm}|' 1433 1434 # format them 1435 tex = u"""\\noindent %s 1436 1437 \\noindent \\begin{tabular}{|l|%s} 1438 \\hline 1439 & %s \\tabularnewline 1440 \\hline 1441 1442 %%s \\tabularnewline 1443 1444 \\hline 1445 1446 \\end{tabular}""" % ( 1447 _('Test results'), 1448 col_def, 1449 u' & '.join(col_labels) 1450 ) 1451 1452 rows = [] 1453 1454 # loop over rows 1455 for rl in row_labels: 1456 cells = [rl] 1457 # loop over cols per row 1458 for cl in col_labels: 1459 try: 1460 # get tests for this (row/col) position 1461 tests = grid[rl][cl] 1462 except KeyError: 1463 # none there, so insert empty cell 1464 cells.append(u' ') 1465 continue 1466 1467 cells.append ( 1468 __tests2latex_cell ( 1469 results = tests, 1470 show_time = (len(tests) > 1), 1471 show_range = True 1472 ) 1473 ) 1474 1475 rows.append(u' & '.join(cells)) 1476 1477 return tex % u' \\tabularnewline\n \\hline\n'.join(rows)
1478 1479 #============================================================
1480 -def export_results_for_gnuplot(results=None, filename=None, show_year=True):
1481 1482 if filename is None: 1483 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat') 1484 1485 # sort results into series by test type 1486 series = {} 1487 for r in results: 1488 try: 1489 series[r['unified_name']].append(r) 1490 except KeyError: 1491 series[r['unified_name']] = [r] 1492 1493 gp_data = codecs.open(filename, 'wb', 'utf8') 1494 1495 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting')) 1496 gp_data.write(u'# -------------------------------------------------------------\n') 1497 gp_data.write(u'# first line of index: test type abbreviation & name\n') 1498 gp_data.write(u'#\n') 1499 gp_data.write(u'# clin_when at full precision\n') 1500 gp_data.write(u'# value\n') 1501 gp_data.write(u'# unit\n') 1502 gp_data.write(u'# unified (target or normal) range: lower bound\n') 1503 gp_data.write(u'# unified (target or normal) range: upper bound\n') 1504 gp_data.write(u'# normal range: lower bound\n') 1505 gp_data.write(u'# normal range: upper bound\n') 1506 gp_data.write(u'# target range: lower bound\n') 1507 gp_data.write(u'# target range: upper bound\n') 1508 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n') 1509 gp_data.write(u'# -------------------------------------------------------------\n') 1510 1511 for test_type in series.keys(): 1512 if len(series[test_type]) == 0: 1513 continue 1514 1515 r = series[test_type][0] 1516 title = u'%s (%s)' % ( 1517 r['unified_abbrev'], 1518 r['unified_name'] 1519 ) 1520 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title)) 1521 1522 prev_date = None 1523 prev_year = None 1524 for r in series[test_type]: 1525 curr_date = r['clin_when'].strftime('%Y-%m-%d') 1526 if curr_date == prev_date: 1527 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing for same-day values')) 1528 if show_year: 1529 if r['clin_when'].year == prev_year: 1530 when_template = '%b %d %H:%M' 1531 else: 1532 when_template = '%b %d %H:%M (%Y)' 1533 prev_year = r['clin_when'].year 1534 else: 1535 when_template = '%b %d' 1536 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % ( 1537 r['clin_when'].strftime('%Y-%m-%d_%H:%M'), 1538 r['unified_val'], 1539 gmTools.coalesce(r['val_unit'], u'"<?>"'), 1540 gmTools.coalesce(r['unified_target_min'], u'"<?>"'), 1541 gmTools.coalesce(r['unified_target_max'], u'"<?>"'), 1542 gmTools.coalesce(r['val_normal_min'], u'"<?>"'), 1543 gmTools.coalesce(r['val_normal_max'], u'"<?>"'), 1544 gmTools.coalesce(r['val_target_min'], u'"<?>"'), 1545 gmTools.coalesce(r['val_target_max'], u'"<?>"'), 1546 gmDateTime.pydt_strftime ( 1547 r['clin_when'], 1548 format = when_template, 1549 accuracy = gmDateTime.acc_minutes 1550 ) 1551 )) 1552 prev_date = curr_date 1553 1554 gp_data.close() 1555 1556 return filename
1557 1558 #============================================================
1559 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
1560 """Represents one lab result.""" 1561 1562 _cmd_fetch_payload = """ 1563 select *, xmin_test_result from v_results4lab_req 1564 where pk_result=%s""" 1565 _cmds_lock_rows_for_update = [ 1566 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update""" 1567 ] 1568 _cmds_store_payload = [ 1569 """update test_result set 1570 clin_when = %(val_when)s, 1571 narrative = %(progress_note_result)s, 1572 fk_type = %(pk_test_type)s, 1573 val_num = %(val_num)s::numeric, 1574 val_alpha = %(val_alpha)s, 1575 val_unit = %(val_unit)s, 1576 val_normal_min = %(val_normal_min)s, 1577 val_normal_max = %(val_normal_max)s, 1578 val_normal_range = %(val_normal_range)s, 1579 val_target_min = %(val_target_min)s, 1580 val_target_max = %(val_target_max)s, 1581 val_target_range = %(val_target_range)s, 1582 abnormality_indicator = %(abnormal)s, 1583 norm_ref_group = %(ref_group)s, 1584 note_provider = %(note_provider)s, 1585 material = %(material)s, 1586 material_detail = %(material_detail)s 1587 where pk = %(pk_result)s""", 1588 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s""" 1589 ] 1590 1591 _updatable_fields = [ 1592 'val_when', 1593 'progress_note_result', 1594 'val_num', 1595 'val_alpha', 1596 'val_unit', 1597 'val_normal_min', 1598 'val_normal_max', 1599 'val_normal_range', 1600 'val_target_min', 1601 'val_target_max', 1602 'val_target_range', 1603 'abnormal', 1604 'ref_group', 1605 'note_provider', 1606 'material', 1607 'material_detail' 1608 ] 1609 #--------------------------------------------------------
1610 - def __init__(self, aPK_obj=None, row=None):
1611 """Instantiate. 1612 1613 aPK_obj as dict: 1614 - patient_id 1615 - when_field (see view definition) 1616 - when 1617 - test_type 1618 - val_num 1619 - val_alpha 1620 - unit 1621 """ 1622 # instantiate from row data ? 1623 if aPK_obj is None: 1624 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 1625 return 1626 pk = aPK_obj 1627 # find PK from row data ? 1628 if type(aPK_obj) == types.DictType: 1629 # sanity checks 1630 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]: 1631 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj 1632 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None): 1633 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None' 1634 # get PK 1635 where_snippets = [ 1636 'pk_patient=%(patient_id)s', 1637 'pk_test_type=%(test_type)s', 1638 '%s=%%(when)s' % aPK_obj['when_field'], 1639 'val_unit=%(unit)s' 1640 ] 1641 if aPK_obj['val_num'] is not None: 1642 where_snippets.append('val_num=%(val_num)s::numeric') 1643 if aPK_obj['val_alpha'] is not None: 1644 where_snippets.append('val_alpha=%(val_alpha)s') 1645 1646 where_clause = ' and '.join(where_snippets) 1647 cmd = "select pk_result from v_results4lab_req where %s" % where_clause 1648 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 1649 if data is None: 1650 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj 1651 if len(data) == 0: 1652 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj 1653 pk = data[0][0] 1654 # instantiate class 1655 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1656 #--------------------------------------------------------
1657 - def get_patient(self):
1658 cmd = """ 1659 select 1660 %s, 1661 vbp.title, 1662 vbp.firstnames, 1663 vbp.lastnames, 1664 vbp.dob 1665 from v_basic_person vbp 1666 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']] 1667 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']]) 1668 return pat[0]
1669 #============================================================
1670 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
1671 """Represents one lab request.""" 1672 1673 _cmd_fetch_payload = """ 1674 select *, xmin_lab_request from v_lab_requests 1675 where pk_request=%s""" 1676 _cmds_lock_rows_for_update = [ 1677 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update""" 1678 ] 1679 _cmds_store_payload = [ 1680 """update lab_request set 1681 request_id=%(request_id)s, 1682 lab_request_id=%(lab_request_id)s, 1683 clin_when=%(sampled_when)s, 1684 lab_rxd_when=%(lab_rxd_when)s, 1685 results_reported_when=%(results_reported_when)s, 1686 request_status=%(request_status)s, 1687 is_pending=%(is_pending)s::bool, 1688 narrative=%(progress_note)s 1689 where pk=%(pk_request)s""", 1690 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s""" 1691 ] 1692 _updatable_fields = [ 1693 'request_id', 1694 'lab_request_id', 1695 'sampled_when', 1696 'lab_rxd_when', 1697 'results_reported_when', 1698 'request_status', 1699 'is_pending', 1700 'progress_note' 1701 ] 1702 #--------------------------------------------------------
1703 - def __init__(self, aPK_obj=None, row=None):
1704 """Instantiate lab request. 1705 1706 The aPK_obj can be either a dict with the keys "req_id" 1707 and "lab" or a simple primary key. 1708 """ 1709 # instantiate from row data ? 1710 if aPK_obj is None: 1711 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 1712 return 1713 pk = aPK_obj 1714 # instantiate from "req_id" and "lab" ? 1715 if type(aPK_obj) == types.DictType: 1716 # sanity check 1717 try: 1718 aPK_obj['req_id'] 1719 aPK_obj['lab'] 1720 except: 1721 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info()) 1722 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj) 1723 # generate query 1724 where_snippets = [] 1725 vals = {} 1726 where_snippets.append('request_id=%(req_id)s') 1727 if type(aPK_obj['lab']) == types.IntType: 1728 where_snippets.append('pk_test_org=%(lab)s') 1729 else: 1730 where_snippets.append('lab_name=%(lab)s') 1731 where_clause = ' and '.join(where_snippets) 1732 cmd = "select pk_request from v_lab_requests where %s" % where_clause 1733 # get pk 1734 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 1735 if data is None: 1736 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj) 1737 if len(data) == 0: 1738 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj) 1739 pk = data[0][0] 1740 # instantiate class 1741 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1742 #--------------------------------------------------------
1743 - def get_patient(self):
1744 cmd = """ 1745 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob 1746 from v_pat_items vpi, v_basic_person vbp 1747 where 1748 vpi.pk_item=%s 1749 and 1750 vbp.pk_identity=vpi.pk_patient""" 1751 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']]) 1752 if pat is None: 1753 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']]) 1754 return None 1755 if len(pat) == 0: 1756 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']]) 1757 return None 1758 return pat[0]
1759 #============================================================ 1760 # convenience functions 1761 #------------------------------------------------------------
1762 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
1763 """Create or get lab request. 1764 1765 returns tuple (status, value): 1766 (True, lab request instance) 1767 (False, error message) 1768 (None, housekeeping_todo primary key) 1769 """ 1770 req = None 1771 aPK_obj = { 1772 'lab': lab, 1773 'req_id': req_id 1774 } 1775 try: 1776 req = cLabRequest (aPK_obj) 1777 except gmExceptions.NoSuchClinItemError, msg: 1778 _log.info('%s: will try to create lab request' % str(msg)) 1779 except gmExceptions.ConstructorError, msg: 1780 _log.exception(str(msg), sys.exc_info(), verbose=0) 1781 return (False, msg) 1782 # found 1783 if req is not None: 1784 db_pat = req.get_patient() 1785 if db_pat is None: 1786 _log.error('cannot cross-check patient on lab request') 1787 return (None, '') 1788 # yes but ambigous 1789 if pat_id != db_pat[0]: 1790 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat)) 1791 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $' 1792 to = 'user' 1793 prob = _('The lab request already exists but belongs to a different patient.') 1794 sol = _('Verify which patient this lab request really belongs to.') 1795 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat) 1796 cat = 'lab' 1797 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat) 1798 return (None, data) 1799 return (True, req) 1800 # not found 1801 queries = [] 1802 if type(lab) is types.IntType: 1803 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)" 1804 else: 1805 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)" 1806 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id])) 1807 cmd = "select currval('lab_request_pk_seq')" 1808 queries.append((cmd, [])) 1809 # insert new 1810 result, err = gmPG.run_commit('historica', queries, True) 1811 if result is None: 1812 return (False, err) 1813 try: 1814 req = cLabRequest(aPK_obj=result[0][0]) 1815 except gmExceptions.ConstructorError, msg: 1816 _log.exception(str(msg), sys.exc_info(), verbose=0) 1817 return (False, msg) 1818 return (True, req)
1819 #------------------------------------------------------------
1820 -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):
1821 tres = None 1822 data = { 1823 'patient_id': patient_id, 1824 'when_field': when_field, 1825 'when': when, 1826 'test_type': test_type, 1827 'val_num': val_num, 1828 'val_alpha': val_alpha, 1829 'unit': unit 1830 } 1831 try: 1832 tres = cLabResult(aPK_obj=data) 1833 # exists already, so fail 1834 _log.error('will not overwrite existing test result') 1835 _log.debug(str(tres)) 1836 return (None, tres) 1837 except gmExceptions.NoSuchClinItemError: 1838 _log.debug('test result not found - as expected, will create it') 1839 except gmExceptions.ConstructorError, msg: 1840 _log.exception(str(msg), sys.exc_info(), verbose=0) 1841 return (False, msg) 1842 if request is None: 1843 return (False, _('need lab request when inserting lab result')) 1844 # not found 1845 if encounter_id is None: 1846 encounter_id = request['pk_encounter'] 1847 queries = [] 1848 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)" 1849 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit])) 1850 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)" 1851 queries.append((cmd, [request['pk_request']])) 1852 cmd = "select currval('test_result_pk_seq')" 1853 queries.append((cmd, [])) 1854 # insert new 1855 result, err = gmPG.run_commit('historica', queries, True) 1856 if result is None: 1857 return (False, err) 1858 try: 1859 tres = cLabResult(aPK_obj=result[0][0]) 1860 except gmExceptions.ConstructorError, msg: 1861 _log.exception(str(msg), sys.exc_info(), verbose=0) 1862 return (False, msg) 1863 return (True, tres)
1864 #------------------------------------------------------------
1865 -def get_unreviewed_results(limit=50):
1866 # sanity check 1867 if limit < 1: 1868 limit = 1 1869 # retrieve one more row than needed so we know there's more available ;-) 1870 lim = limit + 1 1871 cmd = """ 1872 select pk_result 1873 from v_results4lab_req 1874 where reviewed is false 1875 order by pk_patient 1876 limit %s""" % lim 1877 rows = gmPG.run_ro_query('historica', cmd) 1878 if rows is None: 1879 _log.error('error retrieving unreviewed lab results') 1880 return (None, _('error retrieving unreviewed lab results')) 1881 if len(rows) == 0: 1882 return (False, []) 1883 # more than LIMIT rows ? 1884 if len(rows) == lim: 1885 more_avail = True 1886 # but deliver only LIMIT rows so that our assumption holds true... 1887 del rows[limit] 1888 else: 1889 more_avail = False 1890 results = [] 1891 for row in rows: 1892 try: 1893 results.append(cLabResult(aPK_obj=row[0])) 1894 except gmExceptions.ConstructorError: 1895 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0) 1896 return (more_avail, results)
1897 #------------------------------------------------------------
1898 -def get_pending_requests(limit=250):
1899 lim = limit + 1 1900 cmd = "select pk from lab_request where is_pending is true limit %s" % lim 1901 rows = gmPG.run_ro_query('historica', cmd) 1902 if rows is None: 1903 _log.error('error retrieving pending lab requests') 1904 return (None, None) 1905 if len(rows) == 0: 1906 return (False, []) 1907 results = [] 1908 # more than LIMIT rows ? 1909 if len(rows) == lim: 1910 too_many = True 1911 # but deliver only LIMIT rows so that our assumption holds true... 1912 del rows[limit] 1913 else: 1914 too_many = False 1915 requests = [] 1916 for row in rows: 1917 try: 1918 requests.append(cLabRequest(aPK_obj=row[0])) 1919 except gmExceptions.ConstructorError: 1920 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0) 1921 return (too_many, requests)
1922 #------------------------------------------------------------
1923 -def get_next_request_ID(lab=None, incrementor_func=None):
1924 """Get logically next request ID for given lab. 1925 1926 - incrementor_func: 1927 - if not supplied the next ID is guessed 1928 - if supplied it is applied to the most recently used ID 1929 """ 1930 if type(lab) == types.IntType: 1931 lab_snippet = 'vlr.fk_test_org=%s' 1932 else: 1933 lab_snippet = 'vlr.lab_name=%s' 1934 lab = str(lab) 1935 cmd = """ 1936 select request_id 1937 from lab_request lr0 1938 where lr0.clin_when = ( 1939 select max(vlr.sampled_when) 1940 from v_lab_requests vlr 1941 where %s 1942 )""" % lab_snippet 1943 rows = gmPG.run_ro_query('historica', cmd, None, lab) 1944 if rows is None: 1945 _log.warning('error getting most recently used request ID for lab [%s]' % lab) 1946 return '' 1947 if len(rows) == 0: 1948 return '' 1949 most_recent = rows[0][0] 1950 # apply supplied incrementor 1951 if incrementor_func is not None: 1952 try: 1953 next = incrementor_func(most_recent) 1954 except TypeError: 1955 _log.error('cannot call incrementor function [%s]' % str(incrementor_func)) 1956 return most_recent 1957 return next 1958 # try to be smart ourselves 1959 for pos in range(len(most_recent)): 1960 header = most_recent[:pos] 1961 trailer = most_recent[pos:] 1962 try: 1963 return '%s%s' % (header, str(int(trailer) + 1)) 1964 except ValueError: 1965 header = most_recent[:-1] 1966 trailer = most_recent[-1:] 1967 return '%s%s' % (header, chr(ord(trailer) + 1))
1968 #============================================================
1969 -def calculate_bmi(mass=None, height=None, age=None):
1970 """Calculate BMI. 1971 1972 mass: kg 1973 height: cm 1974 age: not yet used 1975 1976 returns: 1977 (True/False, data) 1978 True: data = (bmi, lower_normal, upper_normal) 1979 False: data = error message 1980 """ 1981 converted, mass = gmTools.input2decimal(mass) 1982 if not converted: 1983 return False, u'mass: cannot convert <%s> to Decimal' % mass 1984 1985 converted, height = gmTools.input2decimal(height) 1986 if not converted: 1987 return False, u'height: cannot convert <%s> to Decimal' % height 1988 1989 approx_surface = (height / decimal.Decimal(100))**2 1990 bmi = mass / approx_surface 1991 1992 print mass, height, '->', approx_surface, '->', bmi 1993 1994 lower_normal_mass = 20.0 * approx_surface 1995 upper_normal_mass = 25.0 * approx_surface 1996 1997 return True, (bmi, lower_normal_mass, upper_normal_mass)
1998 #============================================================ 1999 # main - unit testing 2000 #------------------------------------------------------------ 2001 if __name__ == '__main__': 2002 2003 if len(sys.argv) < 2: 2004 sys.exit() 2005 2006 if sys.argv[1] != 'test': 2007 sys.exit() 2008 2009 import time 2010 2011 gmI18N.activate_locale() 2012 gmI18N.install_domain() 2013 2014 #------------------------------------------
2015 - def test_create_test_result():
2016 tr = create_test_result ( 2017 encounter = 1, 2018 episode = 1, 2019 type = 1, 2020 intended_reviewer = 1, 2021 val_num = '12', 2022 val_alpha=None, 2023 unit = 'mg/dl' 2024 ) 2025 print tr 2026 return tr
2027 #------------------------------------------
2028 - def test_delete_test_result():
2029 tr = test_create_test_result() 2030 delete_test_result(tr)
2031 #------------------------------------------
2032 - def test_result():
2033 r = cTestResult(aPK_obj=1) 2034 print r 2035 print r.reference_ranges
2036 #------------------------------------------
2037 - def test_lab_result():
2038 print "test_result()" 2039 # lab_result = cLabResult(aPK_obj=4) 2040 data = { 2041 'patient_id': 12, 2042 'when_field': 'val_when', 2043 'when': '2000-09-17 18:23:00+02', 2044 'test_type': 9, 2045 'val_num': 17.3, 2046 'val_alpha': None, 2047 'unit': 'mg/l' 2048 } 2049 lab_result = cLabResult(aPK_obj=data) 2050 print lab_result 2051 fields = lab_result.get_fields() 2052 for field in fields: 2053 print field, ':', lab_result[field] 2054 print "updatable:", lab_result.get_updatable_fields() 2055 print time.time() 2056 print lab_result.get_patient() 2057 print time.time()
2058 #------------------------------------------
2059 - def test_request():
2060 print "test_request()" 2061 try: 2062 # lab_req = cLabRequest(aPK_obj=1) 2063 # lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2) 2064 data = { 2065 'req_id': 'EML#SC937-0176-CEC#11', 2066 'lab': 'Enterprise Main Lab' 2067 } 2068 lab_req = cLabRequest(aPK_obj=data) 2069 except gmExceptions.ConstructorError, msg: 2070 print "no such lab request:", msg 2071 return 2072 print lab_req 2073 fields = lab_req.get_fields() 2074 for field in fields: 2075 print field, ':', lab_req[field] 2076 print "updatable:", lab_req.get_updatable_fields() 2077 print time.time() 2078 print lab_req.get_patient() 2079 print time.time()
2080 #--------------------------------------------------------
2081 - def test_unreviewed():
2082 data = get_unreviewed_results() 2083 for result in data: 2084 print result
2085 #--------------------------------------------------------
2086 - def test_pending():
2087 data = get_pending_requests() 2088 for result in data: 2089 print result
2090 #--------------------------------------------------------
2091 - def test_create_measurement_type():
2092 print create_measurement_type ( 2093 lab = None, 2094 abbrev = u'tBZ2', 2095 unit = u'mg%', 2096 name = 'BZ (test 2)' 2097 )
2098 #--------------------------------------------------------
2099 - def test_meta_test_type():
2100 mtt = cMetaTestType(aPK_obj = 1) 2101 print mtt 2102 print get_meta_test_types()
2103 #--------------------------------------------------------
2104 - def test_test_type():
2105 tt = cMeasurementType(aPK_obj = 1) 2106 print tt 2107 print get_measurement_types()
2108 #--------------------------------------------------------
2109 - def test_format_test_results():
2110 results = [ 2111 cTestResult(aPK_obj=1), 2112 cTestResult(aPK_obj=2), 2113 cTestResult(aPK_obj=3) 2114 # cTestResult(aPK_obj=4) 2115 ] 2116 print format_test_results(results = results)
2117 #--------------------------------------------------------
2118 - def test_calculate_bmi():
2119 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3]) 2120 bmi, low, high = data 2121 2122 print "BMI:", bmi 2123 print "low:", low, "kg" 2124 print "hi :", high, "kg"
2125 #--------------------------------------------------------
2126 - def test_test_panel():
2127 tp = cTestPanel(aPK_obj = 1) 2128 print tp 2129 print tp.format()
2130 #-------------------------------------------------------- 2131 2132 #test_result() 2133 #test_create_test_result() 2134 #test_delete_test_result() 2135 #test_create_measurement_type() 2136 #test_lab_result() 2137 #test_request() 2138 #test_create_result() 2139 #test_unreviewed() 2140 #test_pending() 2141 #test_meta_test_type() 2142 #test_test_type() 2143 #test_format_test_results() 2144 #test_calculate_bmi() 2145 test_test_panel() 2146 2147 #============================================================ 2148