DHIS2 fidelity: what the guide carries, what it is worth carrying, and what it drops on purpose¶
DHIS2 is not a generic form server. A handful of concepts are what make it DHIS2 - the category model, the organisation unit hierarchy, period types, the data set's reporting calendar, program rules, the tracked entity attribute machinery - and every one of them either survives into the generated implementation guide or does not. This page audits all of them, one at a time, and gives each a verdict.
The rule this page exists to enforce: nothing is dropped silently. A DHIS2 concept the guide does not carry is either written here with the reason, or it is a gap. There is no third state.
A reader who knows DHIS2 but not FHIR should be able to follow this. Where a FHIR element is named, its cardinality is stated, because in several of these decisions the cardinality is the argument.
1. Method¶
The inventory was built from the platform outward rather than from the guide outward, so the audit could not be blinded by what the generator already happens to emit.
- What DHIS2 says about itself. The exhaustive field-level truth is the generated
OpenAPI tree at
packages/dhis2w-client/src/dhis2w_client/generated/v43/oas/- one module per DHIS2 schema, every field DHIS2 puts on the wire. Every concept below was read off that tree first, before any guide code was opened. - What the guide carries. The carrier inventory is the emitter itself: the
extensions declared in
packages/dhis2w-fhir/src/dhis2w_fhir/foundation/schemas.py, the profiles infoundation/templates/d2-responses.fsh.jinja, the CodeSystem properties in eachresources/*/target, and - decisively - thefields=projections at the top ofpackages/dhis2w-fhir/src/dhis2w_fhir/service.py. A DHIS2 field the projection does not ask for cannot reach any carrier, so those constants are the shortest honest answer to "is it carried". - What the instance does. Every claim about behaviour was checked against the local
DHIS2 at
localhost:8080, reported as 2.43.1 (revision 9cbfbf3). Two of those checks changed a verdict and are stated in full below. Both writes were made in the endpoint's own validate-only mode, so the audit committed nothing.
Three verdicts, and each row carries exactly one.
| Verdict | Meaning |
|---|---|
CARRIED |
The guide expresses it today. The row names the carrier. |
WORTH CARRYING |
The guide does not express it and should. The row names the proposed carrier and the consumer that wants it. |
DELIBERATELY NOT |
The guide does not express it and should not. The row states the reason. |
2. What the audit found¶
Counts. 135 rows across 13 domains: 40 CARRIED, 49 WORTH CARRYING, 46
DELIBERATELY NOT. A concept that DHIS2 puts on more than one object - expiryDays on
both a data set and a programme, periodType on both a data set and a stage - is one row
per object, because the carrier and the consumer differ. The ranked list in section 4
condenses the 49 into 34 items, since several rows share one proposed extension.
The one asymmetry worth knowing before reading anything else. DHIS2 enforces program rules server-side on import, and does not enforce validation rules server-side on import. Both were tested on the live instance:
# Tracker leg: the instance REFUSES a value its program rule forbids.
curl -u admin:district -X POST \
'http://localhost:8080/api/tracker?async=false&importMode=VALIDATE' \
-H 'Content-Type: application/json' \
-d '{"events":[{"program":"lxAQ7Zs9VYR","programStage":"dBwrot7S420",
"orgUnit":"y77LiPqLMoq","occurredAt":"2026-06-15","status":"COMPLETED",
"dataValues":[{"dataElement":"vANAXwtLwcT","value":"150"}]}]}'
# {"status":"ERROR", ... "errorCode":"E1300",
# "message":"Generated by ProgramRule (`dahuKlP7jR2`) -
# `The hemoglobin value cannot be above 99 (vANAXwtLwcT)`."}
# Aggregate leg: the instance ACCEPTS values both its validation rules forbid.
curl -u admin:district -X POST \
'http://localhost:8080/api/dataValueSets?dryRun=true' \
-H 'Content-Type: application/json' \
-d '{"dataSet":"BfMAe6Itzgt","period":"202606","orgUnit":"y77LiPqLMoq","dataValues":[
{"dataElement":"s46m5MS0hxu","categoryOptionCombo":"Prlt0C1RF0s","value":"0"},
{"dataElement":"s46m5MS0hxu","categoryOptionCombo":"V6L425pT3A0","value":"99"}]}'
# {"status":"SUCCESS","importCount":{"updated":2,"ignored":0},"conflicts":[]}
# - violating "BCG doses <1y must be positive" (VrBCGPos001, greater_than 0)
# and "BCG doses <1y must equal BCG doses >1y" (VrBCGInf001, equal_to).
That single difference decides two rows that would otherwise look identical. Program
rules are a WORTH CARRYING item with a consumer that exists today, because
d2w fhir forward can spool a response DHIS2 will refuse. Validation rules are a
DELIBERATELY NOT, because a form that omits them still produces data the aggregate
import accepts - DHIS2 runs them on demand, not on import.
The four things the guide half-carries. These are not gaps of omission; they are places where the guide carries one side of a concept and quietly loses the other. They are the most important findings on this page, because a half-carry reads as complete.
- The attribute option combo rides the aggregate leg only.
Program.categoryCombois never read (_PROGRAM_FIELDS,packages/dhis2w-fhir/src/dhis2w_fhir/service.py:246), so an event or tracker program riding a non-default attribute combo forwards its events with DHIS2's default combo and misattributes them silently. The whole machinery - the extension pair, the CodeSystem pair, the ConceptMap, the resolution tiers in the forwarder - already exists and is bound toQuestionnaireResponseprofiles the event kinds do not slice. - An organisation unit's opening date is read and then discarded.
_ORGANISATION_UNIT_FIELDSasks foropeningDateandclosedDate; the projection keeps onlyclosed: bool, computed as "hasclosedDatepassed" (_is_closed,service.py:3510).Organization.activeandLocation.statuscarry the boolean. The dates themselves - the only way to ask which facilities existed in a past reporting period - do not survive. One case where the guide silently substitutes a different artifact. A data set withformType: CUSTOMis rendered in DHIS2 by hand-written HTML keyed on data element operands. The guide reads neitherformTypenordataEntryFormand publishes a default-shapedQuestionnairein its place. On the live instance theChild Healthdata set isCUSTOM. The published form is not wrong - it is a faithful list of the data set's elements - but it is not the form the data set has, and the guide does not say so.
3. The fidelity matrix¶
3.1 The category model¶
DHIS2's disaggregation model is the concept least like anything in FHIR and the one the guide has invested most in.
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
| Category | One axis of disaggregation | CARRIED |
D2CAT_<stem>_CS / _VS per category, plus a ConceptMap to <base>/id/category-option and its -code sibling |
capture, forward |
| Category option | One value on that axis | CARRIED |
A concept in its category's CodeSystem, with dhis2-code / dhis2-id properties carrying the complementary DHIS2 identifier |
capture, forward |
| Category combo | The cross-product a data element or data set is disaggregated by | CARRIED |
Expressed as decomposition: each option combo concept carries one category-<slug> property of type Coding per axis |
capture |
| Category option combo | One cell of that cross-product | CARRIED |
D2COC_CS concepts; one child Questionnaire.item per combo, linkId = <dataElementUid>.<categoryOptionComboUid> |
capture, forward |
| Attribute option combo | The third key of every aggregate value, beside organisation unit and period | CARRIED |
D2AttributeOptionCombo (Coding 1..1) on the response, D2AttributeOptionCombos (canonical 1..1) on the form, D2AOC_<stem>_CS / _VS per distinct non-default combo, ConceptMap to both identifier systems; the forwarder writes DataValueSet.attributeOptionCombo |
capture, forward |
Category option startDate / endDate |
The window in which an option may be reported against - how DHIS2 models a funding cycle or a partner's contract | WORTH CARRYING |
Two dateTime properties, valid-from / valid-to, on the category CodeSystem beside dhis2-code, assigned per concept |
capture, forward |
Category option organisationUnits |
The places an option may be reported for - how DHIS2 scopes an implementing partner to its districts | WORTH CARRYING |
A restricted boolean property on the concept, with resolution left to the instance; publishing the membership itself would dwarf the guide |
capture, forward |
Category option combo ignoreApproval |
Exempts a combo from the approval workflow | DELIBERATELY NOT |
Approval is instance governance layered on captured data; the capture contract does not own it | - |
CategoryCombo.skipTotal |
Suppresses the total column in analytics | DELIBERATELY NOT |
Analytics presentation. A Questionnaire has no total column to suppress |
- |
Category.dataDimensionType (DISAGGREGATION / ATTRIBUTE) |
Which of the two roles a category plays | CARRIED |
Implicit and complete: a disaggregation category reaches D2COC_CS, an attribute category reaches D2AOC_*_CS. The label itself is redundant once the artifact says which |
capture |
| Category option groups / group sets | A second grouping over category options, for analytics | DELIBERATELY NOT |
Analytics dimension. Nothing on the capture path consults it | - |
Category.dataDimension, allItems, dimension, filter |
Analytics query surface on the category object | DELIBERATELY NOT |
Analytics query vocabulary, not a fact about the form | - |
Live check. GET /api/categoryOptions on 2.43.1 returns 15 options, of which four are
restricted to organisation units (1166 each) and one - i4Nbp8S2G6A, "Improve access to
clean water" - carries startDate: 2016-04-01. These are the implementing-partner
options of the attribute category dimension, which is precisely the case the guide
publishes an AOC CodeSystem for. The guide publishes the option and drops both of the
facts that say when and where it may be used.
3.2 Organisation units¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
| Hierarchy | Every place is a node under exactly one parent | CARRIED |
Organization.partOf and Location.partOf, both constrained to the D2 profile; a parent code property on D2OU_CS |
capture, output leg |
| Level | Depth in the hierarchy, named by the instance | CARRIED |
D2OrganisationUnitLevel (Coding 1..1) on Location, D2OU_Level_CS / _VS, and Organization.type bound to the same ValueSet; the concept displays are the instance's own organisationUnitLevels names, with the depth as the fall-back where it names none |
capture, output leg |
path |
The materialised ancestor chain | CARRIED |
Implicitly, through partOf and the parent property; the emitter orders the registry by path so a regenerate is byte-stable |
- |
| Geometry | A point or a boundary polygon | CARRIED |
Location.position (point, or the area-weighted centroid of a polygon) plus location-boundary-geojson carrying the full Feature |
output leg, harmonization |
| Contact person, email, phone number | Who to reach at a facility | CARRIED |
Organization.contact.name (HumanName.text) and Organization.telecom (phone, email) |
output leg |
address, url |
Street address and the facility's own web address | WORTH CARRYING |
Organization.address (Address.text) and a third Organization.telecom of system url; both are read-free additions to a projection that already asks for the neighbouring fields |
output leg |
closedDate |
The date a facility stopped operating | CARRIED |
Collapsed to Organization.active / Location.status; the date is lost - see openingDate |
capture |
openingDate |
The date a facility started operating | WORTH CARRYING |
A D2OrganisationUnitPeriod extension (Period 1..1) on Organization and Location, start from openingDate and end from closedDate. R4 gives Organization no period element, so an extension is the only carrier |
output leg, harmonization |
| Organisation unit groups and group sets | Classification orthogonal to the hierarchy - facility type, ownership, urban or rural | WORTH CARRYING |
Additional Organization.type codings from OUG / OUGS CodeSystems; the naming tokens are already reserved and the item is already queued in the roadmap |
capture, output leg, harmonization |
Group set compulsory |
Every organisation unit must be in exactly one group of this set | WORTH CARRYING |
A compulsory boolean property on the group set CodeSystem; it is the difference between a classification and a required one |
output leg |
comment |
Free text on the unit | DELIBERATELY NOT |
The description field already reaches the narrative page; a second free-text field with no distinct meaning earns no carrier |
- |
image |
A photo of the facility | DELIBERATELY NOT |
A file resource, not a semantic fact; carrying it would pull binaries into a text guide | - |
type, memberCount, leaf |
Derived or free-text conveniences DHIS2 computes | DELIBERATELY NOT |
Derived from the hierarchy the guide already publishes | - |
Organisation unit level offlineLevels |
How deep the DHIS2 apps cache | DELIBERATELY NOT |
A client caching hint for the DHIS2 apps specifically | - |
Live check. The instance holds four group sets - Area, Facility Ownership,
Facility Type, Location Rural/Urban - three of them compulsory: true. Every
organisation unit in the registry therefore carries three classifications the guide does
not publish.
3.3 Periods¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
| The reported period | Which stretch of time a response reports for | CARRIED |
D2Period on the response: iso (string 1..1), type (code 1..1, required-bound to D2PeriodType_VS), period (Period 0..1) |
capture, forward |
| Period types | The 21 named calendars DHIS2 reports on, financial years included | CARRIED |
D2PeriodType_CS / _VS, with the ISO format and span of each documented on the generated Periods page |
capture, forward |
| A form's own period type | Which of those 21 calendars this data set reports on | CARRIED |
The d2-period-type extension (code 1..1, required-bound to D2PeriodType_VS) on every aggregate Questionnaire; d2w fhir serve reads it for $generate and capture grading |
capture, forward, serve |
dataInputPeriods |
Per-period open and close windows, overriding the general calendar | WORTH CARRYING |
Rows on the data-entry-window extension below, one Period per entry |
capture |
| Relative periods | LAST_12_MONTHS, THIS_FINANCIAL_YEAR, and 42 more |
DELIBERATELY NOT |
An analytics query vocabulary. A captured response always names one absolute ISO period; a relative period is a question, not a fact | - |
3.4 Data set semantics¶
The reporting calendar is where DHIS2's operational culture lives, and it is the domain the guide currently carries least of.
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
periodType |
See 3.3 | WORTH CARRYING |
D2PeriodType on the Questionnaire |
capture, forward, serve |
compulsoryDataElementOperands |
Which cells must be filled, at either of two grains | CARRIED |
item.required, set at both grains: an operand naming a data element alone marks every disaggregated child, an operand naming an option combo marks that one child |
capture |
| Sections | The groups the data-entry form is laid out in | CARRIED |
Questionnaire.item of type #group, in the section's own order; a section holding a disaggregated question gets questionnaire-itemControl #gtable |
capture |
Section greyedFields |
Cells the data-entry form will not let anyone fill | WORTH CARRYING |
Omit the child item entirely and raise a generate note. A greyed cell is not a cell with a constraint - it is a cell DHIS2's form does not have |
capture, forward |
timelyDays |
How many days after a period closes a report still counts as on time - the 15th-of-the-month reporting culture, as a number | WORTH CARRYING |
A D2DataEntryWindow complex extension on Questionnaire: timelyDays, expiryDays, openFuturePeriods, openPeriodsAfterCoEndDate, all integer 0..1 |
capture |
expiryDays |
How long after a period closes the form stays open | WORTH CARRYING |
The same extension | capture |
openFuturePeriods |
How many periods ahead may be reported | WORTH CARRYING |
The same extension | capture |
openPeriodsAfterCoEndDate |
How long a period stays open past a category option's end date | WORTH CARRYING |
The same extension; it is the enforcement half of the category option window in 3.1 | capture |
formType (DEFAULT / SECTION / CUSTOM) |
Which of three renderers DHIS2 uses | WORTH CARRYING |
Not as a carrier - as a generate note. A CUSTOM data set gets a published form that is not the form it has, and the run should say so |
capture |
dataEntryForm |
The hand-written HTML of a CUSTOM form |
DELIBERATELY NOT |
It is DHIS2-app HTML with input ids keyed on operands. A Questionnaire is a different rendering contract, and transliterating one into the other would produce a form faithful to neither |
- |
validCompleteOnly, compulsoryFieldsCompleteOnly, noValueRequiresComment |
Rules that fire when a period is marked complete | WORTH CARRYING |
Fold into the completeness decision (roadmap 5.4); they have no meaning until completeness has a carrier | capture |
| Completeness registration | Whether a period has been declared complete, by whom, and when | WORTH CARRYING |
Open in the roadmap as decision 5.4. Restated here so the audit does not read as if it were settled | forward |
workflow (data approval) |
The approval chain a data set's values climb | DELIBERATELY NOT |
Approval is an instance governance process over data already captured. A capture facade that mirrored it would be a second, stale authority | - |
renderAsTabs, renderHorizontally, mobile, skipOffline, dataElementDecoration, displayOptions |
How the DHIS2 apps draw and cache the form | DELIBERATELY NOT |
Rendering and caching hints for specific DHIS2 clients | - |
version |
Bumped when the form changes | DELIBERATELY NOT |
The guide's own Questionnaire.version and the IG's version are the guide's versioning story |
- |
Section showRowTotals, showColumnTotals, disableDataElementAutoGroup |
Layout switches in the DHIS2 data-entry app | DELIBERATELY NOT |
Rendering hints. A Questionnaire renderer computes no totals |
- |
Data set indicators, legendSets |
Derived values shown beside the form | DELIBERATELY NOT |
Derived, not captured; see 3.13 | - |
Live check. Of the two data sets on the instance, EPI Stock carries
timelyDays: 15 and Child Health carries openFuturePeriods: 10 and
formType: CUSTOM. The Nutrition section of Child Health greys eight operands. All
five facts are dropped today.
A caveat that changes the ranking. The aggregate import options returned by
/api/dataValueSets include strictDataSetLocking, strictDataSetInputPeriods, and
strictPeriods, all false by default. DHIS2 therefore does not usually refuse an
out-of-window import. That makes the data-entry-window item a matter of client fidelity -
a capture client should not offer a period the form is closed for - rather than of
avoiding a refusal, which is why it ranks below the items that do produce refusals.
3.5 Data elements¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
valueType |
One of 27 types, from TEXT to GEOJSON |
CARRIED |
Questionnaire.item.type, a value-type code property on D2DE_CS, and minValue / maxValue for the five bounded numeric types |
capture, forward |
domainType (AGGREGATE / TRACKER) |
Which half of DHIS2 the element belongs to | CARRIED |
A domain code property on D2DE_CS |
capture |
formName |
The spelling written for input, where name is written for analytics and reference |
CARRIED |
Questionnaire.item.text wherever DHIS2 states one, and a form-name string property on D2DE_CS. The concept display stays name, so one concept reaches both spellings |
capture |
optionSet |
The element's answers come from a list | CARRIED |
item.answerValueSet pointing at D2OS_<stem>_VS |
capture, forward |
REFERENCE and TRACKER_ASSOCIATE value types |
The answer points at another DHIS2 object | CARRIED |
As item.type = #string, with the reason recorded in the emitter: the guide publishes no FHIR resource for the object these point at |
capture |
zeroIsSignificant |
Whether a stored zero is a measurement or noise DHIS2 may drop | WORTH CARRYING |
A zero-is-significant boolean property on D2DE_CS, beside value-type |
capture, forward |
commentOptionSet |
A data value may carry a coded comment from this list | WORTH CARRYING |
A sibling item of type #choice bound to that option set, once comments have a carrier at all; it belongs with the completeness decision |
forward |
fieldMask |
An input mask for the entry field | WORTH CARRYING |
The core R4 entryFormat extension on the item |
capture |
valueTypeOptions |
Constraints on a FILE_RESOURCE answer |
WORTH CARRYING |
item.maxLength and an attachment type constraint on the profile |
capture |
aggregationType |
How values combine across time and hierarchy | DELIBERATELY NOT |
Analytics-facing. The capture contract never aggregates anything. It matters at harmonization tier 3, where indicator comparability depends on two countries' elements aggregating the same way | - |
aggregationLevels |
The hierarchy levels aggregation starts from | DELIBERATELY NOT |
Analytics | - |
legendSets |
Colour bands for displaying the value | DELIBERATELY NOT |
Presentation, and a threshold band is a judgement about data rather than a fact about the question | - |
url |
A link to the element's definition | DELIBERATELY NOT |
The generated data-dictionary page is the guide's own version of this | - |
| Data element groups and group sets | Grouping for analytics and reporting | DELIBERATELY NOT |
Analytics dimension; nothing on the capture path consults it | - |
Live check. One of 72 data elements is zeroIsSignificant - vANAXwtLwcT, "WHOMCH
Hemoglobin value", which is also the element the ANC program rule guards. Two carry
legend sets, none carry a field mask, one is FILE_RESOURCE.
3.6 Option sets¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
| Option set | A named list of permitted answers | CARRIED |
D2OS_<stem>_CS / _VS per set, plus a ConceptMap to <base>/id/option and its -code sibling |
capture, forward |
Option sortOrder |
The order the list is offered in | CARRIED |
Concept order in the CodeSystem, read off sortOrder in the projection |
capture |
Option code versus UID |
DHIS2 stores both and either can be the answer | CARRIED |
[generate] concept_code_source picks the concept code; the complementary identifier rides a dhis2-code or dhis2-id property; the forwarder resolves an answer on three tiers |
capture, forward |
OptionSet.valueType |
Whether the option codes are numbers, dates, or text | WORTH CARRYING |
A value-type code property on the option set CodeSystem, the same property D2DE_CS already declares |
capture |
Option style (colour, icon) |
What the DHIS2 Capture app draws for the option | WORTH CARRYING |
A color string property on the concept; a rendering client that has one should use it rather than invent its own |
capture |
| Option groups and group sets | Grouping over options | WORTH CARRYING |
Only if program rules land: SHOWOPTIONGROUP and HIDEOPTIONGROUP are the only things that consult them on the capture path |
capture |
3.7 Programs¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
programType |
With or without registration | CARRIED |
D2FormType (code 1..1) on the form and the response, fixed per profile to #event, #tracker, #tracker-event, or #tracked-entity |
capture, forward |
| Enrollment and incident dates | When an enrollment began and when the event it follows occurred | CARRIED |
D2EnrolledAt (dateTime 1..1) and D2IncidentAt (dateTime 0..1) on the registration response, the second carried exactly where the form declares its programme collects one |
capture, forward |
displayIncidentDate |
Whether the programme asks for an incident date at all | CARRIED |
D2CollectsIncidentDate (boolean 1..1) on the registration Questionnaire, declared true and false alike, which is what decides whether D2IncidentAt is emitted |
capture |
onlyEnrollOnce |
A person may hold only one enrollment in this programme | WORTH CARRYING |
A D2EnrollmentRules complex extension on the registration Questionnaire, carrying onlyEnrollOnce (boolean), selectEnrollmentDatesInFuture (boolean), selectIncidentDatesInFuture (boolean) |
capture, forward |
enrollmentDateLabel, incidentDateLabel |
The words the programme uses for its two dates | WORTH CARRYING |
Two string sub-extensions on the same D2EnrollmentRules; today a form that means "Date of birth" publishes a generic label |
capture |
Program.categoryCombo |
The attribute option combo every event of this programme is keyed under | WORTH CARRYING |
Reuse what exists: read the field, emit the AOC CodeSystem pair, put D2AttributeOptionCombos on the event and tracker forms and slice D2AttributeOptionCombo into the two event response profiles |
capture, forward |
featureType (event geometry) |
Whether an event records its own coordinates | WORTH CARRYING |
Open in the roadmap as decision 5.5. Restated here: it is a silent data loss on the event capture path, and the live Child Programme is featureType: POINT |
capture, forward |
expiryDays, completeEventsExpiryDays |
How long an event stays editable | WORTH CARRYING |
The same D2DataEntryWindow extension as the data set |
capture |
useFirstStageDuringRegistration |
The first stage's form is asked inline with registration | WORTH CARRYING |
A boolean on D2EnrollmentRules. The guide publishes registration and stage one as two forms; DHIS2 asks them as one |
capture |
programSections |
The groups the registration form is laid out in | WORTH CARRYING |
Questionnaire.item groups, exactly as stage sections already are |
capture |
accessLevel (OPEN / AUDITED / PROTECTED / CLOSED) |
How strictly DHIS2 guards a record outside its owning organisation unit | DELIBERATELY NOT |
Ownership and audit are the instance's access model, and only the instance can enforce them. A PROTECTED programme's refusal is real, and the honest place for it to surface is the forward leg's error, not a claim in a public guide |
- |
minAttributesRequiredToSearch, maxTeiCountToReturn |
Search ergonomics | DELIBERATELY NOT |
Tuning for the DHIS2 Capture app's search box | - |
relatedProgram |
A link between two programmes | DELIBERATELY NOT |
Consulted only by DHIS2's own relationship features | - |
notificationTemplates |
Messages DHIS2 sends on programme events | DELIBERATELY NOT |
Instance messaging configuration, not a fact about the form | - |
skipOffline, displayFrontPageList, ignoreOverdueEvents |
DHIS2 app behaviour | DELIBERATELY NOT |
Client behaviour switches | - |
Live check. Child Programme (IpHINAT79UW) is onlyEnrollOnce: true,
incidentDateLabel: "Date of birth", enrollmentDateLabel: "Date of enrollment",
featureType: POINT. Its published registration form carries none of the four.
3.8 Program stages¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
| A stage as a form | Each stage of a tracker programme is its own form | CARRIED |
One Questionnaire per stage under tracker-programs/<program>/<stage>.fsh |
capture, forward |
| Stage order | The order the stages are meant to be followed in | CARRIED |
Read off sortOrder, with unordered stages placed after their peers by name and UID |
capture |
| Stage sections | Layout groups within a stage | CARRIED |
Questionnaire.item of type #group |
capture |
ProgramStageDataElement.compulsory |
This question must be answered | CARRIED |
item.required |
capture |
repeatable |
Whether a stage may occur more than once per enrollment | WORTH CARRYING |
A D2StageScheduling complex extension on the stage Questionnaire: repeatable (boolean 1..1), minDaysFromStart (integer 0..1), standardInterval (integer 0..1), generatedByEnrollmentDate (boolean 0..1), autoGenerateEvent (boolean 0..1) |
capture, forward |
minDaysFromStart, standardInterval, autoGenerateEvent, generatedByEnrollmentDate, openAfterEnrollment |
The appointment machine: when the next visit is due and whether DHIS2 books it | WORTH CARRYING |
The same extension - or, if decision 5.2 takes PlanDefinition, action.timing and action.cardinalityBehavior subsume the scheduling half and only repeatable needs a form-local carrier |
capture |
validationStrategy (ON_COMPLETE / ON_UPDATE_AND_INSERT) |
When DHIS2 enforces the compulsory fields | WORTH CARRYING |
A validation-strategy code on the same extension. The guide marks items required unconditionally, so a client is stricter than DHIS2 for an ON_COMPLETE stage and would refuse a legitimate partial save |
capture |
allowFutureDate |
A date answer may be in the future | WORTH CARRYING |
maxValue on the item when false - the cheapest item on this page |
capture |
renderOptionsAsRadio |
Draw the options as radio buttons | WORTH CARRYING |
questionnaire-itemControl #radio-button, the same core extension #gtable already rides |
capture |
nextScheduleDate |
The data element whose answer schedules the next visit | WORTH CARRYING |
A nextScheduleDate reference on D2StageScheduling, naming the item's linkId; it is the last link in the appointment machine |
capture |
periodType on a stage |
A stage that reports on a calendar rather than a date | WORTH CARRYING |
The same D2PeriodType extension as the data set |
capture, forward |
allowProvidedElsewhere |
The answer may have been collected somewhere else | DELIBERATELY NOT |
A provenance checkbox in the DHIS2 app with no receiving semantics on import | - |
displayInReports, skipAnalytics, skipSynchronization |
Reporting and synchronisation hints | DELIBERATELY NOT |
Analytics and instance-to-instance synchronisation | - |
enableUserAssignment |
Events can be assigned to a user as a worklist | DELIBERATELY NOT |
An instance workflow over captured data | - |
hideDueDate, blockEntryForm, displayGenerateEventBox, preGenerateUID, referral, remindCompleted, allowGenerateNextVisit |
DHIS2 app behaviour switches | DELIBERATELY NOT |
Client behaviour, not form semantics | - |
Live check. ANC visit (PsAncVisit1) is repeatable: true with
standardInterval: 30; Baby Postnatal (ZzYYXq4fJie) has minDaysFromStart: 6; six
of the seven stages use validationStrategy: ON_COMPLETE. None of it reaches the guide.
3.9 Program rules¶
This is the one row on the page where the honest answer is not obvious, so it gets its own section.
What DHIS2 has. A ProgramRule is a condition in DHIS2's own expression language
over ProgramRuleVariables, with one or more ProgramRuleActions of 17 types. Six of
them are validation (SHOWERROR, ERRORONCOMPLETE, SHOWWARNING, WARNINGONCOMPLETE,
SETMANDATORYFIELD, ASSIGN), five are visibility (HIDEFIELD, HIDESECTION,
HIDEPROGRAMSTAGE, HIDEOPTION, HIDEOPTIONGROUP, SHOWOPTIONGROUP), and the rest
are side effects (CREATEEVENT, SENDMESSAGE, SCHEDULEMESSAGE, DISPLAYTEXT,
DISPLAYKEYVALUEPAIR).
Why it is not optional. DHIS2 enforces the error actions on the tracker import, not
only in the Capture app. The E1300 repro in section 2 is a generated-shaped event that
the instance refuses. So the guide today publishes a form whose valid answers DHIS2 will
reject, and d2w fhir forward is the consumer that discovers it.
The verdict: WORTH CARRYING, in three tiers, with the boundary published.
| Rule shape | Carrier | Note |
|---|---|---|
A numeric bound on one variable (#{hemoglobin} > 99 with SHOWERROR) |
minValue / maxValue on the item - core R4, already used for value-type bounds |
Unambiguous. A bound is a bound |
HIDEFIELD / HIDESECTION on a single-variable equality or comparison |
item.enableWhen with the condition negated, plus enableBehavior |
R4 enableWhen is a conjunction of simple comparisons, which is exactly this subset and no more. A coded answer compares on the concept code the bound CodeSystem publishes for the option, so a literal no option carries sends the rule to the third tier |
| Everything else | Published as a non-normative D2ProgramRule listing on the form: condition text, action type, target linkId, and message |
Nothing is dropped silently; a client can display the rule even when it cannot evaluate it |
What the tiers deliberately refuse. Conditional requiredness (SETMANDATORYFIELD)
has no R4 Questionnaire carrier - item.required is unconditional. Computed answers
(ASSIGN) would need the SDC calculatedExpression extension, and richer conditions
would need the SDC constraint extension; the guide uses no SDC extensions today, so
taking them on is a dependency decision rather than a mapping decision, and it belongs
to the owner. Side-effect actions are server behaviour and have no client analogue at
all.
The argument against carrying any of it, stated so the owner can weigh it: a partial translation is a second rule engine, and two engines that disagree are worse than one engine and a documented deferral. The counter-argument is that tier 3 - the listing - carries no evaluation risk at all and closes the silent-loss problem on its own, and tiers 1 and 2 are the two shapes where FHIR and DHIS2 mean the same thing by construction.
| Concept | Verdict | Carrier or reason | Consumer |
|---|---|---|---|
| Program rules and rule actions | WORTH CARRYING |
The three tiers above | forward (today), capture |
| Program rule variables | WORTH CARRYING |
Implied by tiers 1 and 2: a translated condition names the variable's linkId |
forward, capture |
ProgramRuleAction.templateUid, evaluation environments and times |
DELIBERATELY NOT |
Server-side scheduling detail of the side-effect actions | - |
Live check, measured against the shipped grammar. The local instance holds six
program rules: one reaches tier 1 (#{hemoglobin} > 99 with SHOWERROR, published as
maxValue 99), one reaches tier 2 (#{apgarscore} > 7 with HIDEFIELD, published as
enableWhen), and four are published whole. Of those four, two compare three variables
at once, one negates a variable outright (!#{womanSmoking}), and one is a
SHOWWARNING, which DHIS2 does not enforce on import and which a bound would therefore
overstate.
Against the larger play.im.dhis2.org/dev-2-43 corpus - 79 rules across six programs -
the same grammar reaches tier 1 once and tier 2 eighteen times, publishing the remaining
60. The commonest reasons a rule is published rather than translated: 49 conditions fall
outside the single-comparison grammar (an || chain, a d2: function, an A{...}
attribute reference, a !== operator DHIS2's own engine does not define), and the rest
are actions with no R4 carrier - HIDESECTION, DISPLAYTEXT, HIDEPROGRAMSTAGE,
SHOWWARNING.
That 19-of-79 is the honest headline, and it is the number the conservative grammar buys: every one of the 60 is published with its expression verbatim, so nothing is lost, and none of the 19 states a constraint DHIS2 does not enforce.
3.10 Tracked entities¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
| Tracked entity type | What kind of thing is tracked - not always a person | CARRIED |
[generate.tracked_entity_types] maps a type UID onto one of nine FHIR resource types, driving subjectType and the response profiles' reference targets |
capture, forward |
| Tracked entity attribute | A question asked about the entity itself | CARRIED |
D2TEA_CS with value-type, plus D2TrackedEntityAttributeValue on the subject resource |
capture, forward |
formName on an attribute |
The spelling written for input, as on a data element | CARRIED |
Questionnaire.item.text wherever DHIS2 states one, and a form-name string property on D2TEA_CS |
capture |
unique |
The value identifies its subject | CARRIED |
A unique boolean property on D2TEA_CS; unique values ride identifier under <base>/tracked-entity-attribute/<uid> |
capture, output leg |
searchable (per program, per type) |
A person can be found by this attribute | CARRIED |
A searchable roll-up property plus one searchable-<contextUid> property per asking context |
output leg |
mandatory (per program, per type) |
The attribute must be answered | CARRIED |
item.required |
capture |
| Entity level of an answer | Whether an answer belongs to the tracked entity or to the enrollment | CARRIED |
D2EntityLevel (boolean 1..1) on the registration form's item, resolved off the type's own attribute join |
capture, forward |
generated and pattern |
DHIS2 mints this value itself from a text pattern; the client must not supply one | WORTH CARRYING |
A generated boolean and a pattern string property on D2TEA_CS, plus item.readOnly = true on the question so the form itself says so |
capture, forward |
confidential |
The value is encrypted at rest and must not be relayed | WORTH CARRYING |
A confidential boolean property on D2TEA_CS, plus a generate-time refusal to draw the attribute into an instance-sourced example |
capture, examples |
inherit |
The value is copied from a related tracked entity at registration | WORTH CARRYING |
An inherit boolean property on D2TEA_CS; an inherited attribute should not be asked twice |
capture |
orgunitScope |
Uniqueness is scoped to an organisation unit, not the whole instance | WORTH CARRYING |
A unique-scope code property (instance / organisation-unit) beside unique; today a scoped unique reads as instance-wide |
capture, output leg |
| A tracked entity attribute as the subject identifier | Identifying a person by a national identifier rather than a DHIS2 UID | WORTH CARRYING |
Queued in the roadmap; the unique property that unblocks it already ships, and what is left is the nomination |
output leg |
| Relationships and relationship types | Links between tracked entities, enrollments, and events | WORTH CARRYING |
No carrier chosen. Patient.link, RelatedPerson, and Group.member are the candidates; bidirectional and the from/to constraints are the hard part, since a FHIR link has no symmetric form |
output leg |
fieldMask on an attribute |
An input mask | WORTH CARRYING |
The core R4 entryFormat extension, as for data elements |
capture |
displayInListNoProgram, sortOrderInList*, displayOnVisitSchedule |
Which columns the Capture app's list shows | DELIBERATELY NOT |
App list rendering | - |
allowAuditLog on a type |
Whether DHIS2 audits reads of this type | DELIBERATELY NOT |
Instance audit configuration; see 3.12 | - |
expression on an attribute |
A computed attribute | DELIBERATELY NOT |
DHIS2 computes it; a client neither supplies nor needs it | - |
Live check. Unique ID (lZGmxYbs97q) is unique: true, generated: true,
pattern: "RANDOM(#######)", and Child Programme asks for it. It is the only generated
attribute on the instance and it is on the canonical demonstration programme.
3.11 Metadata attributes and translations¶
| Concept | What it means in DHIS2 | Verdict | Carrier or reason | Consumer |
|---|---|---|---|---|
| Custom metadata attributes | The instance extends any metadata class with its own typed fields | CARRIED |
D2AttributeValue (attributeId, attributeCode, value) on Organization, Location, CodeSystem, ValueSet, Questionnaire |
output leg, harmonization |
| Unique attribute values | A value that identifies its object | CARRIED |
identifier under <base>/attribute/<uid>, so a FHIR server can resolve the object by it; only unique attributes qualify |
output leg |
| Attribute values on CodeSystem concepts | The same machinery on data elements and options | WORTH CARRYING |
Queued in the roadmap; a CodeSystem.property needs declaring up front and concept volume decides the cost, so it waits on a measurement rather than a design |
output leg |
Attribute.mandatory |
The instance requires this attribute on objects of a class | DELIBERATELY NOT |
A metadata-authoring rule enforced when metadata is written, not a fact about captured data | - |
NAME translations |
The object's name in other locales | CARRIED |
The core R4 translation extension on the _name / _title primitive sibling, and concept designations |
capture |
SHORT_NAME and DESCRIPTION translations |
Two more translatable properties | WORTH CARRYING |
Queued in the roadmap; each needs a target (Organization.alias, ^description) before it can follow |
capture |
| Locale normalisation | DHIS2 locales are not always FHIR language tags | CARRIED |
normalize_locale in i18n.py |
capture |
3.12 Sharing, access, and audit¶
Every row here is DELIBERATELY NOT, and they share one reason worth stating once:
a published implementation guide is a public artifact, and an access model is not.
Mirroring an instance's ACL into a guide would publish the instance's access design to
anyone who reads the guide, and would create a second authority over rules only the
DHIS2 server can enforce. A capture facade is not an ACL mirror.
| Concept | What it means in DHIS2 | Verdict | Reason |
|---|---|---|---|
sharing (public, users, userGroups, rwrw----) |
Who may read and write each metadata object and its data | DELIBERATELY NOT |
The reason above. The instance is the only enforcer, and the guide must not appear to be a second one |
access (the computed per-user booleans) |
What the requesting user may do right now | DELIBERATELY NOT |
Request-scoped, not metadata. It differs per caller and is stale the moment it is written down |
Program.accessLevel and ownership |
PROTECTED and CLOSED programmes guard records outside the owning organisation unit |
DELIBERATELY NOT |
It produces a real refusal on the forward leg, and that error is the honest place for it to surface |
Audit trail (allowAuditLog, data value audits) |
Who changed what, when | DELIBERATELY NOT |
Instance state, not metadata. The serve spool's receipts are the guide's own audit surface, and they record what was submitted rather than what DHIS2 did with it |
skipSynchronization |
Exclude from instance-to-instance synchronisation | DELIBERATELY NOT |
A deployment topology concern between DHIS2 instances |
3.13 The analytics tier¶
DHIS2's derived layer - indicators, validation rules, predictors, legends - is out of the generator's scope today. That is the right call for a capture guide, and the reason is uniform: none of it is captured. Each is computed from values a form already collects, so a form that omitted it would be no less complete. Where each would matter is harmonization tier 3, indicator comparability, whose design already names them.
| Concept | What it means in DHIS2 | Verdict | Reason, and where it would matter |
|---|---|---|---|
| Indicators and indicator types | A numerator over a denominator with a factor | DELIBERATELY NOT |
Derived. At harmonization tier 3 an indicator becomes a FHIR Measure, which is a different artifact from a capture form |
| Validation rules | Cross-field consistency checks over a period | DELIBERATELY NOT |
Empirically not enforced on aggregate import (section 2), so a form that omits them still produces data DHIS2 accepts. They are the aggregate sibling of program rules, and only the enforcement difference separates the two verdicts |
| Predictors | Values DHIS2 computes into data elements from a sample window | DELIBERATELY NOT |
Generated by the server after capture; a client neither supplies nor sees them |
| Program indicators | Expressions over enrollments and events, with analytics period boundaries | DELIBERATELY NOT |
Derived, and their period-boundary machinery has no capture-side meaning |
| Legends and legend sets | Colour bands over a value range | DELIBERATELY NOT |
Presentation, and a threshold band is a judgement about data rather than a fact about the question |
| Data approval workflows | The chain a data set's values climb | DELIBERATELY NOT |
Governance over already-captured data |
| Relative period vocabulary | LAST_12_MONTHS and 43 siblings |
DELIBERATELY NOT |
An analytics query vocabulary; a response names one absolute period |
4. Worth carrying, ranked¶
Ranked by whether a consumer exists today (something in this repository already breaks, misreports, or works around the absence), soon (a shipped surface would use it on its next step), or someday (a real consumer, no date).
4.1 Rank 1 - a consumer exists today¶
- Program rules reach the guide in three tiers. Numeric bounds to
minValue/maxValue, single-variable hides toenableWhen, everything else to a non-normativeD2ProgramRulelisting. Consumer:d2w fhir forward, which can spool responses the tracker import refuses withE1300. - The attribute option combo reaches the event and tracker legs. Read
Program.categoryCombo, emit theAOCpair, putD2AttributeOptionComboson the event and tracker forms, sliceD2AttributeOptionCombointo the two event response profiles. Consumer:d2w fhir forward, which today misattributes events of a non-default programme to DHIS2's default combo. Nothing new has to be designed - only projected and sliced.
4.2 Rank 2 - a consumer exists soon¶
D2StageSchedulingon the stage form -repeatable,minDaysFromStart,standardInterval,generatedByEnrollmentDate,autoGenerateEvent,validationStrategy. Consumer: a capture client deciding which forms to offer and when, and a forward pre-flight for non-repeatable stages.D2EnrollmentRuleson the registration form -onlyEnrollOnce, the two date labels, the two future-date switches,useFirstStageDuringRegistration. Consumer: the capture path, whereD2SubjectExistsalready ships andonlyEnrollOnceis what makes that fact actionable.- Category option validity windows and restrictions -
valid-from/valid-todateTime properties and arestrictedboolean on the category CodeSystem. Consumer: capture of a non-default attribute combo, where an out-of-window option is an error waiting at the forward leg. zero-is-significantonD2DE_CS. Consumer: capture grading of a zero answer, and the forwarder distinguishing "measured zero" from "not answered".- Organisation unit groups and group sets as
Organization.typecodings, with acompulsoryproperty on the group set. Consumer: the output leg and the harmonization tier; the inspiration guide already classifies its hierarchy this way. D2OrganisationUnitPeriod(Period 1..1) onOrganizationandLocation, fromopeningDateandclosedDate. Consumer: a registry consumer asking which facilities existed in a reporting period.D2DataEntryWindowon the form -timelyDays,expiryDays,openFuturePeriods,openPeriodsAfterCoEndDate,dataInputPeriods. Consumer: a capture client refusing an out-of-window period before the round trip. Ranked here rather than rank 1 because DHIS2's strict-locking import options default to off.confidentialonD2TEA_CS, plus a refusal to draw a confidential attribute into an instance-sourced example. Consumer: the risk profile of roadmap decision 5.6.- Event geometry - roadmap decision 5.5, restated. Consumer: the event capture path, which loses coordinates today.
- A
formType: CUSTOMgenerate note. Consumer: the person reading the run output, who is currently not told the published form is a substitute.
4.3 Rank 3 - someday¶
allowFutureDatetomaxValueon date items - the cheapest item here.renderOptionsAsRadiotoquestionnaire-itemControl#radio-button.fieldMaskon data elements and attributes to the coreentryFormatextension.inheritonD2TEA_CS, so an inherited attribute is not asked twice.unique-scope(instance/organisation-unit) besideuniqueonD2TEA_CS.OptionSet.valueTypeas avalue-typeproperty on the option set CodeSystem.- Option
stylecolour as acolorproperty on the concept. Organization.addressand aurltelecom from the organisation unit's own fields.programSectionsas registration form item groups.- Stage
periodTypereusingD2PeriodType. nextScheduleDateonD2StageScheduling, naming the item'slinkId.commentOptionSetas a sibling choice item, once comments have a carrier.valueTypeOptionsasitem.maxLengthand an attachment constraint.- Option groups and group sets, contingent on program rules landing.
- Completeness registration - roadmap decision 5.4, restated.
validCompleteOnly,compulsoryFieldsCompleteOnly,noValueRequiresComment, folded into the same decision.- Attribute values on CodeSystem concepts - queued, waiting on a volume measurement.
SHORT_NAMEandDESCRIPTIONtranslations - queued, each waiting on a target.- Relationships and relationship types - no carrier chosen;
Patient.link,RelatedPerson, andGroup.memberare the candidates.
5. The four rules that generate a "deliberately not"¶
Every DELIBERATELY NOT on this page falls under one of four principles. Stating them
here means a future concept can be judged against a rule rather than argued from
scratch.
- Derived is not captured. Indicators, program indicators, predictors, validation rules, aggregation types, legend sets, and aggregation levels are all computed from values a form collects. A capture contract that omitted them is no less complete. Where they matter is harmonization tier 3.
- A renderer's hints belong to its renderer.
renderAsTabs,showRowTotals,displayFrontPageList,hideDueDate,dataEntryForm, and their siblings tell the DHIS2 apps how to draw a form. AQuestionnairehas a different renderer, and copying one client's hints into another client's contract produces a form faithful to neither. - An access model is not publishable. Sharing, effective access, programme access level, and audit configuration describe who may do what on one instance. Publishing them exposes the instance's access design and creates a second authority over rules only the server enforces.
- A query vocabulary is not a fact. Relative periods, analytics dimensions, and search-tuning fields are ways of asking DHIS2 questions. A captured response states one absolute period, one organisation unit, one attribute option combo - facts, not queries.
6. Owner decisions¶
Six calls, each of which changes what gets built rather than only when.
6.1 Does the guide take an SDC dependency? Tier 1 and tier 2 of the program rule
proposal use core R4 extensions only. ASSIGN needs SDC calculatedExpression, and
richer conditions need SDC constraint. The guide uses no SDC extension today. Taking
one on is a profile-dependency decision with consequences beyond program rules -
$extract and $populate become available, which decision 5.3 would then weigh.
6.2 How far do program rules translate? Three tiers are proposed. The floor is tier 3 alone - publish the listing, evaluate nothing, keep DHIS2 as the single rule engine. The ceiling is all three. The argument for the floor is that two engines that disagree are worse than one engine and an honest deferral; the argument for the ceiling is that the two translated shapes mean the same thing in both systems by construction.
6.3 Do the stage scheduling facts ride a form extension or a PlanDefinition? If
decision 5.2 takes CarePlan with a PlanDefinition behind it, action.timing and
action.cardinalityBehavior carry the scheduling half of D2StageScheduling natively,
and the extension shrinks to repeatable and validationStrategy. These two decisions
should be made together or the work is done twice.
6.4 Where does the organisation unit period go? Organization has no period in
R4 and Location has none either, so an extension is unavoidable. The question is
whether one extension serves both resources or the registry keeps the boolean on
Organization and puts the dates on Location only.
6.5 What does a restricted category option publish? Publishing 1166 organisation unit
references per option would dwarf the guide it rides in. The proposal is a restricted
boolean with resolution left to the instance. The alternative - a per-option ValueSet of
organisation units - is complete and expensive, and the owner decides which the guide
owes its readers.
6.6 Is a greyed cell an absent question or a disabled one? The proposal is absence:
DHIS2's form does not have the cell, so the guide's form should not either. The
alternative is item.readOnly = true, which keeps the linkId addressable for a client
that renders the data set as a grid and wants the shape of the grid to match DHIS2's.
Absence is simpler; parity of shape is the argument for the other.
Related¶
- The FHIR roadmap and review guide - the open decisions this page restates rather than resolves, and the near-term slate several of these items join.
- The enrollment resource - decision 5.2, which 6.3 above is coupled to.
- The FHIR conversion layer - the forward leg that is the consumer of record for four of the five rank-1 items.
- FHIR harmonization - tier 3, where the analytics-tier concepts this page declines would land.
- The FHIR plugin architecture - the carriers as they exist today, in the emitter's own terms.
- Identifiers and the D2 extensions - the carriers as a consumer of the published guide meets them.
- Glossary - every DHIS2 concept this page grades, with what the toolkit does with it.