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/ExtractInpatientSurgicalPreoperativeAssessment | Version: 0.1.0 | |||
| Active as of 2026-09-14 | Computable Name: ExtractInpatientSurgicalPreoperativeAssessment | |||
The planned procedure becomes a ServiceRequest, the ASA grade and the last oral intake become Observations, and the clinician who took consent becomes a Consent verified by them. The procedure carries the surgeon's wording rather than a procedure code – the form is free text and matching prose to a code system would guess at what is being operated on. Consent.decision is permit because the form only exists once consent has been obtained; a refusal is not something this assessment records.
/// url = 'https://fhir.slade360.co.ke/fhir/StructureMap/ExtractInpatientSurgicalPreoperativeAssessment' /// name = 'ExtractInpatientSurgicalPreoperativeAssessment' /// title = 'Inpatient Surgical Pre-operative Assessment Extraction' /// status = 'active' /// description = 'The planned procedure becomes a ServiceRequest, the ASA grade and the last oral intake become Observations, and the clinician who took consent becomes a Consent verified by them. The procedure carries the surgeon\'s wording rather than a procedure code -- the form is free text and matching prose to a code system would guess at what is being operated on. Consent.decision is `permit` because the form only exists once consent has been obtained; a refusal is not something this assessment records.' 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 uses "http://hl7.org/fhir/StructureDefinition/ServiceRequest" alias ServiceRequest as target uses "http://hl7.org/fhir/StructureDefinition/Consent" alias Consent as target group ExtractInpatientSurgicalPreoperativeAssessment(source qr : QR, target bundle : Bundle) { qr -> bundle.type = 'transaction' "setBundleType"; // ── Planned procedure ────────────────────────────────────────────────────── qr.item as itP where ((linkId = 'procedure') and answer.value.exists()) then { itP.answer first as aP then { aP -> bundle.entry as entryP, entryP.resource = create('ServiceRequest') as sr then { qr -> sr.id = uuid() then SetServiceRequestFullUrl(sr, entryP) "idAndFullUrl"; qr -> entryP.request as request then { qr -> request.method = 'POST' "requestMethod"; qr -> request.url = 'ServiceRequest' "requestUrl"; } "entryRequest"; qr -> sr.status = 'active' "status"; qr -> sr.intent = 'plan' "intent"; qr -> sr.priority = 'routine' "priority"; // R5 ServiceRequest.code is a CodeableReference, so the concept sits one // level down. aP.value as v -> sr.code as codeRef then { aP -> codeRef.concept as concept then { aP -> concept.coding as coding then { aP -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/inpatient-clinical-concept-codesystem' "codeSystem"; aP -> coding.code = 'planned-procedure' "codeCode"; aP -> coding.display = 'Planned procedure' "codeDisplay"; } "codeCoding"; v -> concept.text = v "codeText"; } "codeConcept"; } "serviceRequestCode"; qr.subject as s -> sr.subject = s; qr.encounter as e -> sr.encounter = e; qr.authored as t -> sr.authoredOn = t "authoredOn"; qr.author as a -> sr.requester = a "requesterFromAuthor"; qr where (author.exists().not()) then { qr.source as src -> sr.requester = src "requesterFromSource"; } "requesterFallback"; qr -> sr.supportingInfo as si then { qr -> si.reference = create('Reference') as ref then { qr.id as qid -> ref.reference = qid "supportingInfoRef"; } "supportingInfoTarget"; } "linkQuestionnaireResponse"; } "serviceRequest"; } "procedureAnswer"; } "procedureRule"; // ── ASA grade ────────────────────────────────────────────────────────────── qr.item as itA where ((linkId = 'asa') and answer.value.exists()) then { itA.answer first as aA then { aA -> bundle.entry as entryA, entryA.resource = create('Observation') as obsA then { qr -> obsA, entryA then BuildObsBase(qr, obsA, entryA) "asaBase"; qr -> obsA then SetSurveyCategory(qr, obsA) "asaCategory"; qr -> obsA.code as code then { qr -> code.coding as coding then { qr -> coding.system = 'http://loinc.org' "codeSystem"; qr -> coding.code = '97816-3' "codeCode"; qr -> coding.display = 'American society of anesthesiologists morbidity state' "codeDisplay"; } "codeCoding"; } "asaCode"; aA -> obsA then SetCodedValue(aA, obsA) "asaValue"; } "asaObservation"; } "asaAnswer"; } "asaRule"; // ── Last oral intake ─────────────────────────────────────────────────────── // Reported by the patient rather than measured, hence `social-history`. The // answer is a dateTime and goes through as one: LOINC 67517-3 is defined as a // date and time, so no Quantity is involved. qr.item as itN where ((linkId = 'nbm') and answer.value.exists()) then { itN.answer first as aN then { aN -> bundle.entry as entryN, entryN.resource = create('Observation') as obsN then { qr -> obsN, entryN then BuildObsBase(qr, obsN, entryN) "nbmBase"; qr -> obsN then SetSocialHistoryCategory(qr, obsN) "nbmCategory"; qr -> obsN.code as code then { qr -> code.coding as coding then { qr -> coding.system = 'http://loinc.org' "codeSystem"; qr -> coding.code = '67517-3' "codeCode"; qr -> coding.display = 'Last oral intake [Date and time]' "codeDisplay"; } "codeCoding"; } "nbmCode"; aN.value as v -> obsN.value = v "nbmValue"; } "nbmObservation"; } "nbmAnswer"; } "nbmRule"; // ── Consent ──────────────────────────────────────────────────────────────── // The person named on the form is the person who took the consent, which is // verification.verifiedBy: the patient is the grantor, and the clinician // attests that the conversation happened. qr.item as itC where ((linkId = 'consentBy') and answer.value.exists()) then { itC.answer first as aC then { aC -> bundle.entry as entryC, entryC.resource = create('Consent') as consent then { qr -> consent.id = uuid() then SetConsentFullUrl(consent, entryC) "idAndFullUrl"; qr -> entryC.request as request then { qr -> request.method = 'POST' "requestMethod"; qr -> request.url = 'Consent' "requestUrl"; } "entryRequest"; qr -> consent.status = 'active' "status"; qr -> consent.category as cat then { qr -> cat.coding as coding then { qr -> coding.system = 'http://loinc.org' "categorySystem"; qr -> coding.code = '59284-0' "categoryCode"; qr -> coding.display = 'Consent Document' "categoryDisplay"; } "categoryCoding"; qr -> cat.text = 'Consent for the planned procedure' "categoryText"; } "category"; qr.subject as s -> consent.subject = s; // Consent.date is a `date`, not a dateTime, so the day is taken off the // front of the authored timestamp rather than assigned whole. qr -> consent.date = evaluate(qr, authored.toString().substring(0, 10)) "date"; qr.subject as s -> consent.grantor = s "grantor"; qr -> consent.decision = 'permit' "decision"; aC.value as cv -> consent.verification as ver then { aC -> ver.verified = true "verified"; cv -> ver.verifiedBy = cv "verifiedBy"; qr.authored as t -> ver.verificationDate = t "verificationDate"; } "verification"; qr -> consent.sourceReference = create('Reference') as ref then { qr.id as qid -> ref.reference = qid "sourceReferenceRef"; } "linkQuestionnaireResponse"; } "consent"; } "consentByAnswer"; } "consentRule"; } // ───────────────────────────────────────────────────────────────────────────── // Shared scaffolding. Repeated in each map rather than imported: the transform // engine resolves `imports` against whatever StructureMaps happen to be on the // server, and a form that stops extracting because a shared map was not // uploaded is a worse failure than a duplicated group. // ───────────────────────────────────────────────────────────────────────────── // Two notes on what the publisher's StructureMap validator says about this file. // Source parameters below are left untyped on purpose. Writing `: Element` makes // the validator compare QuestionnaireResponse.item against the literal 'Element' // and report them as incompatible with each other, which is noise, not a finding. // Untyped, it says only that it cannot check the paths. // `evaluate(context, expression)` is flagged as "takes 1 parameter but 2 were // found". Leave it. The FML parser in the same jar refuses the one-argument form // outright -- it wants a parameter, a comma, then the FHIRPath -- so the publisher // is objecting to what its own compiler emits. Two arguments is the only form that // compiles, and it is what the R5 transform engine executes. group BuildObsBase(source qr : QR, target obs : Observation, target entry) { qr -> obs.id = uuid() then SetObservationFullUrl(obs, entry) "idAndFullUrl"; qr -> entry.request as request then { qr -> request.method = 'POST' "requestMethod"; qr -> request.url = 'Observation' "requestUrl"; } "entryRequest"; qr -> obs.status = 'final' "status"; qr.subject as s -> obs.subject = s; qr.encounter as e -> obs.encounter = e; qr.authored as t -> obs.effective = t "effective"; qr.source as src -> obs.performer = src "performerFromSource"; qr where (source.exists().not()) then { qr.author as a -> obs.performer = a "performerFromAuthor"; } "performerFallback"; qr -> obs.derivedFrom = create('Reference') as ref then { qr.id as qid -> ref.reference = qid "derivedFromRef"; } "linkQuestionnaireResponse"; } group SetCodedValue(source a, target obs : Observation) { a.value as cv -> obs.value = create('CodeableConcept') as cc then { cv -> cc.coding = cv "valueCoding"; cv.display as d -> cc.text = d "valueText"; } "codedValue"; } group SetSurveyCategory(source qr : QR, target obs : Observation) { qr -> obs.category as cat then { qr -> cat.coding as coding then { qr -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorySystem"; qr -> coding.code = 'survey' "categoryCode"; qr -> coding.display = 'Survey' "categoryDisplay"; } "categoryCoding"; } "category"; } group SetSocialHistoryCategory(source qr : QR, target obs : Observation) { qr -> obs.category as cat then { qr -> cat.coding as coding then { qr -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorySystem"; qr -> coding.code = 'social-history' "categoryCode"; qr -> coding.display = 'Social History' "categoryDisplay"; } "categoryCoding"; } "category"; } group SetObservationFullUrl(source obs : Observation, target entry) { obs.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/Observation/', id) "assignFullUrl"; } group SetServiceRequestFullUrl(source sr : ServiceRequest, target entry) { sr.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/ServiceRequest/', id) "assignFullUrl"; } group SetConsentFullUrl(source consent : Consent, target entry) { consent.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/Consent/', id) "assignFullUrl"; }