Module Gnumed.business.gmClinicalCalculator

GNUmed clinical calculator(s)

THIS IS NOT A VERIFIED CALCULATOR. DO NOT USE FOR ACTUAL CARE.

Expand source code
# -*- coding: utf-8 -*-
"""GNUmed clinical calculator(s)

THIS IS NOT A VERIFIED CALCULATOR. DO NOT USE FOR ACTUAL CARE.
"""
#============================================================
__author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
__license__ = "GPL v2 or later"

# standard libs
import sys
import logging
import decimal
import datetime as pydt


if __name__ == '__main__':
        sys.path.insert(0, '../../')

from Gnumed.pycommon import gmDateTime
from Gnumed.pycommon import gmI18N

if __name__ == '__main__':
        _ = lambda x:x
        gmI18N.activate_locale()
        gmI18N.install_domain()
        gmDateTime.init()

from Gnumed.pycommon import gmTools
from Gnumed.business import gmLOINC


_log = logging.getLogger('gm.calc')

#============================================================
class cClinicalResult(object):

        def __init__(self, message=None):
                self.message = message
                self.numeric_value = None
                self.unit = None
                self.date_valid = None
                self.formula_name = None
                self.formula_source = None
                self.variables = {}
                self.sub_results = []
                self.warnings = [_('THIS IS NOT A VERIFIED MEASUREMENT. DO NOT USE FOR ACTUAL CARE.')]
                self.hints = []

        #--------------------------------------------------------
        def __str__(self):
                txt = '[cClinicalResult]: %s %s (%s)\n\n%s' % (
                        self.numeric_value,
                        self.unit,
                        self.date_valid,
                        self.format (
                                left_margin = 0,
                                width = 80,
                                eol = '\n',
                                with_formula = True,
                                with_warnings = True,
                                with_variables = True,
                                with_sub_results = True,
                                return_list = False
                        )
                )
                return txt

        #--------------------------------------------------------
        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):
                lines = []
                lines.append(self.message)

                if with_formula:
                        txt = gmTools.wrap (
                                text = '%s %s' % (
                                        _('Algorithm:'),
                                        self.formula_name
                                ),
                                width = width,
                                initial_indent = ' ',
                                subsequent_indent = ' ' * 2,
                                eol = eol
                        )
                        lines.append(txt)
                        txt = gmTools.wrap (
                                text = '%s %s' % (
                                        _('Source:'),
                                        self.formula_source
                                ),
                                width = width,
                                initial_indent = ' ',
                                subsequent_indent = ' ' * 2,
                                eol = eol
                        )
                        lines.append(txt)

                if with_warnings:
                        if len(self.warnings) > 0:
                                lines.append(' Caveat:')
                        for w in self.warnings:
                                txt = gmTools.wrap(text = w, width = width, initial_indent = '  %s ' % gmTools.u_arrow2right, subsequent_indent = '    ', eol = eol)
                                lines.append(txt)

                if with_hints:
                        if len(self.hints) > 0:
                                lines.append(' Hints:')
                        for h in self.hints:
                                txt = gmTools.wrap(text = h, width = width, initial_indent = '  %s ' % gmTools.u_arrow2right, subsequent_indent = '    ', eol = eol)
                                lines.append(txt)

                if with_variables:
                        if len(self.variables) > 0:
                                lines.append(' %s' % _('Variables:'))
                        for key in self.variables:
                                txt = '  %s %s: %s' % (
                                        gmTools.u_arrow2right,
                                        key,
                                        self.variables[key]
                                )
                                lines.append(txt)

                if with_sub_results:
                        if len(self.sub_results) > 0:
                                lines.append(' %s' % _('Intermediate results:'))
                        for r in self.sub_results:
                                lines.extend(r.format (
                                        left_margin = left_margin + 1,
                                        width = width,
                                        eol = eol,
                                        with_formula = with_formula,
                                        with_warnings = with_warnings,
                                        with_variables = with_variables,
                                        with_sub_results = False,                       # break cycles
                                        return_list = True
                                ))

                lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = eol)
                if return_list:
                        return lines

                left_margin = ' ' * left_margin
                return left_margin + (eol + left_margin).join(lines) + eol

