dirigent-integration 0.27.0__tar.gz → 0.28.0__tar.gz

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-0.27.0 → dirigent_integration-0.28.0}/PKG-INFO +7 -6
  2. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/README.md +5 -4
  3. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/pyproject.toml +2 -2
  4. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/pyproject.toml.orig +2 -2
  5. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/dhis2-analytics-to-csv-report.yaml +4 -1
  6. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/dhis2-export-to-s3.yaml +1 -1
  7. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/dhis2-tracker-weekly-window.yaml +1 -1
  8. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/dhis2-values-per-org-unit-to-parquet.yaml +10 -6
  9. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/dhis2-values-to-parquet.yaml +4 -1
  10. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/README.md +1 -1
  11. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/dhis2-to-fhir-observations.yaml +8 -4
  12. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-capture-bundle-to-data-values.yaml +21 -13
  13. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-conceptmap-driven-mapping.yaml +5 -2
  14. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-encounter-to-event.yaml +8 -6
  15. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-measure-report-to-analytics-check.yaml +19 -7
  16. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-nightly-window-sync.yaml +27 -17
  17. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-patient-to-tracked-entity.yaml +4 -2
  18. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-questionnaire-response-to-data-values.yaml +11 -8
  19. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/fhir/fhir-subscription-to-dhis2.yaml +5 -2
  20. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/README.md +1 -1
  21. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/csv-drop-to-data-values.yaml +5 -2
  22. dirigent_integration-0.27.0/src/dirigent_integration/shelves/inbound/endpoint-payload-to-tracker-event.yaml → dirigent_integration-0.28.0/src/dirigent_integration/shelves/inbound/endpoint-payload-to-data-values.yaml +10 -7
  23. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/fan-out-per-facility-import.yaml +8 -6
  24. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/fhir-observations-to-data-values.yaml +10 -7
  25. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/http-json-to-data-values.yaml +6 -7
  26. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/outside-to-dhis2-with-checks.yaml +5 -2
  27. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/parquet-lakehouse-to-dhis2.yaml +5 -2
  28. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/inbound/weekly-window-pull-and-import.yaml +5 -3
  29. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/parquet-to-dhis2-import.yaml +1 -1
  30. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/LICENSE +0 -0
  31. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/__init__.py +0 -0
  32. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/py.typed +0 -0
  33. {dirigent_integration-0.27.0 → dirigent_integration-0.28.0}/src/dirigent_integration/shelves/dhis2-metadata-snapshot.yaml +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirigent-integration
3
- Version: 0.27.0
3
+ Version: 0.28.0
4
4
  Summary: The control center for the dirigent plugin ecosystem: assemble every pack, run everyone's tests and examples together.
5
5
  License-Expression: LicenseRef-Proprietary
6
6
  License-File: LICENSE
7
- Requires-Dist: dirigent-plugin==0.27.0
7
+ Requires-Dist: dirigent-plugin==0.28.0
8
8
  Requires-Python: >=3.13
9
9
  Description-Content-Type: text/markdown
10
10
 
@@ -139,15 +139,16 @@ Most of them read a `d2w fhir serve` facade, which publishes one DHIS2 instance
139
139
  endpoint and takes captures back; where a general-purpose FHIR server is the point -- an
140
140
  `Encounter`, an `Observation`, a `Subscription`, a `_lastUpdated` search, none of which that
141
141
  facade serves -- the default is a public HAPI R4 sandbox instead, and the document's header
142
- says so. Every write is a dry run: `dryRun=true` on `/api/dataValueSets`, `importMode=VALIDATE`
143
- on `/api/tracker`.
142
+ says so. Every write to DHIS2 is a dry run: `dryRun=true` on `/api/dataValueSets`,
143
+ `importMode=VALIDATE` on `/api/tracker`. The one real write as shipped is
144
+ `dhis2-to-fhir-observations`, which publishes to that public sandbox.
144
145
 
145
146
  [`examples/fhir/README.md`](examples/fhir/README.md) has the one-line table and the mapping
146
147
  vocabulary the shelf is built on -- subject, capture, ConceptMap.
147
148
 
148
149
  - **`examples/fhir/fhir-capture-bundle-to-data-values.yaml`** -- the capture pair end to end: a page
149
150
  of captures pulled off the facade, translated, gated on the pack's own id and period formats,
150
- rehearsed and imported.
151
+ rehearsed and imported, the import a dry run too until `commit_dry_run` is false.
151
152
  - **`examples/fhir/fhir-questionnaire-response-to-data-values.yaml`** -- the general case, where a
152
153
  `linkId` is a form designer's name and the lookup is the integration.
153
154
  - **`examples/fhir/fhir-patient-to-tracked-entity.yaml`** -- the `Patient` register folded into an
@@ -205,7 +206,7 @@ export GITHUB_TOKEN=$(gh auth token) # only if a component repository is priva
205
206
  ### Running the stack
206
207
 
207
208
  ```sh
208
- cp .env.example .env # set DIRIGENT_SECRET_KEY and DIRIGENT_BOOTSTRAP_ADMIN_PASSWORD
209
+ cp .env.example .env # set DIRIGENT_SECRET_KEY, DIRIGENT_BOOTSTRAP_ADMIN_PASSWORD and POSTGRES_PASSWORD
209
210
  make up
210
211
  ```
211
212
 
@@ -129,15 +129,16 @@ Most of them read a `d2w fhir serve` facade, which publishes one DHIS2 instance
129
129
  endpoint and takes captures back; where a general-purpose FHIR server is the point -- an
130
130
  `Encounter`, an `Observation`, a `Subscription`, a `_lastUpdated` search, none of which that
131
131
  facade serves -- the default is a public HAPI R4 sandbox instead, and the document's header
132
- says so. Every write is a dry run: `dryRun=true` on `/api/dataValueSets`, `importMode=VALIDATE`
133
- on `/api/tracker`.
132
+ says so. Every write to DHIS2 is a dry run: `dryRun=true` on `/api/dataValueSets`,
133
+ `importMode=VALIDATE` on `/api/tracker`. The one real write as shipped is
134
+ `dhis2-to-fhir-observations`, which publishes to that public sandbox.
134
135
 
135
136
  [`examples/fhir/README.md`](examples/fhir/README.md) has the one-line table and the mapping
136
137
  vocabulary the shelf is built on -- subject, capture, ConceptMap.
137
138
 
138
139
  - **`examples/fhir/fhir-capture-bundle-to-data-values.yaml`** -- the capture pair end to end: a page
