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