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

Source Code for Module Gnumed.business.gmClinicalCalculator

  1  # -*- coding: utf-8 -*- 
  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  import datetime as pydt 
 15   
 16   
 17  if __name__ == '__main__': 
 18          sys.path.insert(0, '../../') 
 19   
 20  from Gnumed.pycommon import gmDateTime 
 21  from Gnumed.pycommon import gmI18N 
 22  from Gnumed.pycommon import gmLog2 
 23   
 24  if __name__ == '__main__': 
 25          gmI18N.activate_locale() 
 26          gmI18N.install_domain() 
 27          gmDateTime.init() 
 28   
 29  from Gnumed.pycommon import gmTools 
 30  from Gnumed.pycommon import gmBorg 
 31  from Gnumed.business import gmLOINC 
 32   
 33   
 34  _log = logging.getLogger('gm.calc') 
 35   
 36  #============================================================ 
37 -class cClinicalResult(object):
38
39 - def __init__(self, message=None):
40 self.message = message 41 self.numeric_value = None 42 self.unit = None 43 self.date_valid = None 44 self.formula_name = None 45 self.formula_source = None 46 self.variables = {} 47 self.sub_results = [] 48 self.warnings = [_('THIS IS NOT A VERIFIED MEASUREMENT. DO NOT USE FOR ACTUAL CARE.')] 49 self.hints = []
50 51 #--------------------------------------------------------
52 - def __str__(self):
53 txt = '[cClinicalResult]: %s %s (%s)\n\n%s' % ( 54 self.numeric_value, 55 self.unit, 56 self.date_valid, 57 self.format ( 58 left_margin = 0, 59 width = 80, 60 eol = '\n', 61 with_formula = True, 62 with_warnings = True, 63 with_variables = True, 64 with_sub_results = True, 65 return_list = False 66 ) 67 ) 68 return txt
69 70 #--------------------------------------------------------
71 - def format(self, left_margin=0, eol='\n', width=None, with_formula=False, with_warnings=True, with_variables=False, with_sub_results=False, with_hints=True, return_list=False):
72 lines = [] 73 lines.append(self.message) 74 75 if with_formula: 76 txt = gmTools.wrap ( 77 text = '%s %s' % ( 78 _('Algorithm:'), 79 self.formula_name 80 ), 81 width = width, 82 initial_indent = ' ', 83 subsequent_indent = ' ' * 2, 84 eol = eol 85 ) 86 lines.append(txt) 87 txt = gmTools.wrap ( 88 text = '%s %s' % ( 89 _('Source:'), 90 self.formula_source 91 ), 92 width = width, 93 initial_indent = ' ', 94 subsequent_indent = ' ' * 2, 95 eol = eol 96 ) 97 lines.append(txt) 98 99 if with_warnings: 100 if len(self.warnings) > 0: 101 lines.append(' Caveat:') 102 for w in self.warnings: 103 txt = gmTools.wrap(text = w, width = width, initial_indent = ' %s ' % gmTools.u_arrow2right, subsequent_indent = ' ', eol = eol) 104 lines.append(txt) 105 106 if with_hints: 107 if len(self.hints) > 0: 108 lines.append(' Hints:') 109 for h in self.hints: 110 txt = gmTools.wrap(text = h, width = width, initial_indent = ' %s ' % gmTools.u_arrow2right, subsequent_indent = ' ', eol = eol) 111 lines.append(txt) 112 113 if with_variables: 114 if len(self.variables) > 0: 115 lines.append(' %s' % _('Variables:')) 116 for key in self.variables.keys(): 117 txt = ' %s %s: %s' % ( 118 gmTools.u_arrow2right, 119 key, 120 self.variables[key] 121 ) 122 lines.append(txt) 123 124 if with_sub_results: 125 if len(self.sub_results) > 0: 126 lines.append(' %s' % _('Intermediate results:')) 127 for r in self.sub_results: 128 lines.extend(r.format ( 129 left_margin = left_margin + 1, 130 width = width, 131 eol = eol, 132 with_formula = with_formula, 133 with_warnings = with_warnings, 134 with_variables = with_variables, 135 with_sub_results = False, # break cycles 136 return_list = True 137 )) 138 139 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = eol) 140 if return_list: 141 return lines 142 143 left_margin = ' ' * left_margin 144 return left_margin + (eol + left_margin).join(lines) + eol
145 146 #============================================================
147 -class cClinicalCalculator(object):
148
149 - def __init__(self, patient=None):
150 self.__cache = {} 151 self.__patient = patient
152 153 #--------------------------------------------------------
154 - def _get_patient(self):
155 return self.__patient
156
157 - def _set_patient(self, patient):
158 if patient == self.__patient: 159 return 160 self.__patient = patient 161 self.remove_from_cache() # uncache all values
162 163 patient = property(lambda x:x, _set_patient) 164 165 #--------------------------------------------------------
166 - def remove_from_cache(self, key=None):
167 if key is None: 168 self.__cache = {} 169 return True 170 try: 171 del self.__cache[key] 172 return True 173 except KeyError: 174 _log.error('key [%s] does not exist in cache', key) 175 return False
176 177 #-------------------------------------------------------- 178 # formulae 179 #--------------------------------------------------------
180 - def get_EDC(self, lmp=None, nullipara=True):
181 182 result = cClinicalResult(_('unknown EDC')) 183 result.formula_name = 'EDC (Mittendorf 1990)' 184 result.formula_source = 'Mittendorf, R. et al., "The length of uncomplicated human gestation," OB/GYN, Vol. 75, No., 6 June, 1990, pp. 907-932.' 185 186 if lmp is None: 187 result.message = _('EDC: unknown LMP') 188 return result 189 190 result.variables['LMP'] = lmp 191 result.variables['nullipara'] = nullipara 192 if nullipara: 193 result.variables['parity_offset'] = 15 # days 194 else: 195 result.variables['parity_offset'] = 10 # days 196 197 now = gmDateTime.pydt_now_here() 198 if lmp > now: 199 result.warnings.append(_('LMP in the future')) 200 201 if self.__patient is None: 202 result.warnings.append(_('cannot run sanity checks, no patient')) 203 else: 204 if self.__patient['dob'] is None: 205 result.warnings.append(_('cannot run sanity checks, no DOB')) 206 else: 207 years, months, days, hours, minutes, seconds = gmDateTime.calculate_apparent_age(start = self.__patient['dob']) 208 # 5 years -- Myth ? 209 # http://www.mirror.co.uk/news/uk-news/top-10-crazy-amazing-and-world-789842 210 if years < 10: 211 result.warnings.append(_('patient less than 10 years old')) 212 if self.__patient['gender'] in [None, 'm']: 213 result.warnings.append(_('atypical gender for pregnancy: %s') % self.__patient.gender_string) 214 if self.__patient['deceased'] is not None: 215 result.warnings.append(_('patient already passed away')) 216 217 if lmp.month > 3: 218 edc_month = lmp.month - 3 219 edc_year = lmp.year + 1 220 else: 221 edc_month = lmp.month + 9 222 edc_year = lmp.year 223 224 result.numeric_value = gmDateTime.pydt_replace(dt = lmp, year = edc_year, month = edc_month, strict = False) + pydt.timedelta(days = result.variables['parity_offset']) 225 226 result.message = _('EDC: %s') % gmDateTime.pydt_strftime ( 227 result.numeric_value, 228 format = '%Y %b %d' 229 ) 230 result.date_valid = now 231 232 _log.debug('%s' % result) 233 234 return result
235 236 #--------------------------------------------------------
237 - def _get_egfrs(self):
238 egfrs = [ 239 self.eGFR_MDRD_short, 240 self.eGFR_Cockcroft_Gault, 241 self.eGFR_CKD_EPI, 242 self.eGFR_Schwartz 243 ] 244 return egfrs
245 246 eGFRs = property(_get_egfrs, lambda x:x) 247 248 #--------------------------------------------------------
249 - def _get_egfr(self):
250 251 # < 18 ? 252 Schwartz = self.eGFR_Schwartz 253 if Schwartz.numeric_value is not None: 254 return Schwartz 255 256 # this logic is based on "KVH aktuell 2/2014 Seite 10-15" 257 # expect normal GFR 258 CKD = self.eGFR_CKD_EPI 259 if CKD.numeric_value is not None: 260 if CKD.numeric_value > self.d(60): 261 return CKD 262 263 # CKD at or below 60 264 if self.__patient['dob'] is None: 265 return CKD # no method will work, so return CKD anyway 266 267 CG = self.eGFR_Cockcroft_Gault 268 MDRD = self.eGFR_MDRD_short 269 age = None 270 if age is None: 271 try: 272 age = CKD.variables['age@crea'] 273 except KeyError: 274 _log.warning('CKD-EPI: no age@crea') 275 if age is None: 276 try: 277 age = CG.variables['age@crea'] 278 except KeyError: 279 _log.warning('CG: no age@crea') 280 if age is None: 281 try: 282 age = MDRD.variables['age@crea'] 283 except KeyError: 284 _log.warning('MDRD: no age@crea') 285 if age is None: 286 age = gmDateTime.calculate_apparent_age(start = self.__patient['dob'])[0] 287 288 # geriatric ? 289 if age > self.d(65): 290 if CG.numeric_value is not None: 291 return CG 292 293 # non-geriatric or CG not computable 294 if MDRD.numeric_value is None: 295 if (CKD.numeric_value is not None) or (CG.numeric_value is None): 296 return CKD 297 return CG 298 299 if MDRD.numeric_value > self.d(60): 300 if CKD.numeric_value is not None: 301 # probably normal after all (>60) -> use CKD-EPI 302 return CKD 303 304 return MDRD
305 306 eGFR = property(_get_egfr, lambda x:x) 307 308 #--------------------------------------------------------
309 - def _get_gfr_mdrd_short(self):
310 311 try: 312 return self.__cache['MDRD_short'] 313 except KeyError: 314 pass 315 316 result = cClinicalResult(_('unknown MDRD (4 vars/IDMS)')) 317 result.formula_name = 'eGFR from 4-variables IDMS-MDRD' 318 result.formula_source = '1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS)' 319 result.hints.append(_('best @ 30 < GFR < 60 ml/min')) 320 321 if self.__patient is None: 322 result.message = _('MDRD (4 vars/IDMS): no patient') 323 return result 324 325 if self.__patient['dob'] is None: 326 result.message = _('MDRD (4 vars/IDMS): no DOB (no age)') 327 return result 328 329 # 1) gender 330 from Gnumed.business.gmPerson import map_gender2mf 331 result.variables['gender'] = self.__patient['gender'] 332 result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']] 333 if result.variables['gender_mf'] == 'm': 334 result.variables['gender_multiplier'] = self.d(1) 335 elif result.variables['gender_mf'] == 'f': 336 result.variables['gender_multiplier'] = self.d('0.742') 337 else: 338 result.message = _('MDRD (4 vars/IDMS): neither male nor female') 339 return result 340 341 # 2) creatinine 342 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1) 343 if result.variables['serum_crea'] is None: 344 result.message = _('MDRD (4 vars/IDMS): serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity 345 return result 346 if result.variables['serum_crea']['val_num'] is None: 347 result.message = _('MDRD (4 vars/IDMS): creatinine value not numeric') 348 return result 349 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 350 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 351 result.variables['unit_multiplier'] = self.d(175) # older: 186 352 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 353 result.variables['unit_multiplier'] = self.d(30849) # older: 32788 354 else: 355 result.message = _('MDRD (4 vars/IDMS): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 356 return result 357 358 # 3) age (at creatinine evaluation) 359 result.variables['dob'] = self.__patient['dob'] 360 result.variables['age@crea'] = self.d ( 361 gmDateTime.calculate_apparent_age ( 362 start = result.variables['dob'], 363 end = result.variables['serum_crea']['clin_when'] 364 )[0] 365 ) 366 if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18): 367 result.message = _('MDRD (4 vars/IDMS): formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea'] 368 return result 369 370 # 4) ethnicity 371 result.variables['ethnicity_multiplier'] = self.d(1) # non-black 372 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor')) 373 374 # calculate 375 result.numeric_value = result.variables['unit_multiplier'] * \ 376 pow(result.variables['serum_crea_val'], self.d('-1.154')) * \ 377 pow(result.variables['age@crea'], self.d('-0.203')) * \ 378 result.variables['ethnicity_multiplier'] * \ 379 result.variables['gender_multiplier'] 380 result.unit = 'ml/min/1.73m²' 381 382 BSA = self.body_surface_area 383 result.sub_results.append(BSA) 384 if BSA.numeric_value is None: 385 result.warnings.append(_('NOT corrected for non-average body surface (average = 1.73m²)')) 386 else: 387 result.variables['BSA'] = BSA.numeric_value 388 result_numeric_value = result.numeric_value / BSA.numeric_value 389 390 result.message = _('eGFR(MDRD): %.1f %s (%s) [4-vars, IDMS]') % ( 391 result.numeric_value, 392 result.unit, 393 gmDateTime.pydt_strftime ( 394 result.variables['serum_crea']['clin_when'], 395 format = '%Y %b %d' 396 ) 397 ) 398 result.date_valid = result.variables['serum_crea']['clin_when'] 399 400 self.__cache['MDRD_short'] = result 401 _log.debug('%s' % result) 402 403 return result
404 405 eGFR_MDRD_short = property(_get_gfr_mdrd_short, lambda x:x) 406 407 #--------------------------------------------------------
408 - def _get_gfr_ckd_epi(self):
409 410 try: 411 return self.__cache['CKD-EPI'] 412 except KeyError: 413 pass 414 415 result = cClinicalResult(_('unknown CKD-EPI')) 416 result.formula_name = 'eGFR from CKD-EPI' 417 result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function' 418 result.hints.append(_('best @ GFR > 60 ml/min')) 419 420 if self.__patient is None: 421 result.message = _('CKD-EPI: no patient') 422 return result 423 424 if self.__patient['dob'] is None: 425 result.message = _('CKD-EPI: no DOB (no age)') 426 return result 427 428 # 1) gender 429 from Gnumed.business.gmPerson import map_gender2mf 430 result.variables['gender'] = self.__patient['gender'] 431 result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']] 432 if result.variables['gender_mf'] == 'm': 433 result.variables['gender_multiplier'] = self.d(1) 434 result.variables['k:gender_divisor'] = self.d('0.9') 435 result.variables['a:gender_power'] = self.d('-0.411') 436 elif result.variables['gender_mf'] == 'f': 437 result.variables['gender_multiplier'] = self.d('1.018') 438 result.variables['k:gender_divisor'] = self.d('0.7') 439 result.variables['a:gender_power'] = self.d('-0.329') 440 else: 441 result.message = _('CKD-EPI: neither male nor female') 442 return result 443 444 # 2) creatinine 445 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1) 446 if result.variables['serum_crea'] is None: 447 result.message = _('CKD-EPI: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity 448 return result 449 if result.variables['serum_crea']['val_num'] is None: 450 result.message = _('CKD-EPI: creatinine value not numeric') 451 return result 452 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 453 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 454 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 455 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 456 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) / self.d('88.4') 457 else: 458 result.message = _('CKD-EPI: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 459 return result 460 461 # 3) age (at creatinine evaluation) 462 result.variables['dob'] = self.__patient['dob'] 463 result.variables['age@crea'] = self.d ( 464 gmDateTime.calculate_apparent_age ( 465 start = result.variables['dob'], 466 end = result.variables['serum_crea']['clin_when'] 467 )[0] 468 ) 469 # if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18): 470 # result.message = _('CKD-EPI: formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea'] 471 # return result 472 473 # 4) ethnicity 474 result.variables['ethnicity_multiplier'] = self.d(1) # non-black 475 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor of 1.519 for "black"')) 476 477 # calculate 478 result.numeric_value = ( 479 self.d(141) * \ 480 pow(min((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), result.variables['a:gender_power']) * \ 481 pow(max((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), self.d('-1.209')) * \ 482 pow(self.d('0.993'), result.variables['age@crea']) * \ 483 result.variables['gender_multiplier'] * \ 484 result.variables['ethnicity_multiplier'] 485 ) 486 result.unit = 'ml/min/1.73m²' 487 488 result.message = _('eGFR(CKD-EPI): %.1f %s (%s)') % ( 489 result.numeric_value, 490 result.unit, 491 gmDateTime.pydt_strftime ( 492 result.variables['serum_crea']['clin_when'], 493 format = '%Y %b %d' 494 ) 495 ) 496 result.date_valid = result.variables['serum_crea']['clin_when'] 497 498 self.__cache['CKD-EPI'] = result 499 _log.debug('%s' % result) 500 501 return result
502 503 eGFR_CKD_EPI = property(_get_gfr_ckd_epi, lambda x:x) 504 505 #--------------------------------------------------------
506 - def _get_gfr_cockcroft_gault(self):
507 508 try: 509 return self.__cache['cockcroft_gault'] 510 except KeyError: 511 pass 512 513 result = cClinicalResult(_('unknown Cockcroft-Gault')) 514 result.formula_name = 'eGFR from Cockcroft-Gault' 515 result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function' 516 result.hints.append(_('best @ age >65')) 517 518 if self.__patient is None: 519 result.message = _('Cockcroft-Gault: no patient') 520 return result 521 522 if self.__patient['dob'] is None: 523 result.message = _('Cockcroft-Gault: no DOB (no age)') 524 return result 525 526 # 1) gender 527 from Gnumed.business.gmPerson import map_gender2mf 528 result.variables['gender'] = self.__patient['gender'] 529 result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']] 530 if result.variables['gender_mf'] not in ['m', 'f']: 531 result.message = _('Cockcroft-Gault: neither male nor female') 532 return result 533 534 # 2) creatinine 535 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1) 536 if result.variables['serum_crea'] is None: 537 result.message = _('Cockcroft-Gault: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity 538 return result 539 if result.variables['serum_crea']['val_num'] is None: 540 result.message = _('Cockcroft-Gault: creatinine value not numeric') 541 return result 542 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 543 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 544 result.variables['unit_multiplier'] = self.d(72) 545 if result.variables['gender_mf'] == 'm': 546 result.variables['gender_multiplier'] = self.d('1') 547 else: #result.variables['gender_mf'] == 'f' 548 result.variables['gender_multiplier'] = self.d('0.85') 549 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 550 result.variables['unit_multiplier'] = self.d(1) 551 if result.variables['gender_mf'] == 'm': 552 result.variables['gender_multiplier'] = self.d('1.23') 553 else: #result.variables['gender_mf'] == 'f' 554 result.variables['gender_multiplier'] = self.d('1.04') 555 else: 556 result.message = _('Cockcroft-Gault: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 557 return result 558 559 # 3) age (at creatinine evaluation) 560 result.variables['dob'] = self.__patient['dob'] 561 result.variables['age@crea'] = self.d ( 562 gmDateTime.calculate_apparent_age ( 563 start = result.variables['dob'], 564 end = result.variables['serum_crea']['clin_when'] 565 )[0] 566 ) 567 if (result.variables['age@crea'] < 18): 568 result.message = _('Cockcroft-Gault: formula does not apply at age [%s] (17 < age)') % result.variables['age@crea'] 569 return result 570 571 result.variables['weight'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_weight, no_of_results = 1) 572 if result.variables['weight'] is None: 573 result.message = _('Cockcroft-Gault: weight not found') 574 return result 575 if result.variables['weight']['val_num'] is None: 576 result.message = _('Cockcroft-Gault: weight not numeric') 577 return result 578 if result.variables['weight']['val_unit'] == 'kg': 579 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num']) 580 elif result.variables['weight']['val_unit'] == 'g': 581 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000)) 582 else: 583 result.message = _('Cockcroft-Gault: weight not in kg or g') 584 return result 585 586 # calculate 587 result.numeric_value = (( 588 (140 - result.variables['age@crea']) * result.variables['weight_kg'] * result.variables['gender_multiplier']) \ 589 / \ 590 (result.variables['unit_multiplier'] * result.variables['serum_crea_val']) 591 ) 592 result.unit = 'ml/min' #/1.73m² 593 594 result.message = _('eGFR(CG): %.1f %s (%s)') % ( 595 result.numeric_value, 596 result.unit, 597 gmDateTime.pydt_strftime ( 598 result.variables['serum_crea']['clin_when'], 599 format = '%Y %b %d' 600 ) 601 ) 602 result.date_valid = result.variables['serum_crea']['clin_when'] 603 604 self.__cache['cockroft_gault'] = result 605 _log.debug('%s' % result) 606 607 return result
608 609 eGFR_Cockcroft_Gault = property(_get_gfr_cockcroft_gault, lambda x:x) 610 611 #--------------------------------------------------------
612 - def _get_gfr_schwartz(self):
613 614 try: 615 return self.__cache['gfr_schwartz'] 616 except KeyError: 617 pass 618 619 result = cClinicalResult(_('unknown eGFR (Schwartz)')) 620 result.formula_name = 'eGFR from updated Schwartz "bedside" formula (age < 19yrs)' 621 result.formula_source = '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' 622 result.hints.append(_('only applies @ age <18')) 623 624 if self.__patient is None: 625 result.message = _('eGFR (Schwartz): no patient') 626 return result 627 628 if self.__patient['dob'] is None: 629 result.message = _('eGFR (Schwartz): DOB needed for age') 630 return result 631 632 result.variables['dob'] = self.__patient['dob'] 633 634 # creatinine 635 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1) 636 if result.variables['serum_crea'] is None: 637 result.message = _('eGFR (Schwartz): serum creatinine value not found (LOINC: %s') % gmLOINC.LOINC_creatinine_quantity 638 return result 639 if result.variables['serum_crea']['val_num'] is None: 640 result.message = _('eGFR (Schwartz): creatinine value not numeric') 641 return result 642 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 643 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 644 result.variables['unit_multiplier'] = self.d(1) 645 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 646 result.variables['unit_multiplier'] = self.d('0.00113') 647 else: 648 result.message = _('eGFR (Schwartz): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 649 return result 650 651 # age 652 result.variables['age@crea'] = self.d ( 653 gmDateTime.calculate_apparent_age ( 654 start = result.variables['dob'], 655 end = result.variables['serum_crea']['clin_when'] 656 )[0] 657 ) 658 if result.variables['age@crea'] > 17: 659 result.message = _('eGFR (Schwartz): formula does not apply at age [%s] (age must be <18)') % result.variables['age@crea'] 660 return result 661 662 # age-dependant constant 663 if result.variables['age@crea'] < 1: 664 # first year pre-term: k = 0.33 665 # first year full-term: k = (0.45) 0.41 (updated) 666 result.variables['constant_for_age'] = self.d('0.41') 667 result.warnings.append(_('eGFR (Schwartz): not known whether pre-term birth, applying full-term formula')) 668 else: 669 result.variables['constant_for_age'] = self.d('0.41') 670 671 # height 672 result.variables['height'] = self.__patient.emr.get_result_at_timestamp ( 673 timestamp = result.variables['serum_crea']['clin_when'], 674 loinc = gmLOINC.LOINC_height, 675 tolerance_interval = '7 days' 676 ) 677 if result.variables['height'] is None: 678 result.message = _('eGFR (Schwartz): height not found') 679 return result 680 if result.variables['height']['val_num'] is None: 681 result.message = _('eGFR (Schwartz): height not numeric') 682 return result 683 if result.variables['height']['val_unit'] == 'cm': 684 result.variables['height_cm'] = self.d(result.variables['height']['val_num']) 685 elif result.variables['height']['val_unit'] == 'mm': 686 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10)) 687 elif result.variables['height']['val_unit'] == 'm': 688 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100) 689 else: 690 result.message = _('eGFR (Schwartz): height not in m, cm, or mm') 691 return result 692 693 # calculate 694 result.numeric_value = ( 695 result.variables['constant_for_age'] * result.variables['height_cm'] 696 ) / ( 697 result.variables['unit_multiplier'] * result.variables['serum_crea_val'] 698 ) 699 result.unit = 'ml/min/1.73m²' 700 701 result.message = _('eGFR (Schwartz): %.1f %s (%s)') % ( 702 result.numeric_value, 703 result.unit, 704 gmDateTime.pydt_strftime ( 705 result.variables['serum_crea']['clin_when'], 706 format = '%Y %b %d' 707 ) 708 ) 709 result.date_valid = result.variables['serum_crea']['clin_when'] 710 711 self.__cache['gfr_schwartz'] = result 712 _log.debug('%s' % result) 713 714 return result
715 716 eGFR_Schwartz = property(_get_gfr_schwartz, lambda x:x) 717 718 #--------------------------------------------------------
719 - def _get_body_surface_area(self):
720 721 try: 722 return self.__cache['body_surface_area'] 723 except KeyError: 724 pass 725 726 result = cClinicalResult(_('unknown body surface area')) 727 result.formula_name = 'Du Bois Body Surface Area' 728 result.formula_source = '12/2012: http://en.wikipedia.org/wiki/Body_surface_area' 729 730 if self.__patient is None: 731 result.message = _('Body Surface Area: no patient') 732 return result 733 734 result.variables['height'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_height, no_of_results = 1) 735 if result.variables['height'] is None: 736 result.message = _('Body Surface Area: height not found') 737 return result 738 if result.variables['height']['val_num'] is None: 739 result.message = _('Body Surface Area: height not numeric') 740 return result 741 if result.variables['height']['val_unit'] == 'cm': 742 result.variables['height_cm'] = self.d(result.variables['height']['val_num']) 743 elif result.variables['height']['val_unit'] == 'mm': 744 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10)) 745 elif result.variables['height']['val_unit'] == 'm': 746 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100) 747 else: 748 result.message = _('Body Surface Area: height not in m, cm, or mm') 749 return result 750 751 result.variables['weight'] = self.__patient.emr.get_result_at_timestamp ( 752 timestamp = result.variables['height']['clin_when'], 753 loinc = gmLOINC.LOINC_weight, 754 tolerance_interval = '10 days' 755 ) 756 if result.variables['weight'] is None: 757 result.message = _('Body Surface Area: weight not found') 758 return result 759 if result.variables['weight']['val_num'] is None: 760 result.message = _('Body Surface Area: weight not numeric') 761 return result 762 if result.variables['weight']['val_unit'] == 'kg': 763 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num']) 764 elif result.variables['weight']['val_unit'] == 'g': 765 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000)) 766 else: 767 result.message = _('Body Surface Area: weight not in kg or g') 768 return result 769 770 result.numeric_value = self.d('0.007184') * \ 771 pow(result.variables['weight_kg'], self.d('0.425')) * \ 772 pow(result.variables['height_cm'], self.d('0.725')) 773 result.unit = 'm²' 774 775 result.message = _('BSA (DuBois): %.2f %s') % ( 776 result.numeric_value, 777 result.unit 778 ) 779 result.date_valid = gmDateTime.pydt_now_here() 780 781 self.__cache['body_surface_area'] = result 782 _log.debug('%s' % result) 783 784 return result
785 786 body_surface_area = property(_get_body_surface_area, lambda x:x) 787 788 #--------------------------------------------------------
789 - def _get_body_mass_index(self):
790 791 try: 792 return self.__cache['body_mass_index'] 793 except KeyError: 794 pass 795 796 result = cClinicalResult(_('unknown BMI')) 797 result.formula_name = 'BMI/Quetelet Index' 798 result.formula_source = '08/2014: https://en.wikipedia.org/wiki/Body_mass_index' 799 800 if self.__patient is None: 801 result.message = _('BMI: no patient') 802 return result 803 804 result.variables['height'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_height, no_of_results = 1) 805 if result.variables['height'] is None: 806 result.message = _('BMI: height not found') 807 return result 808 if result.variables['height']['val_num'] is None: 809 result.message = _('BMI: height not numeric') 810 return result 811 if result.variables['height']['val_unit'] == 'cm': 812 result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(100)) 813 elif result.variables['height']['val_unit'] == 'mm': 814 result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(1000)) 815 elif result.variables['height']['val_unit'] == 'm': 816 result.variables['height_m'] = self.d(result.variables['height']['val_num']) 817 else: 818 result.message = _('BMI: height not in m, cm, or mm') 819 return result 820 821 result.variables['weight'] = self.__patient.emr.get_result_at_timestamp ( 822 timestamp = result.variables['height']['clin_when'], 823 loinc = gmLOINC.LOINC_weight, 824 tolerance_interval = '10 days' 825 ) 826 if result.variables['weight'] is None: 827 result.message = _('BMI: weight not found') 828 return result 829 if result.variables['weight']['val_num'] is None: 830 result.message = _('BMI: weight not numeric') 831 return result 832 if result.variables['weight']['val_unit'] == 'kg': 833 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num']) 834 elif result.variables['weight']['val_unit'] == 'g': 835 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000)) 836 else: 837 result.message = _('BMI: weight not in kg or g') 838 return result 839 840 result.variables['dob'] = self.__patient['dob'] 841 start = result.variables['dob'] 842 end = result.variables['height']['clin_when'] 843 multiplier = 1 844 if end < start: 845 start = result.variables['height']['clin_when'] 846 end = result.variables['dob'] 847 multiplier = -1 848 result.variables['age@height'] = multiplier * self.d(gmDateTime.calculate_apparent_age(start, end)[0]) 849 if (result.variables['age@height'] < 18): 850 result.message = _('BMI (Quetelet): formula does not apply at age [%s] (0 < age < 18)') % result.variables['age@height'] 851 return result 852 853 # bmi = mass kg / height m2 854 result.numeric_value = result.variables['weight_kg'] / (result.variables['height_m'] * result.variables['height_m']) 855 result.unit = 'kg/m²' 856 857 result.message = _('BMI (Quetelet): %.2f %s') % ( 858 result.numeric_value, 859 result.unit 860 ) 861 result.date_valid = gmDateTime.pydt_now_here() 862 863 self.__cache['body_mass_index'] = result 864 _log.debug('%s' % result) 865 866 return result
867 868 body_mass_index = property(_get_body_mass_index, lambda x:x) 869 bmi = property(_get_body_mass_index, lambda x:x) 870 871 #-------------------------------------------------------- 872 # helper functions 873 #--------------------------------------------------------
874 - def d(self, initial):
875 if isinstance(initial, decimal.Decimal): 876 return initial 877 878 val = initial 879 880 # float ? -> to string first 881 if type(val) == type(float(1.4)): 882 val = str(val) 883 884 # string ? -> "," to "." 885 if isinstance(val, str): 886 val = val.replace(',', '.', 1) 887 val = val.strip() 888 889 try: 890 d = decimal.Decimal(val) 891 return d 892 except (TypeError, decimal.InvalidOperation): 893 return None
894 895 #============================================================ 896 # main 897 #------------------------------------------------------------ 898 if __name__ == "__main__": 899 900 if len(sys.argv) == 1: 901 sys.exit() 902 903 if sys.argv[1] != 'test': 904 sys.exit() 905 906 from Gnumed.pycommon import gmLog2 907 #-----------------------------------------
908 - def test_clin_calc():
909 from Gnumed.business.gmPraxis import gmCurrentPraxisBranch 910 praxis = gmCurrentPraxisBranch() 911 from Gnumed.business.gmPerson import cPatient 912 pat = cPatient(aPK_obj = 12) 913 calc = cClinicalCalculator(patient = pat) 914 #result = calc.eGFR_MDRD_short 915 #result = calc.eGFR_Schwartz 916 result = calc.eGFR 917 #result = calc.body_surface_area 918 #result = calc.get_EDC(lmp = gmDateTime.pydt_now_here()) 919 #result = calc.body_mass_index 920 #result = calc.eGFRs 921 print('%s' % result.format(with_formula = True, with_warnings = True, with_variables = True, with_sub_results = True))
922 #----------------------------------------- 923 test_clin_calc() 924