SGHI FHIR Profile Implementation Guide
0.1.0 - ci-build
SGHI FHIR Profile Implementation Guide, published by Kathurima Kimathi. This guide is not an authorized publication; it is the continuous build for version 0.1.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/savannahghi/sil_fhir_profile_ig/ and changes regularly. See the Directory of published versions
| Official URL: https://fhir.slade360.co.ke/fhir/StructureMap/ExtractVitalSigns | Version: 0.1.0 | |||
| Draft as of 2026-09-15 | Computable Name: ExtractVitalSigns | |||
/// url = 'https://fhir.slade360.co.ke/fhir/StructureMap/ExtractVitalSigns' /// name = 'ExtractVitalSigns' /// status = 'draft' uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QR as source uses "http://hl7.org/fhir/StructureDefinition/Bundle" alias Bundle as target uses "http://hl7.org/fhir/StructureDefinition/Observation" alias Observation as target group ExtractVitalSigns(source qr : QR, target bundle : Bundle) { qr -> bundle.type = 'transaction' "setBundleType"; // Height qr.item as itH where (linkId = '8302-2') then { itH.answer first as aH then { aH -> bundle.entry as eH, eH.resource = create('Observation') as oH then { qr -> oH, eH then BuildBaseObs(qr, oH, eH) "heightBase"; qr -> oH.category as catH then { qr -> catH.coding as catHCoding then { qr -> catHCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catHSys"; qr -> catHCoding.code = 'vital-signs' "catHCode"; qr -> catHCoding.display = 'Vital Signs' "catHDisplay"; } "catHCodingRule"; } "categoryH"; qr -> oH.code as codeH then { qr -> codeH.coding as codeHCoding then { qr -> codeHCoding.system = 'http://loinc.org' "codeHSys"; qr -> codeHCoding.code = '8302-2' "codeHCode"; qr -> codeHCoding.display = 'Body Height' "codeHDisplay"; } "codeHCodingRule"; } "codeH"; aH.value as hv -> oH.value = create('Quantity') as vH then { hv -> vH.value = hv "setVal"; qr -> vH.system = 'http://unitsofmeasure.org' "setSys"; qr -> vH.code = 'cm' "setCode"; qr -> vH.unit = 'centimeter' "setUnit"; } "valH"; } "heightObs"; } "heightAns"; } "heightRule"; // Weight qr.item as itW where (linkId = '29463-7') then { itW.answer first as aW then { aW -> bundle.entry as eW, eW.resource = create('Observation') as oW then { qr -> oW, eW then BuildBaseObs(qr, oW, eW) "weightBase"; qr -> oW.category as catW then { qr -> catW.coding as catWCoding then { qr -> catWCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catWSys"; qr -> catWCoding.code = 'vital-signs' "catWCode"; qr -> catWCoding.display = 'Vital Signs' "catWDisplay"; } "catWCodingRule"; } "categoryW"; qr -> oW.code as codeW then { qr -> codeW.coding as codeWCoding then { qr -> codeWCoding.system = 'http://loinc.org' "codeWSys"; qr -> codeWCoding.code = '29463-7' "codeWCode"; qr -> codeWCoding.display = 'Body Weight' "codeWDisplay"; } "codeWCodingRule"; } "codeW"; aW.value as hv -> oW.value = create('Quantity') as vW then { hv -> vW.value = hv "setVal"; qr -> vW.system = 'http://unitsofmeasure.org' "setSys"; qr -> vW.code = 'kg' "setCode"; qr -> vW.unit = 'kilogram' "setUnit"; } "valW"; } "weightObs"; } "weightAns"; } "weightRule"; // BMI qr.item as itBmi where (linkId = '39156-5') then { itBmi.answer first as aBmi then { aBmi -> bundle.entry as eBmi, eBmi.resource = create('Observation') as oBmi then { qr -> oBmi, eBmi then BuildBaseObs(qr, oBmi, eBmi) "bmiBase"; qr -> oBmi.category as catBmi then { qr -> catBmi.coding as catBmiCoding then { qr -> catBmiCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catBmiSys"; qr -> catBmiCoding.code = 'vital-signs' "catBmiCode"; qr -> catBmiCoding.display = 'Vital Signs' "catBmiDisplay"; } "catBmiCodingRule"; } "categoryBmi"; qr -> oBmi.code as codeBmi then { qr -> codeBmi.coding as codeBmiCoding then { qr -> codeBmiCoding.system = 'http://loinc.org' "codeBmiSys"; qr -> codeBmiCoding.code = '39156-5' "codeBmiCode"; qr -> codeBmiCoding.display = 'Body mass index (BMI) [Ratio]' "codeBmiDisplay"; } "codeBmiCodingRule"; } "codeBmi"; aBmi.value as hv -> oBmi.value = create('Quantity') as vBmi then { hv -> vBmi.value = hv "setVal"; qr -> vBmi.system = 'http://unitsofmeasure.org' "setSys"; qr -> vBmi.code = 'kg/m2' "setCode"; qr -> vBmi.unit = 'kilogram per square meter' "setUnit"; } "valBmi"; // Interpretation 39156-5_status itBmi.item as bmiStatusItem where (linkId = '39156-5_status') then { bmiStatusItem.answer first as bmiStatusAns -> oBmi.interpretation as iBmi then { bmiStatusAns.value as statusText -> iBmi.text = statusText "interpBmi"; } "setInterpBmi"; } "findBmiStatus"; } "bmiObs"; } "bmiAns"; } "bmiRule"; // Pulse qr.item as itP where (linkId = '8889-8') then { itP.answer first as aP then { aP -> bundle.entry as eP, eP.resource = create('Observation') as oP then { qr -> oP, eP then BuildBaseObs(qr, oP, eP) "pulseBase"; qr -> oP.category as catP then { qr -> catP.coding as catPCoding then { qr -> catPCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catPSys"; qr -> catPCoding.code = 'vital-signs' "catPCode"; qr -> catPCoding.display = 'Vital Signs' "catPDisplay"; } "catPCodingRule"; } "categoryP"; qr -> oP.code as codeP then { qr -> codeP.coding as codePCoding then { qr -> codePCoding.system = 'http://loinc.org' "codePSys"; qr -> codePCoding.code = '8889-8' "codePCode"; qr -> codePCoding.display = 'Heart rate by Pulse oximetry' "codePDisplay"; } "codePCodingRule"; } "codeP"; aP.value as hv -> oP.value = create('Quantity') as vP then { hv -> vP.value = hv "setVal"; qr -> vP.system = 'http://unitsofmeasure.org' "setSys"; qr -> vP.code = '/min' "setCode"; qr -> vP.unit = 'per minute' "setUnit"; } "valP"; } "pulseObs"; } "pulseAns"; } "pulseRule"; // Blood Pressure qr.item as itBP where (linkId = '55284-4') then { qr -> bundle.entry as eBP, eBP.resource = create('Observation') as oBP then { qr -> oBP, eBP then BuildBaseObs(qr, oBP, eBP) "bpBase"; qr -> oBP.category as catBP then { qr -> catBP.coding as catBPCoding then { qr -> catBPCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catBPSys"; qr -> catBPCoding.code = 'vital-signs' "catBPCode"; qr -> catBPCoding.display = 'Vital Signs' "catBPDisplay"; } "catBPCodingRule"; } "categoryBP"; qr -> oBP.code as codeBP then { qr -> codeBP.coding as codeBPCoding then { qr -> codeBPCoding.system = 'http://loinc.org' "codeBPSys"; qr -> codeBPCoding.code = '55284-4' "codeBPCode"; qr -> codeBPCoding.display = 'Blood pressure systolic and diastolic' "codeBPDisplay"; } "codeBPCodingRule"; } "codeBP"; // Systolic 8480-6 itBP.item as itSys where (linkId = '8480-6') then { itSys.answer first as aSys -> oBP.component as cSys then { aSys -> cSys.code as bpSysCC then { aSys -> bpSysCC.coding as bpSysCoding then { aSys -> bpSysCoding.system = 'http://loinc.org' "bpSysCodingSystem"; aSys -> bpSysCoding.code = '8480-6' "bpSysCodingCode"; aSys -> bpSysCoding.display = 'Systolic blood pressure' "bpSysCodingDisplay"; } "bpSysCodingRule"; } "bpSysCode"; aSys.value as hv -> cSys.value = create('Quantity') as vSys then { hv -> vSys.value = hv "setVal"; qr -> vSys.system = 'http://unitsofmeasure.org' "setSys"; qr -> vSys.code = 'mm[Hg]' "setCode"; qr -> vSys.unit = 'millimeter of mercury' "setUnit"; } "bpSysVal"; } "bpSysComponent"; } "bpSysRule"; // Diastolic 8462-4 itBP.item as itDia where (linkId = '8462-4') then { itDia.answer first as aDia -> oBP.component as cDia then { aDia -> cDia.code as bpDiaCC then { aDia -> bpDiaCC.coding as bpDiaCoding then { aDia -> bpDiaCoding.system = 'http://loinc.org' "bpDiaCodingSystem"; aDia -> bpDiaCoding.code = '8462-4' "bpDiaCodingCode"; aDia -> bpDiaCoding.display = 'Diastolic blood pressure' "bpDiaCodingDisplay"; } "bpDiaCodingRule"; } "bpDiaCode"; aDia.value as hv -> cDia.value = create('Quantity') as vDia then { hv -> vDia.value = hv "setVal"; qr -> vDia.system = 'http://unitsofmeasure.org' "setSys"; qr -> vDia.code = 'mm[Hg]' "setCode"; qr -> vDia.unit = 'millimeter of mercury' "setUnit"; } "bpDiaVal"; } "bpDiaComponent"; } "bpDiaRule"; } "bpObs"; } "bpRule"; // Temperature qr.item as itT where (linkId = '8310-5') then { itT.answer first as aT then { aT -> bundle.entry as eT, eT.resource = create('Observation') as oT then { qr -> oT, eT then BuildBaseObs(qr, oT, eT) "tempBase"; qr -> oT.category as catT then { qr -> catT.coding as catTCoding then { qr -> catTCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catTSys"; qr -> catTCoding.code = 'vital-signs' "catTCode"; qr -> catTCoding.display = 'Vital Signs' "catTDisplay"; } "catTCodingRule"; } "categoryT"; qr -> oT.code as codeT then { qr -> codeT.coding as codeTCoding then { qr -> codeTCoding.system = 'http://loinc.org' "codeTSys"; qr -> codeTCoding.code = '8310-5' "codeTCode"; qr -> codeTCoding.display = 'Body Temperature' "codeTDisplay"; } "codeTCodingRule"; } "codeT"; aT.value as hv -> oT.value = create('Quantity') as vT then { hv -> vT.value = hv "setVal"; qr -> vT.system = 'http://unitsofmeasure.org' "setSys"; qr -> vT.code = 'Cel' "setCode"; qr -> vT.unit = 'degree Celsius' "setUnit"; } "valT"; // Interpretation 8310-5_status itT.item as tStatusItem where (linkId = '8310-5_status') then { tStatusItem.answer first as tStatusAns -> oT.interpretation as iT then { tStatusAns.value as statusText -> iT.text = statusText "interpT"; } "setInterpT"; } "findTStatus"; } "tempObs"; } "tempAns"; } "tempRule"; // SpO2 qr.item as itO2 where (linkId = '20564-1') then { itO2.answer first as aO2 then { aO2 -> bundle.entry as eO2, eO2.resource = create('Observation') as oO2 then { qr -> oO2, eO2 then BuildBaseObs(qr, oO2, eO2) "spo2Base"; qr -> oO2.category as catO2 then { qr -> catO2.coding as catO2Coding then { qr -> catO2Coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catO2Sys"; qr -> catO2Coding.code = 'vital-signs' "catO2Code"; qr -> catO2Coding.display = 'Vital Signs' "catO2Display"; } "catO2CodingRule"; } "categoryO2"; qr -> oO2.code as codeO2 then { qr -> codeO2.coding as codeO2Coding then { qr -> codeO2Coding.system = 'http://loinc.org' "codeO2Sys"; qr -> codeO2Coding.code = '20564-1' "codeO2Code"; qr -> codeO2Coding.display = 'Oxygen saturation in Blood' "codeO2Display"; } "codeO2CodingRule"; } "codeO2"; aO2.value as hv -> oO2.value = create('Quantity') as vO2 then { hv -> vO2.value = hv "setVal"; qr -> vO2.system = 'http://unitsofmeasure.org' "setSys"; qr -> vO2.code = '%' "setCode"; qr -> vO2.unit = '%' "setUnit"; } "valO2"; // Interpretation of SpO2 20564-1_status itO2.item as o2StatusItem where (linkId = '20564-1_status') then { o2StatusItem.answer first as o2StatusAns -> oO2.interpretation as iO2 then { o2StatusAns.value as statusText -> iO2.text = statusText "interpO2"; } "setInterpO2"; } "findO2Status"; } "spo2Obs"; } "spo2Ans"; } "spo2Rule"; // Respiratory rate qr.item as itRR where (linkId = '9279-1') then { itRR.answer first as aRR then { aRR -> bundle.entry as eRR, eRR.resource = create('Observation') as oRR then { qr -> oRR, eRR then BuildBaseObs(qr, oRR, eRR) "rrBase"; qr -> oRR.category as catRR then { qr -> catRR.coding as catRRCoding then { qr -> catRRCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catRRSys"; qr -> catRRCoding.code = 'vital-signs' "catRRCode"; qr -> catRRCoding.display = 'Vital Signs' "catRRDisplay"; } "catRRCodingRule"; } "categoryRR"; qr -> oRR.code as codeRR then { qr -> codeRR.coding as codeRRCoding then { qr -> codeRRCoding.system = 'http://loinc.org' "codeRRSys"; qr -> codeRRCoding.code = '9279-1' "codeRRCode"; qr -> codeRRCoding.display = 'Respiratory rate' "codeRRDisplay"; } "codeRRCodingRule"; } "codeRR"; aRR.value as hv -> oRR.value = create('Quantity') as vRR then { hv -> vRR.value = hv "setVal"; qr -> vRR.system = 'http://unitsofmeasure.org' "setSys"; qr -> vRR.code = '/min' "setCode"; qr -> vRR.unit = 'per minute' "setUnit"; } "valRR"; } "rrObs"; } "rrAns"; } "rrRule"; // MUAC qr.item as itMuac where (linkId = '9847-5') then { itMuac.answer first as aMuac then { aMuac -> bundle.entry as eMuac, eMuac.resource = create('Observation') as oMuac then { qr -> oMuac, eMuac then BuildBaseObs(qr, oMuac, eMuac) "muacBase"; qr -> oMuac.category as catMuac then { qr -> catMuac.coding as catMuacCoding then { qr -> catMuacCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catMuacSys"; qr -> catMuacCoding.code = 'vital-signs' "catMuacCode"; qr -> catMuacCoding.display = 'Vital Signs' "catMuacDisplay"; } "catMuacCodingRule"; } "categoryMuac"; qr -> oMuac.code as codeMuac then { qr -> codeMuac.coding as codeMuacCoding then { qr -> codeMuacCoding.system = 'http://loinc.org' "codeMuacSys"; qr -> codeMuacCoding.code = '9847-5' "codeMuacCode"; qr -> codeMuacCoding.display = 'Circumference' "codeMuacDisplay"; } "codeMuacCodingRule"; } "codeMuac"; aMuac.value as hv -> oMuac.value = create('Quantity') as vMuac then { hv -> vMuac.value = hv "setVal"; qr -> vMuac.system = 'http://unitsofmeasure.org' "setSys"; qr -> vMuac.code = 'mm' "setCode"; qr -> vMuac.unit = 'millimeter' "setUnit"; } "valMuac"; } "muacObs"; } "muacAns"; } "muacRule"; // Chief Complaint qr.item as itCC where (linkId = '10154-3') then { itCC.answer first as aCC then { aCC -> bundle.entry as eCC, eCC.resource = create('Observation') as oCC then { qr -> oCC, eCC then BuildBaseObs(qr, oCC, eCC) "chiefComplaintBase"; qr -> oCC.category as catCC then { qr -> catCC.coding as catCCCoding then { qr -> catCCCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catCCSys"; qr -> catCCCoding.code = 'social-history' "catCCCode"; qr -> catCCCoding.display = 'Social History' "catCCDisplay"; } "catCCCodingRule"; } "chiefComplaintCategory"; qr -> oCC.code as codeCC then { qr -> codeCC.coding as codeCCCoding then { qr -> codeCCCoding.system = 'http://loinc.org' "codeCCSys"; qr -> codeCCCoding.code = '10154-3' "codeCCCode"; qr -> codeCCCoding.display = 'Chief complaint Narrative - Reported' "codeCCDisplay"; } "codeCCCodingRule"; } "chiefComplaintCode"; aCC.value as textVal -> oCC.value = textVal "chiefComplaintValue"; } "chiefComplaintObs"; } "chiefComplaintAns"; } "chiefComplaintRule"; // History of Presenting Illness qr.item as hpiCC where (linkId = '8684-3') then { hpiCC.answer first as aCC then { aCC -> bundle.entry as eCC, eCC.resource = create('Observation') as oCC then { qr -> oCC, eCC then BuildBaseObs(qr, oCC, eCC) "historyOfPIBase"; qr -> oCC.category as catHPI then { qr -> catHPI.coding as catHPICoding then { qr -> catHPICoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catHPISys"; qr -> catHPICoding.code = 'social-history' "catHPICode"; qr -> catHPICoding.display = 'Social History' "catHPIDisplay"; } "catHPICodingRule"; } "historyOfPICategory"; qr -> oCC.code as codeHPI then { qr -> codeHPI.coding as codeHPICoding then { qr -> codeHPICoding.system = 'http://loinc.org' "codeHPISys"; qr -> codeHPICoding.code = '8684-3' "codeHPICode"; qr -> codeHPICoding.display = 'History of Present illness' "codeHPIDisplay"; } "codeHPICodingRule"; } "historyOfPICode"; aCC.value as textVal -> oCC.value = textVal "historyOfPIValue"; } "historyOfPIObs"; } "historyOfPIAns"; } "historyOfPIRule"; // History of Past Illness / Past Medical History qr.item as hPastCC where (linkId = '11349-8') then { hPastCC.answer first as aCC then { aCC -> bundle.entry as eCC, eCC.resource = create('Observation') as oCC then { qr -> oCC, eCC then BuildBaseObs(qr, oCC, eCC) "historyOfPastIBase"; qr -> oCC.category as catPastI then { qr -> catPastI.coding as catPastICoding then { qr -> catPastICoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catPastISys"; qr -> catPastICoding.code = 'social-history' "catPastICode"; qr -> catPastICoding.display = 'Social History' "catPastIDisplay"; } "catPastICodingRule"; } "historyOfPastICategory"; qr -> oCC.code as codePastI then { qr -> codePastI.coding as codePastICoding then { qr -> codePastICoding.system = 'http://loinc.org' "codePastISys"; qr -> codePastICoding.code = '11349-8' "codePastICode"; qr -> codePastICoding.display = 'History of Past illness' "codePastIDisplay"; } "codePastICodingRule"; } "historyOfPastICode"; aCC.value as textVal -> oCC.value = textVal "historyOfPastIValue"; } "historyOfPastIObs"; } "historyOfPastIAns"; } "historyOfPastIRule"; // Family History qr.item as hFH where (linkId = '8670-2') then { hFH.answer first as aCC then { aCC -> bundle.entry as eCC, eCC.resource = create('Observation') as oCC then { qr -> oCC, eCC then BuildBaseObs(qr, oCC, eCC) "familyFHIBase"; qr -> oCC.category as catFH then { qr -> catFH.coding as catFHCoding then { qr -> catFHCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catFHSys"; qr -> catFHCoding.code = 'social-history' "catFHCode"; qr -> catFHCoding.display = 'Social History' "catFHDisplay"; } "catFHCodingRule"; } "familyHICategory"; qr -> oCC.code as codeFH then { qr -> codeFH.coding as codeFHCoding then { qr -> codeFHCoding.system = 'http://loinc.org' "codeFHSys"; qr -> codeFHCoding.code = '8670-2' "codeFHCode"; qr -> codeFHCoding.display = 'History of family member diseases' "codeFHDisplay"; } "codeFHCodingRule"; } "familyHICode"; aCC.value as textVal -> oCC.value = textVal "familyHIValue"; } "familyHIObs"; } "familyHIAns"; } "familyHIRule"; // Social History qr.item as hSH where (linkId = '29762-2') then { hSH.answer first as aCC then { aCC -> bundle.entry as eCC, eCC.resource = create('Observation') as oCC then { qr -> oCC, eCC then BuildBaseObs(qr, oCC, eCC) "socialHistoryIBase"; qr -> oCC.category as catSH then { qr -> catSH.coding as catSHCoding then { qr -> catSHCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catSHSys"; qr -> catSHCoding.code = 'social-history' "catSHCode"; qr -> catSHCoding.display = 'Social History' "catSHDisplay"; } "catSHCodingRule"; } "socialHistoryCategory"; qr -> oCC.code as codeSH then { qr -> codeSH.coding as codeSHCoding then { qr -> codeSHCoding.system = 'http://loinc.org' "codeSHSys"; qr -> codeSHCoding.code = '29762-2' "codeSHCode"; qr -> codeSHCoding.display = 'Social history note' "codeSHDisplay"; } "codeSHCodingRule"; } "socialHistoryCode"; aCC.value as textVal -> oCC.value = textVal "socialHistoryIValue"; } "socialHistoryIObs"; } "socialHistoryIAns"; } "socialHistoryIRule"; // Medication History qr.item as medicationH where (linkId = '10160-0') then { medicationH.answer first as aCC then { aCC -> bundle.entry as eCC, eCC.resource = create('Observation') as oCC then { qr -> oCC, eCC then BuildBaseObs(qr, oCC, eCC) "medicationHIBase"; qr -> oCC.category as catMedH then { qr -> catMedH.coding as catMedHCoding then { qr -> catMedHCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catMedHSys"; qr -> catMedHCoding.code = 'therapy' "catMedHCode"; qr -> catMedHCoding.display = 'Therapy' "catMedHDisplay"; } "catMedHCodingRule"; } "medicationHCategory"; qr -> oCC.code as codeMedH then { qr -> codeMedH.coding as codeMedHCoding then { qr -> codeMedHCoding.system = 'http://loinc.org' "codeMedHSys"; qr -> codeMedHCoding.code = '10160-0' "codeMedHCode"; qr -> codeMedHCoding.display = 'History of Medication use Narrative' "codeMedHDisplay"; } "codeMedHCodingRule"; } "medicationHistoryCode"; aCC.value as textVal -> oCC.value = textVal "medicationHistoryIValue"; } "medicationHistoryIObs"; } "medicationHistoryIAns"; } "medicationHistoryIRule"; // Risk Factors qr.item as riskF where (linkId = '46467-7') then { riskF.answer first as aCC then { aCC -> bundle.entry as eCC, eCC.resource = create('Observation') as oCC then { qr -> oCC, eCC then BuildBaseObs(qr, oCC, eCC) "riskFactorsBase"; qr -> oCC.category as catRF then { qr -> catRF.coding as catRFCoding then { qr -> catRFCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "catRFSys"; qr -> catRFCoding.code = 'social-history' "catRFCode"; qr -> catRFCoding.display = 'Social History' "catRFDisplay"; } "catRFCodingRule"; } "riskFactorsCategory"; qr -> oCC.code as codeRF then { qr -> codeRF.coding as codeRFCoding then { qr -> codeRFCoding.system = 'http://loinc.org' "codeRFSys"; qr -> codeRFCoding.code = '46467-7' "codeRFCode"; qr -> codeRFCoding.display = 'Risk factors' "codeRFDisplay"; } "codeRFCodingRule"; } "riskFactorsCode"; aCC.value as textVal -> oCC.value = textVal "riskFactorsValue"; } "riskFactorsObs"; } "riskFactorsAns"; } "riskFactorsRule"; // ─────────────────────────────────────────────────────────────────────────── // Admissions observation templates. // These forms carry the design's own field ids as linkIds rather than the LOINC // codes the rules above match on, so the linkId and the Observation.code are // stated separately. Everything they produce is category `exam` - a reading // taken at the bedside as part of assessing the patient, not a lab result and // not a vital sign. // ─────────────────────────────────────────────────────────────────────────── // Pain score - pain at rest qr.item as itPar where (linkId = 'painAtRest') then { itPar.answer first as aPar then { aPar -> bundle.entry as ePar, ePar.resource = create('Observation') as oPar then { qr -> oPar, ePar then BuildBaseObs(qr, oPar, ePar) "painAtRestBase"; qr -> oPar then SetExamCategory(qr, oPar) "painAtRestCategory"; qr -> oPar.code as codePar then { qr -> codePar.coding as codeParCoding then { qr -> codeParCoding.system = 'http://loinc.org' "codeParSys"; qr -> codeParCoding.code = '99302-2' "codeParCode"; qr -> codeParCoding.display = 'Verbal complaints while at rest' "codeParDisplay"; } "codeParCodingRule"; } "codePar"; aPar.value as pv -> oPar.value = pv "valPar"; qr -> oPar then SetPainBodySite(qr, oPar) "painAtRestSite"; } "painAtRestObs"; } "painAtRestAns"; } "painAtRestRule"; // Pain score - pain on movement qr.item as itPom where (linkId = 'painOnMovement') then { itPom.answer first as aPom then { aPom -> bundle.entry as ePom, ePom.resource = create('Observation') as oPom then { qr -> oPom, ePom then BuildBaseObs(qr, oPom, ePom) "painOnMovementBase"; qr -> oPom then SetExamCategory(qr, oPom) "painOnMovementCategory"; qr -> oPom.code as codePom then { qr -> codePom.coding as codePomCoding then { qr -> codePomCoding.system = 'http://loinc.org' "codePomSys"; qr -> codePomCoding.code = '99294-1' "codePomCode"; qr -> codePomCoding.display = 'Verbal complaints during movement' "codePomDisplay"; } "codePomCodingRule"; } "codePom"; aPom.value as pv -> oPom.value = pv "valPom"; qr -> oPom then SetPainBodySite(qr, oPom) "painOnMovementSite"; } "painOnMovementObs"; } "painOnMovementAns"; } "painOnMovementRule"; // Blood glucose - capillary glucose qr.item as itCg where (linkId = 'capillaryGlucose') then { itCg.answer first as aCg then { aCg -> bundle.entry as eCg, eCg.resource = create('Observation') as oCg then { qr -> oCg, eCg then BuildBaseObs(qr, oCg, eCg) "capillaryGlucoseBase"; qr -> oCg then SetExamCategory(qr, oCg) "capillaryGlucoseCategory"; qr -> oCg.code as codeCg then { qr -> codeCg.coding as codeCgCoding then { qr -> codeCgCoding.system = 'http://loinc.org' "codeCgSys"; qr -> codeCgCoding.code = '14743-9' "codeCgCode"; qr -> codeCgCoding.display = 'Glucose [Moles/volume] in Capillary blood by Glucometer' "codeCgDisplay"; } "codeCgCodingRule"; } "codeCg"; // The answer is already a Quantity - the item is type `quantity` with // questionnaire-unitOption pinning mmol/L - so copy it through whole. The // vitals rules above rebuild a Quantity because their answers are bare // decimals; rebuilding here would mean restating the unit, and restating a // unit the client actually sent turns a wrong unit into a wrong reading. aCg.value as gv -> oCg.value = gv "valCg"; } "capillaryGlucoseObs"; } "capillaryGlucoseAns"; } "capillaryGlucoseRule"; // Blood glucose - insulin given against the reading. // 4287-9 is "Insulin regular [Mass] of Dose": PROPERTY Mass, SCALE_TYP Qn. The // form captures free text ("4 units Actrapid"), and insulin is dosed in // international units rather than by mass, so the answer lands in valueString // rather than the Quantity the code implies. It records what the nurse wrote, // which is the point; a mass in mg is not what anyone is charting. qr.item as itIns where (linkId = 'insulinGiven') then { itIns.answer first as aIns then { aIns -> bundle.entry as eIns, eIns.resource = create('Observation') as oIns then { qr -> oIns, eIns then BuildBaseObs(qr, oIns, eIns) "insulinGivenBase"; qr -> oIns then SetExamCategory(qr, oIns) "insulinGivenCategory"; qr -> oIns.code as codeIns then { qr -> codeIns.coding as codeInsCoding then { qr -> codeInsCoding.system = 'http://loinc.org' "codeInsSys"; qr -> codeInsCoding.code = '4287-9' "codeInsCode"; qr -> codeInsCoding.display = 'Insulin regular [Mass] of Dose' "codeInsDisplay"; } "codeInsCodingRule"; } "codeIns"; aIns.value as iv -> oIns.value = iv "valIns"; } "insulinGivenObs"; } "insulinGivenAns"; } "insulinGivenRule"; } // Category `exam`, shared by every observation the admissions templates produce. group SetExamCategory(source qr : QR, target obs : Observation) { qr -> obs.category as cat then { qr -> cat.coding as catCoding then { qr -> catCoding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "examSys"; qr -> catCoding.code = 'exam' "examCode"; qr -> catCoding.display = 'Exam' "examDisplay"; } "examCodingRule"; } "examCategory"; } // The pain form asks for one site covering both scores, so it lands on each of // the two Observations. It is free text today, hence bodySite.text rather than a // coding; swap to bodySite.coding once the picker's option list is coded. group SetPainBodySite(source qr : QR, target obs : Observation) { qr.item as siteItem where (linkId = 'site') then { siteItem.answer first as siteAns then { siteAns.value as siteVal -> obs.bodySite as bs then { siteVal -> bs.text = siteVal "bodySiteText"; } "bodySite"; } "siteAns"; } "siteRule"; } group BuildBaseObs(source qr : QR, target obs : Observation, target entry) { qr -> obs.status = 'final' "status"; qr.subject as s -> obs.subject = s; qr.encounter as e -> obs.encounter = e; // `effective`, not `effectiveDateTime`: the R5 transform engine writes choice // elements by their base name and rejects the typed form with "Attempt to write // to invalid property 'effectiveDateTime' on type Observation". The value is a // dateTime, so the engine picks effectiveDateTime itself. // The admissions templates ask for the time the reading was taken, which is the // clinically meaningful one - a nurse writing up at the end of a round would // otherwise stamp every reading with the moment they saved the form. Fall back // to `authored` for the forms that do not ask. qr.item as tItem where (linkId = 'timeTaken') then { tItem.answer first as tAns then { tAns.value as tv -> obs.effective = tv "effectiveFromTimeTaken"; } "timeTakenAns"; } "timeTakenRule"; qr where (item.where(linkId = 'timeTaken').answer.value.exists().not()) then { qr.authored as t -> obs.effective = t "effectiveFromAuthored"; } "authoredFallback"; // Performer from QuestionnaireResponse.source qr.source as src -> obs.performer = create('Reference') as perf then { src.reference as ref -> perf.reference = ref "perfRef"; src.display as disp -> perf.display = disp "perfDisplay"; } "performer"; // Observation id and fullUrl qr -> obs.id = uuid() then SetObservationFullUrl(obs, entry) "setObsIdAndFullUrl"; // Bundle.entry.request (transaction POST Observation) qr -> entry.request as request then { qr -> request.method = 'POST' "reqMethod"; qr -> request.url = 'Observation' "reqUrl"; } "entryRequest"; // Link Observation -> QuestionnaireResponse. // No append: under this engine `qr.id` already resolves to the type-qualified // relative reference ("QuestionnaireResponse/xyz"), so prefixing it again // produced "QuestionnaireResponse/QuestionnaireResponse/xyz" - a reference that // resolves to nothing. qr -> obs.derivedFrom = create('Reference') as newRef then { qr.id as qid -> newRef.reference = qid "setQRRef"; } "linkQR"; } group SetObservationFullUrl(source obs : Observation, target entry) { obs.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/Observation/', id) "assignFullUrl"; }