Skip to content

Pipeline Tutorial: Custom EHR → FHIR R4

The standardize pipeline aimed at FHIR R4 — same five stages as the OMOP tutorial, different target universe — ending in a transaction Bundle and NDJSON export.

Runnable notebook: 23_pipeline_custom_ehr_to_fhir.ipynb (fully offline, executed with outputs).

The route

lab_results.csv ──ingest──► schema map (FHIR) ──► concept map (LOINC) ──► ETL ──► Bundle / NDJSON

What changes vs the OMOP pipeline

Exactly one line:

project = portiere.init(
    name="ehr-to-fhir",
    target_model="fhir_r4",        # ← the whole target universe swaps
    vocabularies=["LOINC"],
    config=config,                  # identical offline config
)

Schema mapping now aims at FHIR resources/elements — lab_code → Observation.code, lab_value → Observation.valueQuantity, patient_id → Patient.id. Portiere ships 18 R4 resources; coverage matrix: multi-standard support.

Since v0.4.0 the local schema mapper honors non-OMOP targets correctly — earlier versions silently mapped against OMOP regardless of target_model (fixed; regression-tested).

Source column naming

The offline configuration (no embedding model) relies on the standard's source_patterns for schema matching. The notebook's columns (patient_id, lab_code, lab_value, lab_unit, lab_date) are real FHIR source patterns. With SapBERT enabled (production default), cryptic names like obs_val_num match semantically as well.

Concept mapping

concept_map = project.map_concepts(source=src_obs, code_columns=["lab_code"])

LOINC codes resolve against the bundled subset offline; against your full Athena export in production.

Bundle + NDJSON export

from portiere.export.fhir.bundle import to_transaction_bundle
from portiere.export.fhir.ndjson import to_ndjson_files

bundle = to_transaction_bundle(resources)          # POST-ready transaction
paths = to_ndjson_files(resources, out_dir=out)    # bulk-import files

Each resource dict must carry resourceType; the notebook assembles Observation resources from the ETL rows. Validate against US Core / mCODE before shipping:

portiere validate --fhir-profile us-core-6.1.0 --input resources.json

(FHIR profile validation · Bundle export)

Going to production

Same checklist as the OMOP tutorial, plus: pick the FHIR profiles you must conform to first — profile constraints (required elements, bindings) shape which source columns are mandatory, and portiere export --fhir-profile ... can gate writes on validation.