1
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
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
38
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
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
145
146
148
150 self.__cache = {}
151 self.__patient = patient
152
153
155 return self.__patient
156
162
163 patient = property(lambda x:x, _set_patient)
164
165
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
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
194 else:
195 result.variables['parity_offset'] = 10
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
209
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
245
246 eGFRs = property(_get_egfrs, lambda x:x)
247
248
250
251
252 Schwartz = self.eGFR_Schwartz
253 if Schwartz.numeric_value is not None:
254 return Schwartz
255
256
257
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
264 if self.__patient['dob'] is None:
265 return CKD
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
289 if age > self.d(65):
290 if CG.numeric_value is not None:
291 return CG
292
293
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
302 return CKD
303
304 return MDRD
305
306 eGFR = property(_get_egfr, lambda x:x)
307
308
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
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
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)
353 elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
354 result.variables['unit_multiplier'] = self.d(30849)
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
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
372 result.variables['ethnicity_multiplier'] = self.d(1)
373 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor'))
374
375
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
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
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
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
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
472
473
474
475
476 result.variables['ethnicity_multiplier'] = self.d(1)
477 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor of 1.519 for "black"'))
478
479
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
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
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
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:
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:
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
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
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'
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
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
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
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
668 if result.variables['age@crea'] < 1:
669
670
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
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
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
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
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
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
883
884 - def d(self, initial):
885 if isinstance(initial, decimal.Decimal):
886 return initial
887
888 val = initial
889
890
891 if type(val) == type(float(1.4)):
892 val = str(val)
893
894
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
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
936
937
938 test_clin_calc()
939