#============================================================
class cClinicalCalculator(object):

        def __init__(self, patient=None):
                self.__cache = {}
                self.__patient = patient

        #--------------------------------------------------------
        def _get_patient(self):
                return self.__patient

        def _set_patient(self, patient):
                if patient == self.__patient:
                        return
                self.__patient = patient
                self.remove_from_cache()                        # uncache all values

        patient = property(lambda x:x, _set_patient)

        #--------------------------------------------------------
        def remove_from_cache(self, key=None):
                if key is None:
                        self.__cache = {}
                        return True
                try:
                        del self.__cache[key]
                        return True
                except KeyError:
                        _log.error('key [%s] does not exist in cache', key)
                        return False

        #--------------------------------------------------------
        # formulae
        #--------------------------------------------------------
        def get_EDC(self, lmp=None, nullipara=True):

                result = cClinicalResult(_('unknown EDC'))
                result.formula_name = 'EDC (Mittendorf 1990)'
                result.formula_source = 'Mittendorf, R. et al., "The length of uncomplicated human gestation," OB/GYN, Vol. 75, No., 6 June, 1990, pp. 907-932.'

                if lmp is None:
                        result.message = _('EDC: unknown LMP')
                        return result

                result.variables['LMP'] = lmp
                result.variables['nullipara'] = nullipara
                if nullipara:
                        result.variables['parity_offset'] = 15          # days
                else:
                        result.variables['parity_offset'] = 10          # days

                now = gmDateTime.pydt_now_here()
                if lmp > now:
                        result.warnings.append(_('LMP in the future'))

                if self.__patient is None:
                        result.warnings.append(_('cannot run sanity checks, no patient'))
                else:
                        if self.__patient['dob'] is None:
                                result.warnings.append(_('cannot run sanity checks, no DOB'))
                        else:
                                years, months, days, hours, minutes, seconds = gmDateTime.calculate_apparent_age(start = self.__patient['dob'])
                                # 5 years -- Myth ?
                                # http://www.mirror.co.uk/news/uk-news/top-10-crazy-amazing-and-world-789842
                                if years < 10:
                                        result.warnings.append(_('patient less than 10 years old'))
                        if self.__patient['gender'] in [None, 'm']:
                                result.warnings.append(_('atypical gender for pregnancy: %s') % self.__patient.gender_string)
                        if self.__patient['deceased'] is not None:
                                result.warnings.append(_('patient already passed away'))

                if lmp.month > 3:
                        edc_month = lmp.month - 3
                        edc_year = lmp.year + 1
                else:
                        edc_month = lmp.month + 9
                        edc_year = lmp.year

                result.numeric_value = gmDateTime.pydt_replace(dt = lmp, year = edc_year, month = edc_month, strict = False) + pydt.timedelta(days = result.variables['parity_offset'])

                result.message = _('EDC: %s') % gmDateTime.pydt_strftime (
                        result.numeric_value,
                        format = '%Y %b %d'
                )
                result.date_valid = now

                _log.debug('%s' % result)

                return result

        #--------------------------------------------------------
        def _get_egfrs(self):
                egfrs = [
                        self.eGFR_MDRD_short,
                        self.eGFR_Cockcroft_Gault,
                        self.eGFR_CKD_EPI,
                        self.eGFR_Schwartz
                ]
                return egfrs

        eGFRs = property(_get_egfrs)

        #--------------------------------------------------------
        def _get_egfr(self):

                # < 18 ?
                Schwartz = self.eGFR_Schwartz
                if Schwartz.numeric_value is not None:
                        return Schwartz

                # this logic is based on "KVH aktuell 2/2014 Seite 10-15"
                # expect normal GFR
                CKD = self.eGFR_CKD_EPI
                if CKD.numeric_value is not None:
                        if CKD.numeric_value > self.d(60):
                                return CKD

                # CKD at or below 60
                if self.__patient['dob'] is None:
                        return CKD                      # no method will work, so return CKD anyway

                CG = self.eGFR_Cockcroft_Gault
                MDRD = self.eGFR_MDRD_short
                age = None
                if age is None:
                        try:
                                age = CKD.variables['age@crea']
                        except KeyError:
                                _log.warning('CKD-EPI: no age@crea')
                if age is None:
                        try:
                                age = CG.variables['age@crea']
                        except KeyError:
                                _log.warning('CG: no age@crea')
                if age is None:
                        try:
                                age = MDRD.variables['age@crea']
                        except KeyError:
                                _log.warning('MDRD: no age@crea')
                if age is None:
                        age = gmDateTime.calculate_apparent_age(start = self.__patient['dob'])[0]

                # geriatric ?
                if age > self.d(65):
                        if CG.numeric_value is not None:
                                return CG

                # non-geriatric or CG not computable
                if MDRD.numeric_value is None:
                        if (CKD.numeric_value is not None) or (CG.numeric_value is None):
                                return CKD
                        return CG

                if MDRD.numeric_value > self.d(60):
                        if CKD.numeric_value is not None:
                                # probably normal after all (>60) -> use CKD-EPI
                                return CKD

                return MDRD

        eGFR = property(_get_egfr)

        #--------------------------------------------------------
        def _get_gfr_mdrd_short(self):

                try:
                        return self.__cache['MDRD_short']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown MDRD (4 vars/IDMS)'))
                result.formula_name = 'eGFR from 4-variables IDMS-MDRD'
                result.formula_source = '1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS)'
                result.hints.append(_('best @ 30 < GFR < 60 ml/min'))

                if self.__patient is None:
                        result.message = _('MDRD (4 vars/IDMS): no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('MDRD (4 vars/IDMS): no DOB (no age)')
                        return result

                # 1) gender
                from Gnumed.business.gmPerson import map_gender2mf
                result.variables['gender'] = self.__patient['gender']
                result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
                if result.variables['gender_mf'] == 'm':
                        result.variables['gender_multiplier'] = self.d(1)
                elif result.variables['gender_mf'] == 'f':
                        result.variables['gender_multiplier'] = self.d('0.742')
                else:
                        result.message = _('MDRD (4 vars/IDMS): neither male nor female')
                        return result

                # 2) creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('MDRD (4 vars/IDMS): serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('MDRD (4 vars/IDMS): creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['unit_multiplier'] = self.d(175)                                               # older: 186
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['unit_multiplier'] = self.d(30849)                                     # older: 32788
                else:
                        result.message = _('MDRD (4 vars/IDMS): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # 3) age (at creatinine evaluation)
                result.variables['dob'] = self.__patient['dob']
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
                if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18):
                        result.message = _('MDRD (4 vars/IDMS): formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea']
                        return result

                # 4) ethnicity
                result.variables['ethnicity_multiplier'] = self.d(1)                    # non-black
                result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor'))

                # calculate
                result.numeric_value =  result.variables['unit_multiplier'] * \
                        pow(result.variables['serum_crea_val'], self.d('-1.154')) * \
                        pow(result.variables['age@crea'], self.d('-0.203')) * \
                        result.variables['ethnicity_multiplier'] * \
                        result.variables['gender_multiplier']
                result.unit = 'ml/min/1.73m²'

                BSA = self.body_surface_area
                result.sub_results.append(BSA)
                if BSA.numeric_value is None:
                        result.warnings.append(_('NOT corrected for non-average body surface (average = 1.73m²)'))
                else:
                        result.variables['BSA'] = BSA.numeric_value
                        result.numeric_value = result.numeric_value / BSA.numeric_value

                result.message = _('eGFR(MDRD): %.1f %s (%s) [4-vars, IDMS]') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['MDRD_short'] = result
                _log.debug('%s' % result)

                return result

        eGFR_MDRD_short = property(_get_gfr_mdrd_short)

        #--------------------------------------------------------
        def _get_gfr_ckd_epi(self):

                try:
                        return self.__cache['CKD-EPI']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown CKD-EPI'))
                result.formula_name = 'eGFR from CKD-EPI'
                result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function'
                result.hints.append(_('best @ GFR > 60 ml/min'))

                if self.__patient is None:
                        result.message = _('CKD-EPI: no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('CKD-EPI: no DOB (no age)')
                        return result

                # 1) gender
                from Gnumed.business.gmPerson import map_gender2mf
                result.variables['gender'] = self.__patient['gender']
                result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
                if result.variables['gender_mf'] == 'm':
                        result.variables['gender_multiplier'] = self.d(1)
                        result.variables['k:gender_divisor'] = self.d('0.9')
                        result.variables['a:gender_power'] = self.d('-0.411')
                elif result.variables['gender_mf'] == 'f':
                        result.variables['gender_multiplier'] = self.d('1.018')
                        result.variables['k:gender_divisor'] = self.d('0.7')
                        result.variables['a:gender_power'] = self.d('-0.329')
                else:
                        result.message = _('CKD-EPI: neither male nor female')
                        return result

                # 2) creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('CKD-EPI: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('CKD-EPI: creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) / self.d('88.4')
                else:
                        result.message = _('CKD-EPI: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # 3) age (at creatinine evaluation)
                result.variables['dob'] = self.__patient['dob']
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
#               if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18):
#                       result.message = _('CKD-EPI: formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea']
#                       return result

                # 4) ethnicity
                result.variables['ethnicity_multiplier'] = self.d(1)                    # non-black
                result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor of 1.519 for "black"'))

                # calculate
                result.numeric_value = (
                        self.d(141) * \
                        pow(min((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), result.variables['a:gender_power']) * \
                        pow(max((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), self.d('-1.209')) * \
                        pow(self.d('0.993'), result.variables['age@crea']) * \
                        result.variables['gender_multiplier'] * \
                        result.variables['ethnicity_multiplier']
                )
                result.unit = 'ml/min/1.73m²'

                result.message = _('eGFR(CKD-EPI): %.1f %s (%s)') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['CKD-EPI'] = result
                _log.debug('%s' % result)

                return result

        eGFR_CKD_EPI = property(_get_gfr_ckd_epi)

        #--------------------------------------------------------
        def _get_gfr_cockcroft_gault(self):

                try:
                        return self.__cache['cockcroft_gault']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown Cockcroft-Gault'))
                result.formula_name = 'eGFR from Cockcroft-Gault'
                result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function'
                result.hints.append(_('best @ age >65'))

                if self.__patient is None:
                        result.message = _('Cockcroft-Gault: no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('Cockcroft-Gault: no DOB (no age)')
                        return result

                # 1) gender
                from Gnumed.business.gmPerson import map_gender2mf
                result.variables['gender'] = self.__patient['gender']
                result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
                if result.variables['gender_mf'] not in ['m', 'f']:
                        result.message = _('Cockcroft-Gault: neither male nor female')
                        return result

                # 2) creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('Cockcroft-Gault: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('Cockcroft-Gault: creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['unit_multiplier'] = self.d(72)
                        if result.variables['gender_mf'] == 'm':
                                result.variables['gender_multiplier'] = self.d('1')
                        else: #result.variables['gender_mf'] == 'f'
                                result.variables['gender_multiplier'] = self.d('0.85')
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['unit_multiplier'] = self.d(1)
                        if result.variables['gender_mf'] == 'm':
                                result.variables['gender_multiplier'] = self.d('1.23')
                        else: #result.variables['gender_mf'] == 'f'
                                result.variables['gender_multiplier'] = self.d('1.04')
                else:
                        result.message = _('Cockcroft-Gault: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # 3) age (at creatinine evaluation)
                result.variables['dob'] = self.__patient['dob']
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
                if (result.variables['age@crea'] < 18):
                        result.message = _('Cockcroft-Gault: formula does not apply at age [%s] (17 < age)') % result.variables['age@crea']
                        return result

                weights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_weight, max_no_of_results = 1)
                result.variables['weight'] = weights[0] if len(weights) > 0 else None
                if result.variables['weight'] is None:
                        result.message = _('Cockcroft-Gault: weight not found')
                        return result
                if result.variables['weight']['val_num'] is None:
                        result.message = _('Cockcroft-Gault: weight not numeric')
                        return result
                if result.variables['weight']['val_unit'] == 'kg':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
                elif result.variables['weight']['val_unit'] == 'g':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
                else:
                        result.message = _('Cockcroft-Gault: weight not in kg or g')
                        return result

                # calculate
                result.numeric_value = ((
                        (140 - result.variables['age@crea']) * result.variables['weight_kg'] * result.variables['gender_multiplier']) \
                                        /                                                               \
                        (result.variables['unit_multiplier'] * result.variables['serum_crea_val'])
                )
                result.unit = 'ml/min'          #/1.73m²

                result.message = _('eGFR(CG): %.1f %s (%s)') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['cockroft_gault'] = result
                _log.debug('%s' % result)

                return result

        eGFR_Cockcroft_Gault = property(_get_gfr_cockcroft_gault)

        #--------------------------------------------------------
        def _get_gfr_schwartz(self):

                try:
                        return self.__cache['gfr_schwartz']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown eGFR (Schwartz)'))
                result.formula_name = 'eGFR from updated Schwartz "bedside" formula (age < 19yrs)'
                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'
                result.hints.append(_('only applies @ age <18'))

                if self.__patient is None:
                        result.message = _('eGFR (Schwartz): no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('eGFR (Schwartz): DOB needed for age')
                        return result

                result.variables['dob'] = self.__patient['dob']

                # creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('eGFR (Schwartz): serum creatinine value not found (LOINC: %s') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('eGFR (Schwartz): creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['unit_multiplier'] = self.d(1)
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['unit_multiplier'] = self.d('0.00113')
                else:
                        result.message = _('eGFR (Schwartz): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # age
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
                if result.variables['age@crea'] > 17:
                        result.message = _('eGFR (Schwartz): formula does not apply at age [%s] (age must be <18)') % result.variables['age@crea']
                        return result

                # age-dependant constant
                if result.variables['age@crea'] < 1:
                        # first year pre-term: k = 0.33
                        # first year full-term: k = (0.45)              0.41 (updated)
                        result.variables['constant_for_age'] = self.d('0.41')
                        result.warnings.append(_('eGFR (Schwartz): not known whether pre-term birth, applying full-term formula'))
                else:
                        result.variables['constant_for_age'] = self.d('0.41')

                # height
                result.variables['height'] = self.__patient.emr.get_result_at_timestamp (
                        timestamp = result.variables['serum_crea']['clin_when'],
                        loinc = gmLOINC.LOINC_height,
                        tolerance_interval = '7 days'
                )
                if result.variables['height'] is None:
                        result.message = _('eGFR (Schwartz): height not found')
                        return result
                if result.variables['height']['val_num'] is None:
                        result.message = _('eGFR (Schwartz): height not numeric')
                        return result
                if result.variables['height']['val_unit'] == 'cm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
                elif result.variables['height']['val_unit'] == 'mm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
                elif result.variables['height']['val_unit'] == 'm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
                else:
                        result.message = _('eGFR (Schwartz): height not in m, cm, or mm')
                        return result

                # calculate
                result.numeric_value = (
                        result.variables['constant_for_age'] * result.variables['height_cm']
                ) / (
                        result.variables['unit_multiplier'] * result.variables['serum_crea_val']
                )
                result.unit = 'ml/min/1.73m²'

                result.message = _('eGFR (Schwartz): %.1f %s (%s)') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['gfr_schwartz'] = result
                _log.debug('%s' % result)

                return result

        eGFR_Schwartz = property(_get_gfr_schwartz)

        #--------------------------------------------------------
        def _get_body_surface_area(self):

                try:
                        return self.__cache['body_surface_area']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown body surface area'))
                result.formula_name = 'Du Bois Body Surface Area'
                result.formula_source = '12/2012: http://en.wikipedia.org/wiki/Body_surface_area'

                if self.__patient is None:
                        result.message = _('Body Surface Area: no patient')
                        return result

                heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1)
                result.variables['height'] = heights[0] if len(heights) > 0 else None
                if result.variables['height'] is None:
                        result.message = _('Body Surface Area: height not found')
                        return result

                if result.variables['height']['val_num'] is None:
                        result.message = _('Body Surface Area: height not numeric')
                        return result

                if result.variables['height']['val_unit'] == 'cm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
                elif result.variables['height']['val_unit'] == 'mm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
                elif result.variables['height']['val_unit'] == 'm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
                else:
                        result.message = _('Body Surface Area: height not in m, cm, or mm')
                        return result

                result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
                        timestamp = result.variables['height']['clin_when'],
                        loinc = gmLOINC.LOINC_weight,
                        tolerance_interval = '10 days'
                )
                if result.variables['weight'] is None:
                        result.message = _('Body Surface Area: weight not found')
                        return result

                if result.variables['weight']['val_num'] is None:
                        result.message = _('Body Surface Area: weight not numeric')
                        return result

                if result.variables['weight']['val_unit'] == 'kg':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
                elif result.variables['weight']['val_unit'] == 'g':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
                else:
                        result.message = _('Body Surface Area: weight not in kg or g')
                        return result

                result.numeric_value = self.d('0.007184') * \
                        pow(result.variables['weight_kg'], self.d('0.425')) * \
                        pow(result.variables['height_cm'], self.d('0.725'))
                result.unit = 'm²'
                result.message = _('BSA (DuBois): %.2f %s') % (
                        result.numeric_value,
                        result.unit
                )
                result.date_valid = gmDateTime.pydt_now_here()
                self.__cache['body_surface_area'] = result
                _log.debug('%s' % result)
                return result

        body_surface_area = property(_get_body_surface_area)

        #--------------------------------------------------------
        def _get_body_mass_index(self):

                try:
                        return self.__cache['body_mass_index']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown BMI'))
                result.formula_name = 'BMI/Quetelet Index'
                result.formula_source = '08/2014: https://en.wikipedia.org/wiki/Body_mass_index'

                if self.__patient is None:
                        result.message = _('BMI: no patient')
                        return result

                heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1)
                result.variables['height'] = heights[0] if len(heights) > 0 else None
                if result.variables['height'] is None:
                        result.message = _('BMI: height not found')
                        return result

                if result.variables['height']['val_num'] is None:
                        result.message = _('BMI: height not numeric')
                        return result

                if result.variables['height']['val_unit'] == 'cm':
                        result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(100))
                elif result.variables['height']['val_unit'] == 'mm':
                        result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(1000))
                elif result.variables['height']['val_unit'] == 'm':
                        result.variables['height_m'] = self.d(result.variables['height']['val_num'])
                else:
                        result.message = _('BMI: height not in m, cm, or mm')
                        return result

                result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
                        timestamp = result.variables['height']['clin_when'],
                        loinc = gmLOINC.LOINC_weight,
                        tolerance_interval = '10 days'
                )
                if result.variables['weight'] is None:
                        result.message = _('BMI: weight not found')
                        return result

                if result.variables['weight']['val_num'] is None:
                        result.message = _('BMI: weight not numeric')
                        return result

                if result.variables['weight']['val_unit'] == 'kg':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
                elif result.variables['weight']['val_unit'] == 'g':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
                else:
                        result.message = _('BMI: weight not in kg or g')
                        return result

                result.variables['dob'] = self.__patient['dob']
                start = result.variables['dob']
                end = result.variables['height']['clin_when']
                multiplier = 1
                if end < start:
                        start = result.variables['height']['clin_when']
                        end = result.variables['dob']
                        multiplier = -1
                result.variables['age@height'] = multiplier * self.d(gmDateTime.calculate_apparent_age(start, end)[0])
                if (result.variables['age@height'] < 18):
                        result.message = _('BMI (Quetelet): formula does not apply at age [%s] (0 < age < 18)') % result.variables['age@height']
                        return result

                # bmi = mass kg / height m2
                result.numeric_value = result.variables['weight_kg'] / (result.variables['height_m'] * result.variables['height_m'])
                result.unit = 'kg/m²'

                result.message = _('BMI (Quetelet): %.2f %s') % (
                        result.numeric_value,
                        result.unit
                )
                result.date_valid = max(result.variables['height']['clin_when'], result.variables['weight']['clin_when'])

                self.__cache['body_mass_index'] = result
                _log.debug('%s' % result)
                return result

        body_mass_index = property(_get_body_mass_index)
        bmi = property(_get_body_mass_index)

        #--------------------------------------------------------
        # helper functions
        #--------------------------------------------------------
        def d(self, initial):
                if isinstance(initial, decimal.Decimal):
                        return initial

                val = initial

                # float ? -> to string first
                if type(val) == type(float(1.4)):
                        val = str(val)

                # string ? -> "," to "."
                if isinstance(val, str):
                        val = val.replace(',', '.', 1)
                        val = val.strip()

                try:
                        d = decimal.Decimal(val)
                        return d
                except (TypeError, decimal.InvalidOperation):
                        return None

#============================================================
# main
#------------------------------------------------------------
if __name__ == "__main__":

        if len(sys.argv) == 1:
                sys.exit()

        if sys.argv[1] != 'test':
                sys.exit()

        #-----------------------------------------
        def test_clin_calc():
                from Gnumed.business import gmPraxis
                branches = gmPraxis.get_praxis_branches()
                gmPraxis.gmCurrentPraxisBranch(branches[0])

                from Gnumed.business.gmPerson import cPatient
                pat = cPatient(aPK_obj = 201)

                calc = cClinicalCalculator(patient = pat)
                #result = calc.eGFR_MDRD_short
                #result = calc.eGFR_Schwartz
                result = calc.eGFR
                #result = calc.body_surface_area
                #result = calc.get_EDC(lmp = gmDateTime.pydt_now_here())
                #result = calc.body_mass_index
                #result = calc.eGFRs
                print('%s' % result.format(with_formula = True, with_warnings = True, with_variables = True, with_sub_results = True))

        #-----------------------------------------
        test_clin_calc()

Classes

class cClinicalCalculator (patient=None)
Expand source code
class cClinicalCalculator(object):

        def __init__(self, patient=None):
                self.__cache = {}
                self.__patient = patient

        #--------------------------------------------------------
        def _get_patient(self):
                return self.__patient

        def _set_patient(self, patient):
                if patient == self.__patient:
                        return
                self.__patient = patient
                self.remove_from_cache()                        # uncache all values

        patient = property(lambda x:x, _set_patient)

        #--------------------------------------------------------
        def remove_from_cache(self, key=None):
                if key is None:
                        self.__cache = {}
                        return True
                try:
                        del self.__cache[key]
                        return True
                except KeyError:
                        _log.error('key [%s] does not exist in cache', key)
                        return False

        #--------------------------------------------------------
        # formulae
        #--------------------------------------------------------
        def get_EDC(self, lmp=None, nullipara=True):

                result = cClinicalResult(_('unknown EDC'))
                result.formula_name = 'EDC (Mittendorf 1990)'
                result.formula_source = 'Mittendorf, R. et al., "The length of uncomplicated human gestation," OB/GYN, Vol. 75, No., 6 June, 1990, pp. 907-932.'

                if lmp is None:
                        result.message = _('EDC: unknown LMP')
                        return result

                result.variables['LMP'] = lmp
                result.variables['nullipara'] = nullipara
                if nullipara:
                        result.variables['parity_offset'] = 15          # days
                else:
                        result.variables['parity_offset'] = 10          # days

                now = gmDateTime.pydt_now_here()
                if lmp > now:
                        result.warnings.append(_('LMP in the future'))

                if self.__patient is None:
                        result.warnings.append(_('cannot run sanity checks, no patient'))
                else:
                        if self.__patient['dob'] is None:
                                result.warnings.append(_('cannot run sanity checks, no DOB'))
                        else:
                                years, months, days, hours, minutes, seconds = gmDateTime.calculate_apparent_age(start = self.__patient['dob'])
                                # 5 years -- Myth ?
                                # http://www.mirror.co.uk/news/uk-news/top-10-crazy-amazing-and-world-789842
                                if years < 10:
                                        result.warnings.append(_('patient less than 10 years old'))
                        if self.__patient['gender'] in [None, 'm']:
                                result.warnings.append(_('atypical gender for pregnancy: %s') % self.__patient.gender_string)
                        if self.__patient['deceased'] is not None:
                                result.warnings.append(_('patient already passed away'))

                if lmp.month > 3:
                        edc_month = lmp.month - 3
                        edc_year = lmp.year + 1
                else:
                        edc_month = lmp.month + 9
                        edc_year = lmp.year

                result.numeric_value = gmDateTime.pydt_replace(dt = lmp, year = edc_year, month = edc_month, strict = False) + pydt.timedelta(days = result.variables['parity_offset'])

                result.message = _('EDC: %s') % gmDateTime.pydt_strftime (
                        result.numeric_value,
                        format = '%Y %b %d'
                )
                result.date_valid = now

                _log.debug('%s' % result)

                return result

        #--------------------------------------------------------
        def _get_egfrs(self):
                egfrs = [
                        self.eGFR_MDRD_short,
                        self.eGFR_Cockcroft_Gault,
                        self.eGFR_CKD_EPI,
                        self.eGFR_Schwartz
                ]
                return egfrs

        eGFRs = property(_get_egfrs)

        #--------------------------------------------------------
        def _get_egfr(self):

                # < 18 ?
                Schwartz = self.eGFR_Schwartz
                if Schwartz.numeric_value is not None:
                        return Schwartz

                # this logic is based on "KVH aktuell 2/2014 Seite 10-15"
                # expect normal GFR
                CKD = self.eGFR_CKD_EPI
                if CKD.numeric_value is not None:
                        if CKD.numeric_value > self.d(60):
                                return CKD

                # CKD at or below 60
                if self.__patient['dob'] is None:
                        return CKD                      # no method will work, so return CKD anyway

                CG = self.eGFR_Cockcroft_Gault
                MDRD = self.eGFR_MDRD_short
                age = None
                if age is None:
                        try:
                                age = CKD.variables['age@crea']
                        except KeyError:
                                _log.warning('CKD-EPI: no age@crea')
                if age is None:
                        try:
                                age = CG.variables['age@crea']
                        except KeyError:
                                _log.warning('CG: no age@crea')
                if age is None:
                        try:
                                age = MDRD.variables['age@crea']
                        except KeyError:
                                _log.warning('MDRD: no age@crea')
                if age is None:
                        age = gmDateTime.calculate_apparent_age(start = self.__patient['dob'])[0]

                # geriatric ?
                if age > self.d(65):
                        if CG.numeric_value is not None:
                                return CG

                # non-geriatric or CG not computable
                if MDRD.numeric_value is None:
                        if (CKD.numeric_value is not None) or (CG.numeric_value is None):
                                return CKD
                        return CG

                if MDRD.numeric_value > self.d(60):
                        if CKD.numeric_value is not None:
                                # probably normal after all (>60) -> use CKD-EPI
                                return CKD

                return MDRD

        eGFR = property(_get_egfr)

        #--------------------------------------------------------
        def _get_gfr_mdrd_short(self):

                try:
                        return self.__cache['MDRD_short']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown MDRD (4 vars/IDMS)'))
                result.formula_name = 'eGFR from 4-variables IDMS-MDRD'
                result.formula_source = '1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS)'
                result.hints.append(_('best @ 30 < GFR < 60 ml/min'))

                if self.__patient is None:
                        result.message = _('MDRD (4 vars/IDMS): no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('MDRD (4 vars/IDMS): no DOB (no age)')
                        return result

                # 1) gender
                from Gnumed.business.gmPerson import map_gender2mf
                result.variables['gender'] = self.__patient['gender']
                result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
                if result.variables['gender_mf'] == 'm':
                        result.variables['gender_multiplier'] = self.d(1)
                elif result.variables['gender_mf'] == 'f':
                        result.variables['gender_multiplier'] = self.d('0.742')
                else:
                        result.message = _('MDRD (4 vars/IDMS): neither male nor female')
                        return result

                # 2) creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('MDRD (4 vars/IDMS): serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('MDRD (4 vars/IDMS): creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['unit_multiplier'] = self.d(175)                                               # older: 186
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['unit_multiplier'] = self.d(30849)                                     # older: 32788
                else:
                        result.message = _('MDRD (4 vars/IDMS): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # 3) age (at creatinine evaluation)
                result.variables['dob'] = self.__patient['dob']
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
                if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18):
                        result.message = _('MDRD (4 vars/IDMS): formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea']
                        return result

                # 4) ethnicity
                result.variables['ethnicity_multiplier'] = self.d(1)                    # non-black
                result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor'))

                # calculate
                result.numeric_value =  result.variables['unit_multiplier'] * \
                        pow(result.variables['serum_crea_val'], self.d('-1.154')) * \
                        pow(result.variables['age@crea'], self.d('-0.203')) * \
                        result.variables['ethnicity_multiplier'] * \
                        result.variables['gender_multiplier']
                result.unit = 'ml/min/1.73m²'

                BSA = self.body_surface_area
                result.sub_results.append(BSA)
                if BSA.numeric_value is None:
                        result.warnings.append(_('NOT corrected for non-average body surface (average = 1.73m²)'))
                else:
                        result.variables['BSA'] = BSA.numeric_value
                        result.numeric_value = result.numeric_value / BSA.numeric_value

                result.message = _('eGFR(MDRD): %.1f %s (%s) [4-vars, IDMS]') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['MDRD_short'] = result
                _log.debug('%s' % result)

                return result

        eGFR_MDRD_short = property(_get_gfr_mdrd_short)

        #--------------------------------------------------------
        def _get_gfr_ckd_epi(self):

                try:
                        return self.__cache['CKD-EPI']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown CKD-EPI'))
                result.formula_name = 'eGFR from CKD-EPI'
                result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function'
                result.hints.append(_('best @ GFR > 60 ml/min'))

                if self.__patient is None:
                        result.message = _('CKD-EPI: no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('CKD-EPI: no DOB (no age)')
                        return result

                # 1) gender
                from Gnumed.business.gmPerson import map_gender2mf
                result.variables['gender'] = self.__patient['gender']
                result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
                if result.variables['gender_mf'] == 'm':
                        result.variables['gender_multiplier'] = self.d(1)
                        result.variables['k:gender_divisor'] = self.d('0.9')
                        result.variables['a:gender_power'] = self.d('-0.411')
                elif result.variables['gender_mf'] == 'f':
                        result.variables['gender_multiplier'] = self.d('1.018')
                        result.variables['k:gender_divisor'] = self.d('0.7')
                        result.variables['a:gender_power'] = self.d('-0.329')
                else:
                        result.message = _('CKD-EPI: neither male nor female')
                        return result

                # 2) creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('CKD-EPI: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('CKD-EPI: creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) / self.d('88.4')
                else:
                        result.message = _('CKD-EPI: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # 3) age (at creatinine evaluation)
                result.variables['dob'] = self.__patient['dob']
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
#               if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18):
#                       result.message = _('CKD-EPI: formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea']
#                       return result

                # 4) ethnicity
                result.variables['ethnicity_multiplier'] = self.d(1)                    # non-black
                result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor of 1.519 for "black"'))

                # calculate
                result.numeric_value = (
                        self.d(141) * \
                        pow(min((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), result.variables['a:gender_power']) * \
                        pow(max((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), self.d('-1.209')) * \
                        pow(self.d('0.993'), result.variables['age@crea']) * \
                        result.variables['gender_multiplier'] * \
                        result.variables['ethnicity_multiplier']
                )
                result.unit = 'ml/min/1.73m²'

                result.message = _('eGFR(CKD-EPI): %.1f %s (%s)') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['CKD-EPI'] = result
                _log.debug('%s' % result)

                return result

        eGFR_CKD_EPI = property(_get_gfr_ckd_epi)

        #--------------------------------------------------------
        def _get_gfr_cockcroft_gault(self):

                try:
                        return self.__cache['cockcroft_gault']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown Cockcroft-Gault'))
                result.formula_name = 'eGFR from Cockcroft-Gault'
                result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function'
                result.hints.append(_('best @ age >65'))

                if self.__patient is None:
                        result.message = _('Cockcroft-Gault: no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('Cockcroft-Gault: no DOB (no age)')
                        return result

                # 1) gender
                from Gnumed.business.gmPerson import map_gender2mf
                result.variables['gender'] = self.__patient['gender']
                result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
                if result.variables['gender_mf'] not in ['m', 'f']:
                        result.message = _('Cockcroft-Gault: neither male nor female')
                        return result

                # 2) creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('Cockcroft-Gault: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('Cockcroft-Gault: creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['unit_multiplier'] = self.d(72)
                        if result.variables['gender_mf'] == 'm':
                                result.variables['gender_multiplier'] = self.d('1')
                        else: #result.variables['gender_mf'] == 'f'
                                result.variables['gender_multiplier'] = self.d('0.85')
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['unit_multiplier'] = self.d(1)
                        if result.variables['gender_mf'] == 'm':
                                result.variables['gender_multiplier'] = self.d('1.23')
                        else: #result.variables['gender_mf'] == 'f'
                                result.variables['gender_multiplier'] = self.d('1.04')
                else:
                        result.message = _('Cockcroft-Gault: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # 3) age (at creatinine evaluation)
                result.variables['dob'] = self.__patient['dob']
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
                if (result.variables['age@crea'] < 18):
                        result.message = _('Cockcroft-Gault: formula does not apply at age [%s] (17 < age)') % result.variables['age@crea']
                        return result

                weights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_weight, max_no_of_results = 1)
                result.variables['weight'] = weights[0] if len(weights) > 0 else None
                if result.variables['weight'] is None:
                        result.message = _('Cockcroft-Gault: weight not found')
                        return result
                if result.variables['weight']['val_num'] is None:
                        result.message = _('Cockcroft-Gault: weight not numeric')
                        return result
                if result.variables['weight']['val_unit'] == 'kg':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
                elif result.variables['weight']['val_unit'] == 'g':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
                else:
                        result.message = _('Cockcroft-Gault: weight not in kg or g')
                        return result

                # calculate
                result.numeric_value = ((
                        (140 - result.variables['age@crea']) * result.variables['weight_kg'] * result.variables['gender_multiplier']) \
                                        /                                                               \
                        (result.variables['unit_multiplier'] * result.variables['serum_crea_val'])
                )
                result.unit = 'ml/min'          #/1.73m²

                result.message = _('eGFR(CG): %.1f %s (%s)') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['cockroft_gault'] = result
                _log.debug('%s' % result)

                return result

        eGFR_Cockcroft_Gault = property(_get_gfr_cockcroft_gault)

        #--------------------------------------------------------
        def _get_gfr_schwartz(self):

                try:
                        return self.__cache['gfr_schwartz']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown eGFR (Schwartz)'))
                result.formula_name = 'eGFR from updated Schwartz "bedside" formula (age < 19yrs)'
                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'
                result.hints.append(_('only applies @ age <18'))

                if self.__patient is None:
                        result.message = _('eGFR (Schwartz): no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('eGFR (Schwartz): DOB needed for age')
                        return result

                result.variables['dob'] = self.__patient['dob']

                # creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('eGFR (Schwartz): serum creatinine value not found (LOINC: %s') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('eGFR (Schwartz): creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['unit_multiplier'] = self.d(1)
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['unit_multiplier'] = self.d('0.00113')
                else:
                        result.message = _('eGFR (Schwartz): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # age
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
                if result.variables['age@crea'] > 17:
                        result.message = _('eGFR (Schwartz): formula does not apply at age [%s] (age must be <18)') % result.variables['age@crea']
                        return result

                # age-dependant constant
                if result.variables['age@crea'] < 1:
                        # first year pre-term: k = 0.33
                        # first year full-term: k = (0.45)              0.41 (updated)
                        result.variables['constant_for_age'] = self.d('0.41')
                        result.warnings.append(_('eGFR (Schwartz): not known whether pre-term birth, applying full-term formula'))
                else:
                        result.variables['constant_for_age'] = self.d('0.41')

                # height
                result.variables['height'] = self.__patient.emr.get_result_at_timestamp (
                        timestamp = result.variables['serum_crea']['clin_when'],
                        loinc = gmLOINC.LOINC_height,
                        tolerance_interval = '7 days'
                )
                if result.variables['height'] is None:
                        result.message = _('eGFR (Schwartz): height not found')
                        return result
                if result.variables['height']['val_num'] is None:
                        result.message = _('eGFR (Schwartz): height not numeric')
                        return result
                if result.variables['height']['val_unit'] == 'cm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
                elif result.variables['height']['val_unit'] == 'mm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
                elif result.variables['height']['val_unit'] == 'm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
                else:
                        result.message = _('eGFR (Schwartz): height not in m, cm, or mm')
                        return result

                # calculate
                result.numeric_value = (
                        result.variables['constant_for_age'] * result.variables['height_cm']
                ) / (
                        result.variables['unit_multiplier'] * result.variables['serum_crea_val']
                )
                result.unit = 'ml/min/1.73m²'

                result.message = _('eGFR (Schwartz): %.1f %s (%s)') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['gfr_schwartz'] = result
                _log.debug('%s' % result)

                return result

        eGFR_Schwartz = property(_get_gfr_schwartz)

        #--------------------------------------------------------
        def _get_body_surface_area(self):

                try:
                        return self.__cache['body_surface_area']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown body surface area'))
                result.formula_name = 'Du Bois Body Surface Area'
                result.formula_source = '12/2012: http://en.wikipedia.org/wiki/Body_surface_area'

                if self.__patient is None:
                        result.message = _('Body Surface Area: no patient')
                        return result

                heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1)
                result.variables['height'] = heights[0] if len(heights) > 0 else None
                if result.variables['height'] is None:
                        result.message = _('Body Surface Area: height not found')
                        return result

                if result.variables['height']['val_num'] is None:
                        result.message = _('Body Surface Area: height not numeric')
                        return result

                if result.variables['height']['val_unit'] == 'cm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
                elif result.variables['height']['val_unit'] == 'mm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
                elif result.variables['height']['val_unit'] == 'm':
                        result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
                else:
                        result.message = _('Body Surface Area: height not in m, cm, or mm')
                        return result

                result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
                        timestamp = result.variables['height']['clin_when'],
                        loinc = gmLOINC.LOINC_weight,
                        tolerance_interval = '10 days'
                )
                if result.variables['weight'] is None:
                        result.message = _('Body Surface Area: weight not found')
                        return result

                if result.variables['weight']['val_num'] is None:
                        result.message = _('Body Surface Area: weight not numeric')
                        return result

                if result.variables['weight']['val_unit'] == 'kg':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
                elif result.variables['weight']['val_unit'] == 'g':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
                else:
                        result.message = _('Body Surface Area: weight not in kg or g')
                        return result

                result.numeric_value = self.d('0.007184') * \
                        pow(result.variables['weight_kg'], self.d('0.425')) * \
                        pow(result.variables['height_cm'], self.d('0.725'))
                result.unit = 'm²'
                result.message = _('BSA (DuBois): %.2f %s') % (
                        result.numeric_value,
                        result.unit
                )
                result.date_valid = gmDateTime.pydt_now_here()
                self.__cache['body_surface_area'] = result
                _log.debug('%s' % result)
                return result

        body_surface_area = property(_get_body_surface_area)

        #--------------------------------------------------------
        def _get_body_mass_index(self):

                try:
                        return self.__cache['body_mass_index']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown BMI'))
                result.formula_name = 'BMI/Quetelet Index'
                result.formula_source = '08/2014: https://en.wikipedia.org/wiki/Body_mass_index'

                if self.__patient is None:
                        result.message = _('BMI: no patient')
                        return result

                heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1)
                result.variables['height'] = heights[0] if len(heights) > 0 else None
                if result.variables['height'] is None:
                        result.message = _('BMI: height not found')
                        return result

                if result.variables['height']['val_num'] is None:
                        result.message = _('BMI: height not numeric')
                        return result

                if result.variables['height']['val_unit'] == 'cm':
                        result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(100))
                elif result.variables['height']['val_unit'] == 'mm':
                        result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(1000))
                elif result.variables['height']['val_unit'] == 'm':
                        result.variables['height_m'] = self.d(result.variables['height']['val_num'])
                else:
                        result.message = _('BMI: height not in m, cm, or mm')
                        return result

                result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
                        timestamp = result.variables['height']['clin_when'],
                        loinc = gmLOINC.LOINC_weight,
                        tolerance_interval = '10 days'
                )
                if result.variables['weight'] is None:
                        result.message = _('BMI: weight not found')
                        return result

                if result.variables['weight']['val_num'] is None:
                        result.message = _('BMI: weight not numeric')
                        return result

                if result.variables['weight']['val_unit'] == 'kg':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
                elif result.variables['weight']['val_unit'] == 'g':
                        result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
                else:
                        result.message = _('BMI: weight not in kg or g')
                        return result

                result.variables['dob'] = self.__patient['dob']
                start = result.variables['dob']
                end = result.variables['height']['clin_when']
                multiplier = 1
                if end < start:
                        start = result.variables['height']['clin_when']
                        end = result.variables['dob']
                        multiplier = -1
                result.variables['age@height'] = multiplier * self.d(gmDateTime.calculate_apparent_age(start, end)[0])
                if (result.variables['age@height'] < 18):
                        result.message = _('BMI (Quetelet): formula does not apply at age [%s] (0 < age < 18)') % result.variables['age@height']
                        return result

                # bmi = mass kg / height m2
                result.numeric_value = result.variables['weight_kg'] / (result.variables['height_m'] * result.variables['height_m'])
                result.unit = 'kg/m²'

                result.message = _('BMI (Quetelet): %.2f %s') % (
                        result.numeric_value,
                        result.unit
                )
                result.date_valid = max(result.variables['height']['clin_when'], result.variables['weight']['clin_when'])

                self.__cache['body_mass_index'] = result
                _log.debug('%s' % result)
                return result

        body_mass_index = property(_get_body_mass_index)
        bmi = property(_get_body_mass_index)

        #--------------------------------------------------------
        # helper functions
        #--------------------------------------------------------
        def d(self, initial):
                if isinstance(initial, decimal.Decimal):
                        return initial

                val = initial

                # float ? -> to string first
                if type(val) == type(float(1.4)):
                        val = str(val)

                # string ? -> "," to "."
                if isinstance(val, str):
                        val = val.replace(',', '.', 1)
                        val = val.strip()

                try:
                        d = decimal.Decimal(val)
                        return d
                except (TypeError, decimal.InvalidOperation):
                        return None

Instance variables

var bmi
Expand source code
def _get_body_mass_index(self):

        try:
                return self.__cache['body_mass_index']
        except KeyError:
                pass

        result = cClinicalResult(_('unknown BMI'))
        result.formula_name = 'BMI/Quetelet Index'
        result.formula_source = '08/2014: https://en.wikipedia.org/wiki/Body_mass_index'

        if self.__patient is None:
                result.message = _('BMI: no patient')
                return result

        heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1)
        result.variables['height'] = heights[0] if len(heights) > 0 else None
        if result.variables['height'] is None:
                result.message = _('BMI: height not found')
                return result

        if result.variables['height']['val_num'] is None:
                result.message = _('BMI: height not numeric')
                return result

        if result.variables['height']['val_unit'] == 'cm':
                result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(100))
        elif result.variables['height']['val_unit'] == 'mm':
                result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(1000))
        elif result.variables['height']['val_unit'] == 'm':
                result.variables['height_m'] = self.d(result.variables['height']['val_num'])
        else:
                result.message = _('BMI: height not in m, cm, or mm')
                return result

        result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
                timestamp = result.variables['height']['clin_when'],
                loinc = gmLOINC.LOINC_weight,
                tolerance_interval = '10 days'
        )
        if result.variables['weight'] is None:
                result.message = _('BMI: weight not found')
                return result

        if result.variables['weight']['val_num'] is None:
                result.message = _('BMI: weight not numeric')
                return result

        if result.variables['weight']['val_unit'] == 'kg':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
        elif result.variables['weight']['val_unit'] == 'g':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
        else:
                result.message = _('BMI: weight not in kg or g')
                return result

        result.variables['dob'] = self.__patient['dob']
        start = result.variables['dob']
        end = result.variables['height']['clin_when']
        multiplier = 1
        if end < start:
                start = result.variables['height']['clin_when']
                end = result.variables['dob']
                multiplier = -1
        result.variables['age@height'] = multiplier * self.d(gmDateTime.calculate_apparent_age(start, end)[0])
        if (result.variables['age@height'] < 18):
                result.message = _('BMI (Quetelet): formula does not apply at age [%s] (0 < age < 18)') % result.variables['age@height']
                return result

        # bmi = mass kg / height m2
        result.numeric_value = result.variables['weight_kg'] / (result.variables['height_m'] * result.variables['height_m'])
        result.unit = 'kg/m²'

        result.message = _('BMI (Quetelet): %.2f %s') % (
                result.numeric_value,
                result.unit
        )
        result.date_valid = max(result.variables['height']['clin_when'], result.variables['weight']['clin_when'])

        self.__cache['body_mass_index'] = result
        _log.debug('%s' % result)
        return result
