/* * Copyright (c) 1995, 1996 Gunther Schadow. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "results.h" #include "LOINC.h" #include "Unit.h" #include "exception.h" #include "logfile.h" #include const char *specimen_for_LOINC(const ZStyp &ss) { switch((SpeTypeCode::Value)ss.Type) { case SpeTypeCode::Blood: switch((SpeSouCode::Value)ss.Sou) { case SpeSouCode::Art: return "BLDA"; case SpeSouCode::Cap: return "BLDC"; case SpeSouCode::Ven: return "BLDV"; case SpeSouCode::MixedVen: return "BLDV"; // LOINC has no BLDVM case SpeSouCode::NotSpe: default: return "BLDA"; } case SpeTypeCode::Gas: return "GAS"; case SpeTypeCode::Urine: return "UR"; default: return ""; } /* // the following are implicit specimen "IHG", // inhaled gas "PAT", // patient "BLD", // simply blood where artherial/venous is meaningless (e.g. E'lytes) */ } const char *specimen_for_the_world(const ZStyp &ss) { switch((SpeTypeCode::Value)ss.Type) { case SpeTypeCode::Blood: switch((SpeSouCode::Value)ss.Sou) { case SpeSouCode::Art: return "BLDA"; case SpeSouCode::Cap: return "BLDC"; case SpeSouCode::Ven: return "BLDV"; case SpeSouCode::MixedVen: return "BLDVM"; // FIXME! LOINC has no BLDVM case SpeSouCode::NotSpe: default: return "BLD"; } case SpeTypeCode::Gas: return "GAS"; case SpeTypeCode::Urine: return "UR"; default: return ""; } } Unit un_one; Unit un_percent; Unit un_ph; // LOINC PROPERTIES class property { #define PCT 0 #define NFR 1 #define SFR 2 #define VFR 3 #define MFR 4 #define AFRp(x) ((PCT <= x) && (x <= MFR)) #define SCNC 5 #define PH 6 #define MCNCb 7 #define MCNC 8 #define ACNC 9 #define ACNCp(x) ((SCNC <= x) && (x <= ACNC)) #define PRES 10 #define PPRES 11 #define APRESp(x) ((PRES <= x) && (x <= PPRES)) #define TEMP 12 #define NPROP 13 public: const char *name; Unit unit; property() {} property(const char * const n, Unit u) { name = n; unit = u; }; static property tab[NPROP]; static num(Unit iu) { Unit::dimension id = dim(iu); for(u_int i = 0; i < NPROP; i++) if(dim(tab[i].unit) == id) return i; ERROR("LOINC property not found for unit: `%s'", (const char *)iu); } static void init() { tab[PCT] = property("PCT", un_percent); tab[NFR] = property("NFR", un_percent); tab[SFR] = property("SFR", un_percent); tab[VFR] = property("VFR", un_percent); tab[MFR] = property("MFR", un_percent); tab[SCNC] = property("SCNC", "MMOL/L"); tab[ACNC] = property("ACNC", "MMOL/L"); tab[PH] = property("SCNC", "PH"); tab[MCNCb]= property("MCNC", "G/DL"); tab[MCNC] = property("MCNC", "MG/DL"); tab[PRES] = property("PRES", "KPAL"); tab[PPRES]= property("PRES", "KPAL"); // new LOINC version uses PPRES tab[TEMP] = property("TEMP", "CEL"); } }; property property::tab[NPROP]; void init() { un_one = "1"; un_percent = "%"; un_ph = "pH"; property::init(); } // translate between Radiometer and ISO+ units Unit unit(const IDtyp &un) { if(!un.ispresent() || un.isnull()) return un_one; else { const char *uns = (const char *)un; #define EQ(s1,s2) ((tolower(*s1) == *s2) && (strcasecmp(&s1[1],&s2[1]) == 0)) if(EQ(uns,"mmhg")) return "MM(HG)"; if(EQ(uns,"kpa")) return "KPAL"; if(EQ(uns,"torr")) return "MM(HG)"; if(EQ(uns,"vol %")) return "%"; if(EQ(uns,"c")) return "DEGC"; if(EQ(uns,"f")) return "DEGF"; else return uns; } } /* The following function maps the Radiometer representation of * * * measurement * * unit * * specimen (already in LOINC coding) * * observation value * * into LOINC code / ISO+ unit representation, where the observation value * is converted if the unit is to be changed. */ void toLOINC(const ZTtyp &iMs, const IDtyp &iUn, STtyp spec, // in NMtyp &val, // in + out Loinc &loinc, Unit &ounit) // out { loinc.unset(); /* there must be a value for anything here to make sense */ if(!val.ispresent() || val.isnull()) return; // value double ival = (double)val; double oval = ival; u_int prec = val.getPrecision(); // unit & properties Unit iunit = unit(iUn); ounit = iunit; u_int iprop = property::num(iunit); u_int oprop = iprop; // optional conversion constant double cval; Unit cunit; // LOINC parameter name const char *par = NULL; switch((ParaMeterNameCode::Value)iMs.ParaMeterName) { // BLOOD GAS PARTIAL PRESSURES case ParaMeterNameCode::Pco2: // PPRES,BLDA/BLDC/BLDV par = "CARBON DIOXIDE"; oprop = PPRES; break; case ParaMeterNameCode::Pco2T: // PPRES,BLDA/BLDC/BLDV par = "CARBON DIOXIDE^^ADJUSTED TO BODY TEMPERATURE"; oprop = PPRES; break; case ParaMeterNameCode::Po2: // PPRES,BLDA/BLDC/BLDV par = "OXYGEN"; oprop = PPRES; break; case ParaMeterNameCode::Po2T: // PPRES,BLDA/BLDC/BLDV par = "OXYGEN^^ADJUSTED TO BODY TEMPERATURE"; oprop = PPRES; break; case ParaMeterNameCode::P50Act: // PPRES,BLDA/BLDC/BLDV par = "OXYGEN^^SATURATION ADJUSTED TO 0.5"; oprop = PPRES; break; case ParaMeterNameCode::P50ActT: // PPRES,BLDA/BLDC/BLDV par = "OXYGEN^^SATURATION ADJUSTED TO 0.5 AND BODY TEMPERATURE"; oprop = PPRES; break; case ParaMeterNameCode::P50St: // PPRES,BLDA/BLDC/BLDV par = "OXYGEN^^SATURATION ADJUSTED TO 0.5 STANDARD"; oprop = PPRES; break; // HYDROGENCARBONAT AND BASE EXCESS case ParaMeterNameCode::Hco3: par = "BICARBONATE"; // SCNC,BLD/BLDA/BLDC/UR oprop = SCNC; break; case ParaMeterNameCode::Sbc: par = "BICARBONATE^^STANDARD"; // SCNC,BLDA/BLDV/BLDC oprop = SCNC; break; case ParaMeterNameCode::Abe: par = "BASE EXCESS"; // SCNC,BLDA/BLDC/BLDV oprop = SCNC; break; case ParaMeterNameCode::Sbe: par = "BASE EXCESS^^STANDARD"; // SCNC,BLDA/BLDV/BLDC oprop = SCNC; break; // HEMOGLOBIN & HEMATOCRIT case ParaMeterNameCode::Hct: // NFR,BLD par = "HEMATOCRIT"; oprop = NFR; break; case ParaMeterNameCode::Thb: // MCNC,BLD/UR par = "HEMOGLOBIN"; cval = 16.113439; cunit = "kg/mol"; oprop = MCNCb; break; case ParaMeterNameCode::O2hb: // NFR,BLDA/BLDC/BLDV par = "OXYHEMOGLOBIN.TOTAL"; oprop = NFR; break; case ParaMeterNameCode::Rhb: // SFR,BLDA/BLDC/BLDV par = "DEOXYHEMOGLOBIN"; oprop = SFR; break; case ParaMeterNameCode::Methb: // MFR,BLDA/BLDC/BLDV par = "METHEMOGLOBIN"; oprop = NFR; break; case ParaMeterNameCode::Cohb: // NFR,BLDA/BLDC/BLDV par = "CARBON MONOXIDE.HEMOGLOBIN"; oprop = NFR; break; // INERT TO ARTERIAL/VENOUS DIFFERENTIATION case ParaMeterNameCode::Hbf: // SFR,BLD par = "HEMOGLOBIN F"; oprop = SFR; goto inert; case ParaMeterNameCode::Shb: // SFR,BLD par = "SULFHEMOGLOBIN"; oprop = SFR; goto inert; case ParaMeterNameCode::Na: // SCNC,BLD/UR par = "SODIUM"; oprop = SCNC; goto inert; case ParaMeterNameCode::K: // SCNC,BLD/UR par = "POTASSIUM"; oprop = SCNC; goto inert; case ParaMeterNameCode::Ca: // SCNC,BLD par = "CALCIUM.FREE"; oprop = SCNC; goto inert; case ParaMeterNameCode::Ca74: // SCNC,BLD par = "CALCIUM.FREE^^PH ADJUSTED TO 7.4"; oprop = SCNC; goto inert; case ParaMeterNameCode::Cl: // SCNC,BLD/UR par = "CHLORIDE"; oprop = SCNC; goto inert; case ParaMeterNameCode::Glu: // ACNC,BLD/UR par = "GLUCOSE"; oprop = MCNC; cval = 180; cunit = "g/mol"; loinc.setMethod(""); goto inert; inert: if(spec != "UR") spec = "BLD"; break; // ANION GAP, is a differentiation about A/C/V needed? case ParaMeterNameCode::AnionGapK: // SCNC,BLD par = "ANION GAP^^INCLUDING POTASSIUM"; oprop = SCNC; break; case ParaMeterNameCode::AnionGap: // SCNC,BLD par = "ANION GAP"; oprop = SCNC; break; // CONTENTS case ParaMeterNameCode::O2cap: par = "OXYGEN BINDING CAPACITY"; // SCNC,BLD spec = "BLD"; goto content; case ParaMeterNameCode::Tco2B: par = "CARBON DIOXIDE.TOTAL"; // SCNC,BLDA/BLDC/BLDV goto content; case ParaMeterNameCode::Tco2P: par = "CARBON DIOXIDE.TOTAL"; // SCNC,PLAS spec = "PLAS"; goto content; case ParaMeterNameCode::To2: par = "OXYGEN.TOTAL"; // SCNC,BLDA/BLDC/BLDV content: oprop = SCNC; cval = 22.41; cunit = "l/mol"; break; // SATURATION case ParaMeterNameCode::So2: // SFR,BLDA/BLDC/BLDV par = "OXYGEN SATURATION"; oprop = SFR; break; // pH case ParaMeterNameCode::Ph: par = "PH"; // SCNC,BLDA/BLDC/BLDV/UR goto pH; case ParaMeterNameCode::PhSt: par = "PH^^STANDARD"; // SCNC,BLDA/BLDV/BLDC/UR goto pH; case ParaMeterNameCode::PhT: par = "PH^^ADJUSTED TO BODY TEMPERATURE"; // SCNC,BLDA/BLDV/BLDC/UR pH: oprop = PH; if(iunit == un_one) // i.e. if it went to default iunit = un_ph; // use pseudo unit `PH' break; // GAS case ParaMeterNameCode::Fio2: par = "OXYGEN INHALED"; // NFR,IHG oprop = PCT; spec = "IHG"; break; case ParaMeterNameCode::Co2: par = "CARBON DIOXIDE"; // NFR,GAS oprop = NFR; goto gas; case ParaMeterNameCode::O2: par = "OXYGEN"; // NFR,GAS oprop = NFR; goto gas; gas: spec = "GAS"; break; case ParaMeterNameCode::T: par = "BODY TEMPERATURE"; // TEMP,PAT oprop = TEMP; spec = "PAT"; break; case ParaMeterNameCode::B: par = "BAROMETRIC PRESSURE"; // PRES,GAS oprop = PRES; spec = "GAS"; break; default: LOGINFO("unhandled parameter `%s'", (const char *)iMs.ParaMeterName); return; } // adjusting the property ounit = property::tab[oprop].unit; if(ounit != iunit) // need a conversion { if(dim(ounit) != dim(iunit)) // need a constant { if(cunit.ispresent()) // have a constant { // determine use of constant // remember that the is u and v are units then // dim(u * v) = dim(u) + dim(v) // and dim(u / v) = dim(u) - dim(v) if(dim(iunit) + dim(cunit) == dim(ounit)) { ival = ival * cval; iunit = iunit * cunit; } else if(dim(iunit) - dim(cunit) == dim(ounit)) { ival = ival / cval; iunit = iunit / cunit; } else // no way! { LOGWARNING("no way to convert %s from %s to %s" "unsing %0.2f %s", par, (const char *)iunit, (const char *)ounit, cval, (const char *)cunit); oval = ival; ounit = iunit; goto after_conversion; } } else // don't have constant { LOGWARNING("incommensurable units for %s have %s need %s", par, (const char *)iunit, (const char *)ounit); oval = ival; ounit = iunit; goto after_conversion; } } oval = convert(ival, iunit, ounit); after_conversion: val = NMtyp(oval, prec + 1); // FIXME adding one is not the final way } loinc.setMeasure(par); loinc.setSpecimen(spec); loinc.setProperty(property::tab[oprop].name); if(! loinc.lookup()) { LOGWARNING("unable to find a LOINC code for:\n" " MEASURE = %s\n" " SPECIMEN = %s\n" " PROPERTY = %s\n", par, (const char *)spec, property::tab[oprop].name); } return; }