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   
  32   
  33  _log = logging.getLogger('gm.lab') 
  34   
  35  #============================================================ 
36 -def _on_test_result_modified():
37 """Always relates to the active patient.""" 38 gmHooks.run_hook_script(hook = u'after_test_result_modified')
39 40 gmDispatcher.connect(_on_test_result_modified, u'test_result_mod_db') 41 42 #============================================================
43 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
44 """Represents one test org/lab.""" 45 _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s""" 46 _cmds_store_payload = [ 47 u"""UPDATE clin.test_org SET 48 fk_org_unit = %(pk_org_unit)s, 49 contact = gm.nullify_empty_string(%(test_org_contact)s), 50 comment = gm.nullify_empty_string(%(comment)s) 51 WHERE 52 pk = %(pk_test_org)s 53 AND 54 xmin = %(xmin_test_org)s 55 RETURNING 56 xmin AS xmin_test_org 57 """ 58 ] 59 _updatable_fields = [ 60 u'pk_org_unit', 61 u'test_org_contact', 62 u'comment' 63 ]
64 #------------------------------------------------------------
65 -def create_test_org(name=None, comment=None, pk_org_unit=None):
66 67 if name is None: 68 name = _('inhouse lab') 69 comment = _('auto-generated') 70 71 # get org unit 72 if pk_org_unit is None: 73 org = gmOrganization.org_exists(organization = name) 74 if org is None: 75 org = gmOrganization.create_org ( 76 organization = name, 77 category = u'Laboratory' 78 ) 79 org_unit = gmOrganization.create_org_unit ( 80 pk_organization = org['pk_org'], 81 unit = name 82 ) 83 pk_org_unit = org_unit['pk_org_unit'] 84 85 # test org exists ? 86 args = {'pk_unit': pk_org_unit} 87 cmd = u'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s' 88 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 89 90 if len(rows) == 0: 91 cmd = u'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk' 92 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 93 94 test_org = cTestOrg(aPK_obj = rows[0][0]) 95 if comment is not None: 96 comment = comment.strip() 97 test_org['comment'] = comment 98 test_org.save() 99 100 return test_org
101 #------------------------------------------------------------
102 -def delete_test_org(test_org=None):
103 args = {'pk': test_org} 104 cmd = u""" 105 DELETE FROM clin.test_org 106 WHERE 107 pk = %(pk)s 108 AND 109 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1) 110 AND 111 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1) 112 """ 113 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
114 #------------------------------------------------------------
115 -def get_test_orgs(order_by=u'unit'):
116 cmd = u'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by 117 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 118 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
119 #============================================================
120 -class cMetaTestType(gmBusinessDBObject.cBusinessDBObject):
121 """Represents one meta test type under which actual test types can be aggregated.""" 122 123 _cmd_fetch_payload = u"""select * from clin.meta_test_type where pk = %s""" 124 125 _cmds_store_payload = [] 126 127 _updatable_fields = []
128 #------------------------------------------------------------
129 -def delete_meta_type(meta_type=None):
130 cmd = u'delete from clin.meta_test_type where pk = %(pk)s' 131 args = {'pk': meta_type} 132 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
133 #------------------------------------------------------------
134 -def get_meta_test_types():
135 cmd = u'select * from clin.meta_test_type' 136 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 137 return [ cMetaTestType(row = {'pk_field': 'pk', 'data': r, 'idx': idx}) for r in rows ]
138 139 #============================================================
140 -class cMeasurementType(gmBusinessDBObject.cBusinessDBObject):
141 """Represents one test result type.""" 142 143 _cmd_fetch_payload = u"""select * from clin.v_test_types where pk_test_type = %s""" 144 145 _cmds_store_payload = [ 146 u"""UPDATE clin.test_type SET 147 abbrev = %(abbrev)s, 148 name = %(name)s, 149 loinc = gm.nullify_empty_string(%(loinc)s), 150 comment = gm.nullify_empty_string(%(comment_type)s), 151 conversion_unit = gm.nullify_empty_string(%(conversion_unit)s), 152 fk_test_org = %(pk_test_org)s, 153 fk_meta_test_type = %(pk_meta_test_type)s 154 WHERE 155 pk = %(pk_test_type)s 156 AND 157 xmin = %(xmin_test_type)s 158 RETURNING 159 xmin AS xmin_test_type""" 160 ] 161 162 _updatable_fields = [ 163 'abbrev', 164 'name', 165 'loinc', 166 'comment_type', 167 'conversion_unit', 168 'pk_test_org', 169 'pk_meta_test_type' 170 ] 171 #-------------------------------------------------------- 172 # def __setitem__(self, attribute, value): 173 # 174 # # find fk_test_org from name 175 # if (attribute == 'fk_test_org') and (value is not None): 176 # try: 177 # int(value) 178 # except: 179 # cmd = u"select pk from clin.test_org where internal _name = %(val)s" 180 # rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'val': value}}]) 181 # if len(rows) == 0: 182 # raise ValueError('[%s]: no test org for [%s], cannot set <%s>' % (self.__class__.__name__, value, attribute)) 183 # value = rows[0][0] 184 # 185 # gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value) 186 #--------------------------------------------------------
187 - def _get_in_use(self):
188 cmd = u'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)' 189 args = {'pk_type': self._payload[self._idx['pk_test_type']]} 190 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 191 return rows[0][0]
192 193 in_use = property(_get_in_use, lambda x:x) 194 #--------------------------------------------------------
195 - def get_most_recent_results(self, patient=None, no_of_results=1):
196 results = get_most_recent_results ( 197 test_type = self._payload[self._idx['pk_test_type']], 198 loinc = None, 199 no_of_results = no_of_results, 200 patient = patient 201 ) 202 if results is None: 203 if self._payload[self._idx['loinc']] is not None: 204 results = get_most_recent_results ( 205 test_type = None, 206 loinc = self._payload[self._idx['loinc']], 207 no_of_results = no_of_results, 208 patient = patient 209 ) 210 return results
211 #--------------------------------------------------------
212 - def format(self, patient=None):
213 tt = u'' 214 tt += _('Test type "%s" (%s) [#%s]\n') % ( 215 self._payload[self._idx['name']], 216 self._payload[self._idx['abbrev']], 217 self._payload[self._idx['pk_test_type']] 218 ) 219 tt += u'\n' 220 tt += gmTools.coalesce(self._payload[self._idx['loinc']], u'', u' LOINC: %s\n') 221 tt += gmTools.coalesce(self._payload[self._idx['conversion_unit']], u'', _(' Conversion unit: %s\n')) 222 tt += gmTools.coalesce(self._payload[self._idx['comment_type']], u'', _(' Comment: %s\n')) 223 224 if self._payload[self._idx['is_fake_meta_type']] is False: 225 tt += u'\n' 226 tt += _('Aggregated under meta type:\n') 227 tt += _(' Name: %s - %s [#%s]\n') % ( 228 self._payload[self._idx['abbrev_meta']], 229 self._payload[self._idx['name_meta']], 230 self._payload[self._idx['pk_meta_test_type']] 231 ) 232 tt += gmTools.coalesce(self._payload[self._idx['loinc_meta']], u'', u' LOINC: %s\n') 233 tt += gmTools.coalesce(self._payload[self._idx['comment_meta']], u'', _(' Comment: %s\n')) 234 235 tt += u'\n' 236 tt += _('Lab details:\n') 237 tt += _(' Name: %s\n') % self._payload[self._idx['name_org']] 238 tt += gmTools.coalesce(self._payload[self._idx['contact_org']], u'', _(' Contact: %s\n')) 239 tt += gmTools.coalesce(self._payload[self._idx['comment_org']], u'', _(' Comment: %s\n')) 240 241 if patient is not None: 242 result = self.get_most_recent_results(patient = patient, no_of_results = 1) 243 if result is not None: 244 tt += u'\n' 245 tt += _('Most recent result:\n') 246 tt += _(' %s: %s%s%s') % ( 247 result['clin_when'].strftime('%Y-%m-%d'), 248 result['unified_val'], 249 gmTools.coalesce(result['val_unit'], u'', u' %s'), 250 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)') 251 ) 252 253 return tt
254 255 #------------------------------------------------------------
256 -def get_measurement_types(order_by=None):
257 cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s') 258 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True) 259 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
260 #------------------------------------------------------------
261 -def find_measurement_type(lab=None, abbrev=None, name=None):
262 263 if (abbrev is None) and (name is None): 264 raise ValueError('must have <abbrev> and/or <name> set') 265 266 where_snippets = [] 267 268 if lab is None: 269 where_snippets.append('pk_test_org IS NULL') 270 else: 271 try: 272 int(lab) 273 where_snippets.append('pk_test_org = %(lab)s') 274 except (TypeError, ValueError): 275 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)') 276 277 if abbrev is not None: 278 where_snippets.append('abbrev = %(abbrev)s') 279 280 if name is not None: 281 where_snippets.append('name = %(name)s') 282 283 where_clause = u' and '.join(where_snippets) 284 cmd = u"select * from clin.v_test_types where %s" % where_clause 285 args = {'lab': lab, 'abbrev': abbrev, 'name': name} 286 287 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 288 289 if len(rows) == 0: 290 return None 291 292 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx}) 293 return tt
294 #------------------------------------------------------------
295 -def delete_measurement_type(measurement_type=None):
296 cmd = u'delete from clin.test_type where pk = %(pk)s' 297 args = {'pk': measurement_type} 298 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
299 #------------------------------------------------------------
300 -def create_measurement_type(lab=None, abbrev=None, unit=None, name=None):
301 """Create or get test type.""" 302 303 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name) 304 # found ? 305 if ttype is not None: 306 return ttype 307 308 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit) 309 310 # not found, so create it 311 if unit is None: 312 _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit)) 313 raise ValueError('need <unit> to create test type') 314 315 # make query 316 cols = [] 317 val_snippets = [] 318 vals = {} 319 320 # lab 321 if lab is None: 322 lab = create_test_org()['pk_test_org'] 323 324 cols.append('fk_test_org') 325 try: 326 vals['lab'] = int(lab) 327 val_snippets.append('%(lab)s') 328 except: 329 vals['lab'] = lab 330 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)') 331 332 # code 333 cols.append('abbrev') 334 val_snippets.append('%(abbrev)s') 335 vals['abbrev'] = abbrev 336 337 # unit 338 cols.append('conversion_unit') 339 val_snippets.append('%(unit)s') 340 vals['unit'] = unit 341 342 # name 343 if name is not None: 344 cols.append('name') 345 val_snippets.append('%(name)s') 346 vals['name'] = name 347 348 col_clause = u', '.join(cols) 349 val_clause = u', '.join(val_snippets) 350 queries = [ 351 {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals}, 352 {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"} 353 ] 354 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True) 355 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx}) 356 357 return ttype
358 359 #============================================================
360 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
361 """Represents one test result.""" 362 363 _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s" 364 365 _cmds_store_payload = [ 366 u"""update clin.test_result set 367 clin_when = %(clin_when)s, 368 narrative = nullif(trim(%(comment)s), ''), 369 val_num = %(val_num)s, 370 val_alpha = nullif(trim(%(val_alpha)s), ''), 371 val_unit = nullif(trim(%(val_unit)s), ''), 372 val_normal_min = %(val_normal_min)s, 373 val_normal_max = %(val_normal_max)s, 374 val_normal_range = nullif(trim(%(val_normal_range)s), ''), 375 val_target_min = %(val_target_min)s, 376 val_target_max = %(val_target_max)s, 377 val_target_range = nullif(trim(%(val_target_range)s), ''), 378 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''), 379 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''), 380 note_test_org = nullif(trim(%(note_test_org)s), ''), 381 material = nullif(trim(%(material)s), ''), 382 material_detail = nullif(trim(%(material_detail)s), ''), 383 fk_intended_reviewer = %(pk_intended_reviewer)s, 384 fk_encounter = %(pk_encounter)s, 385 fk_episode = %(pk_episode)s, 386 fk_type = %(pk_test_type)s, 387 fk_request = %(pk_request)s 388 where 389 pk = %(pk_test_result)s and 390 xmin = %(xmin_test_result)s""", 391 u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s""" 392 ] 393 394 _updatable_fields = [ 395 'clin_when', 396 'comment', 397 'val_num', 398 'val_alpha', 399 'val_unit', 400 'val_normal_min', 401 'val_normal_max', 402 'val_normal_range', 403 'val_target_min', 404 'val_target_max', 405 'val_target_range', 406 'abnormality_indicator', 407 'norm_ref_group', 408 'note_test_org', 409 'material', 410 'material_detail', 411 'pk_intended_reviewer', 412 'pk_encounter', 413 'pk_episode', 414 'pk_test_type', 415 'pk_request' 416 ] 417 #--------------------------------------------------------
418 - def format(self, with_review=True, with_comments=True, date_format='%Y-%m-%d %H:%M'):
419 420 lines = [] 421 422 lines.append(u' %s %s (%s): %s %s%s' % ( 423 self._payload[self._idx['clin_when']].strftime(date_format), 424 self._payload[self._idx['unified_abbrev']], 425 self._payload[self._idx['unified_name']], 426 self._payload[self._idx['unified_val']], 427 self._payload[self._idx['val_unit']], 428 gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], u'', u' (%s)') 429 )) 430 431 if with_comments: 432 if gmTools.coalesce(self._payload[self._idx['comment']], u'').strip() != u'': 433 lines.append(_(' Doc: %s') % self._payload[self._idx['comment']].strip()) 434 if gmTools.coalesce(self._payload[self._idx['note_test_org']], u'').strip() != u'': 435 lines.append(_(' MTA: %s') % self._payload[self._idx['note_test_org']].strip()) 436 437 if with_review: 438 if self._payload[self._idx['reviewed']]: 439 if self._payload[self._idx['is_clinically_relevant']]: 440 lines.append(u' %s %s: %s' % ( 441 self._payload[self._idx['last_reviewer']], 442 self._payload[self._idx['last_reviewed']].strftime('%Y-%m-%d %H:%M'), 443 gmTools.bool2subst ( 444 self._payload[self._idx['is_technically_abnormal']], 445 _('abnormal and relevant'), 446 _('normal but relevant') 447 ) 448 )) 449 else: 450 lines.append(_(' unreviewed')) 451 452 return lines
453 #--------------------------------------------------------
454 - def _get_reference_ranges(self):
455 456 cmd = u""" 457 select 458 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) 459 pk_patient, 460 val_unit, 461 val_normal_min, val_normal_max, val_normal_range, 462 val_target_min, val_target_max, val_target_range, 463 norm_ref_group, 464 coalesce(norm_ref_group, '') as norm_ref_group_str 465 from 466 clin.v_test_results 467 where 468 pk_test_type = %(pk_type)s 469 """ 470 args = {'pk_type': self._payload[self._idx['pk_test_type']]} 471 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}]) 472 return rows
473
474 - def _set_reference_ranges(self, val):
475 raise AttributeError('[%s]: reference ranges not settable') % self.__class__.__name__
476 477 reference_ranges = property(_get_reference_ranges, _set_reference_ranges) 478 #--------------------------------------------------------
479 - def _get_test_type(self):
480 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
481 482 test_type = property(_get_test_type, lambda x:x) 483 #--------------------------------------------------------
484 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
485 486 # FIXME: this is not concurrency safe 487 if self._payload[self._idx['reviewed']]: 488 self.__change_existing_review ( 489 technically_abnormal = technically_abnormal, 490 clinically_relevant = clinically_relevant, 491 comment = comment 492 ) 493 else: 494 # do not sign off unreviewed results if 495 # NOTHING AT ALL is known about them 496 if technically_abnormal is None: 497 if clinically_relevant is None: 498 comment = gmTools.none_if(comment, u'', strip_string = True) 499 if comment is None: 500 if make_me_responsible is False: 501 return True 502 self.__set_new_review ( 503 technically_abnormal = technically_abnormal, 504 clinically_relevant = clinically_relevant, 505 comment = comment 506 ) 507 508 if make_me_responsible is True: 509 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user" 510 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}]) 511 self['pk_intended_reviewer'] = rows[0][0] 512 self.save_payload() 513 return 514 515 self.refetch_payload()
516 #--------------------------------------------------------
517 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
518 519 if desired_earlier_results < 1: 520 raise ValueError('<desired_earlier_results> must be > 0') 521 522 if desired_later_results < 1: 523 raise ValueError('<desired_later_results> must be > 0') 524 525 args = { 526 'pat': self._payload[self._idx['pk_patient']], 527 'ttyp': self._payload[self._idx['pk_test_type']], 528 'tloinc': self._payload[self._idx['loinc_tt']], 529 'mtyp': self._payload[self._idx['pk_meta_test_type']], 530 'mloinc': self._payload[self._idx['loinc_meta']], 531 'when': self._payload[self._idx['clin_when']], 532 'offset': max_offset 533 } 534 WHERE = u'((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))' 535 WHERE_meta = u'((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))' 536 if max_offset is not None: 537 WHERE = WHERE + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 538 WHERE_meta = WHERE_meta + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))' 539 540 SQL = u""" 541 SELECT * FROM clin.v_test_results 542 WHERE 543 pk_patient = %%(pat)s 544 AND 545 clin_when %s %%(when)s 546 AND 547 %s 548 ORDER BY clin_when 549 LIMIT %s""" 550 551 # get earlier results 552 earlier_results = [] 553 # by type 554 cmd = SQL % (u'<', WHERE, desired_earlier_results) 555 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 556 if len(rows) > 0: 557 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 558 # by meta type ? 559 missing_results = desired_earlier_results - len(earlier_results) 560 if missing_results > 0: 561 cmd = SQL % (u'<', WHERE_meta, missing_results) 562 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 563 if len(rows) > 0: 564 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 565 566 # get later results 567 later_results = [] 568 # by type 569 cmd = SQL % (u'>', WHERE, desired_later_results) 570 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 571 if len(rows) > 0: 572 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 573 # by meta type ? 574 missing_results = desired_later_results - len(later_results) 575 if missing_results > 0: 576 cmd = SQL % (u'>', WHERE_meta, missing_results) 577 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 578 if len(rows) > 0: 579 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]) 580 581 return earlier_results, later_results
582 #-------------------------------------------------------- 583 # internal API 584 #--------------------------------------------------------
585 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
586 """Add a review to a row. 587 588 - if technically abnormal is not provided/None it will be set 589 to True if the lab's indicator has a meaningful value 590 - if clinically relevant is not provided/None it is set to 591 whatever technically abnormal is 592 """ 593 if technically_abnormal is None: 594 technically_abnormal = False 595 if self._payload[self._idx['abnormality_indicator']] is not None: 596 if self._payload[self._idx['abnormality_indicator']].strip() != u'': 597 technically_abnormal = True 598 599 if clinically_relevant is None: 600 clinically_relevant = technically_abnormal 601 602 cmd = u""" 603 INSERT INTO clin.reviewed_test_results ( 604 fk_reviewed_row, 605 is_technically_abnormal, 606 clinically_relevant, 607 comment 608 ) VALUES ( 609 %(pk)s, 610 %(abnormal)s, 611 %(relevant)s, 612 gm.nullify_empty_string(%(cmt)s) 613 )""" 614 args = { 615 'pk': self._payload[self._idx['pk_test_result']], 616 'abnormal': technically_abnormal, 617 'relevant': clinically_relevant, 618 'cmt': comment 619 } 620 621 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
622 #--------------------------------------------------------
623 - def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
624 """Change a review on a row. 625 626 - if technically abnormal/clinically relevant are 627 None they are not set 628 """ 629 args = { 630 'pk_row': self._payload[self._idx['pk_test_result']], 631 'abnormal': technically_abnormal, 632 'relevant': clinically_relevant, 633 'cmt': comment 634 } 635 636 set_parts = [ 637 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)', 638 u'comment = gm.nullify_empty_string(%(cmt)s)' 639 ] 640 641 if technically_abnormal is not None: 642 set_parts.append(u'is_technically_abnormal = %(abnormal)s') 643 644 if clinically_relevant is not None: 645 set_parts.append(u'clinically_relevant = %(relevant)s') 646 647 cmd = u""" 648 UPDATE clin.reviewed_test_results SET 649 %s 650 WHERE 651 fk_reviewed_row = %%(pk_row)s 652 """ % u',\n '.join(set_parts) 653 654 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
655 656 #------------------------------------------------------------
657 -def get_most_recent_results(test_type=None, loinc=None, no_of_results=1, patient=None):
658 659 if None not in [test_type, loinc]: 660 raise ValueError('either <test_type> or <loinc> must be None') 661 662 if no_of_results < 1: 663 raise ValueError('<no_of_results> must be > 0') 664 665 args = { 666 'pat': patient, 667 'ttyp': test_type, 668 'loinc': loinc 669 } 670 671 where_parts = [u'pk_patient = %(pat)s'] 672 if test_type is not None: 673 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']] 674 elif loinc is not None: 675 where_parts.append(u'((loinc_tt = %(loinc)s) OR (loinc_meta = %(loinc)s))') 676 677 cmd = u""" 678 SELECT * FROM clin.v_test_results 679 WHERE 680 %s 681 ORDER BY clin_when DESC 682 LIMIT %s""" % ( 683 u' AND '.join(where_parts), 684 no_of_results 685 ) 686 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 687 if len(rows) == 0: 688 return None 689 690 if no_of_results == 1: 691 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]}) 692 693 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
694 #------------------------------------------------------------
695 -def delete_test_result(result=None):
696 697 try: 698 pk = int(result) 699 except (TypeError, AttributeError): 700 pk = result['pk_test_result'] 701 702 cmd = u'delete from clin.test_result where pk = %(pk)s' 703 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
704 #------------------------------------------------------------
705 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
706 707 cmd1 = u""" 708 insert into clin.test_result ( 709 fk_encounter, 710 fk_episode, 711 fk_type, 712 fk_intended_reviewer, 713 val_num, 714 val_alpha, 715 val_unit 716 ) values ( 717 %(enc)s, 718 %(epi)s, 719 %(type)s, 720 %(rev)s, 721 %(v_num)s, 722 %(v_alpha)s, 723 %(unit)s 724 )""" 725 726 cmd2 = u""" 727 select * 728 from 729 clin.v_test_results 730 where 731 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))""" 732 733 args = { 734 u'enc': encounter, 735 u'epi': episode, 736 u'type': type, 737 u'rev': intended_reviewer, 738 u'v_num': val_num, 739 u'v_alpha': val_alpha, 740 u'unit': unit 741 } 742 743 rows, idx = gmPG2.run_rw_queries ( 744 queries = [ 745 {'cmd': cmd1, 'args': args}, 746 {'cmd': cmd2} 747 ], 748 return_data = True, 749 get_col_idx = True 750 ) 751 752 tr = cTestResult(row = { 753 'pk_field': 'pk_test_result', 754 'idx': idx, 755 'data': rows[0] 756 }) 757 758 return tr
759 #------------------------------------------------------------
760 -def format_test_results(results=None, output_format=u'latex'):
761 762 _log.debug(u'formatting test results into [%s]', output_format) 763 764 if output_format == u'latex': 765 return __format_test_results_latex(results = results) 766 767 msg = _('unknown test results output format [%s]') % output_format 768 _log.error(msg) 769 return msg
770 #------------------------------------------------------------
771 -def __tests2latex_minipage(results=None, width=u'1.5cm', show_time=False, show_range=True):
772 773 if len(results) == 0: 774 return u'\\begin{minipage}{%s} \\end{minipage}' % width 775 776 lines = [] 777 for t in results: 778 779 tmp = u'' 780 781 if show_time: 782 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M') 783 784 tmp += u'%.8s' % t['unified_val'] 785 786 lines.append(tmp) 787 tmp = u'' 788 789 if show_range: 790 has_range = ( 791 t['unified_target_range'] is not None 792 or 793 t['unified_target_min'] is not None 794 or 795 t['unified_target_max'] is not None 796 ) 797 if has_range: 798 if t['unified_target_range'] is not None: 799 tmp += u'{\\tiny %s}' % t['unified_target_range'] 800 else: 801 tmp += u'{\\tiny %s}' % ( 802 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '), 803 gmTools.coalesce(t['unified_target_max'], u'', u'%s') 804 ) 805 lines.append(tmp) 806 807 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
808 #------------------------------------------------------------
809 -def __tests2latex_cell(results=None, show_time=False, show_range=True):
810 811 if len(results) == 0: 812 return u'' 813 814 lines = [] 815 for t in results: 816 817 tmp = u'' 818 819 if show_time: 820 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M') 821 822 tmp += u'\\normalsize %.8s' % t['unified_val'] 823 824 lines.append(tmp) 825 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ') 826 827 if not show_range: 828 lines.append(tmp) 829 continue 830 831 has_range = ( 832 t['unified_target_range'] is not None 833 or 834 t['unified_target_min'] is not None 835 or 836 t['unified_target_max'] is not None 837 ) 838 839 if not has_range: 840 lines.append(tmp) 841 continue 842 843 if t['unified_target_range'] is not None: 844 tmp += u'[%s]' % t['unified_target_range'] 845 else: 846 tmp += u'[%s%s]' % ( 847 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'), 848 gmTools.coalesce(t['unified_target_max'], u'', u'%s') 849 ) 850 lines.append(tmp) 851 852 return u' \\\\ '.join(lines)
853 #------------------------------------------------------------
854 -def __format_test_results_latex(results=None):
855 856 if len(results) == 0: 857 return u'\\noindent %s' % _('No test results to format.') 858 859 # discover the columns and rows 860 dates = {} 861 tests = {} 862 grid = {} 863 for result in results: 864 # row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name']) 865 row_label = result['unified_abbrev'] 866 tests[row_label] = None 867 col_label = u'{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d') 868 dates[col_label] = None 869 try: 870 grid[row_label] 871 except KeyError: 872 grid[row_label] = {} 873 try: 874 grid[row_label][col_label].append(result) 875 except KeyError: 876 grid[row_label][col_label] = [result] 877 878 col_labels = sorted(dates.keys(), reverse = True) 879 del dates 880 row_labels = sorted(tests.keys()) 881 del tests 882 883 col_def = len(col_labels) * u'>{\\raggedleft}p{1.7cm}|' 884 885 # format them 886 tex = u"""\\noindent %s 887 888 \\noindent \\begin{tabular}{|l|%s} 889 \\hline 890 & %s \\tabularnewline 891 \\hline 892 893 %%s \\tabularnewline 894 895 \\hline 896 897 \\end{tabular}""" % ( 898 _('Test results'), 899 col_def, 900 u' & '.join(col_labels) 901 ) 902 903 rows = [] 904 905 # loop over rows 906 for rl in row_labels: 907 cells = [rl] 908 # loop over cols per row 909 for cl in col_labels: 910 try: 911 # get tests for this (row/col) position 912 tests = grid[rl][cl] 913 except KeyError: 914 # none there, so insert empty cell 915 cells.append(u' ') 916 continue 917 918 cells.append ( 919 __tests2latex_cell ( 920 results = tests, 921 show_time = (len(tests) > 1), 922 show_range = True 923 ) 924 ) 925 926 rows.append(u' & '.join(cells)) 927 928 return tex % u' \\tabularnewline\n \\hline\n'.join(rows)
929 930 #============================================================
931 -def export_results_for_gnuplot(results=None, filename=None, show_year=True):
932 933 if filename is None: 934 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat') 935 936 # sort results into series by test type 937 series = {} 938 for r in results: 939 try: 940 series[r['unified_name']].append(r) 941 except KeyError: 942 series[r['unified_name']] = [r] 943 944 gp_data = codecs.open(filename, 'wb', 'utf8') 945 946 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting')) 947 gp_data.write(u'# -------------------------------------------------------------\n') 948 gp_data.write(u'# first line of index: test type abbreviation & name\n') 949 gp_data.write(u'#\n') 950 gp_data.write(u'# clin_when at full precision\n') 951 gp_data.write(u'# value\n') 952 gp_data.write(u'# unit\n') 953 gp_data.write(u'# unified (target or normal) range: lower bound\n') 954 gp_data.write(u'# unified (target or normal) range: upper bound\n') 955 gp_data.write(u'# normal range: lower bound\n') 956 gp_data.write(u'# normal range: upper bound\n') 957 gp_data.write(u'# target range: lower bound\n') 958 gp_data.write(u'# target range: upper bound\n') 959 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n') 960 gp_data.write(u'# -------------------------------------------------------------\n') 961 962 for test_type in series.keys(): 963 if len(series[test_type]) == 0: 964 continue 965 966 r = series[test_type][0] 967 title = u'%s (%s)' % ( 968 r['unified_abbrev'], 969 r['unified_name'] 970 ) 971 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title)) 972 973 prev_date = None 974 prev_year = None 975 for r in series[test_type]: 976 curr_date = r['clin_when'].strftime('%Y-%m-%d') 977 if curr_date == prev_date: 978 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing for same-day values')) 979 if show_year: 980 if r['clin_when'].year == prev_year: 981 when_template = '%b %d %H:%M' 982 else: 983 when_template = '%b %d %H:%M (%Y)' 984 prev_year = r['clin_when'].year 985 else: 986 when_template = '%b %d' 987 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % ( 988 r['clin_when'].strftime('%Y-%m-%d_%H:%M'), 989 r['unified_val'], 990 gmTools.coalesce(r['val_unit'], u'"<?>"'), 991 gmTools.coalesce(r['unified_target_min'], u'"<?>"'), 992 gmTools.coalesce(r['unified_target_max'], u'"<?>"'), 993 gmTools.coalesce(r['val_normal_min'], u'"<?>"'), 994 gmTools.coalesce(r['val_normal_max'], u'"<?>"'), 995 gmTools.coalesce(r['val_target_min'], u'"<?>"'), 996 gmTools.coalesce(r['val_target_max'], u'"<?>"'), 997 gmDateTime.pydt_strftime ( 998 r['clin_when'], 999 format = when_template, 1000 accuracy = gmDateTime.acc_minutes 1001 ) 1002 )) 1003 prev_date = curr_date 1004 1005 gp_data.close() 1006 1007 return filename
1008 1009 #============================================================
1010 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
1011 """Represents one lab result.""" 1012 1013 _cmd_fetch_payload = """ 1014 select *, xmin_test_result from v_results4lab_req 1015 where pk_result=%s""" 1016 _cmds_lock_rows_for_update = [ 1017 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update""" 1018 ] 1019 _cmds_store_payload = [ 1020 """update test_result set 1021 clin_when = %(val_when)s, 1022 narrative = %(progress_note_result)s, 1023 fk_type = %(pk_test_type)s, 1024 val_num = %(val_num)s::numeric, 1025 val_alpha = %(val_alpha)s, 1026 val_unit = %(val_unit)s, 1027 val_normal_min = %(val_normal_min)s, 1028 val_normal_max = %(val_normal_max)s, 1029 val_normal_range = %(val_normal_range)s, 1030 val_target_min = %(val_target_min)s, 1031 val_target_max = %(val_target_max)s, 1032 val_target_range = %(val_target_range)s, 1033 abnormality_indicator = %(abnormal)s, 1034 norm_ref_group = %(ref_group)s, 1035 note_provider = %(note_provider)s, 1036 material = %(material)s, 1037 material_detail = %(material_detail)s 1038 where pk = %(pk_result)s""", 1039 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s""" 1040 ] 1041 1042 _updatable_fields = [ 1043 'val_when', 1044 'progress_note_result', 1045 'val_num', 1046 'val_alpha', 1047 'val_unit', 1048 'val_normal_min', 1049 'val_normal_max', 1050 'val_normal_range', 1051 'val_target_min', 1052 'val_target_max', 1053 'val_target_range', 1054 'abnormal', 1055 'ref_group', 1056 'note_provider', 1057 'material', 1058 'material_detail' 1059 ] 1060 #--------------------------------------------------------
1061 - def __init__(self, aPK_obj=None, row=None):
1062 """Instantiate. 1063 1064 aPK_obj as dict: 1065 - patient_id 1066 - when_field (see view definition) 1067 - when 1068 - test_type 1069 - val_num 1070 - val_alpha 1071 - unit 1072 """ 1073 # instantiate from row data ? 1074 if aPK_obj is None: 1075 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 1076 return 1077 pk = aPK_obj 1078 # find PK from row data ? 1079 if type(aPK_obj) == types.DictType: 1080 # sanity checks 1081 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]: 1082 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj 1083 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None): 1084 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None' 1085 # get PK 1086 where_snippets = [ 1087 'pk_patient=%(patient_id)s', 1088 'pk_test_type=%(test_type)s', 1089 '%s=%%(when)s' % aPK_obj['when_field'], 1090 'val_unit=%(unit)s' 1091 ] 1092 if aPK_obj['val_num'] is not None: 1093 where_snippets.append('val_num=%(val_num)s::numeric') 1094 if aPK_obj['val_alpha'] is not None: 1095 where_snippets.append('val_alpha=%(val_alpha)s') 1096 1097 where_clause = ' and '.join(where_snippets) 1098 cmd = "select pk_result from v_results4lab_req where %s" % where_clause 1099 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 1100 if data is None: 1101 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj 1102 if len(data) == 0: 1103 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj 1104 pk = data[0][0] 1105 # instantiate class 1106 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1107 #--------------------------------------------------------
1108 - def get_patient(self):
1109 cmd = """ 1110 select 1111 %s, 1112 vbp.title, 1113 vbp.firstnames, 1114 vbp.lastnames, 1115 vbp.dob 1116 from v_basic_person vbp 1117 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']] 1118 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']]) 1119 return pat[0]
1120 #============================================================
1121 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
1122 """Represents one lab request.""" 1123 1124 _cmd_fetch_payload = """ 1125 select *, xmin_lab_request from v_lab_requests 1126 where pk_request=%s""" 1127 _cmds_lock_rows_for_update = [ 1128 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update""" 1129 ] 1130 _cmds_store_payload = [ 1131 """update lab_request set 1132 request_id=%(request_id)s, 1133 lab_request_id=%(lab_request_id)s, 1134 clin_when=%(sampled_when)s, 1135 lab_rxd_when=%(lab_rxd_when)s, 1136 results_reported_when=%(results_reported_when)s, 1137 request_status=%(request_status)s, 1138 is_pending=%(is_pending)s::bool, 1139 narrative=%(progress_note)s 1140 where pk=%(pk_request)s""", 1141 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s""" 1142 ] 1143 _updatable_fields = [ 1144 'request_id', 1145 'lab_request_id', 1146 'sampled_when', 1147 'lab_rxd_when', 1148 'results_reported_when', 1149 'request_status', 1150 'is_pending', 1151 'progress_note' 1152 ] 1153 #--------------------------------------------------------
1154 - def __init__(self, aPK_obj=None, row=None):
1155 """Instantiate lab request. 1156 1157 The aPK_obj can be either a dict with the keys "req_id" 1158 and "lab" or a simple primary key. 1159 """ 1160 # instantiate from row data ? 1161 if aPK_obj is None: 1162 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row) 1163 return 1164 pk = aPK_obj 1165 # instantiate from "req_id" and "lab" ? 1166 if type(aPK_obj) == types.DictType: 1167 # sanity check 1168 try: 1169 aPK_obj['req_id'] 1170 aPK_obj['lab'] 1171 except: 1172 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info()) 1173 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj) 1174 # generate query 1175 where_snippets = [] 1176 vals = {} 1177 where_snippets.append('request_id=%(req_id)s') 1178 if type(aPK_obj['lab']) == types.IntType: 1179 where_snippets.append('pk_test_org=%(lab)s') 1180 else: 1181 where_snippets.append('lab_name=%(lab)s') 1182 where_clause = ' and '.join(where_snippets) 1183 cmd = "select pk_request from v_lab_requests where %s" % where_clause 1184 # get pk 1185 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj) 1186 if data is None: 1187 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj) 1188 if len(data) == 0: 1189 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj) 1190 pk = data[0][0] 1191 # instantiate class 1192 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1193 #--------------------------------------------------------
1194 - def get_patient(self):
1195 cmd = """ 1196 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob 1197 from v_pat_items vpi, v_basic_person vbp 1198 where 1199 vpi.pk_item=%s 1200 and 1201 vbp.pk_identity=vpi.pk_patient""" 1202 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']]) 1203 if pat is None: 1204 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']]) 1205 return None 1206 if len(pat) == 0: 1207 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']]) 1208 return None 1209 return pat[0]
1210 #============================================================ 1211 # convenience functions 1212 #------------------------------------------------------------
1213 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
1214 """Create or get lab request. 1215 1216 returns tuple (status, value): 1217 (True, lab request instance) 1218 (False, error message) 1219 (None, housekeeping_todo primary key) 1220 """ 1221 req = None 1222 aPK_obj = { 1223 'lab': lab, 1224 'req_id': req_id 1225 } 1226 try: 1227 req = cLabRequest (aPK_obj) 1228 except gmExceptions.NoSuchClinItemError, msg: 1229 _log.info('%s: will try to create lab request' % str(msg)) 1230 except gmExceptions.ConstructorError, msg: 1231 _log.exception(str(msg), sys.exc_info(), verbose=0) 1232 return (False, msg) 1233 # found 1234 if req is not None: 1235 db_pat = req.get_patient() 1236 if db_pat is None: 1237 _log.error('cannot cross-check patient on lab request') 1238 return (None, '') 1239 # yes but ambigous 1240 if pat_id != db_pat[0]: 1241 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat)) 1242 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $' 1243 to = 'user' 1244 prob = _('The lab request already exists but belongs to a different patient.') 1245 sol = _('Verify which patient this lab request really belongs to.') 1246 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat) 1247 cat = 'lab' 1248 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat) 1249 return (None, data) 1250 return (True, req) 1251 # not found 1252 queries = [] 1253 if type(lab) is types.IntType: 1254 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)" 1255 else: 1256 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)" 1257 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id])) 1258 cmd = "select currval('lab_request_pk_seq')" 1259 queries.append((cmd, [])) 1260 # insert new 1261 result, err = gmPG.run_commit('historica', queries, True) 1262 if result is None: 1263 return (False, err) 1264 try: 1265 req = cLabRequest(aPK_obj=result[0][0]) 1266 except gmExceptions.ConstructorError, msg: 1267 _log.exception(str(msg), sys.exc_info(), verbose=0) 1268 return (False, msg) 1269 return (True, req)
1270 #------------------------------------------------------------
1271 -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):
1272 tres = None 1273 data = { 1274 'patient_id': patient_id, 1275 'when_field': when_field, 1276 'when': when, 1277 'test_type': test_type, 1278 'val_num': val_num, 1279 'val_alpha': val_alpha, 1280 'unit': unit 1281 } 1282 try: 1283 tres = cLabResult(aPK_obj=data) 1284 # exists already, so fail 1285 _log.error('will not overwrite existing test result') 1286 _log.debug(str(tres)) 1287 return (None, tres) 1288 except gmExceptions.NoSuchClinItemError: 1289 _log.debug('test result not found - as expected, will create it') 1290 except gmExceptions.ConstructorError, msg: 1291 _log.exception(str(msg), sys.exc_info(), verbose=0) 1292 return (False, msg) 1293 if request is None: 1294 return (False, _('need lab request when inserting lab result')) 1295 # not found 1296 if encounter_id is None: 1297 encounter_id = request['pk_encounter'] 1298 queries = [] 1299 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)" 1300 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit])) 1301 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)" 1302 queries.append((cmd, [request['pk_request']])) 1303 cmd = "select currval('test_result_pk_seq')" 1304 queries.append((cmd, [])) 1305 # insert new 1306 result, err = gmPG.run_commit('historica', queries, True) 1307 if result is None: 1308 return (False, err) 1309 try: 1310 tres = cLabResult(aPK_obj=result[0][0]) 1311 except gmExceptions.ConstructorError, msg: 1312 _log.exception(str(msg), sys.exc_info(), verbose=0) 1313 return (False, msg) 1314 return (True, tres)
1315 #------------------------------------------------------------
1316 -def get_unreviewed_results(limit=50):
1317 # sanity check 1318 if limit < 1: 1319 limit = 1 1320 # retrieve one more row than needed so we know there's more available ;-) 1321 lim = limit + 1 1322 cmd = """ 1323 select pk_result 1324 from v_results4lab_req 1325 where reviewed is false 1326 order by pk_patient 1327 limit %s""" % lim 1328 rows = gmPG.run_ro_query('historica', cmd) 1329 if rows is None: 1330 _log.error('error retrieving unreviewed lab results') 1331 return (None, _('error retrieving unreviewed lab results')) 1332 if len(rows) == 0: 1333 return (False, []) 1334 # more than LIMIT rows ? 1335 if len(rows) == lim: 1336 more_avail = True 1337 # but deliver only LIMIT rows so that our assumption holds true... 1338 del rows[limit] 1339 else: 1340 more_avail = False 1341 results = [] 1342 for row in rows: 1343 try: 1344 results.append(cLabResult(aPK_obj=row[0])) 1345 except gmExceptions.ConstructorError: 1346 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0) 1347 return (more_avail, results)
1348 #------------------------------------------------------------
1349 -def get_pending_requests(limit=250):
1350 lim = limit + 1 1351 cmd = "select pk from lab_request where is_pending is true limit %s" % lim 1352 rows = gmPG.run_ro_query('historica', cmd) 1353 if rows is None: 1354 _log.error('error retrieving pending lab requests') 1355 return (None, None) 1356 if len(rows) == 0: 1357 return (False, []) 1358 results = [] 1359 # more than LIMIT rows ? 1360 if len(rows) == lim: 1361 too_many = True 1362 # but deliver only LIMIT rows so that our assumption holds true... 1363 del rows[limit] 1364 else: 1365 too_many = False 1366 requests = [] 1367 for row in rows: 1368 try: 1369 requests.append(cLabRequest(aPK_obj=row[0])) 1370 except gmExceptions.ConstructorError: 1371 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0) 1372 return (too_many, requests)
1373 #------------------------------------------------------------
1374 -def get_next_request_ID(lab=None, incrementor_func=None):
1375 """Get logically next request ID for given lab. 1376 1377 - incrementor_func: 1378 - if not supplied the next ID is guessed 1379 - if supplied it is applied to the most recently used ID 1380 """ 1381 if type(lab) == types.IntType: 1382 lab_snippet = 'vlr.fk_test_org=%s' 1383 else: 1384 lab_snippet = 'vlr.lab_name=%s' 1385 lab = str(lab) 1386 cmd = """ 1387 select request_id 1388 from lab_request lr0 1389 where lr0.clin_when = ( 1390 select max(vlr.sampled_when) 1391 from v_lab_requests vlr 1392 where %s 1393 )""" % lab_snippet 1394 rows = gmPG.run_ro_query('historica', cmd, None, lab) 1395 if rows is None: 1396 _log.warning('error getting most recently used request ID for lab [%s]' % lab) 1397 return '' 1398 if len(rows) == 0: 1399 return '' 1400 most_recent = rows[0][0] 1401 # apply supplied incrementor 1402 if incrementor_func is not None: 1403 try: 1404 next = incrementor_func(most_recent) 1405 except TypeError: 1406 _log.error('cannot call incrementor function [%s]' % str(incrementor_func)) 1407 return most_recent 1408 return next 1409 # try to be smart ourselves 1410 for pos in range(len(most_recent)): 1411 header = most_recent[:pos] 1412 trailer = most_recent[pos:] 1413 try: 1414 return '%s%s' % (header, str(int(trailer) + 1)) 1415 except ValueError: 1416 header = most_recent[:-1] 1417 trailer = most_recent[-1:] 1418 return '%s%s' % (header, chr(ord(trailer) + 1))
1419 #============================================================
1420 -def calculate_bmi(mass=None, height=None, age=None):
1421 """Calculate BMI. 1422 1423 mass: kg 1424 height: cm 1425 age: not yet used 1426 1427 returns: 1428 (True/False, data) 1429 True: data = (bmi, lower_normal, upper_normal) 1430 False: data = error message 1431 """ 1432 converted, mass = gmTools.input2decimal(mass) 1433 if not converted: 1434 return False, u'mass: cannot convert <%s> to Decimal' % mass 1435 1436 converted, height = gmTools.input2decimal(height) 1437 if not converted: 1438 return False, u'height: cannot convert <%s> to Decimal' % height 1439 1440 approx_surface = (height / decimal.Decimal(100))**2 1441 bmi = mass / approx_surface 1442 1443 print mass, height, '->', approx_surface, '->', bmi 1444 1445 lower_normal_mass = 20.0 * approx_surface 1446 upper_normal_mass = 25.0 * approx_surface 1447 1448 return True, (bmi, lower_normal_mass, upper_normal_mass)
1449 #============================================================ 1450 # main - unit testing 1451 #------------------------------------------------------------ 1452 if __name__ == '__main__': 1453 1454 if len(sys.argv) < 2: 1455 sys.exit() 1456 1457 if sys.argv[1] != 'test': 1458 sys.exit() 1459 1460 import time 1461 1462 gmI18N.activate_locale() 1463 gmI18N.install_domain() 1464 1465 #------------------------------------------
1466 - def test_create_test_result():
1467 tr = create_test_result ( 1468 encounter = 1, 1469 episode = 1, 1470 type = 1, 1471 intended_reviewer = 1, 1472 val_num = '12', 1473 val_alpha=None, 1474 unit = 'mg/dl' 1475 ) 1476 print tr 1477 return tr
1478 #------------------------------------------
1479 - def test_delete_test_result():
1480 tr = test_create_test_result() 1481 delete_test_result(tr)
1482 #------------------------------------------
1483 - def test_result():
1484 r = cTestResult(aPK_obj=1) 1485 print r 1486 print r.reference_ranges
1487 #------------------------------------------
1488 - def test_lab_result():
1489 print "test_result()" 1490 # lab_result = cLabResult(aPK_obj=4) 1491 data = { 1492 'patient_id': 12, 1493 'when_field': 'val_when', 1494 'when': '2000-09-17 18:23:00+02', 1495 'test_type': 9, 1496 'val_num': 17.3, 1497 'val_alpha': None, 1498 'unit': 'mg/l' 1499 } 1500 lab_result = cLabResult(aPK_obj=data) 1501 print lab_result 1502 fields = lab_result.get_fields() 1503 for field in fields: 1504 print field, ':', lab_result[field] 1505 print "updatable:", lab_result.get_updatable_fields() 1506 print time.time() 1507 print lab_result.get_patient() 1508 print time.time()
1509 #------------------------------------------
1510 - def test_request():
1511 print "test_request()" 1512 try: 1513 # lab_req = cLabRequest(aPK_obj=1) 1514 # lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2) 1515 data = { 1516 'req_id': 'EML#SC937-0176-CEC#11', 1517 'lab': 'Enterprise Main Lab' 1518 } 1519 lab_req = cLabRequest(aPK_obj=data) 1520 except gmExceptions.ConstructorError, msg: 1521 print "no such lab request:", msg 1522 return 1523 print lab_req 1524 fields = lab_req.get_fields() 1525 for field in fields: 1526 print field, ':', lab_req[field] 1527 print "updatable:", lab_req.get_updatable_fields() 1528 print time.time() 1529 print lab_req.get_patient() 1530 print time.time()
1531 #--------------------------------------------------------
1532 - def test_unreviewed():
1533 data = get_unreviewed_results() 1534 for result in data: 1535 print result
1536 #--------------------------------------------------------
1537 - def test_pending():
1538 data = get_pending_requests() 1539 for result in data: 1540 print result
1541 #--------------------------------------------------------
1542 - def test_create_measurement_type():
1543 print create_measurement_type ( 1544 lab = None, 1545 abbrev = u'tBZ2', 1546 unit = u'mg%', 1547 name = 'BZ (test 2)' 1548 )
1549 #--------------------------------------------------------
1550 - def test_meta_test_type():
1551 mtt = cMetaTestType(aPK_obj = 1) 1552 print mtt 1553 print get_meta_test_types()
1554 #--------------------------------------------------------
1555 - def test_test_type():
1556 tt = cMeasurementType(aPK_obj = 1) 1557 print tt 1558 print get_measurement_types()
1559 #--------------------------------------------------------
1560 - def test_format_test_results():
1561 results = [ 1562 cTestResult(aPK_obj=1), 1563 cTestResult(aPK_obj=2), 1564 cTestResult(aPK_obj=3) 1565 # cTestResult(aPK_obj=4) 1566 ] 1567 print format_test_results(results = results)
1568 #--------------------------------------------------------
1569 - def test_calculate_bmi():
1570 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3]) 1571 bmi, low, high = data 1572 1573 print "BMI:", bmi 1574 print "low:", low, "kg" 1575 print "hi :", high, "kg"
1576 1577 #-------------------------------------------------------- 1578 1579 #test_result() 1580 #test_create_test_result() 1581 #test_delete_test_result() 1582 #test_create_measurement_type() 1583 #test_lab_result() 1584 #test_request() 1585 #test_create_result() 1586 #test_unreviewed() 1587 #test_pending() 1588 #test_meta_test_type() 1589 #test_test_type() 1590 #test_format_test_results() 1591 test_calculate_bmi() 1592 1593 #============================================================ 1594