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: 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 creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1) 343 result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None 344 if result.variables['serum_crea'] is None: 345 result.message = _('MDRD (4 vars/IDMS): serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity 346 return result 347 if result.variables['serum_crea']['val_num'] is None: 348 result.message = _('MDRD (4 vars/IDMS): creatinine value not numeric') 349 return result 350 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 351 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 352 result.variables['unit_multiplier'] = self.d(175) # older: 186 353 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 354 result.variables['unit_multiplier'] = self.d(30849) # older: 32788 355 else: 356 result.message = _('MDRD (4 vars/IDMS): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 357 return result 358 359 # 3) age (at creatinine evaluation) 360 result.variables['dob'] = self.__patient['dob'] 361 result.variables['age@crea'] = self.d ( 362 gmDateTime.calculate_apparent_age ( 363 start = result.variables['dob'], 364 end = result.variables['serum_crea']['clin_when'] 365 )[0] 366 ) 367 if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18): 368 result.message = _('MDRD (4 vars/IDMS): formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea'] 369 return result 370 371 # 4) ethnicity 372 result.variables['ethnicity_multiplier'] = self.d(1) # non-black 373 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor')) 374 375 # calculate 376 result.numeric_value = result.variables['unit_multiplier'] * \ 377 pow(result.variables['serum_crea_val'], self.d('-1.154')) * \ 378 pow(result.variables['age@crea'], self.d('-0.203')) * \ 379 result.variables['ethnicity_multiplier'] * \ 380 result.variables['gender_multiplier'] 381 result.unit = 'ml/min/1.73m²' 382 383 BSA = self.body_surface_area 384 result.sub_results.append(BSA) 385 if BSA.numeric_value is None: 386 result.warnings.append(_('NOT corrected for non-average body surface (average = 1.73m²)')) 387 else: 388 result.variables['BSA'] = BSA.numeric_value 389 result_numeric_value = result.numeric_value / BSA.numeric_value 390 391 result.message = _('eGFR(MDRD): %.1f %s (%s) [4-vars, IDMS]') % ( 392 result.numeric_value, 393 result.unit, 394 gmDateTime.pydt_strftime ( 395 result.variables['serum_crea']['clin_when'], 396 format = '%Y %b %d' 397 ) 398 ) 399 result.date_valid = result.variables['serum_crea']['clin_when'] 400 401 self.__cache['MDRD_short'] = result 402 _log.debug('%s' % result) 403 404 return result
405 406 eGFR_MDRD_short = property(_get_gfr_mdrd_short, lambda x:x) 407 408 #--------------------------------------------------------
409 - def _get_gfr_ckd_epi(self):
410 411 try: 412 return self.__cache['CKD-EPI'] 413 except KeyError: 414 pass 415 416 result = cClinicalResult(_('unknown CKD-EPI')) 417 result.formula_name = 'eGFR from CKD-EPI' 418 result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function' 419 result.hints.append(_('best @ GFR > 60 ml/min')) 420 421 if self.__patient is None: 422 result.message = _('CKD-EPI: no patient') 423 return result 424 425 if self.__patient['dob'] is None: 426 result.message = _('CKD-EPI: no DOB (no age)') 427 return result 428 429 # 1) gender 430 from Gnumed.business.gmPerson import map_gender2mf 431 result.variables['gender'] = self.__patient['gender'] 432 result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']] 433 if result.variables['gender_mf'] == 'm': 434 result.variables['gender_multiplier'] = self.d(1) 435 result.variables['k:gender_divisor'] = self.d('0.9') 436 result.variables['a:gender_power'] = self.d('-0.411') 437 elif result.variables['gender_mf'] == 'f': 438 result.variables['gender_multiplier'] = self.d('1.018') 439 result.variables['k:gender_divisor'] = self.d('0.7') 440 result.variables['a:gender_power'] = self.d('-0.329') 441 else: 442 result.message = _('CKD-EPI: neither male nor female') 443 return result 444 445 # 2) creatinine 446 creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1) 447 result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None 448 if result.variables['serum_crea'] is None: 449 result.message = _('CKD-EPI: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity 450 return result 451 if result.variables['serum_crea']['val_num'] is None: 452 result.message = _('CKD-EPI: creatinine value not numeric') 453 return result 454 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 455 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 456 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 457 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 458 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) / self.d('88.4') 459 else: 460 result.message = _('CKD-EPI: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 461 return result 462 463 # 3) age (at creatinine evaluation) 464 result.variables['dob'] = self.__patient['dob'] 465 result.variables['age@crea'] = self.d ( 466 gmDateTime.calculate_apparent_age ( 467 start = result.variables['dob'], 468 end = result.variables['serum_crea']['clin_when'] 469 )[0] 470 ) 471 # if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18): 472 # result.message = _('CKD-EPI: formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea'] 473 # return result 474 475 # 4) ethnicity 476 result.variables['ethnicity_multiplier'] = self.d(1) # non-black 477 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor of 1.519 for "black"')) 478 479 # calculate 480 result.numeric_value = ( 481 self.d(141) * \ 482 pow(min((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), result.variables['a:gender_power']) * \ 483 pow(max((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), self.d('-1.209')) * \ 484 pow(self.d('0.993'), result.variables['age@crea']) * \ 485 result.variables['gender_multiplier'] * \ 486 result.variables['ethnicity_multiplier'] 487 ) 488 result.unit = 'ml/min/1.73m²' 489 490 result.message = _('eGFR(CKD-EPI): %.1f %s (%s)') % ( 491 result.numeric_value, 492 result.unit, 493 gmDateTime.pydt_strftime ( 494 result.variables['serum_crea']['clin_when'], 495 format = '%Y %b %d' 496 ) 497 ) 498 result.date_valid = result.variables['serum_crea']['clin_when'] 499 500 self.__cache['CKD-EPI'] = result 501 _log.debug('%s' % result) 502 503 return result
504 505 eGFR_CKD_EPI = property(_get_gfr_ckd_epi, lambda x:x) 506 507 #--------------------------------------------------------
508 - def _get_gfr_cockcroft_gault(self):
509 510 try: 511 return self.__cache['cockcroft_gault'] 512 except KeyError: 513 pass 514 515 result = cClinicalResult(_('unknown Cockcroft-Gault')) 516 result.formula_name = 'eGFR from Cockcroft-Gault' 517 result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function' 518 result.hints.append(_('best @ age >65')) 519 520 if self.__patient is None: 521 result.message = _('Cockcroft-Gault: no patient') 522 return result 523 524 if self.__patient['dob'] is None: 525 result.message = _('Cockcroft-Gault: no DOB (no age)') 526 return result 527 528 # 1) gender 529 from Gnumed.business.gmPerson import map_gender2mf 530 result.variables['gender'] = self.__patient['gender'] 531 result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']] 532 if result.variables['gender_mf'] not in ['m', 'f']: 533 result.message = _('Cockcroft-Gault: neither male nor female') 534 return result 535 536 # 2) creatinine 537 creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1) 538 result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None 539 if result.variables['serum_crea'] is None: 540 result.message = _('Cockcroft-Gault: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity 541 return result 542 if result.variables['serum_crea']['val_num'] is None: 543 result.message = _('Cockcroft-Gault: creatinine value not numeric') 544 return result 545 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 546 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 547 result.variables['unit_multiplier'] = self.d(72) 548 if result.variables['gender_mf'] == 'm': 549 result.variables['gender_multiplier'] = self.d('1') 550 else: #result.variables['gender_mf'] == 'f' 551 result.variables['gender_multiplier'] = self.d('0.85') 552 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 553 result.variables['unit_multiplier'] = self.d(1) 554 if result.variables['gender_mf'] == 'm': 555 result.variables['gender_multiplier'] = self.d('1.23') 556 else: #result.variables['gender_mf'] == 'f' 557 result.variables['gender_multiplier'] = self.d('1.04') 558 else: 559 result.message = _('Cockcroft-Gault: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 560 return result 561 562 # 3) age (at creatinine evaluation) 563 result.variables['dob'] = self.__patient['dob'] 564 result.variables['age@crea'] = self.d ( 565 gmDateTime.calculate_apparent_age ( 566 start = result.variables['dob'], 567 end = result.variables['serum_crea']['clin_when'] 568 )[0] 569 ) 570 if (result.variables['age@crea'] < 18): 571 result.message = _('Cockcroft-Gault: formula does not apply at age [%s] (17 < age)') % result.variables['age@crea'] 572 return result 573 574 weights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_weight, max_no_of_results = 1) 575 result.variables['weight'] = weights[0] if len(weights) > 0 else None 576 if result.variables['weight'] is None: 577 result.message = _('Cockcroft-Gault: weight not found') 578 return result 579 if result.variables['weight']['val_num'] is None: 580 result.message = _('Cockcroft-Gault: weight not numeric') 581 return result 582 if result.variables['weight']['val_unit'] == 'kg': 583 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num']) 584 elif result.variables['weight']['val_unit'] == 'g': 585 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000)) 586 else: 587 result.message = _('Cockcroft-Gault: weight not in kg or g') 588 return result 589 590 # calculate 591 result.numeric_value = (( 592 (140 - result.variables['age@crea']) * result.variables['weight_kg'] * result.variables['gender_multiplier']) \ 593 / \ 594 (result.variables['unit_multiplier'] * result.variables['serum_crea_val']) 595 ) 596 result.unit = 'ml/min' #/1.73m² 597 598 result.message = _('eGFR(CG): %.1f %s (%s)') % ( 599 result.numeric_value, 600 result.unit, 601 gmDateTime.pydt_strftime ( 602 result.variables['serum_crea']['clin_when'], 603 format = '%Y %b %d' 604 ) 605 ) 606 result.date_valid = result.variables['serum_crea']['clin_when'] 607 608 self.__cache['cockroft_gault'] = result 609 _log.debug('%s' % result) 610 611 return result
612 613 eGFR_Cockcroft_Gault = property(_get_gfr_cockcroft_gault, lambda x:x) 614 615 #--------------------------------------------------------
616 - def _get_gfr_schwartz(self):
617 618 try: 619 return self.__cache['gfr_schwartz'] 620 except KeyError: 621 pass 622 623 result = cClinicalResult(_('unknown eGFR (Schwartz)')) 624 result.formula_name = 'eGFR from updated Schwartz "bedside" formula (age < 19yrs)' 625 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' 626 result.hints.append(_('only applies @ age <18')) 627 628 if self.__patient is None: 629 result.message = _('eGFR (Schwartz): no patient') 630 return result 631 632 if self.__patient['dob'] is None: 633 result.message = _('eGFR (Schwartz): DOB needed for age') 634 return result 635 636 result.variables['dob'] = self.__patient['dob'] 637 638 # creatinine 639 creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1) 640 result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None 641 if result.variables['serum_crea'] is None: 642 result.message = _('eGFR (Schwartz): serum creatinine value not found (LOINC: %s') % gmLOINC.LOINC_creatinine_quantity 643 return result 644 if result.variables['serum_crea']['val_num'] is None: 645 result.message = _('eGFR (Schwartz): creatinine value not numeric') 646 return result 647 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) 648 if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']: 649 result.variables['unit_multiplier'] = self.d(1) 650 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']: 651 result.variables['unit_multiplier'] = self.d('0.00113') 652 else: 653 result.message = _('eGFR (Schwartz): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit'] 654 return result 655 656 # age 657 result.variables['age@crea'] = self.d ( 658 gmDateTime.calculate_apparent_age ( 659 start = result.variables['dob'], 660 end = result.variables['serum_crea']['clin_when'] 661 )[0] 662 ) 663 if result.variables['age@crea'] > 17: 664 result.message = _('eGFR (Schwartz): formula does not apply at age [%s] (age must be <18)') % result.variables['age@crea'] 665 return result 666 667 # age-dependant constant 668 if result.variables['age@crea'] < 1: 669 # first year pre-term: k = 0.33 670 # first year full-term: k = (0.45) 0.41 (updated) 671 result.variables['constant_for_age'] = self.d('0.41') 672 result.warnings.append(_('eGFR (Schwartz): not known whether pre-term birth, applying full-term formula')) 673 else: 674 result.variables['constant_for_age'] = self.d('0.41') 675 676 # height 677 result.variables['height'] = self.__patient.emr.get_result_at_timestamp ( 678 timestamp = result.variables['serum_crea']['clin_when'], 679 loinc = gmLOINC.LOINC_height, 680 tolerance_interval = '7 days' 681 ) 682 if result.variables['height'] is None: 683 result.message = _('eGFR (Schwartz): height not found') 684 return result 685 if result.variables['height']['val_num'] is None: 686 result.message = _('eGFR (Schwartz): height not numeric') 687 return result 688 if result.variables['height']['val_unit'] == 'cm': 689 result.variables['height_cm'] = self.d(result.variables['height']['val_num']) 690 elif result.variables['height']['val_unit'] == 'mm': 691 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10)) 692 elif result.variables['height']['val_unit'] == 'm': 693 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100) 694 else: 695 result.message = _('eGFR (Schwartz): height not in m, cm, or mm') 696 return result 697 698 # calculate 699 result.numeric_value = ( 700 result.variables['constant_for_age'] * result.variables['height_cm'] 701 ) / ( 702 result.variables['unit_multiplier'] * result.variables['serum_crea_val'] 703 ) 704 result.unit = 'ml/min/1.73m²' 705 706 result.message = _('eGFR (Schwartz): %.1f %s (%s)') % ( 707 result.numeric_value, 708 result.unit, 709 gmDateTime.pydt_strftime ( 710 result.variables['serum_crea']['clin_when'], 711 format = '%Y %b %d' 712 ) 713 ) 714 result.date_valid = result.variables['serum_crea']['clin_when'] 715 716 self.__cache['gfr_schwartz'] = result 717 _log.debug('%s' % result) 718 719 return result
720 721 eGFR_Schwartz = property(_get_gfr_schwartz, lambda x:x) 722 723 #--------------------------------------------------------
724 - def _get_body_surface_area(self):
725 726 try: 727 return self.__cache['body_surface_area'] 728 except KeyError: 729 pass 730 731 result = cClinicalResult(_('unknown body surface area')) 732 result.formula_name = 'Du Bois Body Surface Area' 733 result.formula_source = '12/2012: http://en.wikipedia.org/wiki/Body_surface_area' 734 735 if self.__patient is None: 736 result.message = _('Body Surface Area: no patient') 737 return result 738 739 heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1) 740 result.variables['height'] = heights[0] if len(heights) > 0 else None 741 if result.variables['height'] is None: 742 result.message = _('Body Surface Area: height not found') 743 return result 744 745 if result.variables['height']['val_num'] is None: 746 result.message = _('Body Surface Area: height not numeric') 747 return result 748 749 if result.variables['height']['val_unit'] == 'cm': 750 result.variables['height_cm'] = self.d(result.variables['height']['val_num']) 751 elif result.variables['height']['val_unit'] == 'mm': 752 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10)) 753 elif result.variables['height']['val_unit'] == 'm': 754 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100) 755 else: 756 result.message = _('Body Surface Area: height not in m, cm, or mm') 757 return result 758 759 result.variables['weight'] = self.__patient.emr.get_result_at_timestamp ( 760 timestamp = result.variables['height']['clin_when'], 761 loinc = gmLOINC.LOINC_weight, 762 tolerance_interval = '10 days' 763 ) 764 if result.variables['weight'] is None: 765 result.message = _('Body Surface Area: weight not found') 766 return result 767 768 if result.variables['weight']['val_num'] is None: 769 result.message = _('Body Surface Area: weight not numeric') 770 return result 771 772 if result.variables['weight']['val_unit'] == 'kg': 773 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num']) 774 elif result.variables['weight']['val_unit'] == 'g': 775 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000)) 776 else: 777 result.message = _('Body Surface Area: weight not in kg or g') 778 return result 779 780 result.numeric_value = self.d('0.007184') * \ 781 pow(result.variables['weight_kg'], self.d('0.425')) * \ 782 pow(result.variables['height_cm'], self.d('0.725')) 783 result.unit = 'm²' 784 result.message = _('BSA (DuBois): %.2f %s') % ( 785 result.numeric_value, 786 result.unit 787 ) 788 result.date_valid = gmDateTime.pydt_now_here() 789 self.__cache['body_surface_area'] = result 790 _log.debug('%s' % result) 791 return result
792 793 body_surface_area = property(_get_body_surface_area, lambda x:x) 794 795 #--------------------------------------------------------
796 - def _get_body_mass_index(self):
797 798 try: 799 return self.__cache['body_mass_index'] 800 except KeyError: 801 pass 802 803 result = cClinicalResult(_('unknown BMI')) 804 result.formula_name = 'BMI/Quetelet Index' 805 result.formula_source = '08/2014: https://en.wikipedia.org/wiki/Body_mass_index' 806 807 if self.__patient is None: 808 result.message = _('BMI: no patient') 809 return result 810 811 heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1) 812 result.variables['height'] = heights[0] if len(heights) > 0 else None 813 if result.variables['height'] is None: 814 result.message = _('BMI: height not found') 815 return result 816 817 if result.variables['height']['val_num'] is None: 818 result.message = _('BMI: height not numeric') 819 return result 820 821 if result.variables['height']['val_unit'] == 'cm': 822 result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(100)) 823 elif result.variables['height']['val_unit'] == 'mm': 824 result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(1000)) 825 elif result.variables['height']['val_unit'] == 'm': 826 result.variables['height_m'] = self.d(result.variables['height']['val_num']) 827 else: 828 result.message = _('BMI: height not in m, cm, or mm') 829 return result 830 831 result.variables['weight'] = self.__patient.emr.get_result_at_timestamp ( 832 timestamp = result.variables['height']['clin_when'], 833 loinc = gmLOINC.LOINC_weight, 834 tolerance_interval = '10 days' 835 ) 836 if result.variables['weight'] is None: 837 result.message = _('BMI: weight not found') 838 return result 839 if result.variables['weight']['val_num'] is None: 840 result.message = _('BMI: weight not numeric') 841 return result 842 if result.variables['weight']['val_unit'] == 'kg': 843 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num']) 844 elif result.variables['weight']['val_unit'] == 'g': 845 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000)) 846 else: 847 result.message = _('BMI: weight not in kg or g') 848 return result 849 850 result.variables['dob'] = self.__patient['dob'] 851 start = result.variables['dob'] 852 end = result.variables['height']['clin_when'] 853 multiplier = 1 854 if end < start: 855 start = result.variables['height']['clin_when'] 856 end = result.variables['dob'] 857 multiplier = -1 858 result.variables['age@height'] = multiplier * self.d(gmDateTime.calculate_apparent_age(start, end)[0]) 859 if (result.variables['age@height'] < 18): 860 result.message = _('BMI (Quetelet): formula does not apply at age [%s] (0 < age < 18)') % result.variables['age@height'] 861 return result 862 863 # bmi = mass kg / height m2 864 result.numeric_value = result.variables['weight_kg'] / (result.variables['height_m'] * result.variables['height_m']) 865 result.unit = 'kg/m²' 866 867 result.message = _('BMI (Quetelet): %.2f %s') % ( 868 result.numeric_value, 869 result.unit 870 ) 871 result.date_valid = gmDateTime.pydt_now_here() 872 873 self.__cache['body_mass_index'] = result 874 _log.debug('%s' % result) 875 876 return result
877 878 body_mass_index = property(_get_body_mass_index, lambda x:x) 879 bmi = property(_get_body_mass_index, lambda x:x) 880 881 #-------------------------------------------------------- 882 # helper functions 883 #--------------------------------------------------------
884 - def d(self, initial):
885 if isinstance(initial, decimal.Decimal): 886 return initial 887 888 val = initial 889 890 # float ? -> to string first 891 if type(val) == type(float(1.4)): 892 val = str(val) 893 894 # string ? -> "," to "." 895 if isinstance(val, str): 896 val = val.replace(',', '.', 1) 897 val = val.strip() 898 899 try: 900 d = decimal.Decimal(val) 901 return d 902 except (TypeError, decimal.InvalidOperation): 903 return None
904 905 #============================================================ 906 # main 907 #------------------------------------------------------------ 908 if __name__ == "__main__": 909 910 if len(sys.argv) == 1: 911 sys.exit() 912 913 if sys.argv[1] != 'test': 914 sys.exit() 915 916 from Gnumed.pycommon import gmLog2 917 918 #-----------------------------------------
919 - def test_clin_calc():
920 from Gnumed.business import gmPraxis 921 branches = gmPraxis.get_praxis_branches() 922 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0]) 923 924 from Gnumed.business.gmPerson import cPatient 925 pat = cPatient(aPK_obj = 201) 926 927 calc = cClinicalCalculator(patient = pat) 928 #result = calc.eGFR_MDRD_short 929 #result = calc.eGFR_Schwartz 930 result = calc.eGFR 931 #result = calc.body_surface_area 932 #result = calc.get_EDC(lmp = gmDateTime.pydt_now_here()) 933 #result = calc.body_mass_index 934 #result = calc.eGFRs 935 print('%s' % result.format(with_formula = True, with_warnings = True, with_variables = True, with_sub_results = True))
936 937 #----------------------------------------- 938 test_clin_calc() 939