Turn a Research Database Extract into a Validated FHIR API
Level: Beginner
You have an extract from a research database — rows of patients, diagnoses, and lab values pulled from a data warehouse. To use it beyond one-off analysis (dashboards, downstream apps, multi-site studies), it needs to become validated FHIR behind a real API. This example turns extract rows into typed FHIR resources, catches the dirty data before it ships, and loads it all into a FHIR server you can query like any EHR.
Check out the full working example here — it runs offline with zero setup.
Setup
That's it. The script uses pre-baked extract rows so there's no database or credentials to configure — swap in your own query results and the rest is unchanged.
The Extract
Rows as they come out of a data warehouse: local conventions ("M"/"F" sex codes), one record per line. Note the third row — sex arrived as "U", and real extracts always have at least one value that doesn't fit the standard.
EXTRACT_ROWS = [
{
"mrn": "MRN-001",
"sex": "M",
"birth_date": "1954-03-11",
"dx_code": "44054006",
"dx_display": "Type 2 diabetes mellitus",
"lab_loinc": "4548-4",
"lab_display": "Hemoglobin A1c",
"lab_value": 8.2,
"lab_unit": "%",
},
# ... more rows, including one with sex="U"
]
# Local warehouse codes -> FHIR administrative gender. Deliberately incomplete,
# like every real mapping table.
SEX_MAP = {"M": "male", "F": "female"}
Build and Validate
Each row becomes three typed FHIR resources using the FHIR helpers, and every resource is validated with validate_resource before it enters the bundle:
from healthchain.fhir import (
add_resource,
create_bundle,
create_condition,
create_patient,
create_value_quantity_observation,
validate_resource,
)
bundle = create_bundle()
for row in EXTRACT_ROWS:
patient = create_patient(
gender=SEX_MAP.get(row["sex"], row["sex"]),
birth_date=row["birth_date"],
identifier=row["mrn"],
warn=False, # we validate explicitly below
)
report = validate_resource(patient)
if not report.valid:
for issue in report.issues:
print(f"CAUGHT {row['mrn']}: {issue.diagnostics}")
patient.gender = "unknown" # fix the mapping gap, never ship invalid data
assert validate_resource(patient).valid
patient_ref = f"Patient/{patient.id}"
condition = create_condition(
subject=patient_ref, code=row["dx_code"], display=row["dx_display"], warn=False
)
observation = create_value_quantity_observation(
code=row["lab_loinc"],
display=row["lab_display"],
value=row["lab_value"],
unit=row["lab_unit"],
subject=patient_ref,
warn=False,
)
for resource in (patient, condition, observation):
assert validate_resource(resource).valid
add_resource(bundle, resource)
Running it, the unmapped sex code is caught by the FHIR schema — with the exact field and allowed values named — instead of slipping silently into your dataset:
OK MRN-001: Patient + Condition + Observation validated
OK MRN-002: Patient + Condition + Observation validated
CAUGHT MRN-003: Value 'u' is not in the required value set. Allowed values: male, female, other, unknown
OK MRN-003: Patient + Condition + Observation validated
That's the strict schema working as a built-in unit test for your data.
Load into a FHIR Server
This is the payoff: your extract becomes a queryable FHIR API. Configure a source (free Medplum sandbox setup →) and the FHIRGateway writes each resource:
from healthchain.gateway import FHIRGateway
from healthchain.gateway.clients import FHIRAuthConfig
gateway = FHIRGateway()
gateway.add_source(
"medplum", FHIRAuthConfig.from_env("MEDPLUM").to_connection_string()
)
for entry in bundle.entry:
created = gateway.create(entry.resource, source="medplum")
Afterwards the data answers standard FHIR queries from any FHIR client — no more emailing CSVs:
What You've Built
- Typed FHIR from tabular rows — Patients, Conditions, and Observations built with minimal helpers, correctly coded (SNOMED CT, LOINC)
- Validation as a gate — broken data is caught and named before it ships, not discovered downstream
- A real API — your one-off extract is now infrastructure other tools, sites, and agents can query
Next Steps
- Scale the input: Replace
EXTRACT_ROWSwith your SQL query results or CSV reader — the build-and-validate loop is unchanged. - Add provenance: Use
add_provenance_metadatato tag resources with their source extract for auditability. - Serve it to agents: Point the FHIRToolkit at your bundle or server to give LLM agents validated FHIR tools over the same data.
- Aggregate across sites: Combine with the Multi-Source Data Aggregation pattern once more than one FHIR source is involved.