139
140
  of captures pulled off the facade, translated, gated on the pack's own id and period formats,
140
- rehearsed and imported.
141
+ rehearsed and imported, the import a dry run too until `commit_dry_run` is false.
141
142
  - **`examples/fhir/fhir-questionnaire-response-to-data-values.yaml`** -- the general case, where a
142
143
  `linkId` is a form designer's name and the lookup is the integration.
143
144
  - **`examples/fhir/fhir-patient-to-tracked-entity.yaml`** -- the `Patient` register folded into an
@@ -195,7 +196,7 @@ export GITHUB_TOKEN=$(gh auth token) # only if a component repository is priva
195
196
  ### Running the stack
196
197
 
197
198
  ```sh
198
- cp .env.example .env # set DIRIGENT_SECRET_KEY and DIRIGENT_BOOTSTRAP_ADMIN_PASSWORD
199
+ cp .env.example .env # set DIRIGENT_SECRET_KEY, DIRIGENT_BOOTSTRAP_ADMIN_PASSWORD and POSTGRES_PASSWORD
199
200
  make up
200
201
  ```
201
202
 
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "dirigent-integration"
3
- version = "0.27.0"
3
+ version = "0.28.0"
4
4
  description = "The control center for the dirigent plugin ecosystem: assemble every pack, run everyone's tests and examples together."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  license = "LicenseRef-Proprietary"
8
8
  license-files = ["LICENSE"]
9
- dependencies = ["dirigent-plugin==0.27.0"]
9
+ dependencies = ["dirigent-plugin==0.28.0"]
10
10
 
11
11
  [project.entry-points."dirigent.plugins.v1"]
12
12
  integration = "dirigent_integration:plugin"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dirigent-integration"
3
- version = "0.27.0"
3
+ version = "0.28.0"
4
4
  description = "The control center for the dirigent plugin ecosystem: assemble every pack, run everyone's tests and examples together."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -10,7 +10,7 @@ license-files = ["LICENSE"]
10
10
  # A published install carries the cross-boundary shelves and nothing else: each document
11
11
  # names the packs it needs under requires, and the Examples screen marks what is missing.
12
12
  dependencies = [
13
- "dirigent-plugin==0.27.0",
13
+ "dirigent-plugin==0.28.0",
14
14
  ]
15
15
 
16
16
  # dirigent-testing is dev-only: the packs' suites import it, and nothing at runtime does,
@@ -38,7 +38,10 @@ format: dirigent/v1
38
38
  kind: pipeline
39
39
  code: dhis2-analytics-to-csv-report
40
40
  name: An analytics grid as a csv report
41
- description: Query DHIS2 analytics, flatten the grid into rows, write a csv to S3, and announce it.
41
+ description: |
42
+ Turn a DHIS2 analytics query into a csv report in S3 and announce it.
43
+
44
+ The analytics grid is flattened into rows before the csv is written.
42
45
 
43
46
  tags: [dhis2, s3, csv, cross-boundary]
44
47
 
@@ -24,7 +24,7 @@ format: dirigent/v1
24
24
  kind: pipeline
25
25
  code: dhis2-export-to-s3
26
26
  name: Export a data value set to S3
27
- description: Read a DHIS2 data value set, write it into object storage, then archive a dated copy.
27
+ description: Export a DHIS2 data value set to object storage and archive a dated copy.
28
28
 
29
29
  tags: [dhis2, s3, cross-boundary]
30
30
 
@@ -46,7 +46,7 @@ format: dirigent/v1
46
46
  kind: pipeline
47
47
  code: dhis2-tracker-weekly-window
48
48
  name: A week of tracker events, as parquet
49
- description: Read the tracker events of the week that just closed and archive them as parquet.
49
+ description: Archive the tracker events of the week that just closed as parquet.
50
50
 
51
51
  tags: [dhis2, parquet, s3, schedule, cross-boundary]
52
52
 
@@ -9,11 +9,11 @@
9
9
  # WHY ONE TABLE AND NOT ONE FILE PER ORG UNIT. A value moves through step outputs, and a value
10
10
  # leaves a run through storage.write; a block does not write storage itself. A fan-out step
11
11
  # stores one output -- the list of its items' outputs, in item order, with a failed item absent
12
- # from it -- and `for_each` is expanded when the run is created, so it can read params, run and
13
- # item and never an upstream step's output. Item three of one step therefore cannot ask item
14
- # three of another what it produced: a second fan cannot pick up the export its twin made. So
15
- # the fan-in is a single step over every export, and the org unit is read out of each data
16
- # value where the join key can be seen, rather than out of a storage path that used to carry it.
12
+ # from it. A second fan could adopt the export's grid with `for_each: ${steps.export.items}` and
13
+ # write one file per org unit, which is what dhis2-export-per-org-unit.yaml in the dhis2 pack
14
+ # does. A query over the whole extraction wants one table, so here the fan-in is a single step
15
+ # over every export, and the org unit is read out of each data value where the join key can be
16
+ # seen, rather than out of a storage path.
17
17
  #
18
18
  # What happens, hop by hop:
19
19
  #
@@ -51,7 +51,11 @@ format: dirigent/v1
51
51
  kind: pipeline
52
52
  code: dhis2-values-per-org-unit-to-parquet
53
53
  name: A parquet table over a batch of org units
54
- description: Export a data value set per organisation unit, flatten the batch into one parquet table, and manifest what landed.
54
+ description: |
55
+ Export DHIS2 values per org unit into one parquet table, with a manifest.
56
+
57
+ Each organisation unit gets its own data value set export, the batch is flattened into one
58
+ table, and the manifest says what landed.
55
59
 
56
60
  tags: [dhis2, parquet, s3, cross-boundary]
57
61
 
@@ -21,7 +21,10 @@ format: dirigent/v1
21
21
  kind: pipeline
22
22
  code: dhis2-values-to-parquet
23
23
  name: Export a data value set as parquet
24
- description: Read a DHIS2 data value set into object storage, then re-encode it as parquet beside it.
24
+ description: |
25
+ Save a DHIS2 data value set to object storage and convert it to parquet.
26
+
27
+ The parquet file is written beside the stored data value set.
25
28
 
26
29
  tags: [dhis2, parquet, s3, cross-boundary]
27
30
 
@@ -53,7 +53,7 @@ option set is in the regenerated map, and the pipeline that reads it needs no ed
53
53
 
54
54
  | File | What it teaches |
55
55
  | --- | --- |
