Skip to content

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.

  1. 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.
  2. 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 in foundation/templates/d2-responses.fsh.jinja, the CodeSystem properties in each resources/*/ target, and - decisively - the fields= projections at the top of packages/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".
  3. 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.

  1. The attribute option combo rides the aggregate leg only. Program.categoryCombo is 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 to QuestionnaireResponse profiles the event kinds do not slice.
  2. An organisation unit's opening date is read and then discarded. _ORGANISATION_UNIT_FIELDS asks for openingDate and closedDate; the projection keeps only closed: bool, computed as "has closedDate passed" (_is_closed, service.py:3510). Organization.active and Location.status carry 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 with formType: CUSTOM is rendered in DHIS2 by hand-written HTML keyed on data element operands. The guide reads neither formType nor dataEntryForm and publishes a default-shaped Questionnaire in its place. On the live instance the Child Health data set is CUSTOM. 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

  1. Program rules reach the guide in three tiers. Numeric bounds to minValue / maxValue, single-variable hides to enableWhen, everything else to a non-normative D2ProgramRule listing. Consumer: d2w fhir forward, which can spool responses the tracker import refuses with E1300.
  2. The attribute option combo reaches the event and tracker legs. Read Program.categoryCombo, emit the AOC pair, put D2AttributeOptionCombos on the event and tracker forms, slice D2AttributeOptionCombo into 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

  1. D2StageScheduling on 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.
  2. D2EnrollmentRules on the registration form - onlyEnrollOnce, the two date labels, the two future-date switches, useFirstStageDuringRegistration. Consumer: the capture path, where D2SubjectExists already ships and onlyEnrollOnce is what makes that fact actionable.
  3. Category option validity windows and restrictions - valid-from / valid-to dateTime properties and a restricted boolean 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.
  4. zero-is-significant on D2DE_CS. Consumer: capture grading of a zero answer, and the forwarder distinguishing "measured zero" from "not answered".
  5. Organisation unit groups and group sets as Organization.type codings, with a compulsory property on the group set. Consumer: the output leg and the harmonization tier; the inspiration guide already classifies its hierarchy this way.
  6. D2OrganisationUnitPeriod (Period 1..1) on Organization and Location, from openingDate and closedDate. Consumer: a registry consumer asking which facilities existed in a reporting period.
  7. D2DataEntryWindow on 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.
  8. confidential on D2TEA_CS, plus a refusal to draw a confidential attribute into an instance-sourced example. Consumer: the risk profile of roadmap decision 5.6.
  9. Event geometry - roadmap decision 5.5, restated. Consumer: the event capture path, which loses coordinates today.
  10. A formType: CUSTOM generate note. Consumer: the person reading the run output, who is currently not told the published form is a substitute.

4.3 Rank 3 - someday

  1. allowFutureDate to maxValue on date items - the cheapest item here.
  2. renderOptionsAsRadio to questionnaire-itemControl #radio-button.
  3. fieldMask on data elements and attributes to the core entryFormat extension.
  4. inherit on D2TEA_CS, so an inherited attribute is not asked twice.
  5. unique-scope (instance / organisation-unit) beside unique on D2TEA_CS.
  6. OptionSet.valueType as a value-type property on the option set CodeSystem.
  7. Option style colour as a color property on the concept.
  8. Organization.address and a url telecom from the organisation unit's own fields.
  9. programSections as registration form item groups.
  10. Stage periodType reusing D2PeriodType.
  11. nextScheduleDate on D2StageScheduling, naming the item's linkId.
  12. commentOptionSet as a sibling choice item, once comments have a carrier.
  13. valueTypeOptions as item.maxLength and an attachment constraint.
  14. Option groups and group sets, contingent on program rules landing.
  15. Completeness registration - roadmap decision 5.4, restated.
  16. validCompleteOnly, compulsoryFieldsCompleteOnly, noValueRequiresComment, folded into the same decision.
  17. Attribute values on CodeSystem concepts - queued, waiting on a volume measurement.
  18. SHORT_NAME and DESCRIPTION translations - queued, each waiting on a target.
  19. Relationships and relationship types - no carrier chosen; Patient.link, RelatedPerson, and Group.member are 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.

  1. 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.
  2. 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. A Questionnaire has a different renderer, and copying one client's hints into another client's contract produces a form faithful to neither.
  3. 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.
  4. 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.