var body_mass_index
Expand source code
def _get_body_mass_index(self):

        try:
                return self.__cache['body_mass_index']
        except KeyError:
                pass

        result = cClinicalResult(_('unknown BMI'))
        result.formula_name = 'BMI/Quetelet Index'
        result.formula_source = '08/2014: https://en.wikipedia.org/wiki/Body_mass_index'

        if self.__patient is None:
                result.message = _('BMI: no patient')
                return result

        heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1)
        result.variables['height'] = heights[0] if len(heights) > 0 else None
        if result.variables['height'] is None:
                result.message = _('BMI: height not found')
                return result

        if result.variables['height']['val_num'] is None:
                result.message = _('BMI: height not numeric')
                return result

        if result.variables['height']['val_unit'] == 'cm':
                result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(100))
        elif result.variables['height']['val_unit'] == 'mm':
                result.variables['height_m'] = self.d(result.variables['height']['val_num'] / self.d(1000))
        elif result.variables['height']['val_unit'] == 'm':
                result.variables['height_m'] = self.d(result.variables['height']['val_num'])
        else:
                result.message = _('BMI: height not in m, cm, or mm')
                return result

        result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
                timestamp = result.variables['height']['clin_when'],
                loinc = gmLOINC.LOINC_weight,
                tolerance_interval = '10 days'
        )
        if result.variables['weight'] is None:
                result.message = _('BMI: weight not found')
                return result

        if result.variables['weight']['val_num'] is None:
                result.message = _('BMI: weight not numeric')
                return result

        if result.variables['weight']['val_unit'] == 'kg':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
        elif result.variables['weight']['val_unit'] == 'g':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
        else:
                result.message = _('BMI: weight not in kg or g')
                return result

        result.variables['dob'] = self.__patient['dob']
        start = result.variables['dob']
        end = result.variables['height']['clin_when']
        multiplier = 1
        if end < start:
                start = result.variables['height']['clin_when']
                end = result.variables['dob']
                multiplier = -1
        result.variables['age@height'] = multiplier * self.d(gmDateTime.calculate_apparent_age(start, end)[0])
        if (result.variables['age@height'] < 18):
                result.message = _('BMI (Quetelet): formula does not apply at age [%s] (0 < age < 18)') % result.variables['age@height']
                return result

        # bmi = mass kg / height m2
        result.numeric_value = result.variables['weight_kg'] / (result.variables['height_m'] * result.variables['height_m'])
        result.unit = 'kg/m²'

        result.message = _('BMI (Quetelet): %.2f %s') % (
                result.numeric_value,
                result.unit
        )
        result.date_valid = max(result.variables['height']['clin_when'], result.variables['weight']['clin_when'])

        self.__cache['body_mass_index'] = result
        _log.debug('%s' % result)
        return result
