Documentation / Exploitation / Guides pratiques / Connect a FHIR warehouse

Connect a FHIR warehouse

Wire the patient and prescription ports to a real FHIR R4 warehouse using the bundled @posos/fhir plugin, instead of an in-memory or demo adapter. This assumes you already have an instance manifest — see Your first instance if you don't, and Configure ports and adapters for how port/adapter selection works in general.

Before you start

  • A reachable FHIR R4 server. @posos/fhir is tested against samply/blaze and against Synthea-generated bundles loaded into it (external-plugins/fhir/plugin.yaml, external-plugins/fhir/adapters/patient.ts). Other FHIR R4 servers may work but are untested.
  • @posos/fhir present in your plugin set. It is a core plugin shipped in the API image (external-plugins/fhir) and is loaded like any other plugin (a PLUGINS_PATH root); nothing below is registry-specific.

1. Stand up a test warehouse (optional)

If you don't have a FHIR store yet, run Blaze locally and load Synthea data:

docker run -d --name blaze -p 8090:8080 -e STORAGE=in-memory samply/blaze:latest
node external-plugins/fhir/scripts/load-synthea.mjs   # loads Synthea bundles into Blaze

The docker-compose.yml stack profile also runs a persistent Blaze (samply/blaze:1.10.1, RocksDB storage) on the same port mapping (8090:8080); use that instead for anything longer-lived than a one-off test.

2. Declare the connection to the warehouse

@posos/fhir contributes the connection type fhir-r4; its two adapters and its fhir-poll source require a connection of that type. Declare it in the instance manifest and make it the default of the type:

connections:
  entrepot:
    type: fhir-r4
    label: Entrepôt FHIR local
    baseUrl: http://localhost:8090/fhir
connectionDefaults:
  fhir-r4: entrepot

A protected warehouse takes an authentication scheme among those the type accepts (none, basic, header, bearer, oauth2-client-credentials), its secret written env:NAME:

    auth: { scheme: bearer, token: env:FHIR_TOKEN }

The connection can also be created in the console (Connections), with a credential of the store. See Manage connections.

3. Point the ports at the warehouse

Set the patient and prescription ports to the fhir adapter. Without connection, each uses the default connection of type fhir-r4:

ports:
  patient:      { adapter: fhir }
  prescription: { adapter: fhir }

instance.example.yaml at the repo root wires both ports this way against the local Blaze from step 1. To use another connection for one port, name it: prescription: { adapter: fhir, connection: autre-entrepot }.

The connection is resolved at each call: a connection modified in the console applies to the next request. Without a usable connection, the call fails with Aucune connexion par défaut pour le type « fhir-r4 ». … or Connexion « <id> » inutilisable : ….

The previous configuration — config.baseUrl on the port, or the FHIR_BASE_URL variable — is still read for one version, with the warning [fhir] config.baseUrl est déprécié : déclarer une connexion de type fhir-r4 et la désigner par connection. (or FHIR_BASE_URL est déprécié …).

Note that FHIR has no single "prescription" resource: the prescription port adapter treats the id you pass it as a patient id and aggregates that patient's MedicationRequest resources (external-plugins/fhir/adapters/prescription.ts). For the full resource mapping behind both ports (Patient, MedicationRequest, Condition, AllergyIntolerance, Observation, Communication, Task…), see FHIR resource mapping (apps/ehr-lab/docs/resources/reference/fhir-mapping.md) and the port contracts reference.

4. Declare the plugin's own dependencies

@posos/fhir's manifest lists dependsOn: { "@posos/clinical": "^0.2.0", "@posos/common": "^0.4.0" } (external-plugins/fhir/plugin.yaml): its default event bindings project raw FHIR resources onto events declared (contributes.events) by those two plugins (e.g. fhir-creatinine → BioResultReceived, fhir-communication-received → CommunicationReceived), and are checked against those contracts at load. If you're assembling your instance from plugins pulled off a registry rather than using the bundled set as-is, make sure @posos/clinical and @posos/common are declared alongside @posos/fhir, in versions those ranges accept: the host does not load @posos/fhir next to a version outside them (« dépendance signalée ») — see Declare and install plugins and Set up a plugin registry.