56
- | [fhir-capture-bundle-to-data-values.yaml](fhir-capture-bundle-to-data-values.yaml) | The capture pair end to end: a page of `QuestionnaireResponse` captures pulled off the facade, translated to `/api/dataValueSets`, gated on the pack's `dhis2-uid` and `dhis2-period` formats, rehearsed and then imported. |
56
+ | [fhir-capture-bundle-to-data-values.yaml](fhir-capture-bundle-to-data-values.yaml) | The capture pair end to end: a page of `QuestionnaireResponse` captures pulled off the facade, translated to `/api/dataValueSets`, gated on the pack's `dhis2-uid` and `dhis2-period` formats, rehearsed and then imported, the import a dry run too until `commit_dry_run` is false. |
57
57
  | [fhir-questionnaire-response-to-data-values.yaml](fhir-questionnaire-response-to-data-values.yaml) | The general case, where a `linkId` is a form designer's name rather than a DHIS2 uid: an explicit lookup in params, and every unmapped answer counted rather than dropped in silence. |
58
58
  | [fhir-patient-to-tracked-entity.yaml](fhir-patient-to-tracked-entity.yaml) | The `Patient` register searched by identifier and folded into an `/api/tracker` registration -- and why the write is an `http.request` on the `dhis2` connection, because `dhis2.tracker` reads and does not write. |
59
59
  | [fhir-encounter-to-event.yaml](fhir-encounter-to-event.yaml) | Cardinalities that do not line up: one `Encounter` plus every `Observation` naming it, folded into the single program stage event DHIS2 wants, with the LOINC codes mapped and the rest reported. |
@@ -1,7 +1,8 @@
1
1
  # The other direction: DHIS2 aggregate values published as a FHIR Bundle of Observations.
2
2
  #
3
3
  # THE FLOW, END TO END. Export one data value set from DHIS2, turn every data value into an
4
- # Observation, wrap them in a transaction Bundle, and POST that Bundle to a FHIR server.
4
+ # Observation, wrap them in a transaction Bundle, and POST that Bundle to a FHIR server. As
5
+ # written that server is a public HAPI sandbox, and the POST is real: this one is not a rehearsal.
5
6
  #
6
7
  # WHY IT IS HERE. Every other document on this shelf reads FHIR and writes DHIS2. This one is
7
8
  # the mirror, and a reader who has both in front of them can see what is symmetric and what is
@@ -21,7 +22,8 @@
21
22
  # WHAT LEAVES. A transaction Bundle, one entry per data value:
22
23
  #
23
24
  # {"resourceType": "Bundle", "type": "transaction",
24
- # "entry": [{"request": {"method": "POST", "url": "Observation"},
25
+ # "entry": [{"request": {"method": "PUT",
26
+ # "url": "Observation?identifier=http://dhis2.org/fhir/id/data-value|s46m5MS0hxu.Prlt0C1RF0s-202608-vSbt6cezomG"},
25
27
  # "resource": {
26
28
  # "resourceType": "Observation", "status": "final",
27
29
  # "identifier": [{"system": "http://dhis2.org/fhir/id/data-value",
@@ -61,8 +63,10 @@ kind: pipeline
61
63
  code: dhis2-to-fhir-observations
62
64
  name: DHIS2 data values to FHIR Observations
63
65
  description: |
64
- Export a DHIS2 data value set and publish it as a transaction `Bundle` of `Observation`
65
- resources on a FHIR server.
66
+ Publish DHIS2 data values to a FHIR server as a bundle of Observations.
67
+
68
+ A DHIS2 data value set is exported and published as a transaction `Bundle` of `Observation`
69
+ resources.
66
70
 
67
71
  The outbound half of this shelf. Each entry is a conditional update keyed on the aggregate
68
72
  cell's natural key -- `(dataElement.categoryOptionCombo, period, orgUnit)` -- so publishing
@@ -3,7 +3,8 @@
3
3
  # THE FLOW, END TO END. `d2w fhir serve` publishes one DHIS2 instance as a FHIR endpoint and
4
4
  # takes captures back. This document reads the captures it is holding for one aggregate form,
5
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.
6
+ # and period formats, rehearses the import as a dry run, and only then sends it for real -- which
7
+ # is a dry run too until `commit_dry_run` is set to false, because the demo is shared.
7
8
  #
8
9
  # THE CAPTURE PAIR. The facade's whole capture surface is two resource types, and the pair is
9
10
  # the thing to learn:
@@ -54,8 +55,9 @@
54
55
  # and a dotted one is one (dataElement, categoryOptionCombo) cell of it.
55
56
  #
56
57
  # 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.
58
+ # data set uid. Point the `fhir-facade` connection at your own `d2w fhir serve`, set `questionnaire_uid` to the
59
+ # data set you generated a form from, repoint the `dhis2` connection, and turn `commit_dry_run`
60
+ # off against an instance of your own.
59
61
  #
60
62
  # WHAT THIS DOES NOT DO. It does not invent values for unanswered items -- an absent answer and
61
63
  # a zero are different facts -- and it does not register completeness. Completeness is a second
@@ -63,15 +65,19 @@
63
65
  # values rather than accompany them.
64
66
  #
65
67
  # 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
68
+ # dg run --local examples/fhir/fhir-capture-bundle-to-data-values.yaml -p page_size=10
67
69
 
68
70
  format: dirigent/v1
69
71
  kind: pipeline
70
72
  code: fhir-capture-bundle-to-data-values
71
73
  name: FHIR captures to DHIS2 data values
72
74
  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
+ Import the first completed FHIR form response on a page into DHIS2.
76
+
77
+ A page of `QuestionnaireResponse` captures is read off a `d2w fhir serve` facade. The first
78
+ completed one becomes aggregate data values, and the import is rehearsed first.
79
+ `commit_dry_run` defaults to true, so the second import is a rehearsal too until it is set to
80
+ false against an instance of your own.
75
81
 
76
82
  The capture pair is the vocabulary: a `Questionnaire` is the form definition generated from
77
83
  DHIS2 metadata, and a `QuestionnaireResponse` is one submission against it. Every `linkId`
@@ -95,8 +101,9 @@ connections:
95
101
  config:
96
102
  base_url: http://localhost:8095
97
103
  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.
104
+ # The instance the values land in. This is the demo, which is for reading: both imports below
105
+ # are dry runs against it, and repointing this connection and setting commit_dry_run to false
106
+ # is what makes the real one real.
100
107
  dhis2-demo:
101
108
  kind: dhis2
102
109
  config:
@@ -108,10 +115,6 @@ connections:
108
115
  params:
109
116
  type: object
110
117
  properties:
111
- fhir_base:
112
- type: string
113
- description: The facade to read captures from.
114
- default: http://localhost:8095
115
118
  questionnaire_uid:
116
119
  type: string
117
120
  description: The aggregate form, which on this facade is the DHIS2 data set uid.
@@ -120,6 +123,10 @@ params:
120
123
  type: integer
121
124
  description: How many captures one page holds; the facade caps this at 500.
122
125
  default: 50
126
+ commit_dry_run:
127
+ type: boolean
128
+ description: Whether the second import is still a rehearsal. False makes it real.
129
+ default: true
123
130
 
124
131
  schemas:
125
132
  # The gate is the pack's own formats, not a shape check: dhis2-uid refuses anything that is
@@ -219,12 +226,13 @@ steps:
219
226
 
220
227
  # Second, and only because the rehearsal passed. The default all_success edge is what
221
228
  # enforces that: a refused rehearsal leaves this skipped rather than writing anyway.
229
+ # commit_dry_run keeps this a rehearsal as well, so the shared demo is never written to.
222
230
  import:
223
231
  block: dhis2.data_value_set_import
224
232
  depends_on: [rehearse]
225
233
  config:
226
234
  connection: dhis2-demo
227
235
  data_values: "${steps.gate.output.value}"
228
- dry_run: false
236
+ dry_run: "${params.commit_dry_run}"
229
237
  import_strategy: CREATE_AND_UPDATE
230
238
  atomic_mode: ALL
@@ -60,8 +60,11 @@ kind: pipeline
60
60
  code: fhir-conceptmap-driven-mapping
61
61
  name: A ConceptMap drives the mapping
62
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.
63
+ Fetch a code mapping from a FHIR server and use it to translate form answers.
64
+
65
+ A `ConceptMap` is flattened into a lookup and a capture's coded answers are translated through
66
+ it, so the mapping itself is not written in this document. The translated set goes to DHIS2 as
67
+ a dry run.
65
68
 
66
69
  A `d2w fhir serve` option-set map carries two groups distinguished by `group.target`: one to