var body_surface_area
Expand source code
def _get_body_surface_area(self):

        try:
                return self.__cache['body_surface_area']
        except KeyError:
                pass

        result = cClinicalResult(_('unknown body surface area'))
        result.formula_name = 'Du Bois Body Surface Area'
        result.formula_source = '12/2012: http://en.wikipedia.org/wiki/Body_surface_area'

        if self.__patient is None:
                result.message = _('Body Surface Area: no patient')
                return result

        heights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_height, max_no_of_results = 1)
        result.variables['height'] = heights[0] if len(heights) > 0 else None
        if result.variables['height'] is None:
                result.message = _('Body Surface Area: height not found')
                return result

        if result.variables['height']['val_num'] is None:
                result.message = _('Body Surface Area: height not numeric')
                return result

        if result.variables['height']['val_unit'] == 'cm':
                result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
        elif result.variables['height']['val_unit'] == 'mm':
                result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
        elif result.variables['height']['val_unit'] == 'm':
                result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
        else:
                result.message = _('Body Surface Area: height not in m, cm, or mm')
                return result

        result.variables['weight'] = self.__patient.emr.get_result_at_timestamp (
                timestamp = result.variables['height']['clin_when'],
                loinc = gmLOINC.LOINC_weight,
                tolerance_interval = '10 days'
        )
        if result.variables['weight'] is None:
                result.message = _('Body Surface Area: weight not found')
                return result

        if result.variables['weight']['val_num'] is None:
                result.message = _('Body Surface Area: weight not numeric')
                return result

        if result.variables['weight']['val_unit'] == 'kg':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
        elif result.variables['weight']['val_unit'] == 'g':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
        else:
                result.message = _('Body Surface Area: weight not in kg or g')
                return result

        result.numeric_value = self.d('0.007184') * \
                pow(result.variables['weight_kg'], self.d('0.425')) * \
                pow(result.variables['height_cm'], self.d('0.725'))
        result.unit = 'm²'
        result.message = _('BSA (DuBois): %.2f %s') % (
                result.numeric_value,
                result.unit
        )
        result.date_valid = gmDateTime.pydt_now_here()
        self.__cache['body_surface_area'] = result
        _log.debug('%s' % result)
        return result
