Validation rules + predictors¶
The CRUD flip side of dhis2 maintenance validation run + dhis2 maintenance predictors run. Both domains now expose authoring surfaces alongside the run endpoints:
| Accessor | API path | Purpose |
|---|---|---|
client.validation_rules |
/api/validationRules |
Compare two DHIS2 expressions (leftSide vs rightSide) under a chosen operator; fire violations when the comparison fails. |
client.validation_rule_groups |
/api/validationRuleGroups |
Collect rules into named runs for maintenance validation run --group. |
client.predictors |
/api/predictors |
CRUD + run. Generates synthetic data values from an expression over historical samples. |
client.predictor_groups |
/api/predictorGroups |
Collect predictors into named runs for maintenance predictors run --group. |
Expression sides are nested objects¶
ValidationRule.leftSide / rightSide and Predictor.generator are all DHIS2 Expression sub-objects. Each carries at least an expression string + missingValueStrategy flag. The accessors assemble the wrapper from plain kwargs so callers hand in the expression text directly:
rule = await client.validation_rules.create(
name="BCG doses > 0",
short_name="BCGgt0",
left_expression="#{deBCG000001}",
operator=Operator.GREATER_THAN,
right_expression="0",
importance=Importance.HIGH,
organisation_unit_levels=[4], # facility level
)
predictor = await client.predictors.create(
name="BCG 3-month rolling average",
short_name="BCG3mAvg",
expression="#{deBCG000001}",
output_data_element_uid="deOutput0001",
sequential_sample_count=3,
organisation_unit_level_uids=["ouLvlFac001"],
)
organisationUnitLevels asymmetry (upstream quirk)¶
Both models carry organisationUnitLevels but with different shapes:
ValidationRule.organisationUnitLevels— list of integer level numbers ([4]).Predictor.organisationUnitLevels— list ofOrganisationUnitLevelreferences ([{"id": uid}]).
The accessors expose this as organisation_unit_levels: list[int] vs organisation_unit_level_uids: list[str] respectively, matching DHIS2's wire shape. DHIS2 v42 returns a 500 on predictor create without valid level references.
missingValueStrategy¶
Defaults to SKIP_IF_ALL_VALUES_MISSING on both sides — rows where every operand is null are excluded from the comparison instead of counting as a violation. Flip to NEVER_SKIP to fail any row missing an operand.
No *Spec builder¶
Same call as every other authoring accessor: keyword args. Continues the spec-audit data point.
CLI¶
# ValidationRule + group
dhis2 metadata validation-rules create \
--name "BCG gt zero" --short-name BCGgt0 \
--left "#{deBCG000001}" --operator greater_than --right "0" \
--importance HIGH --ou-level 4
dhis2 metadata validation-rule-groups create --name "BCG rules"
dhis2 metadata validation-rule-groups add-members <GRP_UID> --rule <RULE_UID>
# Predictor + group
dhis2 metadata predictors create \
--name "BCG 3m avg" --short-name BCG3m \
--expression "#{deBCG000001}" --output deOutput0001 \
--sequential 3 --ou-level ouLvlFac001
dhis2 metadata predictor-groups create --name "BCG predictors"
dhis2 metadata predictor-groups add-members <PDG_UID> --predictor <PRD_UID>
Every list has an ls alias; every destructive verb accepts --yes / -y.
MCP¶
24 tools mirroring the CLI: metadata_validation_rule_* (list / get / create / rename / delete), metadata_validation_rule_group_* (list / get / members / create / add-members / remove-members / delete), metadata_predictor_* (list / get / create / rename / delete), metadata_predictor_group_* (list / get / members / create / add-members / remove-members / delete).
Running them¶
Creating the rule or predictor is decoupled from running it:
dhis2 maintenance validation run --group <GRP_UID> --ds <DATASET_UID> --start-date … --end-date …dhis2 maintenance predictors run --group <PDG_UID> --start-date … --end-date …
See the maintenance plugin for the run-side reference.
validation_rules
¶
ValidationRule authoring — Dhis2Client.validation_rules.
ValidationRules compare two DHIS2 expressions (leftSide vs
rightSide) against a configurable operator and fire violations when
the comparison fails for a given period + organisation unit. They
drive dhis2 maintenance validation run; DHIS2 ships dozens of
built-in rules for aggregate data-quality checks.
This module adds the authoring primitives — run lives on
Dhis2Client.validation.run_analysis(...):
create(...)— named kwargs over the minimal required subset (name,short_name,left_expression,operator,right_expression) plus common knobs (period_type,importance,missing_value_strategy,description).update(rule)/rename(uid, ...)— standard edit pathways.delete(uid)— drops the rule and any outstanding results.
leftSide / rightSide are Expression sub-objects on the wire;
we build them from the string + strategy here so callers don't have
to assemble the payload manually.
No *Spec builder — continues the spec-audit data point.
Classes¶
ValidationRule
¶
Bases: BaseModel
Generated model for DHIS2 ValidationRule.
DHIS2 Validation Rule - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/validationRules.
Field Field(description=...) entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/schemas/validation_rule.py
ValidationRulesAccessor
¶
Dhis2Client.validation_rules — CRUD helpers over /api/validationRules.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rules.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
Functions¶
__init__(client)
¶
list_all(*, period_type=None, page=1, page_size=50)
async
¶
Page through ValidationRules, optionally filtered by periodType.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rules.py
get(uid)
async
¶
Fetch one ValidationRule with both expression sides + group refs inline.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rules.py
create(*, name, short_name, left_expression, operator, right_expression, period_type=PeriodType.MONTHLY, importance=Importance.MEDIUM, missing_value_strategy=MissingValueStrategy.SKIP_IF_ALL_VALUES_MISSING, left_description=None, right_description=None, description=None, code=None, organisation_unit_levels=None, uid=None)
async
¶
Create a ValidationRule.
left_expression / right_expression use DHIS2's aggregate
expression syntax (#{<DE_UID>.<CC_UID>} for disaggregated
DEs, #{<DE_UID>} for default-combo DEs). operator picks the
comparison — EQUAL_TO, LESS_THAN_OR_EQUAL_TO, etc. The
missing_value_strategy default skips rows where every ref is
null so blank cells don't count as a violation.
organisation_unit_levels scopes the rule to specific depths —
pass [4] to restrict it to facility-level OUs.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rules.py
update(rule)
async
¶
PUT an edited ValidationRule back. rule.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rules.py
rename(uid, *, name=None, short_name=None, description=None)
async
¶
Partial-update shortcut — read, mutate the label fields, PUT.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rules.py
delete(uid)
async
¶
Delete a ValidationRule — DHIS2 removes any outstanding results it had raised.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rules.py
validation_rule_groups
¶
ValidationRuleGroup authoring — Dhis2Client.validation_rule_groups.
ValidationRuleGroups collect ValidationRules into named runs so
dhis2 maintenance validation run --group <uid> exercises a coherent
subset (BCG-dose rules, ANC rules, …). Follows the same CRUD +
per-item membership pattern as IndicatorGroupsAccessor.
Classes¶
ValidationRuleGroup
¶
Bases: BaseModel
Generated model for DHIS2 ValidationRuleGroup.
DHIS2 Validation Rule Group - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/validationRuleGroups.
Field Field(description=...) entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/schemas/validation_rule_group.py
ValidationRuleGroupsAccessor
¶
Dhis2Client.validation_rule_groups — CRUD + membership over /api/validationRuleGroups.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
Functions¶
__init__(client)
¶
list_all()
async
¶
Return every ValidationRuleGroup.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
get(uid)
async
¶
Fetch one group by UID with its validationRules refs populated.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
list_members(uid, *, page=1, page_size=50)
async
¶
Page through ValidationRules belonging to one group.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
create(*, name, short_name=None, uid=None, code=None, description=None)
async
¶
Create an empty ValidationRuleGroup; add members afterwards via add_members.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
update(group)
async
¶
PUT an edited ValidationRuleGroup back. group.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
add_members(uid, *, validation_rule_uids)
async
¶
Add ValidationRules to the group via the per-item POST shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
remove_members(uid, *, validation_rule_uids)
async
¶
Drop ValidationRules from the group via the per-item DELETE shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
delete(uid)
async
¶
Delete the grouping row — member rules stay.
Source code in packages/dhis2w-client/src/dhis2w_client/validation_rule_groups.py
predictors
¶
Predictor authoring + run — Dhis2Client.predictors.
Predictors generate data values from expressions over historical data (e.g. "3-month rolling average of X" → emit a synthetic DataElement row). The accessor covers both authoring (create / update / delete) and the run endpoints DHIS2 exposes:
POST /api/predictors/run?startDate=…&endDate=…— run every predictor on the instance.POST /api/predictors/{uid}/run?startDate=…&endDate=…— run one.POST /api/predictorGroups/{uid}/run?startDate=…&endDate=…— run a named group of predictors in one pass (exposed fromPredictorsAccessor.run_groupfor backward compatibility + also fromPredictorGroupsAccessor.run).
All three run shapes return a WebMessageResponse with a summary of
predictions written / ignored / failed; none of them kick a background
job, so there's no task to watch.
Authoring surface: Predictor.generator is an Expression sub-object
typed as Any on the generated schema. create(...) assembles the
minimal wrapper here so callers pass the expression string +
description, not the nested payload.
No *Spec builder — continues the spec-audit data point.
Classes¶
Predictor
¶
Bases: BaseModel
Generated model for DHIS2 Predictor.
DHIS2 Predictor - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/predictors.
Field Field(description=...) entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/schemas/predictor.py
PredictorsAccessor
¶
Dhis2Client.predictors — CRUD + run helpers over /api/predictors.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
Functions¶
__init__(client)
¶
list_all(*, period_type=None, page=1, page_size=50)
async
¶
Page through Predictors, optionally filtered by periodType.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
get(uid)
async
¶
Fetch one Predictor with generator, output, OU scope resolved inline.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
create(*, name, short_name, expression, output_data_element_uid, period_type=PeriodType.MONTHLY, sequential_sample_count=3, annual_sample_count=0, sequential_skip_count=0, organisation_unit_descendants=OrganisationUnitDescendants.SELECTED, organisation_unit_level_uids=None, output_combo_uid=None, missing_value_strategy=MissingValueStrategy.SKIP_IF_ALL_VALUES_MISSING, generator_description=None, description=None, code=None, uid=None)
async
¶
Create a Predictor.
expression uses DHIS2's aggregate expression syntax; the
accessor wraps it in the generator Expression sub-object.
output_data_element_uid is the target DE the prediction writes
to — needs a TRACKER or AGGREGATE domain and a numeric
valueType.
sequential_sample_count + annual_sample_count control the
look-back window: 3 monthly samples with the default period
type averages the three prior months.
organisation_unit_level_uids scopes the run — pass the UIDs of
the OrganisationUnitLevel rows the predictor should cover
(typically the facility level for data-entry predictors).
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
update(predictor)
async
¶
PUT an edited Predictor back. predictor.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
rename(uid, *, name=None, short_name=None, description=None)
async
¶
Partial-update shortcut — read, mutate the label fields, PUT.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
delete(uid)
async
¶
Delete a Predictor. DHIS2 keeps any data values it already wrote.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
run_all(*, start_date, end_date)
async
¶
Run every predictor on the instance for the given date range.
Returns the summary envelope — .import_count() gives
imported / updated / ignored / deleted counts for the emitted
data values.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
run_one(predictor_uid, *, start_date, end_date)
async
¶
Run a single predictor by UID over the given date range.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
run_group(group_uid, *, start_date, end_date)
async
¶
Run every predictor in a PredictorGroup over the given date range.
Source code in packages/dhis2w-client/src/dhis2w_client/predictors.py
predictor_groups
¶
PredictorGroup authoring — Dhis2Client.predictor_groups.
PredictorGroups collect Predictors so dhis2 maintenance predictors
run --group <uid> exercises a coherent subset in one pass. Mirrors
the IndicatorGroup / ValidationRuleGroup CRUD + per-item membership
pattern.
Running a group lives on PredictorsAccessor.run_group (kept for
backward compatibility with existing callers); the group accessor
focuses on the authoring verbs.
Classes¶
PredictorGroup
¶
Bases: BaseModel
Generated model for DHIS2 PredictorGroup.
DHIS2 Predictor Group - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/predictorGroups.
Field Field(description=...) entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/schemas/predictor_group.py
PredictorGroupsAccessor
¶
Dhis2Client.predictor_groups — CRUD + membership over /api/predictorGroups.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
Functions¶
__init__(client)
¶
list_all()
async
¶
Return every PredictorGroup.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
get(uid)
async
¶
Fetch one group by UID with its predictors refs populated.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
list_members(uid, *, page=1, page_size=50)
async
¶
Page through Predictors belonging to one group.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
create(*, name, short_name=None, uid=None, code=None, description=None)
async
¶
Create an empty PredictorGroup; add members afterwards via add_members.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
update(group)
async
¶
PUT an edited PredictorGroup back. group.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
add_members(uid, *, predictor_uids)
async
¶
Add Predictors to the group via the per-item POST shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
remove_members(uid, *, predictor_uids)
async
¶
Drop Predictors from the group via the per-item DELETE shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/predictor_groups.py
delete(uid)
async
¶
Delete the grouping row — member predictors stay.