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 2025-12-12 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";
  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.code = cc('http://loinc.org', '8302-2', 'Body Height') "codeH";
        aH.value as hv -> oH.valueQuantity 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"; // Height
  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.code = cc('http://loinc.org', '29463-7', 'Body Weight') "codeW";
        aW.value as hv -> oW.valueQuantity 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"; // Weight
  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.code = cc('http://loinc.org', '39156-5', 'Body mass index (BMI) [Ratio]') "codeBmi";
        aBmi.value as hv -> oBmi.valueQuantity 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";
        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"; // Interpretation 39156-5_status
      } "bmiObs";
    } "bmiAns";
  } "bmiRule"; // BMI
  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.code = cc('http://loinc.org', '8889-8', 'Heart rate by Pulse oximetry') "codeP";
        aP.value as hv -> oP.valueQuantity 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"; // Pulse
  qr.item as itBP where (linkId = '55284-4') then {
    itBP.answer first as aPanel then {
      aPanel ->  bundle.entry as eBP,  eBP.resource = create('Observation') as oBP then {
        qr ->  oBP,  eBP then BuildBaseObs(qr, oBP, eBP) "bpBase";
        qr -> oBP.code = cc('http://loinc.org', '55284-4', 'Blood pressure systolic and diastolic') "codeBP";
        itBP.item as itSys where (linkId = '8480-6') then {
          itSys.answer first as aSys -> oBP.component as cSys then {
            aSys -> cSys.code = cc('http://loinc.org', '8480-6', 'Systolic blood pressure') "bpSysCode";
            aSys.value as hv -> cSys.valueQuantity 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"; // Systolic 8480-6
        itBP.item as itDia where (linkId = '8462-4') then {
          itDia.answer first as aDia -> oBP.component as cDia then {
            aDia -> cDia.code = cc('http://loinc.org', '8462-4', 'Diastolic blood pressure') "bpDiaCode";
            aDia.value as hv -> cDia.valueQuantity 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"; // Diastolic 8462-4
      } "bpObs";
    } "bpAns";
  } "bpRule"; // Blood Pressure
  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.code = cc('http://loinc.org', '8310-5', 'Body Temperature') "codeT";
        aT.value as hv -> oT.valueQuantity 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";
        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"; // Interpretation 8310-5_status
      } "tempObs";
    } "tempAns";
  } "tempRule"; // Temperature
  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.code = cc('http://loinc.org', '20564-1', 'Oxygen saturation in Blood') "codeO2";
        aO2.value as hv -> oO2.valueQuantity as vO2 then {
          hv -> vO2.value = hv "setVal";
          qr -> vO2.system = 'http://unitsofmeasure.org' "setSys";
          qr -> vO2.code = '%' "setCode";
          qr -> vO2.unit = '%' "setUnit";
        } "valO2";
        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"; // Interpretation of SpO2 20564-1_status
      } "spo2Obs";
    } "spo2Ans";
  } "spo2Rule"; // SpO2
  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.code = cc('http://loinc.org', '9279-1', 'Respiratory rate') "codeRR";
        aRR.value as hv -> oRR.valueQuantity 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"; // Respiratory rate
  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.code = cc('http://loinc.org', '9847-5', 'Circumference') "codeMuac";
        aMuac.value as hv -> oMuac.valueQuantity 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"; // MUAC
  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 = cc('http://terminology.hl7.org/CodeSystem/observation-category', 'social-history', 'Social History') "chiefComplaintCategory"; // Override category to social-history
        qr -> oCC.code = cc('http://loinc.org', '10154-3', 'Chief complaint Narrative') "chiefComplaintCode";
        aCC.value as textVal -> oCC.valueString = textVal "chiefComplaintValue";
      } "chiefComplaintObs";
    } "chiefComplaintAns";
  } "chiefComplaintRule"; // Chief Complaint
  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 = cc('http://terminology.hl7.org/CodeSystem/observation-category', 'social-history', 'Social History') "historyOfPICategory"; // Override category to social-history
        qr -> oCC.code = cc('http://loinc.org', '8684-3', 'History of Present illness') "historyOfPICode";
        aCC.value as textVal -> oCC.valueString = textVal "historyOfPIValue";
      } "historyOfPIObs";
    } "historyOfPIAns";
  } "historyOfPIRule"; // History of Presenting Illness
  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 = cc('http://terminology.hl7.org/CodeSystem/observation-category', 'social-history', 'Social History') "historyOfPastICategory"; // Override category to social-history
        qr -> oCC.code = cc('http://loinc.org', '11349-8', 'History of Past illness') "historyOfPastICode";
        aCC.value as textVal -> oCC.valueString = textVal "historyOfPastIValue";
      } "historyOfPastIObs";
    } "historyOfPastIAns";
  } "historyOfPastIRule"; // History of Past Illness
}

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;
  qr.authored as t -> obs.effectiveDateTime = t "effective";
  qr -> obs.category = cc('http://terminology.hl7.org/CodeSystem/observation-category', 'vital-signs', 'Vital Signs') "category"; // Category (vital signs) - defaukt
  qr -> obs.id = uuid() then SetObservationFullUrl(obs, entry) "setObsIdAndFullUrl"; // Observation id and fullUrl
  qr -> entry.request as request then {
    qr -> request.method = 'POST' "reqMethod";
    qr -> request.url = 'Observation' "reqUrl";
  } "entryRequest"; // Bundle.entry.request (transaction POST Observation)
  qr -> obs.derivedFrom = create('Reference') as newRef then {
    qr.id as qid -> newRef.reference = append('QuestionnaireResponse/', qid) "setQRRef";
  } "linkQR"; // Link Observation -> QuestionnaireResponse
}

group SetObservationFullUrl(source obs : Observation, target entry) {
  obs.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/Observation/', id) "assignFullUrl";
}