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: Embalming Authorization Extraction

Official URL: https://fhir.slade360.co.ke/fhir/StructureMap/ExtractEmbalmingAuthorization Version: 0.1.0
Active as of 2026-09-14 Computable Name: ExtractEmbalmingAuthorization

A Consent recorded in both directions – 'permit' where embalming was authorised and 'deny' where it was refused – because a refusal is something the mortuary has to act on and an absent consent is not the same thing. The identifier is whichever of the IP, OB or permit number identifies the body: one brought from home or by the police has no IP number, and that number is the only handle linking it to its paperwork.

/// url = 'https://fhir.slade360.co.ke/fhir/StructureMap/ExtractEmbalmingAuthorization'
/// name = 'ExtractEmbalmingAuthorization'
/// title = 'Embalming Authorization Extraction'
/// status = 'active'
/// description = 'A Consent recorded in both directions -- \'permit\' where embalming was authorised and \'deny\' where it was refused -- because a refusal is something the mortuary has to act on and an absent consent is not the same thing. The identifier is whichever of the IP, OB or permit number identifies the body: one brought from home or by the police has no IP number, and that number is the only handle linking it to its paperwork.'

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/Consent" alias Consent as target

group ExtractEmbalmingAuthorization(source qr : QR, target bundle : Bundle) {
  qr -> bundle.type = 'transaction' "setBundleType";
  qr.item as g where (linkId = 'authorisation') then {
    g.item as it where ((linkId = 'authorisation/given') and answer.value.exists()) ->  bundle.entry as entry,  entry.resource = create('Consent') as con then {
      qr ->  con,  entry then BuildConsentBase(qr, con, entry) "base";
      it -> con.status = 'active' "status";
      it -> con.category as cat then {
        it -> cat.coding as cg then {
          it -> cg.system = 'http://loinc.org' "sys";
          it -> cg.code = '64293-4' "cd";
          it -> cg.display = 'Procedure consent Document' "dsp";
        } "coding";
        it -> cat.text = 'Authorisation to embalm a body' "catText";
      } "category";
      it.answer first as a then {
        a.value as cv where (code = 'Y') -> con.decision = 'permit' "permit";
      } "permitRule";
      it.answer first as a then {
        a.value as cv where (code = 'N') -> con.decision = 'deny' "deny";
      } "denyRule";
      g.item as at where ((linkId = 'authorisation/at') and answer.value.exists()) then {
        at.answer first as a then {
          a.value as v ->  con.period as per,  per.start = v "periodStart";
        };
      } "signedAtRule";
      it -> con.provision as prov then {
        it -> prov.code as pc then {
          it -> pc.coding as cg then {
            it -> cg.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "sys";
            it -> cg.code = 'mortuary-embalming' "cd";
            it -> cg.display = 'Embalming' "dsp";
          } "coding";
        } "provCode";
      } "provision";
      g.item as rs where ((linkId = 'authorisation/reason') and answer.value.exists()) then {
        rs.answer first as a then {
          a.value as txt ->  con.provision as prov,  prov.code as pc,  pc.text = txt "reasonText";
        };
      } "reasonRule";
      qr.item as nk where (linkId = 'next-of-kin') then {
        nk.item as n where ((linkId = 'next-of-kin/name') and answer.value.exists()) then {
          n.answer first as a then {
            a.value as txt ->  con.grantor = create('Reference') as gref,  gref.display = txt "grantor";
          };
        } "nokNameRule";
        nk.item as r where ((linkId = 'next-of-kin/relationship') and answer.value.exists()) then {
          r.answer first as a then {
            a.value as txt ->  con.provision as prov,  prov.code as pc,  pc.text = txt "relationshipText";
          };
        } "nokRelRule";
      } "nextOfKinGroup";
      qr.item as ig where (linkId = 'identification') then {
        ig.item as pn where ((linkId = 'identification/permit-or-ob-number') and answer.value.exists()) then {
          pn.answer first as a then {
            a.value as txt -> con.identifier as ident then {
              txt -> ident.system = 'https://fhir.slade360.co.ke/fhir/identifier/mortuary-body-reference' "identSystem";
              txt -> ident.value = txt "identValue";
            } "identifier";
          };
        } "permitRule";
      } "identificationGroup";
      qr.item as wg where (linkId = 'witnesses') then {
        wg.item as st where ((linkId = 'witnesses/staff') and answer.value.exists()) then {
          st.answer first as a then {
            a.value as ref -> con.verification as ver then {
              ref -> ver.verified = true "verified";
              ref -> ver.verifiedBy = ref "verifiedBy";
            } "verification";
          };
        } "staffRule";
      } "witnessGroup";
    } "embalmingConsent";
  } "authorisationGroup";
  qr.item as g where (linkId = 'witnesses') then {
    g.item as it where ((linkId = 'witnesses/witness-name') and answer.value.exists()) ->  bundle.entry as entry,  entry.resource = create('Observation') as obs then {
      qr ->  obs,  entry then BuildObsBase(qr, obs, entry) "base";
      qr -> obs then SetSurveyCategory(qr, obs) "category";
      it -> obs.code as cc then {
        it -> cc.coding as cg then {
          it -> cg.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "sys";
          it -> cg.code = 'mortuary-source-witness-to-an-embalming-authorisation' "cd";
          it -> cg.display = 'Witness to an embalming authorisation' "dsp";
        } "coding";
      } "code";
      it.answer first as a then {
        a.value as txt ->  obs.note as n,  n.text = txt "text";
      };
    } "narr_mortuary_source_witness_to_an_embalming_authorisation";
  } "witnessObs";
}