67
70
  the DHIS2 option uid (always complete) and one to the DHIS2 option code (only where the
@@ -8,7 +8,7 @@
8
8
  # DHIS2 as forms and captures, so its whole clinical surface is the capture pair, and asking
9
9
  # it for an Observation answers an OperationOutcome with code `not-supported`. Encounters come
10
10
  # from the other kind of FHIR server: a hospital's own, an OpenMRS, a HAPI. That is the point
11
- # of this example, so `fhir_base` defaults to the public HAPI R4 test server. It is a shared
11
+ # of this example, so the `fhir-clinical` connection points at the public HAPI R4 test server. It is a shared
12
12
  # sandbox anyone can write to, so the resources it holds change under you; every id below is a
13
13
  # parameter for that reason, and the searches are written to survive not finding one.
14
14
  #
@@ -51,11 +51,11 @@
51
51
  # each entry is one LOINC code and the DHIS2 data element uid it lands in. The stage the event
52
52
  # belongs to is `program_stage`, and the program and enrollment are `program` and
53
53
  # `enrollment_uid`. An observation whose LOINC code is not in the table is dropped and counted
54
- # -- conceptmap-driven-mapping.yaml is the same mapping fetched from a server instead.
54
+ # -- fhir-conceptmap-driven-mapping.yaml is the same mapping fetched from a server instead.
55
55
  #
56
56
  # WHAT THIS DOES NOT DO. It does not resolve the Encounter's Patient to a DHIS2 tracked entity.
57
- # That is a real lookup against a real identifier crosswalk, and patient-to-tracked-entity.yaml
58
- # is the half that does it; here the entity and its enrollment are parameters.
57
+ # That is a real lookup against a real identifier crosswalk, and
58
+ # fhir-patient-to-tracked-entity.yaml is the half that does it; here the entity and its enrollment are parameters.
59
59
  #
60
60
  # dg run --local examples/fhir/fhir-encounter-to-event.yaml
61
61
  # dg run --local examples/fhir/fhir-encounter-to-event.yaml -p encounter_id=137202491
@@ -65,7 +65,9 @@ kind: pipeline
65
65
  code: fhir-encounter-to-event
66
66
  name: Encounter and Observations to one event
67
67
  description: |
68
- Fold one FHIR `Encounter` and every `Observation` naming it into a single DHIS2 tracker
68
+ Turn one FHIR visit and its measurements into one DHIS2 event, as a dry run.
69
+
70
+ One FHIR `Encounter` and every `Observation` naming it are folded into a single DHIS2 tracker
69
71
  event, posted with `importMode=VALIDATE`.
70
72
 
71
73
  The cardinalities do not line up: FHIR spreads a visit over an Encounter plus one
@@ -119,7 +121,7 @@ params:
119
121
  tracked_entity:
120
122
  type: string
121
123
  description: The DHIS2 person the Encounter's Patient resolves to. A real crosswalk is
122
- patient-to-tracked-entity.yaml.
124
+ fhir-patient-to-tracked-entity.yaml.
123
125
  default: coxwIkMqNBB
124
126
  enrollment_uid:
125
127
  type: string
@@ -15,7 +15,8 @@
15
15
  # population, Denominator, Denominator exclusion, Numerator, Numerator exclusion) -- over a
16
16
  # Bundle of FHIR data, and a measure evaluation engine is what turns that scoring into an R4
17
17
  # MeasureReport. The report is then a resource like any other, so this document fetches it
18
- # from wherever it was published. `fhir_base` defaults to a public HAPI R4 sandbox.
18
+ # from wherever it was published. The `fhir-reports` connection points at a public HAPI R4
19
+ # sandbox by default.
19
20
  #
20
21
  # WHAT ARRIVES -- the report. A proportion measure scored over a period:
21
22
  #
@@ -40,12 +41,15 @@
40
41
  # checked and they agree" is a fact worth delivering too:
41
42
  #
42
43
  # {"measure": "...", "period": "202608", "org_unit": "ImspTQPwCqd",
43
- # "fhir_score": 0.75, "dhis2_value": 0.71, "difference": 0.04, "discrepant": true}
44
+ # "report_period": {"start": "2026-08-01", "end": "2026-08-31"},
45
+ # "fhir_score": 0.75, "dhis2_value": 0.71, "difference": 0.04, "verdict": "discrepant"}
44
46
  #
45
47
  # WHICH CODES LINE UP WITH WHAT. `measure_url` and `indicator` are the two halves of one claim
46
48
  # -- that this FHIR measure and this DHIS2 data element or indicator count the same thing --
47
- # and a reader changes both together or neither. `period` is a DHIS2 ISO period; the FHIR side
48
- # carries a date range, so the two are matched on the range the period covers.
49
+ # and a reader changes both together or neither. `period` and `report_period` are the same
50
+ # stretch of time in two vocabularies, and they change together too: `period` is a DHIS2 ISO
51
+ # period, and `report_period` a FHIR date, whose search matches a report inside that range. The
52
+ # record carries the range the report named, so a reader can see what was compared.
49
53
  #
50
54
  # WHEN IT FINDS NOTHING. A missing report and an empty grid are ordinary answers, not failures:
51
55
  # the diff says `unavailable` and the receiver hears about it. A reconciliation that crashed
@@ -59,8 +63,10 @@ kind: pipeline
59
63
  code: fhir-measure-report-to-analytics-check
60
64
  name: MeasureReport against DHIS2 analytics
61
65
  description: |
62
- Hold a FHIR `MeasureReport` against the DHIS2 analytics value for the same period and org
63
- unit, and deliver the gap.
66
+ Compare a FHIR measure report with DHIS2 analytics and report the difference.
67
+
68
+ The newest FHIR `MeasureReport` for the measure and period is held against the DHIS2 analytics
69
+ value for that period and one org unit, and the gap is delivered.
64
70
 
65
71
  A reconciliation, so it writes nothing: safe on a schedule. The report is produced by
66
72
  scoring a CQL measure over FHIR data -- `d2w fhir serve` publishes no `Measure` or
@@ -114,8 +120,12 @@ params:
114
120
  default: ImspTQPwCqd
115
121
  period:
116
122
  type: string
117
- description: A DHIS2 ISO period; the FHIR side is matched on the range it covers.
123
+ description: The DHIS2 ISO period analytics is asked for.
118
124
  default: "202608"
125
+ report_period:
126
+ type: string
127
+ description: The same period as a FHIR date, which the report search matches within.
128
+ default: "2026-08"
119
129
  tolerance:
120
130
  type: number
121
131
  description: How far apart the two may be before the difference is called a discrepancy.
@@ -133,6 +143,7 @@ steps:
133
143
  method: GET
134
144
  query:
135
145
  measure: "${params.measure_url}"
146
+ period: "${params.report_period}"
136
147
  _sort: "-date"
137
148
  _count: 1
138
149
  headers:
@@ -181,6 +192,7 @@ steps:
181
192
  | {measure: $measure,
182
193
  period: $period,
183
194
  org_unit: $org_unit,
195
+ report_period: $report.period,
184
196
  fhir_score: $score,
185
197
  dhis2_value: $value,
186
198
  populations: [(($report.group[0].population // [])[])
@@ -51,6 +51,12 @@
51
51
  # first page and reports whether there were more, which is the smallest thing that does not
52
52
  # quietly lie about completeness.
53
53
  #
54
+ # WHERE THIS GOES NEXT. The sync exists to feed DHIS2, and the shelf's other documents are what
55
+ # turn each of these types into an import: Patient through fhir-patient-to-tracked-entity.yaml,
56
+ # Encounter through fhir-encounter-to-event.yaml, QuestionnaireResponse through
57
+ # fhir-capture-bundle-to-data-values.yaml. This one only pulls, and its last step only asks the
58
+ # instance they import into whether it answers.
59
+ #
54
60
  # WHERE A READER CHANGES IT. `resource_types` is the fan-out and the whole scope of the sync;
55
61
  # `page_size` is what one page holds. Add a type to the list and the grid grows on the next
56
62
  # firing with nothing else edited.
@@ -65,8 +71,11 @@ kind: pipeline
65
71
  code: fhir-nightly-window-sync
66
72
  name: Nightly windowed FHIR sync
67
73
  description: |
68
- Pull everything a FHIR server changed inside the interval a nightly schedule just closed,
69
- one fan-out item per resource type, and join the results into one summary.
74
+ Pull last night's changes from a FHIR server, type by type, into one summary.
75
+
76
+ Each run reads what changed inside the interval the nightly schedule just closed, with one
77
+ fan-out item per resource type, and joins the results. Only the first page of each type is
78
+ read, and the summary says whether more were left.
70
79
 
71
80
  The window belongs to the run, not to this document: `${run.window.start}` and
72
81
  `${run.window.end}` are half-open ISO instants, so consecutive firings tile the timeline
@@ -80,17 +89,7 @@ requires:
80
89
  - transform.jq
81
90
 
82
91
  connections:
83
- # Carried for its timeout and TLS setting; the searches below give absolute URLs, because a
84
- # half-open range needs the same query parameter twice and the query map holds one per key.
85
- fhir-source:
86
- kind: http
87
- config:
88
- base_url: https://hapi.fhir.org/baseR4
89
- timeout: 60s
90
- # Not read by this document, which only pulls. It is here because the sync exists to feed
91
- # DHIS2, and the shelf's other documents are what turn each of these types into an import:
92
- # Patient through patient-to-tracked-entity.yaml, Encounter through encounter-to-event.yaml,
93
- # QuestionnaireResponse through capture-bundle-to-data-values.yaml.
92
+ # The instance this sync feeds, read by the target step and written by nothing here.
94
93
  dhis2-demo:
95
94
  kind: dhis2
96
95
  config:
@@ -119,10 +118,9 @@ params:
119
118
  default: 50
120
119
 
121
120
  steps:
122
- # for_each is expanded when the run is created, so it reads params, run and item -- never
123
- # another step's output, because the cardinality of the grid has to be known before anything
124
- # executes. That is why the types are a parameter and not something discovered from
125
- # /metadata: a run's shape is fixed before its first request.
121
+ # for_each reads a parameter here, so the grid is written when the run is created. It could
122
+ # read a step's output, and discover the types from /metadata, but then the grid would not
123
+ # exist until that step settled. A parameter fixes the run's shape before its first request.
126
124
  pull:
127
125
  block: http.request
128
126
  for_each: "${params.resource_types}"
@@ -130,6 +128,7 @@ steps:
130
128
  config:
131
129
  url: "${params.fhir_base}/${item}?_lastUpdated=ge${run.window.start}&_lastUpdated=lt${run.window.end}&_count=${params.page_size}&_sort=_lastUpdated"
132
130
  method: GET
131
+ timeout: 60s
133
132
  headers:
134
133
  Accept: application/fhir+json
135
134
  # A type this server does not serve answers 404 or an OperationOutcome, and under
@@ -170,6 +169,17 @@ steps:
170
169
  # what keeps a partial sync from reading like a complete one.
171
170
  types_missed: ($asked - [$seen[] | .type])}
172
171
 
172
+ # One short read of the instance the types above are imported into. Tolerated, because a
173
+ # sleeping DHIS2 does not change what the FHIR server said.
174
+ target:
175
+ block: http.request
176
+ depends_on: [summarise]
177
+ continue_on_failure: true
178
+ config:
179
+ connection: dhis2-demo
180
+ path: /api/system/info
181
+ method: GET
182
+
173
183
  triggers:
174
184
  schedules:
175
185
  - code: nightly
@@ -69,8 +69,10 @@ kind: pipeline
69
69
  code: fhir-patient-to-tracked-entity
70
70
  name: FHIR Patient to a DHIS2 tracked entity
71
71
  description: |
72
- Search the facade's `Patient` register and build the `/api/tracker` payload for one tracked
73
- entity and its enrollment, posted with `importMode=VALIDATE`.
72
+ Turn a FHIR Patient into a DHIS2 tracked entity and enrollment, as a dry run.
73
+
74
+ The run searches the facade's `Patient` register and builds the `/api/tracker` payload for one
75
+ tracked entity and its enrollment, posted with `importMode=VALIDATE`.
74
76
 
75
77
  `dhis2.tracker` reads and does not write, so the write is an ordinary `http.request` on the
76
78
  `dhis2` connection -- which works because the HTTP blocks read `base_url` and the basic
@@ -1,14 +1,15 @@
1
1
  # One QuestionnaireResponse mapped onto data elements by a linkId lookup the document carries.
2
2
  #
3
- # THE FLOW, END TO END. Read one capture off the facade by its receipt id, translate its
4
- # answers into data values through an explicit linkId-to-data-element table held in params,
5
- # and import the result -- dry run first.
3
+ # THE FLOW, END TO END. Take one capture, written inline, translate its answers into data
4
+ # values through an explicit linkId-to-data-element table held in params, and rehearse the
5
+ # import as a dry run. It writes nothing, here or on any instance, until the rehearse step's
6
+ # dry_run is turned off.
6
7
  #
7
8
  # WHY A LOOKUP AT ALL. On a `d2w fhir serve` facade the linkIds already ARE DHIS2 uids, so
8
- # capture-bundle-to-data-values.yaml needs no table: it splits the dotted linkId and is done.
9
+ # fhir-capture-bundle-to-data-values.yaml needs no table: it splits the dotted linkId and is done.
9
10
  # Every other FHIR server in the world uses linkIds a form designer chose -- "anc.visits.total",
10
11
  # "q3b", "weight_kg" -- and then the table IS the integration. This document is the general
11
- # case, and the facade is only where the example gets a real response to run against.
12
+ # case, and the inline response stands in for what such a server sends.
12
13
  #
13
14
  # WHAT ARRIVES. One QuestionnaireResponse, answers keyed by whatever the form calls them:
14
15
  #
@@ -38,8 +39,10 @@ kind: pipeline
38
39
  code: fhir-questionnaire-response-to-data-values
39
40
  name: QuestionnaireResponse through a linkId table
40
41
  description: |
41
- Map one `QuestionnaireResponse` onto DHIS2 data values through an explicit linkId lookup,
42
- for the general case where a form's `linkId` is a designer's name rather than a DHIS2 uid.
42
+ Map one FHIR form response onto DHIS2 data values using a lookup table.
43
+
44
+ The `QuestionnaireResponse` goes through an explicit linkId lookup, for the general case where
45
+ a form's `linkId` is a designer's name rather than a DHIS2 uid. The import is a dry run.
43
46
 
44
47
  A linkId the table does not name is dropped and counted, never guessed at.
45
48
 
@@ -94,7 +97,7 @@ params:
94
97
  steps:
95
98
  # The capture, written inline so the document runs with nothing set up. In service it is the
96
99
  # body of an endpoint delivery (fhir-subscription-to-dhis2.yaml) or one entry of a
97
- # searchset Bundle (capture-bundle-to-data-values.yaml); either way this step's output is
100
+ # searchset Bundle (fhir-capture-bundle-to-data-values.yaml); either way this step's output is
98
101
  # exactly one QuestionnaireResponse, and nothing downstream knows where it came from.
99
102
  receive:
100
103
  block: value.const
@@ -94,8 +94,11 @@ kind: pipeline
94
94
  code: fhir-subscription-to-dhis2
95
95
  name: A FHIR Subscription notification imported
96
96
  description: |
97
- Take a FHIR `Subscription` rest-hook notification on an endpoint source, translate the
98
- notified `QuestionnaireResponse` into a DHIS2 data value set, and rehearse the import.
97
+ Receive a FHIR notification and rehearse importing it into DHIS2.
98
+
99
+ A FHIR `Subscription` rest-hook notification arrives on an endpoint source, and the notified
100
+ `QuestionnaireResponse` is translated into a DHIS2 data value set before the import is
101
+ rehearsed.
99
102
 
100
103
  The subscription contract is three fields: `criteria` (a search string), `channel.type`
101
104
  (`rest-hook` is an HTTP POST), and `channel.endpoint` (the source's route). The server
@@ -24,7 +24,7 @@ setting (`storage_connections: {s3: <code>}`); the rest need only a network.
24
24
  | --- | --- |
25
25
  | [`http-json-to-data-values.yaml`](http-json-to-data-values.yaml) | The shortest inbound path: pull a public JSON API, reshape it, gate it on DHIS2's own id and period formats, import it as a dry run. |
26
26
  | [`csv-drop-to-data-values.yaml`](csv-drop-to-data-values.yaml) | The file drop: wait for a csv to land in a bucket, decode it, translate the sender's codes to uids through a lookup, drop the rows that carry no measurement. |
27
- | [`endpoint-payload-to-tracker-event.yaml`](endpoint-payload-to-tracker-event.yaml) | Push instead of poll: an endpoint source takes one posted encounter, and the jq step that picks its named fields is the whole security boundary. |
27
+ | [`endpoint-payload-to-data-values.yaml`](endpoint-payload-to-data-values.yaml) | Push instead of poll: an endpoint source takes one posted encounter, and the jq step that picks its named fields is the whole security boundary. |
28
28
  | [`parquet-lakehouse-to-dhis2.yaml`](parquet-lakehouse-to-dhis2.yaml) | Grain: a parquet table of encounter-level rows reduced to the figures DHIS2 stores, then a completeness sensor that observes rather than registers. |
29
29
  | [`fhir-observations-to-data-values.yaml`](fhir-observations-to-data-values.yaml) | Two standards meeting: LOINC codes mapped onto data elements, instants truncated to periods, and the org unit the payload cannot supply. |
30
30
  | [`weekly-window-pull-and-import.yaml`](weekly-window-pull-and-import.yaml) | The window: a schedule whose runs pull the week that closed, `${run.window.start}` to `${run.window.end}`, so a late run and a backfill read the same week. |
@@ -39,8 +39,11 @@ kind: pipeline
39
39
  code: csv-drop-to-data-values
40
40
  name: A csv drop into DHIS2
41
41
  description: |
42
- Wait for a csv to land in object storage, decode it, translate the sender's codes into
43
- DHIS2 uids, drop the rows that carry no measurement, and import what is left.
42
+ Wait for a csv in object storage and import its rows into DHIS2.
43
+
44
+ The csv is decoded, the sender's codes are translated into DHIS2 uids, the rows that carry no
45
+ measurement are dropped, and what is left is imported. `dry_run` defaults to true, so a first
46
+ run writes nothing.
44
47
 
45
48
  tags: [inbound, dhis2, storage, transform, cross-boundary]
46
49
 
@@ -34,20 +34,20 @@
34
34
  #
35
35
  # RUN IT BY HAND, here, with no server. No --input takes the source's sample:
36
36
  #
37
- # dg run --local examples/inbound/endpoint-payload-to-tracker-event.yaml
38
- # dg run --local examples/inbound/endpoint-payload-to-tracker-event.yaml --input '{body: {
37
+ # dg run --local examples/inbound/endpoint-payload-to-data-values.yaml
38
+ # dg run --local examples/inbound/endpoint-payload-to-data-values.yaml --input '{body: {
39
39
  # encounter: {facility: DiszpKrYNg8, occurred_at: "2026-06-14T09:20:00Z",
40
40
  # items: [{code: NVP_WITHIN_72H, quantity: 3}]}}}'
41
41
  #
42
42
  # ON A SERVER, apply it, keep the token the apply printed, and post an encounter:
43
43
  #
44
- # dg apply examples/inbound/endpoint-payload-to-tracker-event.yaml
45
- # dg source call endpoint-payload-to-tracker-event posted --source-token "$TOKEN" --body '{
44
+ # dg apply examples/inbound/endpoint-payload-to-data-values.yaml
45
+ # dg source call endpoint-payload-to-data-values posted --source-token "$TOKEN" --body '{
46
46
  # "encounter": {"facility": "DiszpKrYNg8", "occurred_at": "2026-06-14T09:20:00Z",
47
47
  # "items": [{"code": "EXPOSED_REGISTERED", "quantity": 2}]}}'
48
48
  # curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
49
49
  # --data @encounter.json http://localhost:3333/endpoints/captures
50
- # dg source inputs endpoint-payload-to-tracker-event posted
50
+ # dg source inputs endpoint-payload-to-data-values posted
51
51
  #
52
52
  # The token lives on the instance and is shown once; `dg source rotate-token` mints another.
53
53
  #
@@ -55,11 +55,14 @@
55
55
 
56
56
  format: dirigent/v1
57
57
  kind: pipeline
58
- code: endpoint-payload-to-tracker-event
58
+ code: endpoint-payload-to-data-values
59
59
  name: A pushed encounter into DHIS2
60
60
  description: |
61
+ Take an encounter posted by an outside system and import it into DHIS2.
62
+
61
63
  An outside system POSTs one captured encounter to an endpoint source; a jq step picks the
62
- named fields, and the run reshapes them into a DHIS2 data value set and imports it.
64
+ named fields, and the run reshapes them into a DHIS2 data value set and imports it. `dry_run`
65
+ defaults to true, so a first run writes nothing.
63
66
 
64
67
  The pack's `dhis2.tracker` block reads `/api/tracker` and does not write it, so an
65
68
  event-shaped capture lands as the aggregate figures it contributes to.
@@ -6,10 +6,9 @@
6
6
  #
7
7
  # WHY THE FAN CONVERGES AT THE IMPORT. Two reasons, and both are worth knowing:
8
8
  #
9
- # for_each is expanded when the run is created, so it may read params, run and item but
10
- # never another step's output: the cardinality has to exist before anything executes. A
11
- # step fanned over "the facilities that answered" is therefore not expressible, and
12
- # should not be -- it would make the shape of the run depend on the weather.
9
+ # A for_each may read another step's output, so a step fanned over "the facilities that
10
+ # answered" could be written. It should not be: it would make the shape of the run depend
11
+ # on the weather, and its grid would not exist until the pull had settled.
13
12
  #
14
13
  # A data value set carries orgUnit per value, so one document can hold every facility at
15
14
  # once. Fifteen imports of one facility each would be fifteen round trips, fifteen
@@ -50,8 +49,11 @@ kind: pipeline
50
49
  code: fan-out-per-facility-import
51
50
  name: A facility fan-out into one district import
52
51
  description: |
53
- Pull one facility at a time, tolerate the ones that are down, import what answered as a
54
- single data value set, and post a reconciliation of asked-for against imported.
52
+ Collect from each facility, import what answered, and report who did not.
53
+
54
+ There is one pull per facility, tolerating the ones that are down. What answered is imported as
55
+ a single data value set, and a reconciliation of asked-for against imported is posted. `dry_run`
56
+ defaults to true, so a first run writes nothing.
55
57
 
56
58
  tags: [inbound, dhis2, http, graph, transform, cross-boundary]
57
59
 
@@ -18,9 +18,9 @@
18
18
  # The hops, and the shape at each one:
19
19
  #
20
20
  # 1. pull -> a FHIR Bundle: {resourceType: "Bundle", entry: [{resource: {...}}]}
21
- # 2. flatten -> one row per entry that has both a mapped LOINC code and a numeric
22
- # value; everything else is dropped, silently and on purpose
23
- # 3. aggregate -> {dataValues: [...]}, one value per (element, period) pair, summed
21
+ # 2. flatten -> one row per entry that has both a mapped LOINC code and a time it
22
+ # happened; everything else is dropped, silently and on purpose
23
+ # 3. aggregate -> {dataValues: [...]}, one value per (element, period) pair, counted
24
24
  # 4. gate -> refused unless the ids and periods are DHIS2's own
25
25
  # 5. import -> the instance's summary
26
26
  #
@@ -34,8 +34,8 @@
34
34
  # perfectly predictable in shape -- which is the half this example teaches. If a run finds
35
35
  # nothing mappable, the gate refuses an empty set rather than importing nothing quietly.
36
36
  #
37
- # What makes it yours: fhir_base_url pointed at your server, the lookup table, and the
38
- # org unit.
37
+ # What makes it yours: the `fhir-server` connection pointed at your server, the lookup table,
38
+ # and the org unit.
39
39
  #
40
40
  # dg run --local examples/inbound/fhir-observations-to-data-values.yaml
41
41
  # dg run --local examples/inbound/fhir-observations-to-data-values.yaml -p count=50
@@ -45,8 +45,11 @@ kind: pipeline
45
45
  code: fhir-observations-to-data-values
46
46
  name: FHIR Observations into DHIS2
47
47
  description: |
48
- Pull a FHIR Observation bundle, map LOINC codes onto DHIS2 data elements, reduce the
49
- observations to monthly counts, and import them as a data value set.
48
+ Count FHIR Observations by month and import the counts into DHIS2.
49
+
50
+ A FHIR Observation bundle is pulled, LOINC codes are mapped onto DHIS2 data elements, and the
51
+ monthly counts are imported as a data value set. `dry_run` defaults to true, so a first run
52
+ writes nothing.
50
53
 
51
54
  tags: [inbound, dhis2, http, transform, validate, cross-boundary]
52
55
 
@@ -27,15 +27,18 @@
27
27
  # write nothing. The play server is shared, so leave it true there.
28
28
  #
29
29
  # dg run --local examples/inbound/http-json-to-data-values.yaml
30
- # dg run --local examples/inbound/http-json-to-data-values.yaml -p country=NGA
30
+ # dg run --local examples/inbound/http-json-to-data-values.yaml \
31
+ # -p source_url=https://api.worldbank.org/v2/country/NGA/indicator/SP.POP.TOTL
31
32
 
32
33
  format: dirigent/v1
33
34
  kind: pipeline
34
35
  code: http-json-to-data-values
35
36
  name: A public JSON API into DHIS2
36
37
  description: |
37
- Pull a public JSON feed, reshape it into a DHIS2 data value set, gate it on DHIS2's own
38
- id and period formats, and hand it to `dhis2.data_value_set_import` as a dry run.
38
+ Pull a public JSON feed and rehearse importing it into DHIS2.
39
+
40
+ The feed is reshaped into a DHIS2 data value set, gated on DHIS2's own id and period formats,
41
+ and handed to `dhis2.data_value_set_import` as a dry run.
39
42
 
40
43
  tags: [inbound, dhis2, http, transform, validate, cross-boundary]
41
44
 
@@ -62,10 +65,6 @@ params:
62
65
  type: string
63
66
  description: The outside feed. Anything answering JSON works; the reshape is what changes.
64
67
  default: https://api.worldbank.org/v2/country/SLE/indicator/SP.POP.TOTL
65
- country:
66
- type: string
67
- description: The ISO3 code spliced into source_url when it is left at its default.
68
- default: SLE
69
68
  data_element:
70
69
  type: string
71
70
  description: The uid every pulled observation lands on; Total Population, yearly.
@@ -40,16 +40,19 @@
40
40
  # becomes real by setting it false against an instance of your own.
41
41
  #
42
42
  # dg run --local examples/inbound/outside-to-dhis2-with-checks.yaml
43
- # dg run --local examples/inbound/outside-to-dhis2-with-checks.yaml -p commit_dry_run=false
43
+ # dg run --local examples/inbound/outside-to-dhis2-with-checks.yaml -p commit_dry_run=false # against your own instance
44
44
 
45
45
  format: dirigent/v1
46
46
  kind: pipeline
47
47
  code: outside-to-dhis2-with-checks
48
48
  name: An inbound pipeline with every guard
49
49
  description: |
50
+ Import outside data into DHIS2 with a safety check at every stage.
51
+
50
52
  A readiness sensor, a schema on what arrived, a schema on what was built, a rehearsed
51
53
  import, the real import behind it, and the completeness sensor -- each guard buying off a
52
- different failure.
54
+ different failure. `commit_dry_run` defaults to true, so the second import is a rehearsal too
55
+ until it is set to false.
53
56
 
54
57
  tags: [inbound, dhis2, http, sensor, transform, validate, cross-boundary]
55
58
 
@@ -41,8 +41,11 @@ kind: pipeline
41
41
  code: parquet-lakehouse-to-dhis2
42
42
  name: A lakehouse table into DHIS2
43
43
  description: |
44
- Read a parquet export of encounter-grain rows, aggregate it to DHIS2's grain, import it,
45
- and wait for the month to be signed off.
44
+ Sum a parquet export down to DHIS2 figures, import them, and await sign-off.
45
+
46
+ The parquet export holds encounter-grain rows, which are aggregated to DHIS2's grain before the
47
+ import, and the run then waits for the month to be signed off. `dry_run` defaults to true, so a
48
+ first run writes nothing.
46
49
 
47
50
  tags: [inbound, dhis2, parquet, storage, transform, cross-boundary]
48
51
 
@@ -42,9 +42,11 @@ kind: pipeline
42
42
  code: weekly-window-pull-and-import
43
43
  name: A weekly windowed pull into DHIS2
44
44
  description: |
45
- A Monday schedule whose runs pull the week that just closed -- `${run.window.start}` up
46
- to but not including `${run.window.end}` -- reshape it into a data value set, import it,
47
- and wait for the week to be signed off.
45
+ Pull last week's data every Monday, import it into DHIS2, and await sign-off.
46
+
47
+ A Monday schedule's runs pull the week that just closed -- `${run.window.start}` up to but not
48
+ including `${run.window.end}` -- reshape it into a data value set, import it, and wait for the
49
+ week to be signed off. `dry_run` defaults to true, so a first run writes nothing.
48
50
 
49
51
  tags: [inbound, dhis2, http, schedule, transform, cross-boundary]
50
52
 
@@ -41,7 +41,7 @@
41
41
  # (storage_connections: {s3: <code>}).
42
42
  #
43
43
  # dg run --local examples/parquet-to-dhis2-import.yaml
44
- # dg run --local examples/parquet-to-dhis2-import.yaml -p dry_run=false
44
+ # dg run --local examples/parquet-to-dhis2-import.yaml -p dry_run=false # against your own instance
45
45
 
46
46
  format: dirigent/v1
47
47
  kind: pipeline