Docs · FHIR Mapping

FHIR data mapping: from CSV to FHIR-ready fields

Most healthcare data does not arrive as FHIR. It arrives as a CSV export, an Excel sheet, or a legacy table — in five languages, with free-text headers and inconsistent code systems. FHIR mapping is the work of turning those columns into the resources, fields, and terminology a FHIR-aware system expects.

8 templates carry a fhir_resource mapping · schema-only by default

Concepts

What FHIR mapping is

FHIR (Fast Healthcare Interoperability Resources, an HL7 standard) models healthcare data as resources — for example a Patient, an Observation, a Medication, or a Claim. Each resource has defined fields (elements) with types and cardinality, and many fields carry terminology bindings — they expect a coded value drawn from a specific code system rather than free text.

FHIR mapping, then, is a three-part problem: figure out which FHIR resource a row of your data represents, line up each of your columns with the right field on that resource, and make sure coded fields carry values from the right terminology — LOINC for lab observations, ICD-10 for diagnoses, CPT for procedures, and so on. The standard spells out the resources and their fields in detail at hl7.org/fhir.

The hard part

Why CSV → FHIR is hard

The gap between a spreadsheet and a FHIR resource is wider than it looks. Three things make it hard:

  • Free-text headers. A column called DOB, birth_dt or Patient Birthday all mean Patient.birthDate, but no two source systems agree on the spelling.
  • Locale variance. Headers, date formats and gender values differ across German, French, Italian, English and Spanish source systems — common in multi-region healthcare data.
  • Code systems. FHIR’s coded fields expect values from specific terminologies — LOINC, ICD-10, CPT, ATC, NPI — and a raw export rarely arrives validated against them.

Get any of these wrong and the resulting resource is either rejected by the receiving system or — worse, for your-money-or-your-life healthcare data — silently incorrect.

The approach

How AdaptivMapr maps to FHIR

AdaptivMapr maps your inbound columns to a template’s canonical fields, and each healthcare template carries a fhir_resource mapping so you always know which FHIR resource and path a field corresponds to. It resolves and validates the mapping layer; your pipeline serialises the FHIR-ready fields into resources.

The five-layer cascade

Header resolution runs through five layers, cheapest first. When a layer accepts a column, the layers below it never run — and the metered LLM is the last resort, not the first move.

  1. 1 · Statistics — no AI cost, deterministic; auto-accepts headers seen mapped the same way before.
  2. 2 · Heuristic — no AI cost; compares the normalised header against the column name, label and every multilingual hint (DE / FR / IT / EN / ES).
  3. 3 · Fuzzy — no AI cost, pure compute; tolerates typos and reordered tokens.
  4. 4 · Semantic — cheap, cached embeddings for the long tail of paraphrases.
  5. 5 · LLM — metered; fires only on genuinely ambiguous columns, constrained to the template’s allowed field set so it cannot invent a field.

Validators & HITL gating

Healthcare templates attach field-level validators — LOINC, ICD-10, CPT, ATC, NPI, GTIN, IBAN and more — that run on every committed row. Each template also carries a risk level. Medium- and high-risk templates — patient_demographics_v1, lab_results_v1, claims_line_items_v1 and drug_formulary_v1 — return requires_hitl: true with hitl_status: "pending_review" so you can gate your own commit workflow with a human review step.

On data handling: schema-only mapping is the default data-minimization mode — only headers and up to three sample rows (≤80 characters each) leave your environment. Full-data mapping requires a PHI entitlement, where the layer-5 LLM call is routed through a PHI-eligible provider with X-PHI and X-Region headers to force a PHI-eligible, in-region model. A BAA is accepted in-app before any PHI-routed run, and AdaptivMapr is HIPAA-ready — HIPAA is not a certification anyone can hold.

Output

Ask for a bundle, get a bundle.

The FHIR emit path is one field on the call you were already making. It serialises the accepted rows into the resource the template declares.

  • POST/v1/uploads/:id/commitoutput: "fhir" returns a bundle inline, or one {batch_index, bundle} per signed webhook batch.commit
  • POST/v1/transformThe same output field on the one-shot route — upload, map, validate and serialize in a single call.transform
  • GET/v1/templates/:idRead a template’s fhir_resource and its field-level validators before you map anything.public
