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/ExtractMedicationRequest | Version: 0.1.0 | |||
| Draft as of 2026-10-06 | Computable Name: ExtractMedicationRequest | |||
/// url = 'https://fhir.slade360.co.ke/fhir/StructureMap/ExtractMedicationRequest' /// name = 'ExtractMedicationRequest' /// 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/MedicationRequest" alias MedicationRequest as target uses "http://hl7.org/fhir/StructureDefinition/Medication" alias Medication as target group ExtractMedicationRequest(source qr : QR, target bundle : Bundle) { qr -> bundle.type = 'transaction' "setBundleType"; qr.item as prescriptionGroup where (linkId = 'prescription-form') then { prescriptionGroup -> bundle.entry as entry, entry.resource = create('MedicationRequest') as medReq then { qr -> medReq then CopyResponseTags(qr, medReq) "responseTags"; qr -> medReq, entry then BuildMedicationRequest(qr, prescriptionGroup, medReq, entry) "buildMedReq"; } "createMedReq"; } "findPrescriptionGroup"; } group BuildMedicationRequest(source qr : QR, source prescriptionGroup, target medReq : MedicationRequest, target entry) { // status will always be draft until medication has been signed off qr -> medReq.status = 'draft' "setStatus"; qr -> medReq.intent = 'order' "setIntent"; // Subject from QuestionnaireResponse qr.subject as s where ((reference.exists() and (reference.startsWith('Patient/'))) or (reference.empty() and (type.empty() or ('/' + type).endsWith('/Patient')))) -> medReq.subject = s "setSubject"; // Encounter from QuestionnaireResponse qr.encounter as enc where ((reference.exists() and (reference.startsWith('Encounter/'))) or (reference.empty() and (type.empty() or ('/' + type).endsWith('/Encounter')))) -> medReq.encounter = enc "setEncounter"; // AuthoredOn from QuestionnaireResponse.authored qr.authored as authored -> medReq.authoredOn = authored "setAuthoredOn"; // Requester from QuestionnaireResponse.source (the Organization) qr.source as src where ((reference.exists() and (reference.startsWith('Patient/') or reference.startsWith('Organization/'))) or (reference.empty() and (type.empty() or ('/' + type).endsWith('/Patient') or ('/' + type).endsWith('/Organization')))) -> medReq.requester = src "setRequester"; // Performer from QuestionnaireResponse.source (the Organization) qr.source as src where ((reference.exists() and (reference.startsWith('Patient/') or reference.startsWith('Organization/'))) or (reference.empty() and (type.empty() or ('/' + type).endsWith('/Patient') or ('/' + type).endsWith('/Organization')))) -> medReq.performer = create('Reference') as perf then { src.reference as ref -> perf.reference = ref "perfRef"; src.display as disp -> perf.display = disp "perfDisplay"; } "performer"; // Copy identifier from QuestionnaireResponse to MedicationRequest, unless its // assigner is something an identifier cannot be assigned by. qr.identifier first as qrIdent where (assigner.empty() or assigner.where(((reference.exists() and (reference.startsWith('Organization/'))) or (reference.empty() and (type.empty() or ('/' + type).endsWith('/Organization'))))).exists()) -> medReq.identifier = qrIdent "copyIdentifier"; // Medication as CodeableReference from the medication-reference extension, which // the platform writes under either canonical. Assigning the whole CodeableConcept // carries its codings and their systems, so there is nothing further to copy. // R5's engine reads a choice as `value`: `valueCodeableConcept`, and every // `valueX` below, is an invalid property here and failed the whole extraction. qr.extension as ext where (url.endsWith('/StructureDefinition/medication-reference')) then { ext.value as medCC where ($this is CodeableConcept) -> medReq.medication as med then { medCC -> med.concept = medCC "setMedConcept"; } "accessMedication"; } "findMedicationExt"; // Priority prescriptionGroup.item as priorityItem where (linkId = 'priority') then { priorityItem.answer first as priorityAns then { // MedicationRequest.priority is a code, not a Coding: it holds the code // alone and has nowhere to put the system it came from. priorityAns.value as priorityCoding where ($this is Coding) then { priorityCoding.code as pCode -> medReq.priority = pCode "setPriorityCode"; } "extractPriorityCode"; } "extractPriority"; } "findPriority"; // Dosage Instruction qr -> medReq.dosageInstruction = create('Dosage') as dosage then { prescriptionGroup -> dosage then BuildDosage(prescriptionGroup, dosage) "buildDosage"; } "createDosage"; // Substitution prescriptionGroup.item as substItem where (linkId = 'substitutable') then { substItem.answer first as substAns then { substAns.value as substBool where ($this is boolean) -> medReq.substitution as subst then { // allowed[x] is written as `allowed`; the engine picks the type from the value. substBool -> subst.allowed = substBool "setSubstValue"; } "createSubst"; } "extractSubst"; } "findSubst"; // Dispense Request qr -> medReq.dispenseRequest as dispReq then { prescriptionGroup -> dispReq then BuildDispenseRequest(qr, prescriptionGroup, dispReq) "buildDispReq"; } "createDispReq"; // Set ID and FullUrl qr -> medReq.id = uuid() then SetMedicationRequestFullUrl(medReq, entry) "setMedReqIdAndFullUrl"; // Entry request for transaction bundle qr -> entry.request as request then { qr -> request.method = 'POST' "reqMethod"; qr -> request.url = 'MedicationRequest' "reqUrl"; } "entryRequest"; } group BuildDosage(source prescriptionGroup, target dosage : Dosage) { prescriptionGroup -> dosage.timing as timing then { prescriptionGroup -> timing.repeat as repeat then { // Frequency (OD, BD, TID, QID). A positiveInt, which an integer is not converted // to, so each is cast. prescriptionGroup.item as freqItem where (linkId = 'frequency') then { freqItem.answer first as freqAns then { freqAns.value as freqCoding where (($this is Coding) and (code = 'OD')) -> repeat.frequency = cast('1', 'positiveInt') "setFreqOD"; freqAns.value as freqCoding where (($this is Coding) and (code = 'BD')) -> repeat.frequency = cast('2', 'positiveInt') "setFreqBD"; freqAns.value as freqCoding where (($this is Coding) and (code = 'TID')) -> repeat.frequency = cast('3', 'positiveInt') "setFreqTID"; freqAns.value as freqCoding where (($this is Coding) and (code = 'QID')) -> repeat.frequency = cast('4', 'positiveInt') "setFreqQID"; } "extractFreq"; } "findFreq"; // Period (1 day) prescriptionGroup -> repeat.period = 1 "setPeriod"; prescriptionGroup -> repeat.periodUnit = 'd' "setPeriodUnit"; // Duration prescriptionGroup.item as durItem where (linkId = 'duration') then { durItem.answer first as durAns then { durAns.value as durValue where ($this is integer) -> repeat.duration = durValue "setDuration"; } "extractDuration"; } "findDuration"; // Duration UOM - directly use the code from unitsofmeasure.org (h, d, wk, mo, a) prescriptionGroup.item as durUomItem where (linkId = 'duration-uom') then { durUomItem.answer first as durUomAns then { durUomAns.value as durUomCoding where ($this is Coding) then { durUomCoding.code as durUomCode -> repeat.durationUnit = durUomCode "setDurationUnit"; } "extractDurUomCode"; } "extractDurUom"; } "findDurUom"; // When to take - directly use the code (AC, PC, ACM, ACD, ACV, PCM, PCD, PCV, etc.) // Handles both single answer and array of answers prescriptionGroup.item as whenItem where (linkId = 'when-to-take') then { whenItem.answer as whenAns then { whenAns.value as whenCoding where ($this is Coding) then { whenCoding.code as whenCode -> repeat.when = whenCode "setWhenCode"; } "extractWhenCode"; } "extractWhen"; } "findWhen"; // Bounds Period from start-date and end-date prescriptionGroup.item as startItem where (linkId = 'start-date') then { startItem.answer first as startAns then { startAns.value as startDate where ($this is date) -> repeat.bounds = create('Period') as boundsPeriod then { startDate -> boundsPeriod.start = startDate "setBoundsStart"; prescriptionGroup.item as endItem where (linkId = 'end-date') then { endItem.answer first as endAns then { endAns.value as endDate where ($this is date) -> boundsPeriod.end = endDate "setBoundsEnd"; } "extractEndDate"; } "findEndDate"; } "createBoundsPeriod"; } "extractStartDate"; } "findStartDate"; } "accessRepeat"; } "accessTiming"; // Route prescriptionGroup.item as routeItem where (linkId = 'route') then { routeItem.answer first as routeAns then { routeAns.value as routeCoding where ($this is Coding) -> dosage.route = create('CodeableConcept') as routeCC then { routeCoding -> routeCC.coding = routeCoding "setRouteCoding"; routeCoding.display as disp -> routeCC.text = disp "setRouteText"; } "setRouteCC"; } "extractRoute"; } "findRoute"; // Dose and Rate. A dose can be a fraction (half a tablet, 2.5 ml), so a decimal // answer is read as well as the integer ones already stored. prescriptionGroup.item as doseItem where (linkId = 'dose') then { doseItem.answer first as doseAns then { doseAns.value as doseValue where (($this is integer) or ($this is decimal)) -> dosage.doseAndRate as doseRate, doseRate.dose = create('Quantity') as doseQty then { doseValue -> doseQty.value = doseValue "setDoseValue"; prescriptionGroup.item as doseUnitItem where (linkId = 'dosage-unit') then { doseUnitItem.answer first as doseUnitAns then { doseUnitAns.value as unitCoding where ($this is Coding) then { unitCoding.display as unitDisp -> doseQty.unit = unitDisp "setDoseUnit"; unitCoding.code as unitCode -> doseQty.code = unitCode "setDoseCode"; unitCoding.system as unitSys -> doseQty.system = unitSys "setDoseSystem"; } "extractUnitCoding"; } "extractDoseUnitAns"; } "findDoseUnit"; } "createDoseAndRate"; } "extractDoseAns"; } "findDose"; // Dosage Instructions (patient instruction) prescriptionGroup.item as instrItem where (linkId = 'dosage-instructions') then { instrItem.answer first as instrAns then { instrAns.value as instrText where ($this is string) -> dosage.patientInstruction = instrText "setPatientInstruction"; } "extractInstr"; } "findDosageInstr"; // Additional Instructions (text) prescriptionGroup.item as addInstrItem where (linkId = 'additional-instructions') then { addInstrItem.answer first as addInstrAns then { addInstrAns.value as addInstrText where ($this is string) -> dosage.text = addInstrText "setDosageText"; } "extractAddInstr"; } "findAddInstr"; } group BuildDispenseRequest(source qr : QR, source prescriptionGroup, target dispReq) { // Refills prescriptionGroup.item as refillsItem where (linkId = 'refills') then { refillsItem.answer first as refillsAns then { // An unsignedInt, which an integer is not converted to: cast, and only from zero up. refillsAns.value as refillsValue where (($this is integer) and ($this >= 0)) -> dispReq.numberOfRepeatsAllowed = cast(refillsValue, 'unsignedInt') "setRefills"; } "extractRefills"; } "findRefills"; // Validity Period from start-date and end-date prescriptionGroup.item as startItem where (linkId = 'start-date') then { startItem.answer first as startAns then { startAns.value as startDate where ($this is date) -> dispReq.validityPeriod as validPeriod then { startDate -> validPeriod.start = startDate "setValidityStart"; prescriptionGroup.item as endItem where (linkId = 'end-date') then { endItem.answer first as endAns then { endAns.value as endDate where ($this is date) -> validPeriod.end = endDate "setValidityEnd"; } "extractValidityEnd"; } "findValidityEnd"; } "createValidityPeriod"; } "extractValidityStart"; } "findValidityStart"; // Expected Supply Duration prescriptionGroup.item as durItem where (linkId = 'duration') then { durItem.answer first as durAns then { durAns.value as durValue where ($this is integer) -> dispReq.expectedSupplyDuration as supplyDur then { durValue -> supplyDur.value = durValue "setSupplyDurValue"; prescriptionGroup.item as durUomItem where (linkId = 'duration-uom') then { durUomItem.answer first as durUomAns then { durUomAns.value as durUomCoding where ($this is Coding) then { durUomCoding.code as uomCode -> supplyDur.code = uomCode "setSupplyDurCode"; durUomCoding.display as uomDisplay -> supplyDur.unit = uomDisplay "setSupplyDurUnit"; durUomCoding.system as uomSystem -> supplyDur.system = uomSystem "setSupplyDurSystem"; } "extractSupplyDurUom"; } "extractSupplyDurUomAns"; } "findSupplyDurUom"; } "createSupplyDuration"; } "extractSupplyDuration"; } "findSupplyDuration"; // Dispenser - from QuestionnaireResponse.source (Organization) qr.source as src where ((reference.exists() and (reference.startsWith('Organization/'))) or (reference.empty() and (type.empty() or ('/' + type).endsWith('/Organization')))) -> dispReq.dispenser = src "setDispenser"; } group SetMedicationRequestFullUrl(source medReq : MedicationRequest, target entry) { medReq.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/MedicationRequest/', id) "assignFullUrl"; } // ── Tenancy ────────────────────────────────────────────────────────────────── // Whose this is. The platform says which tenant a response belongs to with a // tag on the response, and every tag the response carries is copied onto // everything this map creates, as it is. Copied rather than named here, so the // map never has to know which tenant it is running for, nor which system the // tag is under: that system has moved more than once. group CopyResponseTags(source qr : QR, target r) { qr.meta as qm then { qm.tag as qt -> r.meta as m, m.tag = qt "copyTag"; } "tenantTags"; }