dirigent-integration 0.23.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. dirigent_integration/__init__.py +29 -0
  2. dirigent_integration/py.typed +0 -0
  3. dirigent_integration/shelves/dhis2-analytics-to-csv-report.yaml +174 -0
  4. dirigent_integration/shelves/dhis2-export-to-s3.yaml +66 -0
  5. dirigent_integration/shelves/dhis2-metadata-snapshot.yaml +143 -0
  6. dirigent_integration/shelves/dhis2-tracker-weekly-window.yaml +162 -0
  7. dirigent_integration/shelves/dhis2-values-per-org-unit-to-parquet.yaml +186 -0
  8. dirigent_integration/shelves/dhis2-values-to-parquet.yaml +64 -0
  9. dirigent_integration/shelves/fhir/README.md +79 -0
  10. dirigent_integration/shelves/fhir/dhis2-to-fhir-observations.yaml +212 -0
  11. dirigent_integration/shelves/fhir/fhir-capture-bundle-to-data-values.yaml +230 -0
  12. dirigent_integration/shelves/fhir/fhir-conceptmap-driven-mapping.yaml +222 -0
  13. dirigent_integration/shelves/fhir/fhir-encounter-to-event.yaml +248 -0
  14. dirigent_integration/shelves/fhir/fhir-measure-report-to-analytics-check.yaml +205 -0
  15. dirigent_integration/shelves/fhir/fhir-nightly-window-sync.yaml +182 -0
  16. dirigent_integration/shelves/fhir/fhir-patient-to-tracked-entity.yaml +225 -0
  17. dirigent_integration/shelves/fhir/fhir-questionnaire-response-to-data-values.yaml +187 -0
  18. dirigent_integration/shelves/fhir/fhir-subscription-webhook-to-dhis2.yaml +218 -0
  19. dirigent_integration/shelves/inbound/README.md +32 -0
  20. dirigent_integration/shelves/inbound/csv-drop-to-data-values.yaml +176 -0
  21. dirigent_integration/shelves/inbound/fan-out-per-facility-import.yaml +202 -0
  22. dirigent_integration/shelves/inbound/fhir-observations-to-data-values.yaml +193 -0
  23. dirigent_integration/shelves/inbound/http-json-to-data-values.yaml +151 -0
  24. dirigent_integration/shelves/inbound/outside-to-dhis2-with-checks.yaml +246 -0
  25. dirigent_integration/shelves/inbound/parquet-lakehouse-to-dhis2.yaml +158 -0
  26. dirigent_integration/shelves/inbound/webhook-payload-to-tracker-event.yaml +176 -0
  27. dirigent_integration/shelves/inbound/weekly-window-pull-and-import.yaml +175 -0
  28. dirigent_integration/shelves/parquet-to-dhis2-import.yaml +195 -0
  29. dirigent_integration-0.23.2.dist-info/METADATA +231 -0
  30. dirigent_integration-0.23.2.dist-info/RECORD +33 -0
  31. dirigent_integration-0.23.2.dist-info/WHEEL +4 -0
  32. dirigent_integration-0.23.2.dist-info/entry_points.txt +3 -0
  33. dirigent_integration-0.23.2.dist-info/licenses/LICENSE +15 -0