// ─────────────────────────────────────────────────────────────────────────────
// 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.
// `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 then SetResponseProvenance(qr, obs) "responseProvenance";
  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";
  // A stored response's id already reads "QuestionnaireResponse/<id>", so it is
  // a relative reference as it stands. The hasValue() guard is what stops an
  // unsaved response -- one posted straight to $extract rather than saved first
  // -- from writing "QuestionnaireResponse/null" into the record.
  qr.id as qid where ($this.hasValue()) ->  obs.derivedFrom = create('Reference') as ref,  ref.reference = qid "linkQuestionnaireResponse";
}

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

// The two answer shapes a component can carry. Both read the answer through the
// untyped `.value` accessor: reading `valueCoding` or `valueQuantity` by its
// typed property name fails at transform time on HAPI R5 with "Attempt to read
// invalid property ... on QuestionnaireResponse.item.answer".
group SetComponentCodedValue(source it, target comp) {
  it.answer first as a then {
    a.value as cv -> comp.value = create('CodeableConcept') as cc then {
      cv -> cc.coding = cv "valueCoding";
      cv.display as d -> cc.text = d "valueText";
    } "codedValue";
  } "componentAnswer";
}

group SetComponentValue(source it, target comp) {
  it.answer first as a then {
    a.value as v -> comp.value = v "plainValue";
  } "componentAnswer";
}

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 BuildConsentBase(source qr : QR, target r : Consent, target entry) {
  qr -> r then SetResponseProvenance(qr, r) "responseProvenance";
  qr -> r.id = uuid() then SetConsentFullUrl(r, entry) "idAndFullUrl";
  qr -> entry.request as request then {
    qr -> request.method = 'POST' "requestMethod";
    qr -> request.url = 'Consent' "requestUrl";
  } "entryRequest";
  qr.subject as s -> r.subject = s;
}

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

// ── Provenance and tenancy ───────────────────────────────────────────────────
// Two tags on everything this map creates. The first says which questionnaire
// produced it, so a form that reopens mid-admission recalls its own earlier
// entries and not another form's. The second copies the tenant tags off the
// response, because nothing else on the path puts them there.
// The code is the form's document type, not the questionnaire's id: every
// environment mints its own ids, and there is only one set of maps, so an id
// here would tie the tag to whichever environment stamped it.
// ── Provenance and tenancy ───────────────────────────────────────────────────
group SetResponseProvenance(source qr : QR, target r) {
  qr -> r.meta as m then {
    qr -> m.tag as t then {
      qr -> t.system = 'http://slade360edi.com/questionnaire-provenance' "provenanceSystem";
      qr -> t.code = 'embalming-authorization' "provenanceCode";
      qr -> t.display = 'Embalming authorization form' "provenanceDisplay";
    } "provenanceTag";
  } "provenanceMeta";
  // Every tag the response carries, onto the resource. `meta` is 0..1, so this
  // lands on the same meta the rule above created rather than a second one.
  qr.meta as qm then {
    qm.tag as qt ->  r.meta as m,  m.tag = qt "copyTag";
  } "tenantTags";
}