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

StructureMap: ExtractVitalSigns

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";
}