Connecting the warehouse as an event source (polling FHIR for new resources, e.g. new lab results) is a separate concern from the port wiring above: declare an inbound connection of type fhir-poll that names the fhir-r4 connection and the bindings to enable:

connections:
  fhir-poll:
    type: fhir-poll
    connection: entrepot          # absent: default connection of type fhir-r4
    intervalSeconds: 15
    bindings: [fhir-creatinine, fhir-validation-decided]   # absent: every binding of fhir-poll

The console creates the same connection in Connexions → Connexions entrantes, format « Interrogation FHIR ». See Connect an event source. After each restart of the API, fhir-poll re-reads the last startOverlapSeconds (default 600 s) of each feed: resources modified during a shorter stop start their runs, and resources already received are duplicates. Notes and conditions written to the record by workflows come back through these feeds as events — see Meridian's own writes come back as events. Each poll follows the pages of the search, up to maxPages pages of count resources per feed (10 pages of 50 by default), and the next poll continues right away where it stopped — see Restarts and the overlap window and Pages and cursor of a feed. The next links of a Blaze result carry its BASE_URL; the source requests those pages on the base URL of its connection. Each request is limited to requestTimeoutSeconds (30 s by default). Each resource read is stored in the reception inbox before its binding projects it. A resource whose processing fails (Restate unreachable, for instance) does not block its feed: the reception delivers it again until it passes — see Records that fail to process.

5. Verify the connection

  • Smoke-test the store directly: curl http://localhost:8090/fhir/Patient?_count=1 should return a Bundle.

  • Run the adapter's own integration check, which reads and writes through the real patient port against Blaze:

    FHIR_BASE_URL=http://localhost:8090/fhir pnpm run test:fhir
    

    This requires at least one adult Patient already loaded (the Synthea bundles from step 1 provide this) — see apps/api/tests/test-fhir.ts.

  • Boot the endpoint against the manifest that wires FHIR (INSTANCE_CONFIG=instance.example.yaml pnpm run endpoint:fhir, or pnpm run dev:fhir for the full endpoint+proxy+UI stack) and confirm a workflow can read a patient snapshot end to end.

Validation decisions written in the warehouse

When a run suspends on a human validation and the patient port is on the fhir adapter, the adapter writes a Task (status: requested, code urn:ehr-lab:validation|decision) with two identifiers: urn:ehr-lab:validation-run (the run id) and urn:ehr-lab:validation-pending (the validation, pending.id of the run). The fhir-validation-decided binding resumes the run when that Task moves to completed (accepted) or rejected, and applies the decision only to the validation the Task names.

A run can wait on several validations in turn, each with its own Task under the same run identifier. The system that records the decision must update the Task still requested — for instance the most recent one of the run with Task?identifier=urn:ehr-lab:validation-run|<runId>&status=requested&_sort=-_lastUpdated — and keep both identifiers. A decision written on the Task of an earlier validation is not applied to the current one.

Concurrent updates to a Patient

patient.update-demographics reads the Patient, applies its patch, then writes it back with If-Match: W/"<versionId>", the version read by the GET (external-plugins/fhir/adapters/patient.ts, updateDemographics). When another writer changed the resource in between, the server answers 412 Precondition Failed; the adapter reads the resource again and reapplies the patch, up to three times after the first attempt, then fails the node with HTTP 412 : ressource modifiée en parallèle, échec après 4 essais. Two runs updating the same patient at the same time therefore keep both changes.

The warehouse must support versioned updates (meta.versionId on reads, If-Match on PUT). A resource read without meta.versionId is written back unconditionally.

97 documents9 sectionssource : /docs · généré au build