Package Gnumed :: Package business :: Module gmClinicalCalculator
[frames] | no frames]

Source Code for Module Gnumed.business.gmClinicalCalculator

  1  # -*- coding: utf8 -*- 
  2  """GNUmed clinical calculator(s) 
  3   
  4  THIS IS NOT A VERIFIED CALCULATOR. DO NOT USE FOR ACTUAL CARE. 
  5  """ 
  6  #============================================================ 
  7  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  8  __license__ = "GPL v2 or later" 
  9   
 10  # standard libs 
 11  import sys 
 12  import logging 
 13  import decimal 
 14   
 15   
 16  if __name__ == '__main__': 
 17          sys.path.insert(0, '../../') 
 18   
 19  from Gnumed.pycommon import gmDateTime 
 20  from Gnumed.pycommon import gmI18N 
 21  from Gnumed.pycommon import gmLog2 
 22   
 23  if __name__ == '__main__': 
 24          gmI18N.activate_locale() 
 25          gmI18N.install_domain() 
 26          gmDateTime.init() 
 27   
 28  from Gnumed.pycommon import gmTools 
 29  from Gnumed.business import gmLOINC 
 30   
 31   
 32  _log = logging.getLogger('gm.calc') 
 33   
 34  #============================================================ 
35 -class cClinicalResult(object):
36
37 - def __init__(self, message=None):
38 self.message = message 39 self.numeric_value = None 40 self.unit = None 41 self.date_valid = None 42 self.formula_name = None 43 self.formula_source = None 44 self.variables = {} 45 self.sub_results = [] 46 self.warnings = [_('THIS IS NOT A VERIFIED MEASUREMENT. DO NOT USE FOR ACTUAL CARE.')]
47 #--------------------------------------------------------
48 - def __unicode__(self):
49 txt = u'[cClinicalResult]: %s %s (%s)\n\n%s' % ( 50 self.numeric_value, 51 self.unit, 52 self.date_valid, 53 self.format ( 54 left_margin = 0, 55 width = 80, 56 eol = u'\n', 57 with_formula = True, 58 with_warnings = True, 59 with_variables = True, 60 with_sub_results = True, 61 return_list = False 62 ) 63 ) 64 return txt
65 #--------------------------------------------------------
66 - def format(self, left_margin=0, eol=u'\n', width=None, with_formula=False, with_warnings=True, with_variables=False, with_sub_results=False, return_list=False):
67 lines = [] 68 lines.append(self.message) 69 70 if with_formula: 71 txt = gmTools.wrap ( 72 text = u'%s %s' % ( 73 _('Algorithm:'), 74 self.formula_name 75 ), 76 width = width, 77 initial_indent = u' ', 78 subsequent_indent = u' ' * 2, 79 eol = eol 80 ) 81 lines.append(txt) 82 txt = gmTools.wrap ( 83 text = u'%s %s' % ( 84 _('Source:'), 85 self.formula_source 86 ), 87 width = width, 88 initial_indent = u' ', 89 subsequent_indent = u' ' * 2, 90 eol = eol 91 ) 92 lines.append(txt) 93 94 if with_warnings: 95 if len(self.warnings) > 0: 96 lines.append(u' Caveat:') 97 for w in self.warnings: 98 txt = gmTools.wrap(text = w, width = width, initial_indent = u' %s ' % gmTools.u_right_arrow, subsequent_indent = u' ', eol = eol) 99 lines.append(txt) 100 if len(self.warnings) > 0: 101 lines.append(u'') 102 103 if with_variables: 104 if len(self.variables) > 0: 105 lines.append(u' %s' % _('Variables:')) 106 for key in self.variables.keys(): 107 txt = u' %s %s: %s' % ( 108 gmTools.u_right_arrow, 109 key, 110 self.variables[key] 111 ) 112 lines.append(txt) 113 114 if with_sub_results: 115 if len(self.sub_results) > 0: 116 lines.append(u' %s' % _('Intermediate results:')) 117 for r in self.sub_results: 118 lines.extend(r.format ( 119 left_margin = left_margin + 1, 120 width = width, 121 eol = eol, 122 with_formula = with_formula, 123 with_warnings = with_warnings, 124 with_variables = with_variables, 125 with_sub_results = False, # break cycles 126 return_list = True 127 )) 128 129 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = eol) 130 if return_list: 131 return lines 132 133 left_margin = u' ' * left_margin 134 return left_margin + (eol + left_margin).join(lines) + eol
135 136 #============================================================
137 -class cClinicalCalculator(object):
138
139 - def __init__(self, patient=None):
140 self.__cache = {} 141 self.__patient = patient
142 #--------------------------------------------------------
143 - def _get_patient(self):
144 return self.__patient
145
146 - def _set_patient(self, patient):
147 if patient == self.__patient: 148 return 149 self.__patient = patient 150 self.remove_from_cache() # uncache all values
151 152 patient = property(lambda x:x, _set_patient) 153 #-------------------------------------------------------- 154 # def suggest_algorithm(self, pk_test_type): 155 # return None 156 #--------------------------------------------------------
157 - def remove_from_cache(self, key=None):
158 if key is None: 159 self.__cache = {} 160 return True 161 try: 162 del self.__cache[key] 163 return True 164 except KeyError: 165 _log.error('key [%s] does not exist in cache', key) 166 return False
167 #-------------------------------------------------------- 168 # formulae 169 #--------------------------------------------------------
170 - def _get_egfr(self):
171 eGFR = self.eGFR_Schwartz 172 if eGFR.numeric_value is None: 173 eGFR = self.eGFR_MDRD_short 174 return eGFR
175 176 eGFR = property(_get_egfr, lambda x:x) 177 #--------------------------------------------------------
178 - def _get_mdrd_short(self):
179 180 try: 181 return self.__cache['MDRD_short'] 182 except KeyError: 183 pass 184 185 result = cClinicalResult(_('unknown MDRD (4 vars/IDMS)')) 186 result.formula_name = u'eGFR from 4-variables IDMS-MDRD' 187 result.formula_source = u'1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS)' 188 189 if self.__patient is None: 190 result.message = _('MDRD (4 vars/IDMS): no patient') 191 return result 192 193 if self.__patient['dob'] is None: 194 result.message = _('MDRD (4 vars/IDMS): no DOB (no age)') 195 return result 196 197 # 1) gender 198 from Gnumed.business.gmPerson import map_gender2mf 199 result.variables['gender'] = self.__patient['gender'] 200 result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']] 201 if result.variables['gender_mf'] == 'm': 202 result.variables['gender_multiplier'] = self.d(1) 203 elif result.variables['gender_mf'] == 'f': 204 result.variables['gender_multiplier'] = self.d('0.742') 205 else: 206 result.message = _('MDRD (4 vars/IDMS): neither male nor female') 207 return result 208 209 # 2) creatinine 210 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = ['2160-0', '14682-9', '40264-4'], no_of_results = 1) 211 if result.variables['serum_crea'] is None: 212 result.message = _('MDRD (4 vars/IDMS): serum creatinine value not found (LOINC: 2160-0, 14682-9)') 213 return result 214 if result.variables['serum_crea']['val_num'] is None: 215 result.message = _('MDRD (4 vars/IDMS): creatinine value not numeric') 216 return result 217 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 218 if result.variables['serum_crea']['val_unit'] in [u'mg/dl', u'mg/dL']: 219 result.variables['unit_multiplier'] = self.d(175) # older: 186 220 elif result.variables['serum_crea']['val_unit'] in [u'µmol/L', u'µmol/l']: 221 result.variables['unit_multiplier'] = self.d(30849) # older: 32788 222 else: 223 result.message = _('MDRD (4 vars/IDMS): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 224 return result 225 226 # 3) age (at creatinine evaluation) 227 result.variables['dob'] = self.__patient['dob'] 228 result.variables['age@crea'] = self.d ( 229 gmDateTime.calculate_apparent_age ( 230 start = result.variables['dob'], 231 end = result.variables['serum_crea']['clin_when'] 232 )[0] 233 ) 234 if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18): 235 result.message = _('MDRD (4 vars/IDMS): formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea'] 236 return result 237 238 # 4) ethnicity 239 result.variables['ethnicity_multiplier'] = self.d(1) # non-black 240 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor')) 241 242 # calculate 243 result.numeric_value = result.variables['unit_multiplier'] * \ 244 pow(result.variables['serum_crea_val'], self.d('-1.154')) * \ 245 pow(result.variables['age@crea'], self.d('-0.203')) * \ 246 result.variables['ethnicity_multiplier'] * \ 247 result.variables['gender_multiplier'] 248 result.unit = u'ml/min/1.73m²' 249 250 BSA = self.body_surface_area 251 result.sub_results.append(BSA) 252 if BSA.numeric_value is None: 253 result.warnings.append(_(u'NOT corrected for non-average body surface (average = 1.73m²)')) 254 else: 255 result.variables['BSA'] = BSA.numeric_value 256 result_numeric_value = result.numeric_value / BSA.numeric_value 257 258 result.message = _('eGFR(MDRD): %.1f %s (%s) [4-vars, IDMS]') % ( 259 result.numeric_value, 260 result.unit, 261 gmDateTime.pydt_strftime ( 262 result.variables['serum_crea']['clin_when'], 263 format = '%Y %b %d' 264 ) 265 ) 266 result.date_valid = result.variables['serum_crea']['clin_when'] 267 268 self.__cache['MDRD_short'] = result 269 _log.debug(u'%s' % result) 270 271 return result
272 273 eGFR_MDRD_short = property(_get_mdrd_short, lambda x:x) 274 #--------------------------------------------------------
275 - def _get_gfr_schwartz(self):
276 277 try: 278 return self.__cache['gfr_schwartz'] 279 except KeyError: 280 pass 281 282 result = cClinicalResult(_('unknown eGFR (Schwartz)')) 283 result.formula_name = u'eGFR from updated Schwartz "bedside" formula (age < 19yrs)' 284 result.formula_source = u'1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS) / doi 10.1681/ASN.2008030287 / doi: 10.2215/CJN.01640309' 285 286 if self.__patient is None: 287 result.message = _('eGFR (Schwartz): no patient') 288 return result 289 290 if self.__patient['dob'] is None: 291 result.message = _('eGFR (Schwartz): DOB needed for age') 292 return result 293 294 result.variables['dob'] = self.__patient['dob'] 295 296 # creatinine 297 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = ['2160-0', '14682-9', '40264-4'], no_of_results = 1) 298 if result.variables['serum_crea'] is None: 299 result.message = _('eGFR (Schwartz): serum creatinine value not found (LOINC: 2160-0, 14682-9)') 300 return result 301 if result.variables['serum_crea']['val_num'] is None: 302 result.message = _('eGFR (Schwartz): creatinine value not numeric') 303 return result 304 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 305 if result.variables['serum_crea']['val_unit'] in [u'mg/dl', u'mg/dL']: 306 result.variables['unit_multiplier'] = self.d(1) 307 elif result.variables['serum_crea']['val_unit'] in [u'µmol/L', u'µmol/l']: 308 result.variables['unit_multiplier'] = self.d('0.00113') 309 else: 310 result.message = _('eGFR (Schwartz): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 311 return result 312 313 # age 314 result.variables['age@crea'] = self.d ( 315 gmDateTime.calculate_apparent_age ( 316 start = result.variables['dob'], 317 end = result.variables['serum_crea']['clin_when'] 318 )[0] 319 ) 320 if result.variables['age@crea'] > 17: 321 result.message = _('eGFR (Schwartz): formula does not apply at age [%s] (age must be <18)') % result.variables['age@crea'] 322 return result 323 324 # age-dependant constant 325 if result.variables['age@crea'] < 1: 326 # first year pre-term: k = 0.33 327 # first year full-term: k = (0.45) 0.41 (updated) 328 result.variables['constant_for_age'] = self.d('0.41') 329 result.warnings.append(_('eGFR (Schwartz): not known whether pre-term birth, applying full-term formula')) 330 else: 331 result.variables['constant_for_age'] = self.d('0.41') 332 333 # height 334 result.variables['height'] = self.__patient.emr.get_result_at_timestamp ( 335 timestamp = result.variables['serum_crea']['clin_when'], 336 loinc = gmLOINC.LOINC_height, 337 tolerance_interval = '7 days' 338 ) 339 if result.variables['height'] is None: 340 result.message = _('eGFR (Schwartz): height not found') 341 return result 342 if result.variables['height']['val_num'] is None: 343 result.message = _('eGFR (Schwartz): height not numeric') 344 return result 345 if result.variables['height']['val_unit'] == u'cm': 346 result.variables['height_cm'] = self.d(result.variables['height']['val_num']) 347 elif result.variables['height']['val_unit'] == u'mm': 348 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10)) 349 elif result.variables['height']['val_unit'] == u'm': 350 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100) 351 else: 352 result.message = _('eGFR (Schwartz): height not in m, cm, or mm') 353 return result 354 355 # calculate 356 result.numeric_value = ( 357 result.variables['constant_for_age'] * result.variables['height_cm'] 358 ) / ( 359 result.variables['unit_multiplier'] * result.variables['serum_crea_val'] 360 ) 361 result.unit = u'ml/min/1.73m²' 362 363 result.message = _('eGFR (Schwartz): %.1f %s (%s)') % ( 364 result.numeric_value, 365 result.unit, 366 gmDateTime.pydt_strftime ( 367 result.variables['serum_crea']['clin_when'], 368 format = '%Y %b %d' 369 ) 370 ) 371 result.date_valid = result.variables['serum_crea']['clin_when'] 372 373 self.__cache['gfr_schwartz'] = result 374 _log.debug(u'%s' % result) 375 376 return result
377 378 eGFR_Schwartz = property(_get_gfr_schwartz, lambda x:x) 379 #--------------------------------------------------------
380 - def _get_body_surface_area(self):
381 382 try: 383 return self.__cache['body_surface_area'] 384 except KeyError: 385 pass 386 387 result = cClinicalResult(_('unknown body surface area')) 388 result.formula_name = u'Du Bois Body Surface Area' 389 result.formula_source = u'12/2012: http://en.wikipedia.org/wiki/Body_surface_area' 390 391 if self.__patient is None: 392 result.message = _('Body Surface Area: no patient') 393 return result 394 395 result.variables['height'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_height, no_of_results = 1) 396 if result.variables['height'] is None: 397 result.message = _('Body Surface Area: height not found') 398 return result 399 if result.variables['height']['val_num'] is None: 400 result.message = _('Body Surface Area: height not numeric') 401 return result 402 if result.variables['height']['val_unit'] == u'cm': 403 result.variables['height_cm'] = self.d(result.variables['height']['val_num']) 404 elif result.variables['height']['val_unit'] == u'mm': 405 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10)) 406 elif result.variables['height']['val_unit'] == u'm': 407 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100) 408 else: 409 result.message = _('Body Surface Area: height not in m, cm, or mm') 410 return result 411 412 result.variables['weight'] = self.__patient.emr.get_result_at_timestamp ( 413 timestamp = result.variables['height']['clin_when'], 414 loinc = gmLOINC.LOINC_weight, 415 tolerance_interval = '10 days' 416 ) 417 if result.variables['weight'] is None: 418 result.message = _('Body Surface Area: weight not found') 419 return result 420 if result.variables['weight']['val_num'] is None: 421 result.message = _('Body Surface Area: weight not numeric') 422 return result 423 if result.variables['weight']['val_unit'] == u'kg': 424 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num']) 425 elif result.variables['weight']['val_unit'] == u'g': 426 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000)) 427 else: 428 result.message = _('Body Surface Area: weight not in kg or g') 429 return result 430 431 result.numeric_value = self.d('0.007184') * \ 432 pow(result.variables['weight_kg'], self.d('0.425')) * \ 433 pow(result.variables['height_cm'], self.d('0.725')) 434 result.unit = u'm²' 435 436 result.message = _('BSA (DuBois): %.2f %s') % ( 437 result.numeric_value, 438 result.unit 439 ) 440 result.date_valid = gmDateTime.pydt_now_here() 441 442 self.__cache['body_surface_area'] = result 443 _log.debug(u'%s' % result) 444 445 return result
446 447 body_surface_area = property(_get_body_surface_area, lambda x:x) 448 #-------------------------------------------------------- 449 # helper functions 450 #--------------------------------------------------------
451 - def d(self, initial):
452 if isinstance(initial, decimal.Decimal): 453 return initial 454 455 val = initial 456 457 # float ? -> to string first 458 if type(val) == type(float(1.4)): 459 val = str(val) 460 461 # string ? -> "," to "." 462 if isinstance(val, basestring): 463 val = val.replace(',', '.', 1) 464 val = val.strip() 465 466 try: 467 d = decimal.Decimal(val) 468 return d 469 except (TypeError, decimal.InvalidOperation): 470 return None
471 472 #============================================================ 473 # main 474 #------------------------------------------------------------ 475 if __name__ == "__main__": 476 477 if len(sys.argv) == 1: 478 sys.exit() 479 480 if sys.argv[1] != 'test': 481 sys.exit() 482 483 from Gnumed.pycommon import gmLog2 484 #-----------------------------------------
485 - def test_clin_calc():
486 from Gnumed.business.gmPerson import cPatient 487 pat = cPatient(aPK_obj = 12) 488 calc = cClinicalCalculator(patient = pat) 489 result = calc.eGFR_MDRD_short 490 #result = calc.eGFR_Schwartz 491 #result = calc.eGFR 492 #result = calc.body_surface_area 493 print u'%s' % result
494 #----------------------------------------- 495 test_clin_calc() 496