http
POST /v1/uploads/upl_8f3a92c1/commit
{ "skip_invalid_rows": true, "output": "fhir" }
200 · committed
{
  "committed": true,
  "accepted": 1238,
  "skipped": 6,
  "output": "fhir",
  "fhir_resource": "Patient",
  "bundle": { "resourceType": "Bundle", "type": "collection",
              "entry": [ /* one entry per accepted row */ ] }
}
→ a template with no fhir_resource returns 422 with the emit error, rather than an empty bundle

The fields that matter on that call

FieldTypeWhat it does
output"fhir"required for a bundleSerialises the accepted rows into the resource the template declares. Without it the commit returns plain rows.
skip_invalid_rowsbooleandefault falseA bundle built from rows that failed their validators is a bundle the receiving system will reject. Either skip them and read the count, or fix them and re-commit.
webhook{ url, secret }optionalDelivers one {batch_index, bundle} envelope per 500 rows instead of a single inline bundle, with x-mapr-fhir-resource on each request.

Everything else on POST /v1/uploads/:id/commit behaves exactly as it does for a rows commit — see the quickstart for the full body.

Which templates can emit a bundle. The 8 templates that declare a fhir_resource today:

Read from the catalogue at build time, so this list is whatever the product actually ships.

Worked examples

Three columns, mapped

Each example links to the live template page, where you can see the full canonical schema, its validators and the FHIR resource mapping.

mapping
"Geburtsdatum"  →  date_of_birth
  template  : patient_demographics_v1
  fhir      : Patient.birthDate
  caught by : Layer 2 · Heuristic

The German header matches a registered hint after the normalising pass, so the column resolves without AI. The date_range validator (1900-01-01 to today) fires on commit.

Patient demographics template
mapping
"code loinc"  →  loinc_code
  template  : lab_result_catalog_v1
  fhir      : Observation.code
  caught by : Layer 2 · Heuristic

The French label resolves against the loinc hint, and the loinc_code validator checks the value against LOINC’s code format on commit.

Lab result catalog template
mapping
"Diagnose (ICD)"  →  icd10_code
  template  : claims_line_items_v1
  fhir      : Claim.item
  caught by : Layer 3 · Fuzzy

A messy header with extra tokens still resolves via the fuzzy layer. Because this template is risk medium, the mapping is returned with requires_hitl:true so your team can gate the commit.

Claims line items template

Questions

FHIR mapping FAQ

AdaptivMapr maps your inbound columns to a template’s canonical fields, and each healthcare template carries a fhir_resource mapping (for example Patient, Observation, or Claim.item) so you know which FHIR resource and path each field corresponds to. AdaptivMapr resolves and validates the mapping layer — it does not, on its own, emit a fully conformance-checked FHIR bundle. The output is a clean, validated, FHIR-ready field set that your downstream pipeline serialises into resources.
Healthcare templates ship field-level validators for the common clinical and administrative code systems, including LOINC (lab observations), ICD-10 (diagnoses), CPT (procedures), ATC and GTIN (drugs), NPI and GLN (providers), and IBAN (payment). Validators run on every committed row and surface failures in the per-row validation report.
Not in schema-only mode, the default data-minimization path: only your headers and up to three sample rows (each clamped to 80 characters) ever leave your environment. Full-data mapping requires a PHI subscription; the layer-5 LLM call is then routed through a PHI-eligible provider with X-PHI and X-Region headers so a PHI-eligible, in-region model is used. A BAA is available for full-data use.
Templates carry multilingual hints in German, French, Italian, English, and Spanish. A normalising pass strips accents, punctuation, and whitespace before the heuristic, fuzzy, and semantic layers compare your header against the canonical column name, label, and every hint. Most real-world headers resolve in those free, deterministic or cheap layers before the metered LLM is ever called.

Get started

Map your first file — without shipping raw records.

Schema-only mapping keeps raw records on your side — start there. The documentation covers the /v1 reference, the MCP server, and the template catalogue.

Prepaid token wallet · schema-only data-minimization mode · no free tier