var eGFR
Expand source code
def _get_egfr(self):

        # < 18 ?
        Schwartz = self.eGFR_Schwartz
        if Schwartz.numeric_value is not None:
                return Schwartz

        # this logic is based on "KVH aktuell 2/2014 Seite 10-15"
        # expect normal GFR
        CKD = self.eGFR_CKD_EPI
        if CKD.numeric_value is not None:
                if CKD.numeric_value > self.d(60):
                        return CKD

        # CKD at or below 60
        if self.__patient['dob'] is None:
                return CKD                      # no method will work, so return CKD anyway

        CG = self.eGFR_Cockcroft_Gault
        MDRD = self.eGFR_MDRD_short
        age = None
        if age is None:
                try:
                        age = CKD.variables['age@crea']
                except KeyError:
                        _log.warning('CKD-EPI: no age@crea')
        if age is None:
                try:
                        age = CG.variables['age@crea']
                except KeyError:
                        _log.warning('CG: no age@crea')
        if age is None:
                try:
                        age = MDRD.variables['age@crea']
                except KeyError:
                        _log.warning('MDRD: no age@crea')
        if age is None:
                age = gmDateTime.calculate_apparent_age(start = self.__patient['dob'])[0]

        # geriatric ?
        if age > self.d(65):
                if CG.numeric_value is not None:
                        return CG

        # non-geriatric or CG not computable
        if MDRD.numeric_value is None:
                if (CKD.numeric_value is not None) or (CG.numeric_value is None):
                        return CKD
                return CG

        if MDRD.numeric_value > self.d(60):
                if CKD.numeric_value is not None:
                        # probably normal after all (>60) -> use CKD-EPI
                        return CKD

        return MDRD