@@ -0,0 +1,230 @@
1
+ # A page of captures pulled off a FHIR facade and imported into DHIS2 as aggregate values.
2
+ #
3
+ # THE FLOW, END TO END. `d2w fhir serve` publishes one DHIS2 instance as a FHIR endpoint and
4
+ # takes captures back. This document reads the captures it is holding for one aggregate form,
5
+ # turns them into DHIS2's own `/api/dataValueSets` envelope, gates that envelope on DHIS2's id
6
+ # and period formats, rehearses the import as a dry run, and only then writes it.
7
+ #
8
+ # THE CAPTURE PAIR. The facade's whole capture surface is two resource types, and the pair is
9
+ # the thing to learn:
10
+ #
11
+ # Questionnaire the form DEFINITION, generated from DHIS2 metadata. One per
12
+ # aggregate data set, event program, tracker program, program stage,
13
+ # or tracked entity type. Its `item.linkId` values ARE DHIS2 uids.
14
+ # QuestionnaireResponse one SUBMISSION against that form: it points at the form through
15
+ # `questionnaire`, names a `subject`, and answers item by item on the
16
+ # same linkIds. One aggregate response is exactly one DHIS2
17
+ # (data set, org unit, period, attribute option combo) cell block.
18
+ #
19
+ # Because the linkIds are DHIS2 uids, a response is readable back into DHIS2 without consulting
20
+ # the form -- which is what makes the jq below short.
21
+ #
22
+ # WHAT ARRIVES. One searchset Bundle of aggregate responses, each shaped like this:
23
+ #
24
+ # {"resourceType": "QuestionnaireResponse",
25
+ # "questionnaire": "http://example.org/fhir/Questionnaire/lyLU2wR22tC",
26
+ # "status": "completed",
27
+ # "subject": {"reference": "Location/dczh6Jfd4no"},
28
+ # "extension": [
29
+ # {"url": ".../d2-period",
30
+ # "extension": [{"url": "iso", "valueString": "202608"},
31
+ # {"url": "type", "valueCode": "Monthly"}]},
32
+ # {"url": ".../d2-attribute-option-combo",
33
+ # "valueCoding": {"code": "N7QFN41eTN8"}}],
34
+ # "item": [{"linkId": "GMd99K8gVut",
35
+ # "item": [{"linkId": "GMd99K8gVut.qNCMOhkoQju",
36
+ # "answer": [{"valueDecimal": 426.3}]}]}]}
37
+ #
38
+ # WHAT LEAVES. One `/api/dataValueSets` document per response:
39
+ #
40
+ # {"dataSet": "lyLU2wR22tC", "period": "202608", "orgUnit": "dczh6Jfd4no",
41
+ # "attributeOptionCombo": "N7QFN41eTN8",
42
+ # "dataValues": [{"dataElement": "GMd99K8gVut",
43
+ # "categoryOptionCombo": "qNCMOhkoQju", "value": "426.3"}]}
44
+ #
45
+ # WHICH CODE BECOMES WHAT. Five rules, and every one of them is a field a reader changes:
46
+ #
47
+ # questionnaire canonical, last segment -> dataSet
48
+ # D2Period extension, its `iso` slice -> period (a DHIS2 ISO period, not a date range)
49
+ # subject.reference "Location/<uid>" -> orgUnit
50
+ # D2AttributeOptionCombo coding's code -> attributeOptionCombo
51
+ # an answered linkId "<de>" or "<de>.<coc>" -> one dataValue, split on the dot
52
+ #
53
+ # That dotted linkId is the entire disaggregation mapping: a plain linkId is a data element,
54
+ # and a dotted one is one (dataElement, categoryOptionCombo) cell of it.
55
+ #
56
+ # WHERE A READER CHANGES IT. `questionnaire` names the form; on this facade that is a DHIS2
57
+ # data set uid. Point `fhir_base` at your own `d2w fhir serve`, set `questionnaire_uid` to the
58
+ # data set you generated a form from, and repoint the `dhis2` connection.
59
+ #
60
+ # WHAT THIS DOES NOT DO. It does not invent values for unanswered items -- an absent answer and
61
+ # a zero are different facts -- and it does not register completeness. Completeness is a second
62
+ # write, keyed by (data set, period, org unit, attribute option combo), and it must follow the
63
+ # values rather than accompany them.
64
+ #
65
+ # dg run --local examples/fhir/fhir-capture-bundle-to-data-values.yaml
66
+ # dg run --local examples/fhir/fhir-capture-bundle-to-data-values.yaml -p fhir_base=http://localhost:8096
67
+
68
+ format: dirigent/v1
69
+ kind: pipeline
70
+ code: fhir-capture-bundle-to-data-values
71
+ name: FHIR captures to DHIS2 data values
72
+ description: |
73
+ Read a page of `QuestionnaireResponse` captures off a `d2w fhir serve` facade and import
74
+ them into DHIS2 as aggregate data values, rehearsing the import first.
75
+
76
+ The capture pair is the vocabulary: a `Questionnaire` is the form definition generated from
77
+ DHIS2 metadata, and a `QuestionnaireResponse` is one submission against it. Every `linkId`
78
+ is a DHIS2 uid, and a dotted one, `<dataElement>.<categoryOptionCombo>`, is one
79
+ disaggregated cell.
80
+
81
+ tags: [fhir, dhis2, cross-boundary]
82
+
83
+ requires:
84
+ blocks:
85
+ - http.request
86
+ - transform.jq
87
+ - validate.schema
88
+ - dhis2.data_value_set_import
89
+
90
+ connections:
91
+ # The facade. It takes no credential in the default single-user setup, so the connection
92
+ # carries only where it is; an instance running behind auth gets basic_username here.
93
+ fhir-facade:
94
+ kind: http
95
+ config:
96
+ base_url: http://localhost:8095
97
+ timeout: 30s
98
+ # The instance the values land in. This is the demo, which is for reading: the import below
99
+ # is a dry run against it, and repointing this connection is what makes the real one real.
100
+ dhis2-demo:
101
+ kind: dhis2
102
+ config:
103
+ base_url: https://play.im.dhis2.org/stable-2-43-1
104
+ basic_username: admin
105
+ basic_password: district
106
+ timeout: 60s
107
+
108
+ params:
109
+ type: object
110
+ properties:
111
+ fhir_base:
112
+ type: string
113
+ description: The facade to read captures from.
114
+ default: http://localhost:8095
115
+ questionnaire_uid:
116
+ type: string
117
+ description: The aggregate form, which on this facade is the DHIS2 data set uid.
118
+ default: lyLU2wR22tC
119
+ page_size:
120
+ type: integer
121
+ description: How many captures one page holds; the facade caps this at 500.
122
+ default: 50
123
+
124
+ schemas:
125
+ # The gate is the pack's own formats, not a shape check: dhis2-uid refuses anything that is
126
+ # not an 11-character DHIS2 uid, dhis2-period anything that is not an ISO period. A capture
127
+ # whose linkId was hand-edited fails here rather than at the instance.
128
+ dhis2-data-value-set:
129
+ type: object
130
+ required: [dataSet, period, orgUnit, dataValues]
131
+ properties:
132
+ dataSet: { type: string, format: dhis2-uid }
133
+ period: { type: string, format: dhis2-period }
134
+ orgUnit: { type: string, format: dhis2-uid }
135
+ attributeOptionCombo: { type: string, format: dhis2-uid }
136
+ dataValues:
137
+ type: array
138
+ items:
139
+ type: object
140
+ required: [dataElement, value]
141
+ properties:
142
+ dataElement: { type: string, format: dhis2-uid }
143
+ categoryOptionCombo: { type: string, format: dhis2-uid }
144
+ value: { type: string }
145
+
146
+ steps:
147
+ # The facade's one paged search. `questionnaire` is the canonical of the form, so this asks
148
+ # for every capture against one data set's form and nothing else. There is no _lastUpdated
149
+ # here -- the store is a receipt spool, and its cursor is the opaque `page` token on the
150
+ # Bundle's next link, which is what fhir-nightly-window-sync.yaml pages a real server by.
151
+ read_captures:
152
+ block: http.request
153
+ config:
154
+ connection: fhir-facade
155
+ path: /QuestionnaireResponse
156
+ method: GET
157
+ query:
158
+ questionnaire: "http://example.org/fhir/Questionnaire/${params.questionnaire_uid}"
159
+ _count: "${params.page_size}"
160
+ headers:
161
+ Accept: application/fhir+json
162
+
163
+ # One Bundle in, one DHIS2 document out. The whole response has to be visible at once,
164
+ # because the envelope comes from the extensions and the subject while the values come from
165
+ # the items, so this is one program rather than a map over the entries.
166
+ #
167
+ # Only `completed` responses are translated. An aggregate response that is still in-progress
168
+ # is a half-filled form, and importing one would claim a reporting period is answered.
169
+ to_data_value_set:
170
+ block: transform.jq
171
+ depends_on: [read_captures]
172
+ config:
173
+ input: "${steps.read_captures.output.body}"
174
+ program: |
175
+ [.entry[]?.resource | select(.status == "completed")]
176
+ | .[0] as $r
177
+ | ($r.extension[] | select(.url | endswith("/d2-period")).extension[]
178
+ | select(.url == "iso").valueString) as $period
179
+ # Items nest one level per section and one per disaggregated data element, so the
180
+ # leaves are found by recursion. An item carrying no answer is skipped entirely: a
181
+ # question that was asked and not answered must not import a zero.
182
+ | {dataSet: ($r.questionnaire | split("/") | last),
183
+ period: $period,
184
+ orgUnit: ($r.subject.reference | ltrimstr("Location/")),
185
+ attributeOptionCombo: (
186
+ [$r.extension[] | select(.url | endswith("/d2-attribute-option-combo"))][0]
187
+ | .valueCoding.code),
188
+ dataValues: [
189
+ $r.item[] | recurse(.item[]?)
190
+ | select((.answer // []) | length > 0)
191
+ | (.linkId | split(".")) as $key
192
+ | (.answer[0] | (.valueDecimal // .valueInteger // .valueString // .valueBoolean)) as $v
193
+ | {dataElement: $key[0], value: ($v | tostring)}
194
+ + (if ($key | length) > 1 then {categoryOptionCombo: $key[1]} else {} end)]}
195
+ | del(.attributeOptionCombo | select(. == null))
196
+
197
+ # The gate is also the waypoint: every step past it references the validated value, so the
198
+ # import provably received a document whose ids and period the formats admit.
199
+ gate:
200
+ block: validate.schema
201
+ depends_on: [to_data_value_set]
202
+ config:
203
+ input: "${steps.to_data_value_set.output.value}"
204
+ schema: dhis2-data-value-set
205
+
206
+ # Dry run first, always. On /api/dataValueSets a dry run is dryRun=true; on /api/tracker the
207
+ # same intention is importMode=VALIDATE, and on /api/metadata it is importMode=VALIDATE too.
208
+ # Reaching for the wrong spelling does not fail -- it imports.
209
+ rehearse:
210
+ block: dhis2.data_value_set_import
211
+ depends_on: [gate]
212
+ config:
213
+ connection: dhis2-demo
214
+ data_values: "${steps.gate.output.value}"
215
+ dry_run: true
216
+ # ALL means one refused value refuses the whole document, which is what a rehearsal
217
+ # wants: the answer is "would all of this land", not "how much of it".
218
+ atomic_mode: ALL
219
+
220
+ # Second, and only because the rehearsal passed. The default all_success edge is what
221
+ # enforces that: a refused rehearsal leaves this skipped rather than writing anyway.
222
+ import:
223
+ block: dhis2.data_value_set_import
224
+ depends_on: [rehearse]
225
+ config:
226
+ connection: dhis2-demo
227
+ data_values: "${steps.gate.output.value}"
228
+ dry_run: false
229
+ import_strategy: CREATE_AND_UPDATE
230
+ atomic_mode: ALL
@@ -0,0 +1,222 @@
1
+ # The code mapping fetched from the server at run time instead of written into the document.
2
+ #
3
+ # THE FLOW, END TO END. Fetch one ConceptMap off the FHIR server, turn its groups into a flat
4
+ # lookup, and apply that lookup to the coded answers of a capture -- so the document carries no
5
+ # codes at all and a mapping published today is in force tonight.
6
+ #
7
+ # THE POINT. questionnaire-response-to-data-values.yaml holds its table in params, which is
8
+ # right when the mapping is a decision your project made. But a terminology mapping is not that:
9
+ # it belongs to whoever publishes the terminology, and FHIR has a resource for exactly this.
10
+ # A ConceptMap is a published, versioned, addressable translation between two code systems, and
11
+ # a pipeline that fetches it can never drift from what the server means. When an option is
12
+ # added to a DHIS2 option set, the regenerated ConceptMap has it, and NOTHING here changes.
13
+ #
14
+ # WHAT ARRIVES -- the map. `d2w fhir serve` publishes one ConceptMap per DHIS2 option set,
15
+ # `d2-os-<optionSetUid>-cm`, with TWO groups distinguished by `group.target`:
16
+ #
17
+ # {"resourceType": "ConceptMap", "id": "d2-os-cshsFEran9U-cm",
18
+ # "identifier": {"system": "http://dhis2.org/fhir/id/option-set", "value": "cshsFEran9U"},
19
+ # "group": [
20
+ # {"source": ".../CodeSystem/d2-os-cshsFEran9U-cs",
21
+ # "target": "http://dhis2.org/fhir/id/option",
22
+ # "element": [{"code": "YQe3PFbATvz", "display": "NVP only",
23
+ # "target": [{"code": "YQe3PFbATvz", "equivalence": "equal"}]}]},
24
+ # {"source": ".../CodeSystem/d2-os-cshsFEran9U-cs",
25
+ # "target": "http://dhis2.org/fhir/id/option-code",
26
+ # "element": [{"code": "YQe3PFbATvz",
27
+ # "target": [{"code": "NVP-only", "equivalence": "equal"}]}]}]}
28
+ #
29
+ # The uid group is always complete; the code group only covers options DHIS2 gave a code, so
30
+ # it may be partial or missing entirely. `target_system` below picks which group is in force,
31
+ # and picking the uid group is what makes this safe by default.
32
+ #
33
+ # WHAT ARRIVES -- the capture. A coded answer is a valueCoding naming a concept of the option
34
+ # set's own CodeSystem, and it is the `code` that has to be translated:
35
+ #
36
+ # {"linkId": "s46m5MS0hxu",
37
+ # "answer": [{"valueCoding": {"system": ".../d2-os-cshsFEran9U-cs",
38
+ # "code": "dTVOMl2RHg3", "display": "AZT and NVP"}}]}
39
+ #
40
+ # WHAT LEAVES. The usual `/api/dataValueSets` document, its values translated:
41
+ #
42
+ # {"dataSet": "lyLU2wR22tC", "period": "202608", "orgUnit": "dczh6Jfd4no",
43
+ # "dataValues": [{"dataElement": "s46m5MS0hxu", "value": "dTVOMl2RHg3"}]}
44
+ #
45
+ # WHERE A READER CHANGES IT. `concept_map_id` names the map, and `target_system` picks which
46
+ # of its groups is authoritative -- `.../id/option` for DHIS2 uids, `.../id/option-code` for
47
+ # DHIS2 codes. Neither the source codes nor the targets appear anywhere in this file.
48
+ #
49
+ # THE OTHER WAY. The facade also answers `GET /ConceptMap/$translate?system=&code=`, one
50
+ # concept per call, returning Parameters with a `result` boolean and a `match` part. That is
51
+ # the right shape for a handful of lookups and the wrong one for a bundle: fetching the map
52
+ # once and applying it in-process is one request rather than one per answer.
53
+ #
54
+ # dg run --local examples/fhir/fhir-conceptmap-driven-mapping.yaml
55
+ # dg run --local examples/fhir/fhir-conceptmap-driven-mapping.yaml \
56
+ # -p target_system=http://dhis2.org/fhir/id/option-code
57
+
58
+ format: dirigent/v1
59
+ kind: pipeline
60
+ code: fhir-conceptmap-driven-mapping
61
+ name: A ConceptMap drives the mapping
62
+ description: |
63
+ Fetch a `ConceptMap` from the FHIR server, flatten it into a lookup, and translate a
64
+ capture's coded answers through it -- so no code appears in this document at all.
65
+
66
+ A `d2w fhir serve` option-set map carries two groups distinguished by `group.target`: one to
67
+ the DHIS2 option uid (always complete) and one to the DHIS2 option code (only where the
68
+ option has one). `target_system` picks which is in force.
69
+
70
+ tags: [fhir, dhis2, cross-boundary]
71
+
72
+ requires:
73
+ blocks:
74
+ - http.request
75
+ - value.const
76
+ - transform.jq
77
+ - dhis2.data_value_set_import
78
+
79
+ connections:
80
+ fhir-facade:
81
+ kind: http
82
+ config:
83
+ base_url: http://localhost:8095
84
+ timeout: 30s
85
+ # The public DHIS2 play server, carried inline so this document runs standalone. Against an
86
+ # instance you own, name a connection that instance holds instead of carrying one.
87
+ dhis2-demo:
88
+ kind: dhis2
89
+ config:
90
+ base_url: https://play.im.dhis2.org/stable-2-43-1
91
+ basic_username: admin
92
+ basic_password: district
93
+ timeout: 60s
94
+
95
+ params:
96
+ type: object
97
+ properties:
98
+ concept_map_id:
99
+ type: string
100
+ description: The published map. On this facade, d2-os-<optionSetUid>-cm.
101
+ default: d2-os-cshsFEran9U-cm
102
+ target_system:
103
+ type: string
104
+ description: Which group of the map is authoritative -- the uid group or the code group.
105
+ default: http://dhis2.org/fhir/id/option
106
+ data_set:
107
+ type: string
108
+ default: lyLU2wR22tC
109
+ org_unit:
110
+ type: string
111
+ default: dczh6Jfd4no
112
+ period:
113
+ type: string
114
+ default: "202608"
115
+
116
+ steps:
117
+ # A read by id, not a search: a ConceptMap's id is stable and the read is byte-faithful to
118
+ # what the project published. `GET /ConceptMap?identifier=<option-set system>|<uid>` is the
119
+ # same map found the other way round, by the DHIS2 option set it came from.
120
+ fetch_map:
121
+ block: http.request
122
+ config:
123
+ connection: fhir-facade
124
+ path: "/ConceptMap/${params.concept_map_id}"
125
+ method: GET
126
+ headers:
127
+ Accept: application/fhir+json
128
+
129
+ # Groups in, one flat object out: source code -> target code. Only groups whose target is
130
+ # the system asked for contribute, so switching target_system switches the whole mapping
131
+ # without touching this program. `equivalence` is kept alongside the code because a map is
132
+ # allowed to say `wider` or `inexact`, and a translation that quietly used one of those
133
+ # would be reporting a different fact than the one that was captured.
134
+ build_lookup:
135
+ block: transform.jq
136
+ depends_on: [fetch_map]
137
+ config:
138
+ input:
139
+ map: "${steps.fetch_map.output.body}"
140
+ target_system: "${params.target_system}"
141
+ program: |
142
+ . as {$map, $target_system}
143
+ | [$map.group[] | select(.target == $target_system) | .element[]
144
+ | {key: .code,
145
+ value: {code: .target[0].code, equivalence: (.target[0].equivalence // "equal")}}]
146
+ | from_entries
147
+ | if . == {} then
148
+ error("\($target_system) is not a target group of this ConceptMap")
149
+ else . end
150
+
151
+ # The capture, inline so the document runs with nothing set up. Its answers are codes of the
152
+ # option set's own CodeSystem -- which is exactly the source side of the map fetched above.
153
+ receive:
154
+ block: value.const
155
+ config:
156
+ value:
157
+ - linkId: s46m5MS0hxu
158
+ code: dTVOMl2RHg3
159
+ - linkId: p1MDHOT6ENy
160
+ code: YQe3PFbATvz
161
+ # Coded against something this map has never heard of. It must not be invented into a
162
+ # value, and it must not disappear without a word either.
163
+ - linkId: cZnQDuF3IDz
164
+ code: NotAMappedCode
165
+
166
+ # The join. Both sides ride in on `input` -- the lookup as a value, not as text spliced into
167
+ # the program: a ${...} reference resolves into a jq program as Python's own rendering of the
168
+ # object, which is not jq syntax and does not compile. A program takes its data through
169
+ # input, always, and this is the step where that rule bites.
170
+ translate_answers:
171
+ block: transform.jq
172
+ depends_on: [build_lookup, receive]
173
+ config:
174
+ input:
175
+ answers: "${steps.receive.output.value}"
176
+ lookup: "${steps.build_lookup.output.value}"
177
+ program: |
178
+ . as {$answers, $lookup}
179
+ | [$answers[]
180
+ | . as $answer
181
+ | $lookup[$answer.code] as $hit
182
+ | {linkId: $answer.linkId,
183
+ source: $answer.code,
184
+ translated: $hit.code,
185
+ equivalence: $hit.equivalence}]
186
+
187
+ # Only `equal` becomes a data value. Anything else -- a wider match, a code the map does not
188
+ # carry -- is reported instead of imported, which is the whole reason the equivalence was
189
+ # carried this far.
190
+ to_data_values:
191
+ block: transform.jq
192
+ depends_on: [translate_answers]
193
+ config:
194
+ input:
195
+ answers: "${steps.translate_answers.output.value}"
196
+ data_set: "${params.data_set}"
197
+ org_unit: "${params.org_unit}"
198
+ period: "${params.period}"
199
+ program: |
200
+ . as {$answers, $data_set, $org_unit, $period}
201
+ | {document: {
202
+ dataSet: $data_set,
203
+ period: $period,
204
+ orgUnit: $org_unit,
205
+ dataValues: [$answers[]
206
+ | select(.translated != null and .equivalence == "equal")
207
+ | {dataElement: .linkId, value: .translated}]},
208
+ untranslated: [$answers[] | select(.translated == null) | .source],
209
+ inexact: [$answers[] | select(.translated != null and .equivalence != "equal")]}
210
+
211
+ # The translated set goes to DHIS2 as a rehearsal: dry_run is fixed on, because the point of
212
+ # this example is the mapping, and the import step is here so the whole leg is exercised
213
+ # against a real instance without writing to a shared one. Only the document is sent; the
214
+ # untranslated and inexact lists stay on the step above, where a reader looks for them.
215
+ rehearse:
216
+ block: dhis2.data_value_set_import
217
+ depends_on: [to_data_values]
218
+ config:
219
+ connection: dhis2-demo
220
+ data_values: ${steps.to_data_values.output.value.document}
221
+ dry_run: true
222
+ import_strategy: CREATE_AND_UPDATE