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