Healthy Route Implementation Guide
0.2.0 - ci-build
Healthy Route Implementation Guide, published by Savannah Informatics. This guide is not an authorized publication; it is the continuous build for version 0.2.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/savannahghi/healthy-route-ig/ and changes regularly. See the Directory of published versions
The forms a site fills in during the visit, the keys their questions use, and what each answer becomes on the store.
A Uganda site records the visit on six forms: the eligibility checklist, the interview (history), the medicines, the blood pressure readings (vitals), the laboratory results and the imaging. A Ghana site uses the same six and a socio-demographics form, following the data collection tool in Appendix B of the Ghana protocol, version 6.0. Each is a Questionnaire in this guide (listed on the artifacts page), carrying the guide's version like everything else in a release, and each names the StructureMap that turns a response to it into the resources this guide profiles. A study amendment names the release it was approved with, so forms, maps and terminology move together. The study service serves the forms to the screen, stores the response, asks its FHIR store to run the map, and commits what comes back in one transaction.
Each form is tagged with the identifier of the study it belongs to, HR-UG
for Uganda and HR-GH for Ghana, and the study service lists a study's forms
by that tag. Where the two protocols ask the same question the forms use the
same key, so one map serves both countries. Since version 0.2.0 both
countries' forms follow the 6 November 2026 review of the two protocols (below).
The urine dipstick is a laboratory result: it is on the laboratory form, not
required, and decides nothing, so neither eligibility checklist asks it, and
Ghana's no longer asks about an established CKD diagnosis. CKD is worked out
from the documented eGFR on the checklist and the eGFR the laboratory form
calculates.
Ghana's other forms ask what Appendix B asks and no more. Its socio-demographics
form asks the clinic, the ethnicity and the type of domicile; the tool's Study
ID, hospital number, date of birth, sex and contact are taken when the
participant is registered, and the address is left with the participant's
details rather than the study data. Its vitals form asks the three timed
readings only. The
Ghana protocol takes the average of all three readings where Uganda's takes
readings two and three, so Ghana's mean has its own key, bp-mean-all. The
time of retinal capture is asked once in Ghana, on the imaging form: a question
asked on two forms can be answered two ways, and the study service refuses to
sign a visit whose forms disagree.
The investigator signs a visit once its forms are saved. A form the protocol
leaves to the clinician's discretion is tagged optional from the
sign-off code system, and the study service signs
a visit without it; when it was saved, it is signed with the rest. In Uganda
the medications form is optional. In Ghana the socio-demographics form is
optional, so a visit is not held for it; the medications form is not, because
the Ghana protocol counts treatment towards hypertension.
Every leaf item's linkId is the code its answer becomes: a LOINC code where
LOINC has one with exactly the question's meaning (20454-5 for the urine
dipstick, 72166-2 for smoking status), and a code from the
Healthy Route measurement code system
otherwise (history-dm, retinal-capture-time). The maps find answers by these
keys and nothing else, so the map from question to resource is the identity
and nobody maintains a second table.
Inside a numbered or repeating group the key is the group's key, a dot, then
the code: the three blood pressure readings are bp-reading-1.8480-6,
bp-reading-2.8480-6 and bp-reading-3.8480-6, and a medicine is
medicine.code, medicine.route and so on. A result whose unit the
laboratory chooses has a sibling item for the unit, 2160-0.unit, and the map
picks the LOINC code from it: a creatinine reported in umol/L becomes
14682-9, one in mg/dL becomes 2160-0.
A map reads the response and nothing else, so everything a resource needs beyond the answers is on the response before the map runs: the tags naming the study, site, participant and visit, the tenant extension, the subject, the encounter, and the author. The maps copy them onto every resource.
A map never computes. A value worked out from other answers is either a calculated item on the form, described below, or the rule set's. A map writes the answers it was written for and nothing else, so a calculated item on a response does not become a resource until a map is written for it.
A value that needs only answers on the same form is an item on that form, read-only, carrying its formula as an SDC calculated expression in FHIRPath. The screen recalculates it whenever an answer it reads changes, so the person entering the answers sees the result as they go and can never type it, and the formula ships with the form, so a response names the version of the formula that gave its value.
| Form | Calculated item | Formula |
|---|---|---|
| Vitals | Mean blood pressure, bp-mean.8480-6 and bp-mean.8462-4, mm[Hg] |
the mean of readings two and three, which the Uganda protocol takes as the true blood pressure, whole mm[Hg] |
| Vitals, Ghana | Mean blood pressure, bp-mean-all.8480-6 and bp-mean-all.8462-4, mm[Hg] |
the mean of all three readings, as the Ghana protocol records it, whole mm[Hg] |
| Laboratory | creatinine-converted, mg/dL |
the creatinine as the equation uses it: as reported in mg/dL, divided by 88.4 from umol/L |
| Laboratory | eGFR, 98979-8, mL/min/1.73m2 |
CKD-EPI 2021 without the race coefficient, one decimal |
The eGFR needs the participant's age and sex, which no question on the form
asks. The form declares the participant and the visit as its launch context,
and two read-only items take them when the form opens: egfr-sex from the
participant's administrative gender, and egfr-age as the completed years on
the day the visit started, or on the day the form is opened when the screen
supplies no visit. The eGFR is calculated from those two items, so the age and
sex it used are on the response next to it. A sex other than female or male, a
creatinine in any other unit, or a missing input gives no eGFR rather than a
wrong one.
Each calculated item becomes an Observation when the response is extracted,
under the item's code like any answer, with derivedFrom naming the
Observations it was calculated from, so the calculation can be checked rather
than trusted. The mean blood pressure is a blood pressure Observation like the
readings, with no reading number, timed at reading three, and the readings it
averages in derivedFrom. The eGFR names the
converted creatinine and the age and sex it used, which are Observations of
their own. The derived laboratory values are the study's calculation rather
than the laboratory's, so the DiagnosticReport lists only what the laboratory
returned.
The values a reader looks at also carry a second category, derived from
the study's observation categories,
beside their FHIR one: the mean blood pressure and the eGFR. FHIR's categories have no code for a value that was worked out
rather than measured or asked, so this is how a reader asks for the derived
values alone, with one search on category. The converted creatinine and the
age and sex the eGFR uses are steps towards the eGFR, not results, and do not
carry it.
A value that needs answers from more than one form is not on any form. The hypertension flag reads the readings and the treatment, the diabetes flag the HbA1c and the history, and the CKD flag the laboratory eGFR; the rule set computes them across the visit. The HbA1c needs no conversion on the Uganda laboratory form, which records it in percent only.
The expressions read answers the way the Advantage formula builder writes them,
%resource.repeat(item).where(linkId='...').answer.valueDecimal, so a form can
be opened and edited there. In the repository, tests/run-calculations.js runs
every formula with the FHIRPath engine and version the Advantage renderer uses,
the way that renderer runs them: each example response must hold what its
answers calculate to, the eGFR must reproduce the platform scope's CKD-EPI 2021
test vectors, and a missing or unusable input must give no value.
Every resource a map writes takes the response's logical id followed by the
item's key, and the Bundle asks for it to be PUT. Inside the FHIR store a
stored response's id reads QuestionnaireResponse/<id>/_history/<n>, so the
maps strip it to the logical id first. Saving the same response again
produces the same ids, so the store replaces what the first save wrote rather
than duplicating it. The one exception is a medicine the catalogue does not
list, which has no code to derive an id from and takes a fresh one; the
service clears what the previous save of that response wrote before it
commits, by the response's tag.
| Form | Becomes |
|---|---|
| Eligibility | An Observation per criterion; the documented eGFR dated by the day it was documented. The outcome and the cohort are the rule set's. |
| Socio-demographics | An Observation per answer, category survey. An ethnicity answered other keeps the group the participant named as the value's text. |
| History | An Observation per answer, category survey; alcohol and smoking also social history. A Yes to diabetes, hypertension or CKD is also an HRCondition, unconfirmed, with the answer as its evidence. |
| Medications | No current medication is one Observation. Each medicine is an HRMedicationStatement against the catalogue code, with route and frequency on the study's scales; strength and form as notes. |
| Diet and activity (retired) | Served to no study since 0.2.0. Its map still turns a response recorded before then into an Observation per typed answer, category survey, and one per calculated item, also derived. |
| Vitals | Three HRBloodPressure readings, each timed by its own item, numbered, with the monitor and, on reading two, the position as extensions. The time of retinal capture as its own Observation. The mean blood pressure, also derived. Ghana's form gives the three readings and their mean only. |
| Laboratory | Two HRSpecimen resources dated by the sample item; an Observation per result in the reported unit, with the LOINC code chosen by the unit, and the dipstick under 20454-5 on the urine specimen; an HRDiagnosticReport gathering them. The converted creatinine, the age and sex the eGFR uses, and the eGFR, which alone is also derived. |
| Imaging | One HRImagingStudy for the pack, started at the time of retinal capture, a series per eye answered Yes, the reason as a note; the answers as Observations, category imaging. Ghana does not ask about dilation. |
The review of the Uganda and Ghana protocols applied the Uganda protocol v3.1's list of what the study does not collect to both countries, and removed a few questions neither protocol lists. Version 0.2.0 of this guide carries it.
The codes those questions used stay in their code systems, marked retired,
so answers recorded with them keep their meaning. The guide's version moved
from 0.1.0 to 0.2.0 because a saved response names its form's version: one
recorded against the 0.1.0 forms still says which questions it answered, and the
study service can tell it from a response to the forms in force now.
Each form has an example response under the guide's examples and, in the
repository's tests/expected, the Bundle its map must produce. Continuous
integration replays every example through the validator's R5 mapping engine
and fails when a Bundle differs. Change a map, regenerate the Bundle, read the
diff, commit both.
That replay is not enough on its own. The FHIR store runs the same maps on
HAPI's typed model, where a stored response's id carries its type and version,
a resource id only accepts an id value, and cc() looks up a code system the
store may not hold. None of that shows offline. So the maps strip the response
id to its logical part, cast every id they build, and spell out each
CodeableConcept, and tests/run-on-hapi.py runs every form through a real HAPI
R5 server carrying the SIL $extract provider: it loads the built package,
expands every value set, extracts, commits, saves again to prove the ids are
reused, and validates every committed resource against its profile. Run it
against the hapi-fhir-interceptors local stack, or a store kept for testing,
before a release.