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
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18
19 from Gnumed.pycommon import gmDateTime
20 from Gnumed.pycommon import gmI18N
21 from Gnumed.pycommon import gmLog2
22
23 if __name__ == '__main__':
24 gmI18N.activate_locale()
25 gmI18N.install_domain()
26 gmDateTime.init()
27
28 from Gnumed.pycommon import gmTools
29 from Gnumed.business import gmLOINC
30
31
32 _log = logging.getLogger('gm.calc')
33
34
36
38 self.message = message
39 self.numeric_value = None
40 self.unit = None
41 self.date_valid = None
42 self.formula_name = None
43 self.formula_source = None
44 self.variables = {}
45 self.sub_results = []
46 self.warnings = [_('THIS IS NOT A VERIFIED MEASUREMENT. DO NOT USE FOR ACTUAL CARE.')]
47
49 txt = u'[cClinicalResult]: %s %s (%s)\n\n%s' % (
50 self.numeric_value,
51 self.unit,
52 self.date_valid,
53 self.format (
54 left_margin = 0,
55 width = 80,
56 eol = u'\n',
57 with_formula = True,
58 with_warnings = True,
59 with_variables = True,
60 with_sub_results = True,
61 return_list = False
62 )
63 )
64 return txt
65
135
136
138
140 self.__cache = {}
141 self.__patient = patient
142
144 return self.__patient
145
151
152 patient = property(lambda x:x, _set_patient)
153
154
155
156
158 if key is None:
159 self.__cache = {}
160 return True
161 try:
162 del self.__cache[key]
163 return True
164 except KeyError:
165 _log.error('key [%s] does not exist in cache', key)
166 return False
167
168
169
175
176 eGFR = property(_get_egfr, lambda x:x)
177
179
180 try:
181 return self.__cache['MDRD_short']
182 except KeyError:
183 pass
184
185 result = cClinicalResult(_('unknown MDRD (4 vars/IDMS)'))
186 result.formula_name = u'eGFR from 4-variables IDMS-MDRD'
187 result.formula_source = u'1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS)'
188
189 if self.__patient is None:
190 result.message = _('MDRD (4 vars/IDMS): no patient')
191 return result
192
193 if self.__patient['dob'] is None:
194 result.message = _('MDRD (4 vars/IDMS): no DOB (no age)')
195 return result
196
197
198 from Gnumed.business.gmPerson import map_gender2mf
199 result.variables['gender'] = self.__patient['gender']
200 result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
201 if result.variables['gender_mf'] == 'm':
202 result.variables['gender_multiplier'] = self.d(1)
203 elif result.variables['gender_mf'] == 'f':
204 result.variables['gender_multiplier'] = self.d('0.742')
205 else:
206 result.message = _('MDRD (4 vars/IDMS): neither male nor female')
207 return result
208
209
210 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = ['2160-0', '14682-9', '40264-4'], no_of_results = 1)
211 if result.variables['serum_crea'] is None:
212 result.message = _('MDRD (4 vars/IDMS): serum creatinine value not found (LOINC: 2160-0, 14682-9)')
213 return result
214 if result.variables['serum_crea']['val_num'] is None:
215 result.message = _('MDRD (4 vars/IDMS): creatinine value not numeric')
216 return result
217 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
218 if result.variables['serum_crea']['val_unit'] in [u'mg/dl', u'mg/dL']:
219 result.variables['unit_multiplier'] = self.d(175)
220 elif result.variables['serum_crea']['val_unit'] in [u'µmol/L', u'µmol/l']:
221 result.variables['unit_multiplier'] = self.d(30849)
222 else:
223 result.message = _('MDRD (4 vars/IDMS): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
224 return result
225
226
227 result.variables['dob'] = self.__patient['dob']
228 result.variables['age@crea'] = self.d (
229 gmDateTime.calculate_apparent_age (
230 start = result.variables['dob'],
231 end = result.variables['serum_crea']['clin_when']
232 )[0]
233 )
234 if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18):
235 result.message = _('MDRD (4 vars/IDMS): formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea']
236 return result
237
238
239 result.variables['ethnicity_multiplier'] = self.d(1)
240 result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor'))
241
242
243 result.numeric_value = result.variables['unit_multiplier'] * \
244 pow(result.variables['serum_crea_val'], self.d('-1.154')) * \
245 pow(result.variables['age@crea'], self.d('-0.203')) * \
246 result.variables['ethnicity_multiplier'] * \
247 result.variables['gender_multiplier']
248 result.unit = u'ml/min/1.73m²'
249
250 BSA = self.body_surface_area
251 result.sub_results.append(BSA)
252 if BSA.numeric_value is None:
253 result.warnings.append(_(u'NOT corrected for non-average body surface (average = 1.73m²)'))
254 else:
255 result.variables['BSA'] = BSA.numeric_value
256 result_numeric_value = result.numeric_value / BSA.numeric_value
257
258 result.message = _('eGFR(MDRD): %.1f %s (%s) [4-vars, IDMS]') % (
259 result.numeric_value,
260 result.unit,
261 gmDateTime.pydt_strftime (
262 result.variables['serum_crea']['clin_when'],
263 format = '%Y %b %d'
264 )
265 )
266 result.date_valid = result.variables['serum_crea']['clin_when']
267
268 self.__cache['MDRD_short'] = result
269 _log.debug(u'%s' % result)
270
271 return result
272
273 eGFR_MDRD_short = property(_get_mdrd_short, lambda x:x)
274
276
277 try:
278 return self.__cache['gfr_schwartz']
279 except KeyError:
280 pass
281
282 result = cClinicalResult(_('unknown eGFR (Schwartz)'))
283 result.formula_name = u'eGFR from updated Schwartz "bedside" formula (age < 19yrs)'
284 result.formula_source = u'1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS) / doi 10.1681/ASN.2008030287 / doi: 10.2215/CJN.01640309'
285
286 if self.__patient is None:
287 result.message = _('eGFR (Schwartz): no patient')
288 return result
289
290 if self.__patient['dob'] is None:
291 result.message = _('eGFR (Schwartz): DOB needed for age')
292 return result
293
294 result.variables['dob'] = self.__patient['dob']
295
296
297 result.variables['serum_crea'] = self.__patient.emr.get_most_recent_results(loinc = ['2160-0', '14682-9', '40264-4'], no_of_results = 1)
298 if result.variables['serum_crea'] is None:
299 result.message = _('eGFR (Schwartz): serum creatinine value not found (LOINC: 2160-0, 14682-9)')
300 return result
301 if result.variables['serum_crea']['val_num'] is None:
302 result.message = _('eGFR (Schwartz): creatinine value not numeric')
303 return result
304 result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
305 if result.variables['serum_crea']['val_unit'] in [u'mg/dl', u'mg/dL']:
306 result.variables['unit_multiplier'] = self.d(1)
307 elif result.variables['serum_crea']['val_unit'] in [u'µmol/L', u'µmol/l']:
308 result.variables['unit_multiplier'] = self.d('0.00113')
309 else:
310 result.message = _('eGFR (Schwartz): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
311 return result
312
313
314 result.variables['age@crea'] = self.d (
315 gmDateTime.calculate_apparent_age (
316 start = result.variables['dob'],
317 end = result.variables['serum_crea']['clin_when']
318 )[0]
319 )
320 if result.variables['age@crea'] > 17:
321 result.message = _('eGFR (Schwartz): formula does not apply at age [%s] (age must be <18)') % result.variables['age@crea']
322 return result
323
324
325 if result.variables['age@crea'] < 1:
326
327
328 result.variables['constant_for_age'] = self.d('0.41')
329 result.warnings.append(_('eGFR (Schwartz): not known whether pre-term birth, applying full-term formula'))
330 else:
331 result.variables['constant_for_age'] = self.d('0.41')
332
333
334 result.variables['height'] = self.__patient.emr.get_result_at_timestamp (
335 timestamp = result.variables['serum_crea']['clin_when'],
336 loinc = gmLOINC.LOINC_height,
337 tolerance_interval = '7 days'
338 )
339 if result.variables['height'] is None:
340 result.message = _('eGFR (Schwartz): height not found')
341 return result
342 if result.variables['height']['val_num'] is None:
343 result.message = _('eGFR (Schwartz): height not numeric')
344 return result
345 if result.variables['height']['val_unit'] == u'cm':
346 result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
347 elif result.variables['height']['val_unit'] == u'mm':
348 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
349 elif result.variables['height']['val_unit'] == u'm':
350 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
351 else:
352 result.message = _('eGFR (Schwartz): height not in m, cm, or mm')
353 return result
354
355
356 result.numeric_value = (
357 result.variables['constant_for_age'] * result.variables['height_cm']
358 ) / (
359 result.variables['unit_multiplier'] * result.variables['serum_crea_val']
360 )
361 result.unit = u'ml/min/1.73m²'
362
363 result.message = _('eGFR (Schwartz): %.1f %s (%s)') % (
364 result.numeric_value,
365 result.unit,
366 gmDateTime.pydt_strftime (
367 result.variables['serum_crea']['clin_when'],
368 format = '%Y %b %d'
369 )
370 )
371 result.date_valid = result.variables['serum_crea']['clin_when']
372
373 self.__cache['gfr_schwartz'] = result
374 _log.debug(u'%s' % result)
375
376 return result
377
378 eGFR_Schwartz = property(_get_gfr_schwartz, lambda x:x)
379
381
382 try:
383 return self.__cache['body_surface_area']
384 except KeyError:
385 pass
386
387 result = cClinicalResult(_('unknown body surface area'))
388 result.formula_name = u'Du Bois Body Surface Area'
389 result.formula_source = u'12/2012: http://en.wikipedia.org/wiki/Body_surface_area'
390
391 if self.__patient is None:
392 result.message = _('Body Surface Area: no patient')
393 return result
394
395 result.variables['height'] = self.__patient.emr.get_most_recent_results(loinc = gmLOINC.LOINC_height, no_of_results = 1)
396 if result.variables['height'] is None:
397 result.message = _('Body Surface Area: height not found')
398 return result
399 if result.variables['height']['val_num'] is None:
400 result.message = _('Body Surface Area: height not numeric')
401 return result
402 if result.variables['height']['val_unit'] == u'cm':
403 result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
404 elif result.variables['height']['val_unit'] == u'mm':
405 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
406 elif result.variables['height']['val_unit'] == u'm':
407 result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
408 else:
409 result.message = _('Body Surface Area: height not in m, cm, or mm')
410 return result
411
412 result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
413 timestamp = result.variables['height']['clin_when'],
414 loinc = gmLOINC.LOINC_weight,
415 tolerance_interval = '10 days'
416 )
417 if result.variables['weight'] is None:
418 result.message = _('Body Surface Area: weight not found')
419 return result
420 if result.variables['weight']['val_num'] is None:
421 result.message = _('Body Surface Area: weight not numeric')
422 return result
423 if result.variables['weight']['val_unit'] == u'kg':
424 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
425 elif result.variables['weight']['val_unit'] == u'g':
426 result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
427 else:
428 result.message = _('Body Surface Area: weight not in kg or g')
429 return result
430
431 result.numeric_value = self.d('0.007184') * \
432 pow(result.variables['weight_kg'], self.d('0.425')) * \
433 pow(result.variables['height_cm'], self.d('0.725'))
434 result.unit = u'm²'
435
436 result.message = _('BSA (DuBois): %.2f %s') % (
437 result.numeric_value,
438 result.unit
439 )
440 result.date_valid = gmDateTime.pydt_now_here()
441
442 self.__cache['body_surface_area'] = result
443 _log.debug(u'%s' % result)
444
445 return result
446
447 body_surface_area = property(_get_body_surface_area, lambda x:x)
448
449
450
451 - def d(self, initial):
452 if isinstance(initial, decimal.Decimal):
453 return initial
454
455 val = initial
456
457
458 if type(val) == type(float(1.4)):
459 val = str(val)
460
461
462 if isinstance(val, basestring):
463 val = val.replace(',', '.', 1)
464 val = val.strip()
465
466 try:
467 d = decimal.Decimal(val)
468 return d
469 except (TypeError, decimal.InvalidOperation):
470 return None
471
472
473
474
475 if __name__ == "__main__":
476
477 if len(sys.argv) == 1:
478 sys.exit()
479
480 if sys.argv[1] != 'test':
481 sys.exit()
482
483 from Gnumed.pycommon import gmLog2
484
494
495 test_clin_calc()
496