FHIR IG analytics| Package | silfhirprofileig |
| Resource Type | StructureMap |
| Id | StructureMap-ExtractPaediatricTbIcfScreening.json |
| FHIR Version | R5 |
| Source | https://build.fhir.org/ig/savannahghi/sil_fhir_profile_ig/StructureMap-ExtractPaediatricTbIcfScreening.html |
| URL | https://fhir.slade360.co.ke/fhir/StructureMap/ExtractPaediatricTbIcfScreening |
| Version | 0.1.0 |
| Status | active |
| Date | 2026-09-11T12:18:36+00:00 |
| Name | ExtractPaediatricTbIcfScreening |
| Title | Paediatric TB Intensified Case Finding Screening Extraction |
| Description | The paediatric intensified case finding screen out as one Observation carrying the screen result, with the five questions as components, plus a RiskAssessment for the band that result falls in. The tool has no score: it is answered yes to any one question or no to all five, so the band is binary and the arithmetic is a disjunction rather than a sum. Both branches extract. A screen that came back negative is the record that the screen was done, which is what makes the guideline's `repeat screening on subsequent visits` a thing the notes can show, and it is what carries the IPT workup as its mitigation. The result is derived from the five answers rather than read off an outcome item the form does not have, so the questions and the conclusion cannot disagree. |
No resources found
No resources found
Note: links and images are rebased to the (stated) source
Generated Narrative: StructureMap ExtractPaediatricTbIcfScreening
/// url = 'https://fhir.slade360.co.ke/fhir/StructureMap/ExtractPaediatricTbIcfScreening' /// name = 'ExtractPaediatricTbIcfScreening' /// title = 'Paediatric TB Intensified Case Finding Screening Extraction' /// status = 'active' /// description = 'The paediatric intensified case finding screen out as one Observation carrying the screen result, with the five questions as components, plus a RiskAssessment for the band that result falls in. The tool has no score: it is answered yes to any one question or no to all five, so the band is binary and the arithmetic is a disjunction rather than a sum. Both branches extract. A screen that came back negative is the record that the screen was done, which is what makes the guideline\'s `repeat screening on subsequent visits` a thing the notes can show, and it is what carries the IPT workup as its mitigation. The result is derived from the five answers rather than read off an outcome item the form does not have, so the questions and the conclusion cannot disagree.' 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/RiskAssessment" alias RiskAssessment as target group ExtractPaediatricTbIcfScreening(source qr : QR, target bundle : Bundle) { qr -> bundle.type = 'transaction' "setBundleType"; // The paper is a flat five-row table, so the questions are top-level items and // the response itself is the anchor. One answered question is enough to make // an Observation: a partly filled screen still records findings worth keeping. // Whether it is enough to conclude anything is a separate question, and // SetScreenOutcome is where that is decided. qr where (item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).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"; // Local, from concept-codesystem, because no code system this IG can resolve // names this screen. LOINC publishes no paediatric TB symptom screen -- only // the CPHS exposure-screen family, which is about exposure follow-up in a US // public-health programme -- and the ICD-10 and ICD-11 resources here are // `content: fragment` stubs with no url, so an ICD code could not be // validated against anything. The same code goes on the RiskAssessment: the // resource type already says which of the two is the observation and which // is the assessment, so a second code would only be a second thing to keep // in step. qr -> obs.code as code then { qr -> code.coding as coding then { qr -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "codeSystem"; qr -> coding.code = 'tb-screen' "codeCode"; qr -> coding.display = 'Paediatric TB intensified case finding screen' "codeDisplay"; } "codeCoding"; qr -> code.text = 'Paediatric TB intensified case finding screen' "codeText"; } "code"; // The five questions, each coded to the finding it asks about rather than to // one bucket code with the question in the display. Observation.component // .code is what a query filters on, so `which children screened positive for // a TB contact` has to be a question the record can answer. qr.item as it where ((linkId = 'cough') and answer.value.exists()) -> obs.component as comp then { it -> comp.code as code then { it -> code.coding as coding then { it -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "codeSystem"; it -> coding.code = 'tb-screen-cough' "codeCode"; it -> coding.display = 'Cough of any duration' "codeDisplay"; } "codeCoding"; it -> code.text = 'Cough of any duration' "codeText"; } "coughCode"; it -> comp then SetComponentCodedValue(it, comp) "coughValue"; } "coughRule"; qr.item as it where ((linkId = 'fever') and answer.value.exists()) -> obs.component as comp then { it -> comp.code as code then { it -> code.coding as coding then { it -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "codeSystem"; it -> coding.code = 'tb-screen-fever' "codeCode"; it -> coding.display = 'Fever' "codeDisplay"; } "codeCoding"; it -> code.text = 'Fever' "codeText"; } "feverCode"; it -> comp then SetComponentCodedValue(it, comp) "feverValue"; } "feverRule"; // One item on the paper, and one component here. The tool asks failure to // thrive *or* poor weight gain as a single question and the reviewer wrote // `malnutrition` against it, so splitting it into two components would // invent a distinction the nurse was never asked to make. The question as // printed is what the code and its text both say. qr.item as it where ((linkId = 'failure-to-thrive') and answer.value.exists()) -> obs.component as comp then { it -> comp.code as code then { it -> code.coding as coding then { it -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "codeSystem"; it -> coding.code = 'tb-screen-failure-to-thrive' "codeCode"; it -> coding.display = 'Failure to thrive or poor weight gain' "codeDisplay"; } "codeCoding"; it -> code.text = 'Failure to thrive or poor weight gain' "codeText"; } "thriveCode"; it -> comp then SetComponentCodedValue(it, comp) "thriveValue"; } "thriveRule"; qr.item as it where ((linkId = 'lethargy') and answer.value.exists()) -> obs.component as comp then { it -> comp.code as code then { it -> code.coding as coding then { it -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "codeSystem"; it -> coding.code = 'tb-screen-lethargy' "codeCode"; it -> coding.display = 'Lethargy, less playful than usual' "codeDisplay"; } "codeCoding"; it -> code.text = 'Lethargy, less playful than usual' "codeText"; } "lethargyCode"; it -> comp then SetComponentCodedValue(it, comp) "lethargyValue"; } "lethargyRule"; // The one question of the five that is about exposure rather than about the // child. It stays a component of the same screen rather than becoming its // own resource: what makes it meaningful is the four answers beside it. qr.item as it where ((linkId = 'tb-contact') and answer.value.exists()) -> obs.component as comp then { it -> comp.code as code then { it -> code.coding as coding then { it -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "codeSystem"; it -> coding.code = 'tb-screen-contact' "codeCode"; it -> coding.display = 'Contact with a TB case' "codeDisplay"; } "codeCoding"; it -> code.text = 'Contact with a TB case' "codeText"; } "contactCode"; it -> comp then SetComponentCodedValue(it, comp) "contactValue"; } "contactRule"; qr -> obs then SetScreenOutcome(qr, obs) "outcome"; qr -> obs, bundle then AddTbScreenRiskAssessment(obs, qr, bundle) "band"; } "screenRule"; } // ───────────────────────────────────────────────────────────────────────────── // The screen result // The tool states its own rule, in the two bullets printed under the table: yes // to any one question means suspect TB, no to all of them means initiate workup // for IPT and screen again next visit. So the result is a disjunction over five // yes/no answers, and there is nothing to sum. // **Both branches are guarded on all five having been answered.** The positive // branch does not need to be for the yes to be true -- one yes is one yes -- but // it does need to be for the *negative* branch to be safe, and the two have to // agree about what a complete screen is or a form answered `no, no, yes` to the // first three questions would extract a positive result while `no, no, no` to // the same three extracted nothing at all. A screen short of an item is not this // screen: four questions answered no says nothing about the fifth, and a clinician // reading `no signs of TB` off a form where nobody asked about the TB contact is // being told something the form does not know. // The answers are `Y` and `N`, from CodeSystem/v2-0532. The value set the form // binds is ValueSet/v2-0136, whose codes come from 0532 -- table 0136 was folded // into it -- so a guard testing 'yes' compiles, never fires, and silently // extracts nothing. // The result reuses `special-clinic-code-system#presumed-tb` and `#no-tb-signs`, // which this IG already defines for TB screening in the ANC and CWC workflows. // Nothing new was minted for it: a query for presumed TB should find this form // and those together, and a second code saying the same thing in a different // system would be a synonym to keep in step rather than information. // ───────────────────────────────────────────────────────────────────────────── group SetScreenOutcome(source qr : QR, target obs : Observation) { qr where ((item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).count() = 5) and item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).where(code = 'Y').exists()) -> obs.value = create('CodeableConcept') as cc then { qr -> cc.coding as local then { qr -> local.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system' "localSystem"; qr -> local.code = 'presumed-tb' "localCode"; qr -> local.display = 'Presumed TB' "localDisplay"; } "localCoding"; qr -> cc.text = 'Presumed TB — at least one screening question answered yes' "outcomeText"; } "screenPositive"; qr where ((item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).count() = 5) and item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).where(code = 'Y').exists().not()) -> obs.value = create('CodeableConcept') as cc then { qr -> cc.coding as local then { qr -> local.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system' "localSystem"; qr -> local.code = 'no-tb-signs' "localCode"; qr -> local.display = 'No Signs of TB' "localDisplay"; } "localCoding"; qr -> cc.text = 'No signs of TB — all five screening questions answered no' "outcomeText"; } "screenNegative"; } // ───────────────────────────────────────────────────────────────────────────── // The band // Read off the Observation the rule above just built, not recomputed from the // answers. Deriving it a second time would be a second implementation of the // same disjunction, and two implementations of one rule is one more than can be // kept in agreement. It is also why a partly answered screen produces no // RiskAssessment at all: no result, nothing to band. // `qualitativeRisk` carries HL7 risk-probability -- which is what a consumer // escalates on -- alongside the local screen-result code, and the plain wording // in `.text`. `prediction.outcome` is the disease being predicted, which is the // question a RiskAssessment exists to answer and is the same concept in both // branches; only the likelihood differs. // `mitigation` is the guideline's own instruction, verbatim in substance. That // element is the right home for it: it is what to do to reduce the risk, it // travels with the assessment, and it means a screen recorded at a health centre // arrives at the next clinician with the action already attached. // A negative screen bands `low`, not `negligible`. The tool does not clear a // child -- it sends them for IPT workup and asks for the screen again next visit // -- so a code that reads as no further action would misstate it. // ───────────────────────────────────────────────────────────────────────────── group AddTbScreenRiskAssessment(source obs : Observation, source qr : QR, target bundle : Bundle) { obs where (value.ofType(CodeableConcept).coding.where(code = 'presumed-tb').exists()) -> bundle.entry as entry, entry.resource = create('RiskAssessment') as ra then { qr -> ra, entry then BuildRiskBase(qr, obs, ra, entry) "riskBase"; qr -> ra then SetTbScreenRiskCode(qr, ra) "riskCode"; qr -> ra.prediction as pred then { qr -> pred then SetTbPredictionOutcome(qr, pred) "predictionOutcome"; qr -> pred.qualitativeRisk as risk then { qr -> risk.coding as coding then { qr -> coding.system = 'http://terminology.hl7.org/CodeSystem/risk-probability' "riskSystem"; qr -> coding.code = 'high' "riskCode"; qr -> coding.display = 'High likelihood' "riskDisplay"; } "hl7Coding"; qr -> risk.coding as local then { qr -> local.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system' "localSystem"; qr -> local.code = 'presumed-tb' "localCode"; qr -> local.display = 'Presumed TB' "localDisplay"; } "localCoding"; qr -> risk.text = 'Presumed TB' "riskText"; } "qualitativeRisk"; qr -> pred.rationale = 'At least one of the five intensified case finding questions was answered yes.' "rationale"; } "prediction"; qr -> ra.mitigation = 'Suspect TB. Examine the child and use the paediatric TB diagnostic algorithm to evaluate for active disease. Rule out underlying conditions and refer if necessary.' "mitigation"; } "presumedTb"; obs where (value.ofType(CodeableConcept).coding.where(code = 'no-tb-signs').exists()) -> bundle.entry as entry, entry.resource = create('RiskAssessment') as ra then { qr -> ra, entry then BuildRiskBase(qr, obs, ra, entry) "riskBase"; qr -> ra then SetTbScreenRiskCode(qr, ra) "riskCode"; qr -> ra.prediction as pred then { qr -> pred then SetTbPredictionOutcome(qr, pred) "predictionOutcome"; qr -> pred.qualitativeRisk as risk then { qr -> risk.coding as coding then { qr -> coding.system = 'http://terminology.hl7.org/CodeSystem/risk-probability' "riskSystem"; qr -> coding.code = 'low' "riskCode"; qr -> coding.display = 'Low likelihood' "riskDisplay"; } "hl7Coding"; qr -> risk.coding as local then { qr -> local.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system' "localSystem"; qr -> local.code = 'no-tb-signs' "localCode"; qr -> local.display = 'No Signs of TB' "localDisplay"; } "localCoding"; qr -> risk.text = 'No signs of TB' "riskText"; } "qualitativeRisk"; qr -> pred.rationale = 'All five intensified case finding questions were answered no.' "rationale"; } "prediction"; qr -> ra.mitigation = 'Initiate workup for isoniazid preventive therapy and repeat this screen at subsequent visits.' "mitigation"; } "noTbSigns"; } group SetTbScreenRiskCode(source qr : QR, target ra : RiskAssessment) { qr -> ra.code as code then { qr -> code.coding as coding then { qr -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "codeSystem"; qr -> coding.code = 'tb-screen' "codeCode"; qr -> coding.display = 'Paediatric TB intensified case finding screen' "codeDisplay"; } "codeCoding"; qr -> code.text = 'Paediatric TB intensified case finding screen' "codeText"; } "code"; } group SetTbPredictionOutcome(source qr : QR, target pred) { qr -> pred.outcome as outcome then { qr -> outcome.coding as coding then { qr -> coding.system = 'https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem' "outcomeSystem"; qr -> coding.code = 'tb-screen-tuberculosis' "outcomeCode"; qr -> coding.display = 'Tuberculosis' "outcomeDisplay"; } "outcomeCoding"; } "predictionOutcome"; } // ───────────────────────────────────────────────────────────────────────────── // 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"; } // The RiskAssessment stands on two things: the response that was filled in and // the Observation that holds the result. Both go in basis, which is what the // SGHI RiskAssessment profile binds to a QuestionnaireResponse. group BuildRiskBase(source qr : QR, source obs : Observation, target ra : RiskAssessment, target entry) { qr -> ra then SetResponseProvenance(qr, ra) "responseProvenance"; qr -> ra.id = uuid() then SetRiskAssessmentFullUrl(ra, entry) "idAndFullUrl"; qr -> entry.request as request then { qr -> request.method = 'POST' "requestMethod"; qr -> request.url = 'RiskAssessment' "requestUrl"; } "entryRequest"; qr -> ra.status = 'final' "status"; qr.subject as s -> ra.subject = s; qr.encounter as e -> ra.encounter = e; qr.authored as t -> ra.occurrence = t "occurrence"; qr.author as a -> ra.performer = a "performerFromAuthor"; qr where (author.exists().not()) then { qr.source as src -> ra.performer = src "performerFromSource"; } "performerFallback"; qr.id as qid where ($this.hasValue()) -> ra.basis = create('Reference') as qref, qref.reference = qid "basisResponse"; obs.id as oid -> ra.basis = create('Reference') as oref then { oid -> oref.reference = append('Observation/', oid) "basisObservationRef"; } "basisObservation"; } // 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 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 SetObservationFullUrl(source obs : Observation, target entry) { obs.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/Observation/', id) "assignFullUrl"; } group SetRiskAssessmentFullUrl(source ra : RiskAssessment, target entry) { ra.id as id -> entry.fullUrl = append('https://fhir.slade360.co.ke/fhir/RiskAssessment/', id) "assignFullUrl"; } // ── Provenance and tenancy ─────────────────────────────────────────────────── // Two tags on everything this map creates. The first says which questionnaire // produced it, so a screen recorded at one visit can be told from the screen // recorded at the next and from every other form that codes the same findings. // 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 = 'paediatric-tb-icf-screening' "provenanceCode"; qr -> t.display = 'Paediatric TB intensified case finding screening tool' "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"; }
{
"resourceType": "StructureMap",
"id": "ExtractPaediatricTbIcfScreening",
"text": {
"status": "generated",
"div": "<!-- snip (see above) -->"
},
"url": "https://fhir.slade360.co.ke/fhir/StructureMap/ExtractPaediatricTbIcfScreening",
"version": "0.1.0",
"name": "ExtractPaediatricTbIcfScreening",
"title": "Paediatric TB Intensified Case Finding Screening Extraction",
"status": "active",
"date": "2026-09-11T12:18:36+00:00",
"publisher": "Kathurima Kimathi",
"contact": [
{
"name": "Kathurima Kimathi",
"telecom": [
{
"system": "url",
"value": "https://www.linkedin.com/in/kathurima-kimathi/"
},
{
"system": "email",
"value": "kathurimakimathi415@gmail.com"
}
]
},
{
"name": "Oscar John",
"telecom": [
{
"system": "email",
"value": "oscarjohnotieno@gmail.com",
"use": "work"
}
]
},
{
"name": "Kennedy Omondi",
"telecom": [
{
"system": "email",
"value": "kennankole@gmail.com",
"use": "work"
}
]
}
],
"description": "The paediatric intensified case finding screen out as one Observation carrying the screen result, with the five questions as components, plus a RiskAssessment for the band that result falls in. The tool has no score: it is answered yes to any one question or no to all five, so the band is binary and the arithmetic is a disjunction rather than a sum. Both branches extract. A screen that came back negative is the record that the screen was done, which is what makes the guideline's `repeat screening on subsequent visits` a thing the notes can show, and it is what carries the IPT workup as its mitigation. The result is derived from the five answers rather than read off an outcome item the form does not have, so the questions and the conclusion cannot disagree.",
"structure": [
{
"url": "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse",
"mode": "source",
"alias": "QR"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/Bundle",
"mode": "target",
"alias": "Bundle"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/Observation",
"mode": "target",
"alias": "Observation"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/RiskAssessment",
"mode": "target",
"alias": "RiskAssessment"
}
],
"group": [
{
"name": "ExtractPaediatricTbIcfScreening",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "bundle",
"type": "Bundle",
"mode": "target"
}
],
"rule": [
{
"name": "setBundleType",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "bundle",
"element": "type",
"transform": "copy",
"parameter": [
{
"valueString": "transaction"
}
]
}
]
},
{
"name": "screenRule",
"source": [
{
"context": "qr",
"condition": "item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).exists()"
}
],
"target": [
{
"context": "bundle",
"element": "entry",
"variable": "entry"
},
{
"context": "entry",
"element": "resource",
"variable": "obs",
"transform": "create",
"parameter": [
{
"valueString": "Observation"
}
]
}
],
"rule": [
{
"name": "base",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs"
},
{
"context": "entry"
}
],
"dependent": [
{
"name": "BuildObsBase",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "obs"
},
{
"valueId": "entry"
}
]
}
]
},
{
"name": "category",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs"
}
],
"dependent": [
{
"name": "SetSurveyCategory",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "obs"
}
]
}
]
},
{
"name": "code",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs",
"element": "code",
"variable": "code"
}
],
"rule": [
{
"name": "codeCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "code",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "codeSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "codeCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen"
}
]
}
]
},
{
"name": "codeDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Paediatric TB intensified case finding screen"
}
]
}
]
}
]
},
{
"name": "codeText",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "code",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Paediatric TB intensified case finding screen"
}
]
}
]
}
],
"documentation": "Local, from concept-codesystem, because no code system this IG can resolve\r\nnames this screen. LOINC publishes no paediatric TB symptom screen -- only\r\nthe CPHS exposure-screen family, which is about exposure follow-up in a US\r\npublic-health programme -- and the ICD-10 and ICD-11 resources here are\r\n`content: fragment` stubs with no url, so an ICD code could not be\r\nvalidated against anything. The same code goes on the RiskAssessment: the\r\nresource type already says which of the two is the observation and which\r\nis the assessment, so a second code would only be a second thing to keep\r\nin step."
},
{
"name": "coughRule",
"source": [
{
"context": "qr",
"element": "item",
"variable": "it",
"condition": "(linkId = 'cough') and answer.value.exists()"
}
],
"target": [
{
"context": "obs",
"element": "component",
"variable": "comp"
}
],
"rule": [
{
"name": "coughCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp",
"element": "code",
"variable": "code"
}
],
"rule": [
{
"name": "codeCoding",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "codeSystem",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "codeCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen-cough"
}
]
}
]
},
{
"name": "codeDisplay",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Cough of any duration"
}
]
}
]
}
]
},
{
"name": "codeText",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Cough of any duration"
}
]
}
]
}
]
},
{
"name": "coughValue",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp"
}
],
"dependent": [
{
"name": "SetComponentCodedValue",
"parameter": [
{
"valueId": "it"
},
{
"valueId": "comp"
}
]
}
]
}
],
"documentation": "The five questions, each coded to the finding it asks about rather than to\r\none bucket code with the question in the display. Observation.component\r\n.code is what a query filters on, so `which children screened positive for\r\na TB contact` has to be a question the record can answer."
},
{
"name": "feverRule",
"source": [
{
"context": "qr",
"element": "item",
"variable": "it",
"condition": "(linkId = 'fever') and answer.value.exists()"
}
],
"target": [
{
"context": "obs",
"element": "component",
"variable": "comp"
}
],
"rule": [
{
"name": "feverCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp",
"element": "code",
"variable": "code"
}
],
"rule": [
{
"name": "codeCoding",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "codeSystem",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "codeCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen-fever"
}
]
}
]
},
{
"name": "codeDisplay",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Fever"
}
]
}
]
}
]
},
{
"name": "codeText",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Fever"
}
]
}
]
}
]
},
{
"name": "feverValue",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp"
}
],
"dependent": [
{
"name": "SetComponentCodedValue",
"parameter": [
{
"valueId": "it"
},
{
"valueId": "comp"
}
]
}
]
}
]
},
{
"name": "thriveRule",
"source": [
{
"context": "qr",
"element": "item",
"variable": "it",
"condition": "(linkId = 'failure-to-thrive') and answer.value.exists()"
}
],
"target": [
{
"context": "obs",
"element": "component",
"variable": "comp"
}
],
"rule": [
{
"name": "thriveCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp",
"element": "code",
"variable": "code"
}
],
"rule": [
{
"name": "codeCoding",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "codeSystem",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "codeCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen-failure-to-thrive"
}
]
}
]
},
{
"name": "codeDisplay",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Failure to thrive or poor weight gain"
}
]
}
]
}
]
},
{
"name": "codeText",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Failure to thrive or poor weight gain"
}
]
}
]
}
]
},
{
"name": "thriveValue",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp"
}
],
"dependent": [
{
"name": "SetComponentCodedValue",
"parameter": [
{
"valueId": "it"
},
{
"valueId": "comp"
}
]
}
]
}
],
"documentation": "One item on the paper, and one component here. The tool asks failure to\r\nthrive *or* poor weight gain as a single question and the reviewer wrote\r\n`malnutrition` against it, so splitting it into two components would\r\ninvent a distinction the nurse was never asked to make. The question as\r\nprinted is what the code and its text both say."
},
{
"name": "lethargyRule",
"source": [
{
"context": "qr",
"element": "item",
"variable": "it",
"condition": "(linkId = 'lethargy') and answer.value.exists()"
}
],
"target": [
{
"context": "obs",
"element": "component",
"variable": "comp"
}
],
"rule": [
{
"name": "lethargyCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp",
"element": "code",
"variable": "code"
}
],
"rule": [
{
"name": "codeCoding",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "codeSystem",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "codeCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen-lethargy"
}
]
}
]
},
{
"name": "codeDisplay",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Lethargy, less playful than usual"
}
]
}
]
}
]
},
{
"name": "codeText",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Lethargy, less playful than usual"
}
]
}
]
}
]
},
{
"name": "lethargyValue",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp"
}
],
"dependent": [
{
"name": "SetComponentCodedValue",
"parameter": [
{
"valueId": "it"
},
{
"valueId": "comp"
}
]
}
]
}
]
},
{
"name": "contactRule",
"source": [
{
"context": "qr",
"element": "item",
"variable": "it",
"condition": "(linkId = 'tb-contact') and answer.value.exists()"
}
],
"target": [
{
"context": "obs",
"element": "component",
"variable": "comp"
}
],
"rule": [
{
"name": "contactCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp",
"element": "code",
"variable": "code"
}
],
"rule": [
{
"name": "codeCoding",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "codeSystem",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "codeCode",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen-contact"
}
]
}
]
},
{
"name": "codeDisplay",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Contact with a TB case"
}
]
}
]
}
]
},
{
"name": "codeText",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "code",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Contact with a TB case"
}
]
}
]
}
]
},
{
"name": "contactValue",
"source": [
{
"context": "it"
}
],
"target": [
{
"context": "comp"
}
],
"dependent": [
{
"name": "SetComponentCodedValue",
"parameter": [
{
"valueId": "it"
},
{
"valueId": "comp"
}
]
}
]
}
],
"documentation": "The one question of the five that is about exposure rather than about the\r\nchild. It stays a component of the same screen rather than becoming its\r\nown resource: what makes it meaningful is the four answers beside it."
},
{
"name": "outcome",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs"
}
],
"dependent": [
{
"name": "SetScreenOutcome",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "obs"
}
]
}
]
},
{
"name": "band",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs"
},
{
"context": "bundle"
}
],
"dependent": [
{
"name": "AddTbScreenRiskAssessment",
"parameter": [
{
"valueId": "obs"
},
{
"valueId": "qr"
},
{
"valueId": "bundle"
}
]
}
]
}
],
"documentation": "The paper is a flat five-row table, so the questions are top-level items and\r\nthe response itself is the anchor. One answered question is enough to make\r\nan Observation: a partly filled screen still records findings worth keeping.\r\nWhether it is enough to conclude anything is a separate question, and\r\nSetScreenOutcome is where that is decided."
}
]
},
{
"name": "SetScreenOutcome",
"documentation": "─────────────────────────────────────────────────────────────────────────────\r\nThe screen result\r\nThe tool states its own rule, in the two bullets printed under the table: yes\r\nto any one question means suspect TB, no to all of them means initiate workup\r\nfor IPT and screen again next visit. So the result is a disjunction over five\r\nyes/no answers, and there is nothing to sum.\r\n**Both branches are guarded on all five having been answered.** The positive\r\nbranch does not need to be for the yes to be true -- one yes is one yes -- but\r\nit does need to be for the *negative* branch to be safe, and the two have to\r\nagree about what a complete screen is or a form answered `no, no, yes` to the\r\nfirst three questions would extract a positive result while `no, no, no` to\r\nthe same three extracted nothing at all. A screen short of an item is not this\r\nscreen: four questions answered no says nothing about the fifth, and a clinician\r\nreading `no signs of TB` off a form where nobody asked about the TB contact is\r\nbeing told something the form does not know.\r\nThe answers are `Y` and `N`, from CodeSystem/v2-0532. The value set the form\r\nbinds is ValueSet/v2-0136, whose codes come from 0532 -- table 0136 was folded\r\ninto it -- so a guard testing 'yes' compiles, never fires, and silently\r\nextracts nothing.\r\nThe result reuses `special-clinic-code-system#presumed-tb` and `#no-tb-signs`,\r\nwhich this IG already defines for TB screening in the ANC and CWC workflows.\r\nNothing new was minted for it: a query for presumed TB should find this form\r\nand those together, and a second code saying the same thing in a different\r\nsystem would be a synonym to keep in step rather than information.\r\n─────────────────────────────────────────────────────────────────────────────",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "obs",
"type": "Observation",
"mode": "target"
}
],
"rule": [
{
"name": "screenPositive",
"source": [
{
"context": "qr",
"condition": "(item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).count() = 5) and item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).where(code = 'Y').exists()"
}
],
"target": [
{
"context": "obs",
"element": "value",
"variable": "cc",
"transform": "create",
"parameter": [
{
"valueString": "CodeableConcept"
}
]
}
],
"rule": [
{
"name": "localCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "cc",
"element": "coding",
"variable": "local"
}
],
"rule": [
{
"name": "localSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system"
}
]
}
]
},
{
"name": "localCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "presumed-tb"
}
]
}
]
},
{
"name": "localDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Presumed TB"
}
]
}
]
}
]
},
{
"name": "outcomeText",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "cc",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Presumed TB — at least one screening question answered yes"
}
]
}
]
}
]
},
{
"name": "screenNegative",
"source": [
{
"context": "qr",
"condition": "(item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).count() = 5) and item.where(linkId in ('cough' | 'fever' | 'failure-to-thrive' | 'lethargy' | 'tb-contact')).answer.value.ofType(Coding).where(code = 'Y').exists().not()"
}
],
"target": [
{
"context": "obs",
"element": "value",
"variable": "cc",
"transform": "create",
"parameter": [
{
"valueString": "CodeableConcept"
}
]
}
],
"rule": [
{
"name": "localCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "cc",
"element": "coding",
"variable": "local"
}
],
"rule": [
{
"name": "localSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system"
}
]
}
]
},
{
"name": "localCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "no-tb-signs"
}
]
}
]
},
{
"name": "localDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "No Signs of TB"
}
]
}
]
}
]
},
{
"name": "outcomeText",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "cc",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "No signs of TB — all five screening questions answered no"
}
]
}
]
}
]
}
]
},
{
"name": "AddTbScreenRiskAssessment",
"documentation": "─────────────────────────────────────────────────────────────────────────────\r\nThe band\r\nRead off the Observation the rule above just built, not recomputed from the\r\nanswers. Deriving it a second time would be a second implementation of the\r\nsame disjunction, and two implementations of one rule is one more than can be\r\nkept in agreement. It is also why a partly answered screen produces no\r\nRiskAssessment at all: no result, nothing to band.\r\n`qualitativeRisk` carries HL7 risk-probability -- which is what a consumer\r\nescalates on -- alongside the local screen-result code, and the plain wording\r\nin `.text`. `prediction.outcome` is the disease being predicted, which is the\r\nquestion a RiskAssessment exists to answer and is the same concept in both\r\nbranches; only the likelihood differs.\r\n`mitigation` is the guideline's own instruction, verbatim in substance. That\r\nelement is the right home for it: it is what to do to reduce the risk, it\r\ntravels with the assessment, and it means a screen recorded at a health centre\r\narrives at the next clinician with the action already attached.\r\nA negative screen bands `low`, not `negligible`. The tool does not clear a\r\nchild -- it sends them for IPT workup and asks for the screen again next visit\r\n-- so a code that reads as no further action would misstate it.\r\n─────────────────────────────────────────────────────────────────────────────",
"input": [
{
"name": "obs",
"type": "Observation",
"mode": "source"
},
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "bundle",
"type": "Bundle",
"mode": "target"
}
],
"rule": [
{
"name": "presumedTb",
"source": [
{
"context": "obs",
"condition": "value.ofType(CodeableConcept).coding.where(code = 'presumed-tb').exists()"
}
],
"target": [
{
"context": "bundle",
"element": "entry",
"variable": "entry"
},
{
"context": "entry",
"element": "resource",
"variable": "ra",
"transform": "create",
"parameter": [
{
"valueString": "RiskAssessment"
}
]
}
],
"rule": [
{
"name": "riskBase",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra"
},
{
"context": "entry"
}
],
"dependent": [
{
"name": "BuildRiskBase",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "obs"
},
{
"valueId": "ra"
},
{
"valueId": "entry"
}
]
}
]
},
{
"name": "riskCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra"
}
],
"dependent": [
{
"name": "SetTbScreenRiskCode",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "ra"
}
]
}
]
},
{
"name": "prediction",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra",
"element": "prediction",
"variable": "pred"
}
],
"rule": [
{
"name": "predictionOutcome",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "pred"
}
],
"dependent": [
{
"name": "SetTbPredictionOutcome",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "pred"
}
]
}
]
},
{
"name": "qualitativeRisk",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "pred",
"element": "qualitativeRisk",
"variable": "risk"
}
],
"rule": [
{
"name": "hl7Coding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "risk",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "riskSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "http://terminology.hl7.org/CodeSystem/risk-probability"
}
]
}
]
},
{
"name": "riskCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "high"
}
]
}
]
},
{
"name": "riskDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "High likelihood"
}
]
}
]
}
]
},
{
"name": "localCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "risk",
"element": "coding",
"variable": "local"
}
],
"rule": [
{
"name": "localSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system"
}
]
}
]
},
{
"name": "localCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "presumed-tb"
}
]
}
]
},
{
"name": "localDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Presumed TB"
}
]
}
]
}
]
},
{
"name": "riskText",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "risk",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Presumed TB"
}
]
}
]
}
]
},
{
"name": "rationale",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "pred",
"element": "rationale",
"transform": "copy",
"parameter": [
{
"valueString": "At least one of the five intensified case finding questions was answered yes."
}
]
}
]
}
]
},
{
"name": "mitigation",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra",
"element": "mitigation",
"transform": "copy",
"parameter": [
{
"valueString": "Suspect TB. Examine the child and use the paediatric TB diagnostic algorithm to evaluate for active disease. Rule out underlying conditions and refer if necessary."
}
]
}
]
}
]
},
{
"name": "noTbSigns",
"source": [
{
"context": "obs",
"condition": "value.ofType(CodeableConcept).coding.where(code = 'no-tb-signs').exists()"
}
],
"target": [
{
"context": "bundle",
"element": "entry",
"variable": "entry"
},
{
"context": "entry",
"element": "resource",
"variable": "ra",
"transform": "create",
"parameter": [
{
"valueString": "RiskAssessment"
}
]
}
],
"rule": [
{
"name": "riskBase",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra"
},
{
"context": "entry"
}
],
"dependent": [
{
"name": "BuildRiskBase",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "obs"
},
{
"valueId": "ra"
},
{
"valueId": "entry"
}
]
}
]
},
{
"name": "riskCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra"
}
],
"dependent": [
{
"name": "SetTbScreenRiskCode",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "ra"
}
]
}
]
},
{
"name": "prediction",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra",
"element": "prediction",
"variable": "pred"
}
],
"rule": [
{
"name": "predictionOutcome",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "pred"
}
],
"dependent": [
{
"name": "SetTbPredictionOutcome",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "pred"
}
]
}
]
},
{
"name": "qualitativeRisk",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "pred",
"element": "qualitativeRisk",
"variable": "risk"
}
],
"rule": [
{
"name": "hl7Coding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "risk",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "riskSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "http://terminology.hl7.org/CodeSystem/risk-probability"
}
]
}
]
},
{
"name": "riskCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "low"
}
]
}
]
},
{
"name": "riskDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Low likelihood"
}
]
}
]
}
]
},
{
"name": "localCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "risk",
"element": "coding",
"variable": "local"
}
],
"rule": [
{
"name": "localSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/special-clinic-code-system"
}
]
}
]
},
{
"name": "localCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "no-tb-signs"
}
]
}
]
},
{
"name": "localDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "local",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "No Signs of TB"
}
]
}
]
}
]
},
{
"name": "riskText",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "risk",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "No signs of TB"
}
]
}
]
}
]
},
{
"name": "rationale",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "pred",
"element": "rationale",
"transform": "copy",
"parameter": [
{
"valueString": "All five intensified case finding questions were answered no."
}
]
}
]
}
]
},
{
"name": "mitigation",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra",
"element": "mitigation",
"transform": "copy",
"parameter": [
{
"valueString": "Initiate workup for isoniazid preventive therapy and repeat this screen at subsequent visits."
}
]
}
]
}
]
}
]
},
{
"name": "SetTbScreenRiskCode",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "ra",
"type": "RiskAssessment",
"mode": "target"
}
],
"rule": [
{
"name": "code",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra",
"element": "code",
"variable": "code"
}
],
"rule": [
{
"name": "codeCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "code",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "codeSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "codeCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen"
}
]
}
]
},
{
"name": "codeDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Paediatric TB intensified case finding screen"
}
]
}
]
}
]
},
{
"name": "codeText",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "code",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueString": "Paediatric TB intensified case finding screen"
}
]
}
]
}
]
}
]
},
{
"name": "SetTbPredictionOutcome",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "pred",
"mode": "target"
}
],
"rule": [
{
"name": "predictionOutcome",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "pred",
"element": "outcome",
"variable": "outcome"
}
],
"rule": [
{
"name": "outcomeCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "outcome",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "outcomeSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/CodeSystem/concept-codesystem"
}
]
}
]
},
{
"name": "outcomeCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "tb-screen-tuberculosis"
}
]
}
]
},
{
"name": "outcomeDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Tuberculosis"
}
]
}
]
}
]
}
]
}
]
},
{
"name": "BuildObsBase",
"documentation": "─────────────────────────────────────────────────────────────────────────────\r\nShared scaffolding. Repeated in each map rather than imported: the transform\r\nengine resolves `imports` against whatever StructureMaps happen to be on the\r\nserver, and a form that stops extracting because a shared map was not\r\nuploaded is a worse failure than a duplicated group.\r\nTwo notes on what the publisher's StructureMap validator says about this file.\r\nSource parameters below are left untyped on purpose. Writing `: Element` makes\r\nthe validator compare QuestionnaireResponse.item against the literal 'Element'\r\nand report them as incompatible with each other, which is noise, not a finding.\r\n`evaluate(context, expression)` is flagged as \"takes 1 parameter but 2 were\r\nfound\". Leave it. The FML parser in the same jar refuses the one-argument form\r\noutright -- it wants a parameter, a comma, then the FHIRPath -- so the\r\npublisher is objecting to what its own compiler emits. Two arguments is the\r\nonly form that compiles, and it is what the R5 transform engine executes.\r\n─────────────────────────────────────────────────────────────────────────────",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "obs",
"type": "Observation",
"mode": "target"
},
{
"name": "entry",
"mode": "target"
}
],
"rule": [
{
"name": "responseProvenance",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs"
}
],
"dependent": [
{
"name": "SetResponseProvenance",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "obs"
}
]
}
]
},
{
"name": "idAndFullUrl",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs",
"element": "id",
"transform": "uuid"
}
],
"dependent": [
{
"name": "SetObservationFullUrl",
"parameter": [
{
"valueId": "obs"
},
{
"valueId": "entry"
}
]
}
]
},
{
"name": "entryRequest",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "entry",
"element": "request",
"variable": "request"
}
],
"rule": [
{
"name": "requestMethod",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "request",
"element": "method",
"transform": "copy",
"parameter": [
{
"valueString": "POST"
}
]
}
]
},
{
"name": "requestUrl",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "request",
"element": "url",
"transform": "copy",
"parameter": [
{
"valueString": "Observation"
}
]
}
]
}
]
},
{
"name": "status",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs",
"element": "status",
"transform": "copy",
"parameter": [
{
"valueString": "final"
}
]
}
]
},
{
"name": "subject",
"source": [
{
"context": "qr",
"element": "subject",
"variable": "s"
}
],
"target": [
{
"context": "obs",
"element": "subject",
"transform": "copy",
"parameter": [
{
"valueId": "s"
}
]
}
]
},
{
"name": "encounter",
"source": [
{
"context": "qr",
"element": "encounter",
"variable": "e"
}
],
"target": [
{
"context": "obs",
"element": "encounter",
"transform": "copy",
"parameter": [
{
"valueId": "e"
}
]
}
]
},
{
"name": "effective",
"source": [
{
"context": "qr",
"element": "authored",
"variable": "t"
}
],
"target": [
{
"context": "obs",
"element": "effective",
"transform": "copy",
"parameter": [
{
"valueId": "t"
}
]
}
]
},
{
"name": "performerFromSource",
"source": [
{
"context": "qr",
"element": "source",
"variable": "src"
}
],
"target": [
{
"context": "obs",
"element": "performer",
"transform": "copy",
"parameter": [
{
"valueId": "src"
}
]
}
]
},
{
"name": "performerFallback",
"source": [
{
"context": "qr",
"condition": "source.exists().not()"
}
],
"rule": [
{
"name": "performerFromAuthor",
"source": [
{
"context": "qr",
"element": "author",
"variable": "a"
}
],
"target": [
{
"context": "obs",
"element": "performer",
"transform": "copy",
"parameter": [
{
"valueId": "a"
}
]
}
]
}
]
},
{
"name": "linkQuestionnaireResponse",
"source": [
{
"context": "qr",
"element": "id",
"variable": "qid",
"condition": "$this.hasValue()"
}
],
"target": [
{
"context": "obs",
"element": "derivedFrom",
"variable": "ref",
"transform": "create",
"parameter": [
{
"valueString": "Reference"
}
]
},
{
"context": "ref",
"element": "reference",
"transform": "copy",
"parameter": [
{
"valueId": "qid"
}
]
}
],
"documentation": "A stored response's id already reads \"QuestionnaireResponse/<id>\", so it is\r\na relative reference as it stands. The hasValue() guard is what stops an\r\nunsaved response -- one posted straight to $extract rather than saved first\r\n-- from writing \"QuestionnaireResponse/null\" into the record."
}
]
},
{
"name": "BuildRiskBase",
"documentation": "The RiskAssessment stands on two things: the response that was filled in and\r\nthe Observation that holds the result. Both go in basis, which is what the\r\nSGHI RiskAssessment profile binds to a QuestionnaireResponse.",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "obs",
"type": "Observation",
"mode": "source"
},
{
"name": "ra",
"type": "RiskAssessment",
"mode": "target"
},
{
"name": "entry",
"mode": "target"
}
],
"rule": [
{
"name": "responseProvenance",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra"
}
],
"dependent": [
{
"name": "SetResponseProvenance",
"parameter": [
{
"valueId": "qr"
},
{
"valueId": "ra"
}
]
}
]
},
{
"name": "idAndFullUrl",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra",
"element": "id",
"transform": "uuid"
}
],
"dependent": [
{
"name": "SetRiskAssessmentFullUrl",
"parameter": [
{
"valueId": "ra"
},
{
"valueId": "entry"
}
]
}
]
},
{
"name": "entryRequest",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "entry",
"element": "request",
"variable": "request"
}
],
"rule": [
{
"name": "requestMethod",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "request",
"element": "method",
"transform": "copy",
"parameter": [
{
"valueString": "POST"
}
]
}
]
},
{
"name": "requestUrl",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "request",
"element": "url",
"transform": "copy",
"parameter": [
{
"valueString": "RiskAssessment"
}
]
}
]
}
]
},
{
"name": "status",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "ra",
"element": "status",
"transform": "copy",
"parameter": [
{
"valueString": "final"
}
]
}
]
},
{
"name": "subject",
"source": [
{
"context": "qr",
"element": "subject",
"variable": "s"
}
],
"target": [
{
"context": "ra",
"element": "subject",
"transform": "copy",
"parameter": [
{
"valueId": "s"
}
]
}
]
},
{
"name": "encounter",
"source": [
{
"context": "qr",
"element": "encounter",
"variable": "e"
}
],
"target": [
{
"context": "ra",
"element": "encounter",
"transform": "copy",
"parameter": [
{
"valueId": "e"
}
]
}
]
},
{
"name": "occurrence",
"source": [
{
"context": "qr",
"element": "authored",
"variable": "t"
}
],
"target": [
{
"context": "ra",
"element": "occurrence",
"transform": "copy",
"parameter": [
{
"valueId": "t"
}
]
}
]
},
{
"name": "performerFromAuthor",
"source": [
{
"context": "qr",
"element": "author",
"variable": "a"
}
],
"target": [
{
"context": "ra",
"element": "performer",
"transform": "copy",
"parameter": [
{
"valueId": "a"
}
]
}
]
},
{
"name": "performerFallback",
"source": [
{
"context": "qr",
"condition": "author.exists().not()"
}
],
"rule": [
{
"name": "performerFromSource",
"source": [
{
"context": "qr",
"element": "source",
"variable": "src"
}
],
"target": [
{
"context": "ra",
"element": "performer",
"transform": "copy",
"parameter": [
{
"valueId": "src"
}
]
}
]
}
]
},
{
"name": "basisResponse",
"source": [
{
"context": "qr",
"element": "id",
"variable": "qid",
"condition": "$this.hasValue()"
}
],
"target": [
{
"context": "ra",
"element": "basis",
"variable": "qref",
"transform": "create",
"parameter": [
{
"valueString": "Reference"
}
]
},
{
"context": "qref",
"element": "reference",
"transform": "copy",
"parameter": [
{
"valueId": "qid"
}
]
}
]
},
{
"name": "basisObservation",
"source": [
{
"context": "obs",
"element": "id",
"variable": "oid"
}
],
"target": [
{
"context": "ra",
"element": "basis",
"variable": "oref",
"transform": "create",
"parameter": [
{
"valueString": "Reference"
}
]
}
],
"rule": [
{
"name": "basisObservationRef",
"source": [
{
"context": "oid"
}
],
"target": [
{
"context": "oref",
"element": "reference",
"transform": "append",
"parameter": [
{
"valueString": "Observation/"
},
{
"valueId": "oid"
}
]
}
]
}
]
}
]
},
{
"name": "SetComponentCodedValue",
"documentation": "The two answer shapes a component can carry. Both read the answer through the\r\nuntyped `.value` accessor: reading `valueCoding` or `valueQuantity` by its\r\ntyped property name fails at transform time on HAPI R5 with \"Attempt to read\r\ninvalid property ... on QuestionnaireResponse.item.answer\".",
"input": [
{
"name": "it",
"mode": "source"
},
{
"name": "comp",
"mode": "target"
}
],
"rule": [
{
"name": "componentAnswer",
"source": [
{
"context": "it",
"element": "answer",
"listMode": "first",
"variable": "a"
}
],
"rule": [
{
"name": "codedValue",
"source": [
{
"context": "a",
"element": "value",
"variable": "cv"
}
],
"target": [
{
"context": "comp",
"element": "value",
"variable": "cc",
"transform": "create",
"parameter": [
{
"valueString": "CodeableConcept"
}
]
}
],
"rule": [
{
"name": "valueCoding",
"source": [
{
"context": "cv"
}
],
"target": [
{
"context": "cc",
"element": "coding",
"transform": "copy",
"parameter": [
{
"valueId": "cv"
}
]
}
]
},
{
"name": "valueText",
"source": [
{
"context": "cv",
"element": "display",
"variable": "d"
}
],
"target": [
{
"context": "cc",
"element": "text",
"transform": "copy",
"parameter": [
{
"valueId": "d"
}
]
}
]
}
]
}
]
}
]
},
{
"name": "SetSurveyCategory",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "obs",
"type": "Observation",
"mode": "target"
}
],
"rule": [
{
"name": "category",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "obs",
"element": "category",
"variable": "cat"
}
],
"rule": [
{
"name": "categoryCoding",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "cat",
"element": "coding",
"variable": "coding"
}
],
"rule": [
{
"name": "categorySystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "http://terminology.hl7.org/CodeSystem/observation-category"
}
]
}
]
},
{
"name": "categoryCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "survey"
}
]
}
]
},
{
"name": "categoryDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "coding",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Survey"
}
]
}
]
}
]
}
]
}
]
},
{
"name": "SetObservationFullUrl",
"input": [
{
"name": "obs",
"type": "Observation",
"mode": "source"
},
{
"name": "entry",
"mode": "target"
}
],
"rule": [
{
"name": "assignFullUrl",
"source": [
{
"context": "obs",
"element": "id",
"variable": "id"
}
],
"target": [
{
"context": "entry",
"element": "fullUrl",
"transform": "append",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/Observation/"
},
{
"valueId": "id"
}
]
}
]
}
]
},
{
"name": "SetRiskAssessmentFullUrl",
"input": [
{
"name": "ra",
"type": "RiskAssessment",
"mode": "source"
},
{
"name": "entry",
"mode": "target"
}
],
"rule": [
{
"name": "assignFullUrl",
"source": [
{
"context": "ra",
"element": "id",
"variable": "id"
}
],
"target": [
{
"context": "entry",
"element": "fullUrl",
"transform": "append",
"parameter": [
{
"valueString": "https://fhir.slade360.co.ke/fhir/RiskAssessment/"
},
{
"valueId": "id"
}
]
}
]
}
]
},
{
"name": "SetResponseProvenance",
"documentation": "── Provenance and tenancy ───────────────────────────────────────────────────\r\nTwo tags on everything this map creates. The first says which questionnaire\r\nproduced it, so a screen recorded at one visit can be told from the screen\r\nrecorded at the next and from every other form that codes the same findings.\r\nThe second copies the tenant tags off the response, because nothing else on\r\nthe path puts them there.\r\nThe code is the form's document type, not the questionnaire's id: every\r\nenvironment mints its own ids, and there is only one set of maps, so an id\r\nhere would tie the tag to whichever environment stamped it.\r\n── Provenance and tenancy ───────────────────────────────────────────────────",
"input": [
{
"name": "qr",
"type": "QR",
"mode": "source"
},
{
"name": "r",
"mode": "target"
}
],
"rule": [
{
"name": "provenanceMeta",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "r",
"element": "meta",
"variable": "m"
}
],
"rule": [
{
"name": "provenanceTag",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "m",
"element": "tag",
"variable": "t"
}
],
"rule": [
{
"name": "provenanceSystem",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "t",
"element": "system",
"transform": "copy",
"parameter": [
{
"valueString": "http://slade360edi.com/questionnaire-provenance"
}
]
}
]
},
{
"name": "provenanceCode",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "t",
"element": "code",
"transform": "copy",
"parameter": [
{
"valueString": "paediatric-tb-icf-screening"
}
]
}
]
},
{
"name": "provenanceDisplay",
"source": [
{
"context": "qr"
}
],
"target": [
{
"context": "t",
"element": "display",
"transform": "copy",
"parameter": [
{
"valueString": "Paediatric TB intensified case finding screening tool"
}
]
}
]
}
]
}
]
},
{
"name": "tenantTags",
"source": [
{
"context": "qr",
"element": "meta",
"variable": "qm"
}
],
"rule": [
{
"name": "copyTag",
"source": [
{
"context": "qm",
"element": "tag",
"variable": "qt"
}
],
"target": [
{
"context": "r",
"element": "meta",
"variable": "m"
},
{
"context": "m",
"element": "tag",
"transform": "copy",
"parameter": [
{
"valueId": "qt"
}
]
}
]
}
],
"documentation": "Every tag the response carries, onto the resource. `meta` is 0..1, so this\r\nlands on the same meta the rule above created rather than a second one."
}
]
}
]
}