Skip to main content

The FHIR-ICR OpenFn adaptor

@openfn/language-fhir-icr is an OpenFn language adaptor that turns ODK Central submissions into ICR-profiled FHIR R4 and loads them into a FHIR store — in the reference stack, a Google Cloud Healthcare FHIR store.

It is the transform-and-load half of the ICR ingestion path:

ODK Central ──OData──▶ OpenFn (fhir-icr adaptor) ──FHIR transaction──▶ FHIR store
submissions transform + idempotent upsert (Google Healthcare)

It is a thin layer over @openfn/language-fhir (which provides the FHIR HTTP client) and adds three things the reference solution needs.

What it adds

1. Google Healthcare authentication

Generic FHIR adaptors take a static access_token, which expires in an hour — useless for a scheduled pipeline. fhir-icr accepts a GCP service-account key in the credential and mints a short-lived OAuth token on every run:

// configuration.service_account -> the adaptor mints an access_token per run

Grant the service account roles/healthcare.fhirResourceEditor. See Setup.

2. ICR-profiled resource builders

Builders emit FHIR that conforms to the ICR Implementation Guide profiles — ICRLocation, ICRTargetPopulation, ICRSupplyDelivery, ICRAdministrativeCoverage, QuestionnaireResponse — with the correct ICR code systems, identifiers, and extensions baked in. Job authors call one function per form; the ICR conformance detail lives in the adaptor.

3. Idempotent upsert

upsertBundle posts a FHIR transaction of conditional PUTs, keyed by business identifier, with deterministic urn:uuid references between resources created in the same bundle. Re-running the pipeline updates resources in place instead of duplicating them — essential for a cron/incremental sync. (Google resolves urn:uuid references within a transaction; it does not resolve conditional references to sibling entries, which is why the adaptor uses urn:uuid for intra-bundle links.)

The ESPEN MDA form → FHIR mapping

The six ESPEN Mass Drug Administration forms map to FHIR per the ICR IG's extraction design (aggregate-vs-individual rule: individual record when you have a person, aggregate on a report when you don't):

FormTransformFHIR output
1. Location registrationfromLocationFormICRLocation cascade (admin hierarchy → village, GPS position, partOf) + ICRTargetPopulation Groups (total, MDA-eligible, 1–4 / 5–14 / 15+ age bands)
2. Drug receiptfromDrugReceiptFormICRSupplyDelivery per drug received at the health facility
3. Treatment tallyfromTreatmentFormICRAdministrativeCoverage (MeasureReport) per drug — numerator/denominator + sex × age-band × disposition stratifiers
4. Case managementfromCaseMgmtFormICRSupplyDelivery per drug distributed + a QuestionnaireResponse for the aggregate side-effect / other-NTD counts (which can't become person-level resources)
5 & 6. Supervision (HF / CDD)fromSupervisionFormQuestionnaireResponse — per the IG, the supervision record is the QuestionnaireResponse

Proven end-to-end: 57 synthetic ESPEN submissions across all six forms → 132 FHIR resources (21 Location, 60 Group, 12 SupplyDelivery, 33 MeasureReport, 6 QuestionnaireResponse) in a Google Healthcare store, fully idempotent.

Next

  • Setup — install the adaptor, configure the credential, run it locally, and deploy the workflow to OpenFn Lightning.
  • Extending it — add a new form transform or builder, or target a different FHIR store or IG.