var eGFR_CKD_EPI
Expand source code
        def _get_gfr_ckd_epi(self):

                try:
                        return self.__cache['CKD-EPI']
                except KeyError:
                        pass

                result = cClinicalResult(_('unknown CKD-EPI'))
                result.formula_name = 'eGFR from CKD-EPI'
                result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function'
                result.hints.append(_('best @ GFR > 60 ml/min'))

                if self.__patient is None:
                        result.message = _('CKD-EPI: no patient')
                        return result

                if self.__patient['dob'] is None:
                        result.message = _('CKD-EPI: no DOB (no age)')
                        return result

                # 1) gender
                from Gnumed.business.gmPerson import map_gender2mf
                result.variables['gender'] = self.__patient['gender']
                result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
                if result.variables['gender_mf'] == 'm':
                        result.variables['gender_multiplier'] = self.d(1)
                        result.variables['k:gender_divisor'] = self.d('0.9')
                        result.variables['a:gender_power'] = self.d('-0.411')
                elif result.variables['gender_mf'] == 'f':
                        result.variables['gender_multiplier'] = self.d('1.018')
                        result.variables['k:gender_divisor'] = self.d('0.7')
                        result.variables['a:gender_power'] = self.d('-0.329')
                else:
                        result.message = _('CKD-EPI: neither male nor female')
                        return result

                # 2) creatinine
                creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
                result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
                if result.variables['serum_crea'] is None:
                        result.message = _('CKD-EPI: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                        return result
                if result.variables['serum_crea']['val_num'] is None:
                        result.message = _('CKD-EPI: creatinine value not numeric')
                        return result
                result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
                elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num']) / self.d('88.4')
                else:
                        result.message = _('CKD-EPI: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                        return result

                # 3) age (at creatinine evaluation)
                result.variables['dob'] = self.__patient['dob']
                result.variables['age@crea'] = self.d (
                        gmDateTime.calculate_apparent_age (
                                start = result.variables['dob'],
                                end = result.variables['serum_crea']['clin_when']
                        )[0]
                )
#               if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18):
#                       result.message = _('CKD-EPI: formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea']
#                       return result

                # 4) ethnicity
                result.variables['ethnicity_multiplier'] = self.d(1)                    # non-black
                result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor of 1.519 for "black"'))

                # calculate
                result.numeric_value = (
                        self.d(141) * \
                        pow(min((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), result.variables['a:gender_power']) * \
                        pow(max((result.variables['serum_crea_val'] / result.variables['k:gender_divisor']), self.d(1)), self.d('-1.209')) * \
                        pow(self.d('0.993'), result.variables['age@crea']) * \
                        result.variables['gender_multiplier'] * \
                        result.variables['ethnicity_multiplier']
                )
                result.unit = 'ml/min/1.73m²'

                result.message = _('eGFR(CKD-EPI): %.1f %s (%s)') % (
                        result.numeric_value,
                        result.unit,
                        gmDateTime.pydt_strftime (
                                result.variables['serum_crea']['clin_when'],
                                format = '%Y %b %d'
                        )
                )
                result.date_valid = result.variables['serum_crea']['clin_when']

                self.__cache['CKD-EPI'] = result
                _log.debug('%s' % result)

                return result
var eGFR_Cockcroft_Gault
Expand source code
def _get_gfr_cockcroft_gault(self):

        try:
                return self.__cache['cockcroft_gault']
        except KeyError:
                pass

        result = cClinicalResult(_('unknown Cockcroft-Gault'))
        result.formula_name = 'eGFR from Cockcroft-Gault'
        result.formula_source = '8/2014: http://en.wikipedia.org/Renal_function'
        result.hints.append(_('best @ age >65'))

        if self.__patient is None:
                result.message = _('Cockcroft-Gault: no patient')
                return result

        if self.__patient['dob'] is None:
                result.message = _('Cockcroft-Gault: no DOB (no age)')
                return result

        # 1) gender
        from Gnumed.business.gmPerson import map_gender2mf
        result.variables['gender'] = self.__patient['gender']
        result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
        if result.variables['gender_mf'] not in ['m', 'f']:
                result.message = _('Cockcroft-Gault: neither male nor female')
                return result

        # 2) creatinine
        creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
        result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
        if result.variables['serum_crea'] is None:
                result.message = _('Cockcroft-Gault: serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                return result
        if result.variables['serum_crea']['val_num'] is None:
                result.message = _('Cockcroft-Gault: creatinine value not numeric')
                return result
        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
        if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                result.variables['unit_multiplier'] = self.d(72)
                if result.variables['gender_mf'] == 'm':
                        result.variables['gender_multiplier'] = self.d('1')
                else: #result.variables['gender_mf'] == 'f'
                        result.variables['gender_multiplier'] = self.d('0.85')
        elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                result.variables['unit_multiplier'] = self.d(1)
                if result.variables['gender_mf'] == 'm':
                        result.variables['gender_multiplier'] = self.d('1.23')
                else: #result.variables['gender_mf'] == 'f'
                        result.variables['gender_multiplier'] = self.d('1.04')
        else:
                result.message = _('Cockcroft-Gault: unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                return result

        # 3) age (at creatinine evaluation)
        result.variables['dob'] = self.__patient['dob']
        result.variables['age@crea'] = self.d (
                gmDateTime.calculate_apparent_age (
                        start = result.variables['dob'],
                        end = result.variables['serum_crea']['clin_when']
                )[0]
        )
        if (result.variables['age@crea'] < 18):
                result.message = _('Cockcroft-Gault: formula does not apply at age [%s] (17 < age)') % result.variables['age@crea']
                return result

        weights = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_weight, max_no_of_results = 1)
        result.variables['weight'] = weights[0] if len(weights) > 0 else None
        if result.variables['weight'] is None:
                result.message = _('Cockcroft-Gault: weight not found')
                return result
        if result.variables['weight']['val_num'] is None:
                result.message = _('Cockcroft-Gault: weight not numeric')
                return result
        if result.variables['weight']['val_unit'] == 'kg':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'])
        elif result.variables['weight']['val_unit'] == 'g':
                result.variables['weight_kg'] = self.d(result.variables['weight']['val_num'] / self.d(1000))
        else:
                result.message = _('Cockcroft-Gault: weight not in kg or g')
                return result

        # calculate
        result.numeric_value = ((
                (140 - result.variables['age@crea']) * result.variables['weight_kg'] * result.variables['gender_multiplier']) \
                                /                                                               \
                (result.variables['unit_multiplier'] * result.variables['serum_crea_val'])
        )
        result.unit = 'ml/min'          #/1.73m²

        result.message = _('eGFR(CG): %.1f %s (%s)') % (
                result.numeric_value,
                result.unit,
                gmDateTime.pydt_strftime (
                        result.variables['serum_crea']['clin_when'],
                        format = '%Y %b %d'
                )
        )
        result.date_valid = result.variables['serum_crea']['clin_when']

        self.__cache['cockroft_gault'] = result
        _log.debug('%s' % result)

        return result
var eGFR_MDRD_short
Expand source code
def _get_gfr_mdrd_short(self):

        try:
                return self.__cache['MDRD_short']
        except KeyError:
                pass

        result = cClinicalResult(_('unknown MDRD (4 vars/IDMS)'))
        result.formula_name = 'eGFR from 4-variables IDMS-MDRD'
        result.formula_source = '1/2013: http://en.wikipedia.org/Renal_function / http://www.ganfyd.org/index.php?title=Estimated_glomerular_filtration_rate (NHS)'
        result.hints.append(_('best @ 30 < GFR < 60 ml/min'))

        if self.__patient is None:
                result.message = _('MDRD (4 vars/IDMS): no patient')
                return result

        if self.__patient['dob'] is None:
                result.message = _('MDRD (4 vars/IDMS): no DOB (no age)')
                return result

        # 1) gender
        from Gnumed.business.gmPerson import map_gender2mf
        result.variables['gender'] = self.__patient['gender']
        result.variables['gender_mf'] = map_gender2mf[self.__patient['gender']]
        if result.variables['gender_mf'] == 'm':
                result.variables['gender_multiplier'] = self.d(1)
        elif result.variables['gender_mf'] == 'f':
                result.variables['gender_multiplier'] = self.d('0.742')
        else:
                result.message = _('MDRD (4 vars/IDMS): neither male nor female')
                return result

        # 2) creatinine
        creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
        result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
        if result.variables['serum_crea'] is None:
                result.message = _('MDRD (4 vars/IDMS): serum creatinine value not found (LOINC: %s)') % gmLOINC.LOINC_creatinine_quantity
                return result
        if result.variables['serum_crea']['val_num'] is None:
                result.message = _('MDRD (4 vars/IDMS): creatinine value not numeric')
                return result
        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
        if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                result.variables['unit_multiplier'] = self.d(175)                                               # older: 186
        elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                result.variables['unit_multiplier'] = self.d(30849)                                     # older: 32788
        else:
                result.message = _('MDRD (4 vars/IDMS): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                return result

        # 3) age (at creatinine evaluation)
        result.variables['dob'] = self.__patient['dob']
        result.variables['age@crea'] = self.d (
                gmDateTime.calculate_apparent_age (
                        start = result.variables['dob'],
                        end = result.variables['serum_crea']['clin_when']
                )[0]
        )
        if (result.variables['age@crea'] > 84) or (result.variables['age@crea'] < 18):
                result.message = _('MDRD (4 vars/IDMS): formula does not apply at age [%s] (17 < age < 85)') % result.variables['age@crea']
                return result

        # 4) ethnicity
        result.variables['ethnicity_multiplier'] = self.d(1)                    # non-black
        result.warnings.append(_('ethnicity: GNUmed does not know patient ethnicity, ignoring correction factor'))

        # calculate
        result.numeric_value =  result.variables['unit_multiplier'] * \
                pow(result.variables['serum_crea_val'], self.d('-1.154')) * \
                pow(result.variables['age@crea'], self.d('-0.203')) * \
                result.variables['ethnicity_multiplier'] * \
                result.variables['gender_multiplier']
        result.unit = 'ml/min/1.73m²'

        BSA = self.body_surface_area
        result.sub_results.append(BSA)
        if BSA.numeric_value is None:
                result.warnings.append(_('NOT corrected for non-average body surface (average = 1.73m²)'))
        else:
                result.variables['BSA'] = BSA.numeric_value
                result.numeric_value = result.numeric_value / BSA.numeric_value

        result.message = _('eGFR(MDRD): %.1f %s (%s) [4-vars, IDMS]') % (
                result.numeric_value,
                result.unit,
                gmDateTime.pydt_strftime (
                        result.variables['serum_crea']['clin_when'],
                        format = '%Y %b %d'
                )
        )
        result.date_valid = result.variables['serum_crea']['clin_when']

        self.__cache['MDRD_short'] = result
        _log.debug('%s' % result)

        return result
var eGFR_Schwartz
Expand source code
def _get_gfr_schwartz(self):

        try:
                return self.__cache['gfr_schwartz']
        except KeyError:
                pass

        result = cClinicalResult(_('unknown eGFR (Schwartz)'))
        result.formula_name = 'eGFR from updated Schwartz "bedside" formula (age < 19yrs)'
        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'
        result.hints.append(_('only applies @ age <18'))

        if self.__patient is None:
                result.message = _('eGFR (Schwartz): no patient')
                return result

        if self.__patient['dob'] is None:
                result.message = _('eGFR (Schwartz): DOB needed for age')
                return result

        result.variables['dob'] = self.__patient['dob']

        # creatinine
        creas = self.__patient.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, max_no_of_results = 1)
        result.variables['serum_crea'] = creas[0] if len(creas) > 0 else None
        if result.variables['serum_crea'] is None:
                result.message = _('eGFR (Schwartz): serum creatinine value not found (LOINC: %s') % gmLOINC.LOINC_creatinine_quantity
                return result
        if result.variables['serum_crea']['val_num'] is None:
                result.message = _('eGFR (Schwartz): creatinine value not numeric')
                return result
        result.variables['serum_crea_val'] = self.d(result.variables['serum_crea']['val_num'])
        if result.variables['serum_crea']['val_unit'] in ['mg/dl', 'mg/dL']:
                result.variables['unit_multiplier'] = self.d(1)
        elif result.variables['serum_crea']['val_unit'] in ['µmol/L', 'µmol/l']:
                result.variables['unit_multiplier'] = self.d('0.00113')
        else:
                result.message = _('eGFR (Schwartz): unknown serum creatinine unit (%s)') % result.variables['serum_crea']['val_unit']
                return result

        # age
        result.variables['age@crea'] = self.d (
                gmDateTime.calculate_apparent_age (
                        start = result.variables['dob'],
                        end = result.variables['serum_crea']['clin_when']
                )[0]
        )
        if result.variables['age@crea'] > 17:
                result.message = _('eGFR (Schwartz): formula does not apply at age [%s] (age must be <18)') % result.variables['age@crea']
                return result

        # age-dependant constant
        if result.variables['age@crea'] < 1:
                # first year pre-term: k = 0.33
                # first year full-term: k = (0.45)              0.41 (updated)
                result.variables['constant_for_age'] = self.d('0.41')
                result.warnings.append(_('eGFR (Schwartz): not known whether pre-term birth, applying full-term formula'))
        else:
                result.variables['constant_for_age'] = self.d('0.41')

        # height
        result.variables['height'] = self.__patient.emr.get_result_at_timestamp (
                timestamp = result.variables['serum_crea']['clin_when'],
                loinc = gmLOINC.LOINC_height,
                tolerance_interval = '7 days'
        )
        if result.variables['height'] is None:
                result.message = _('eGFR (Schwartz): height not found')
                return result
        if result.variables['height']['val_num'] is None:
                result.message = _('eGFR (Schwartz): height not numeric')
                return result
        if result.variables['height']['val_unit'] == 'cm':
                result.variables['height_cm'] = self.d(result.variables['height']['val_num'])
        elif result.variables['height']['val_unit'] == 'mm':
                result.variables['height_cm'] = self.d(result.variables['height']['val_num'] / self.d(10))
        elif result.variables['height']['val_unit'] == 'm':
                result.variables['height_cm'] = self.d(result.variables['height']['val_num'] * 100)
        else:
                result.message = _('eGFR (Schwartz): height not in m, cm, or mm')
                return result

        # calculate
        result.numeric_value = (
                result.variables['constant_for_age'] * result.variables['height_cm']
        ) / (
                result.variables['unit_multiplier'] * result.variables['serum_crea_val']
        )
        result.unit = 'ml/min/1.73m²'

        result.message = _('eGFR (Schwartz): %.1f %s (%s)') % (
                result.numeric_value,
                result.unit,
                gmDateTime.pydt_strftime (
                        result.variables['serum_crea']['clin_when'],
                        format = '%Y %b %d'
                )
        )
        result.date_valid = result.variables['serum_crea']['clin_when']

        self.__cache['gfr_schwartz'] = result
        _log.debug('%s' % result)

        return result
var eGFRs
Expand source code
def _get_egfrs(self):
        egfrs = [
                self.eGFR_MDRD_short,
                self.eGFR_Cockcroft_Gault,
                self.eGFR_CKD_EPI,
                self.eGFR_Schwartz
        ]
        return egfrs
var patient
Expand source code
patient = property(lambda x:x, _set_patient)

Methods

def d(self, initial)
Expand source code
def d(self, initial):
        if isinstance(initial, decimal.Decimal):
                return initial

        val = initial

        # float ? -> to string first
        if type(val) == type(float(1.4)):
                val = str(val)

        # string ? -> "," to "."
        if isinstance(val, str):
                val = val.replace(',', '.', 1)
                val = val.strip()

        try:
                d = decimal.Decimal(val)
                return d
        except (TypeError, decimal.InvalidOperation):
                return None
def get_EDC(self, lmp=None, nullipara=True)
Expand source code
def get_EDC(self, lmp=None, nullipara=True):

        result = cClinicalResult(_('unknown EDC'))
        result.formula_name = 'EDC (Mittendorf 1990)'
        result.formula_source = 'Mittendorf, R. et al., "The length of uncomplicated human gestation," OB/GYN, Vol. 75, No., 6 June, 1990, pp. 907-932.'

        if lmp is None:
                result.message = _('EDC: unknown LMP')
                return result

        result.variables['LMP'] = lmp
        result.variables['nullipara'] = nullipara
        if nullipara:
                result.variables['parity_offset'] = 15          # days
        else:
                result.variables['parity_offset'] = 10          # days

        now = gmDateTime.pydt_now_here()
        if lmp > now:
                result.warnings.append(_('LMP in the future'))

        if self.__patient is None:
                result.warnings.append(_('cannot run sanity checks, no patient'))
        else:
                if self.__patient['dob'] is None:
                        result.warnings.append(_('cannot run sanity checks, no DOB'))
                else:
                        years, months, days, hours, minutes, seconds = gmDateTime.calculate_apparent_age(start = self.__patient['dob'])
                        # 5 years -- Myth ?
                        # http://www.mirror.co.uk/news/uk-news/top-10-crazy-amazing-and-world-789842
                        if years < 10:
                                result.warnings.append(_('patient less than 10 years old'))
                if self.__patient['gender'] in [None, 'm']:
                        result.warnings.append(_('atypical gender for pregnancy: %s') % self.__patient.gender_string)
                if self.__patient['deceased'] is not None:
                        result.warnings.append(_('patient already passed away'))

        if lmp.month > 3:
                edc_month = lmp.month - 3
                edc_year = lmp.year + 1
        else:
                edc_month = lmp.month + 9
                edc_year = lmp.year

        result.numeric_value = gmDateTime.pydt_replace(dt = lmp, year = edc_year, month = edc_month, strict = False) + pydt.timedelta(days = result.variables['parity_offset'])

        result.message = _('EDC: %s') % gmDateTime.pydt_strftime (
                result.numeric_value,
                format = '%Y %b %d'
        )
        result.date_valid = now

        _log.debug('%s' % result)

        return result
def remove_from_cache(self, key=None)
Expand source code
def remove_from_cache(self, key=None):
        if key is None:
                self.__cache = {}
                return True
        try:
                del self.__cache[key]
                return True
        except KeyError:
                _log.error('key [%s] does not exist in cache', key)
                return False
class cClinicalResult (message=None)
Expand source code
class cClinicalResult(object):

        def __init__(self, message=None):
                self.message = message
                self.numeric_value = None
                self.unit = None
                self.date_valid = None
                self.formula_name = None
                self.formula_source = None
                self.variables = {}
                self.sub_results = []
                self.warnings = [_('THIS IS NOT A VERIFIED MEASUREMENT. DO NOT USE FOR ACTUAL CARE.')]
                self.hints = []

        #--------------------------------------------------------
        def __str__(self):
                txt = '[cClinicalResult]: %s %s (%s)\n\n%s' % (
                        self.numeric_value,
                        self.unit,
                        self.date_valid,
                        self.format (
                                left_margin = 0,
                                width = 80,
                                eol = '\n',
                                with_formula = True,
                                with_warnings = True,
                                with_variables = True,
                                with_sub_results = True,
                                return_list = False
                        )
                )
                return txt

        #--------------------------------------------------------
        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):
                lines = []
                lines.append(self.message)

                if with_formula:
                        txt = gmTools.wrap (
                                text = '%s %s' % (
                                        _('Algorithm:'),
                                        self.formula_name
                                ),
                                width = width,
                                initial_indent = ' ',
                                subsequent_indent = ' ' * 2,
                                eol = eol
                        )
                        lines.append(txt)
                        txt = gmTools.wrap (
                                text = '%s %s' % (
                                        _('Source:'),
                                        self.formula_source
                                ),
                                width = width,
                                initial_indent = ' ',
                                subsequent_indent = ' ' * 2,
                                eol = eol
                        )
                        lines.append(txt)

                if with_warnings:
                        if len(self.warnings) > 0:
                                lines.append(' Caveat:')
                        for w in self.warnings:
                                txt = gmTools.wrap(text = w, width = width, initial_indent = '  %s ' % gmTools.u_arrow2right, subsequent_indent = '    ', eol = eol)
                                lines.append(txt)

                if with_hints:
                        if len(self.hints) > 0:
                                lines.append(' Hints:')
                        for h in self.hints:
                                txt = gmTools.wrap(text = h, width = width, initial_indent = '  %s ' % gmTools.u_arrow2right, subsequent_indent = '    ', eol = eol)
                                lines.append(txt)

                if with_variables:
                        if len(self.variables) > 0:
                                lines.append(' %s' % _('Variables:'))
                        for key in self.variables:
                                txt = '  %s %s: %s' % (
                                        gmTools.u_arrow2right,
                                        key,
                                        self.variables[key]
                                )
                                lines.append(txt)

                if with_sub_results:
                        if len(self.sub_results) > 0:
                                lines.append(' %s' % _('Intermediate results:'))
                        for r in self.sub_results:
                                lines.extend(r.format (
                                        left_margin = left_margin + 1,
                                        width = width,
                                        eol = eol,
                                        with_formula = with_formula,
                                        with_warnings = with_warnings,
                                        with_variables = with_variables,
                                        with_sub_results = False,                       # break cycles
                                        return_list = True
                                ))

                lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = eol)
                if return_list:
                        return lines

                left_margin = ' ' * left_margin
                return left_margin + (eol + left_margin).join(lines) + eol

Methods

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)
Expand source code
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):
        lines = []
        lines.append(self.message)

        if with_formula:
                txt = gmTools.wrap (
                        text = '%s %s' % (
                                _('Algorithm:'),
                                self.formula_name
                        ),
                        width = width,
                        initial_indent = ' ',
                        subsequent_indent = ' ' * 2,
                        eol = eol
                )
                lines.append(txt)
                txt = gmTools.wrap (
                        text = '%s %s' % (
                                _('Source:'),
                                self.formula_source
                        ),
                        width = width,
                        initial_indent = ' ',
                        subsequent_indent = ' ' * 2,
                        eol = eol
                )
                lines.append(txt)

        if with_warnings:
                if len(self.warnings) > 0:
                        lines.append(' Caveat:')
                for w in self.warnings:
                        txt = gmTools.wrap(text = w, width = width, initial_indent = '  %s ' % gmTools.u_arrow2right, subsequent_indent = '    ', eol = eol)
                        lines.append(txt)

        if with_hints:
                if len(self.hints) > 0:
                        lines.append(' Hints:')
                for h in self.hints:
                        txt = gmTools.wrap(text = h, width = width, initial_indent = '  %s ' % gmTools.u_arrow2right, subsequent_indent = '    ', eol = eol)
                        lines.append(txt)

        if with_variables:
                if len(self.variables) > 0:
                        lines.append(' %s' % _('Variables:'))
                for key in self.variables:
                        txt = '  %s %s: %s' % (
                                gmTools.u_arrow2right,
                                key,
                                self.variables[key]
                        )
                        lines.append(txt)

        if with_sub_results:
                if len(self.sub_results) > 0:
                        lines.append(' %s' % _('Intermediate results:'))
                for r in self.sub_results:
                        lines.extend(r.format (
                                left_margin = left_margin + 1,
                                width = width,
                                eol = eol,
                                with_formula = with_formula,
                                with_warnings = with_warnings,
                                with_variables = with_variables,
                                with_sub_results = False,                       # break cycles
                                return_list = True
                        ))

        lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = eol)
        if return_list:
                return lines

        left_margin = ' ' * left_margin
        return left_margin + (eol + left_margin).join(lines) + eol