BlogApril 30, 20266 min read

Importing FHIR Patient resources from CSV

How AdaptivMapr maps messy patient roster spreadsheets to a FHIR Patient resource, including identifiers, name parts, and contact points.

By AdaptivMapr Team

A FHIR Patient resource looks deceptively simple in the spec. In practice, every customer who hands you a roster CSV will flatten it into something that does not match the spec at all. Names live in one column. Identifiers are mixed in with phone numbers. Birthdates carry a four-digit year half the time and a two-digit year the rest.

The patient_demographics template that ships with AdaptivMapr exists to absorb that mismatch. It is a medium-risk template (auto-routed through the HITL queue when AgentGate is wired) and emits a FHIR R4 Patient resource, with identifiers split out into the right Identifier.system slots.

What the template knows

Each field carries multilingual hints (DE/FR/IT/EN/ES) and one or more validators. For the Swiss case, the AHV/AVS/NSS hint lives on the same field — see the dedicated post on the Swiss healthcare ID problem for why that is a single column, not three.

The mapper sees a header like:

["Pat-Nr.", "Vorname", "Familienname", "Geb.Dat.", "AHV", "Telefon"]

and resolves it to identifier, name.given, name.family, birthDate, identifier[ahv], and telecom[phone]. The first match comes from the heuristic layer (the German hint list contains "Geburtsdatum", "Geb.Dat.", "Geboren"). The AHV hit comes from the validator layer — the regex matches even when the column is labelled "Sozialversicherungsnummer".

The output shape

Once the mapping is confirmed, the row emits a partial FHIR Patient resource. Identifiers go into identifier[] with the right system URI; the name parts get bundled into a HumanName with use: "official"; the phone number becomes a ContactPoint with system: "phone".

{
  "resourceType": "Patient",
  "identifier": [
    { "system": "urn:oid:2.16.756.5.32", "value": "756.1234.5678.97" }
  ],
  "name": [
    { "use": "official", "family": "Müller", "given": ["Anna"] }
  ],
  "birthDate": "1981-03-14",
  "telecom": [
    { "system": "phone", "value": "+41 79 123 45 67" }
  ]
}

What the template does not do

AdaptivMapr maps and validates. It does not write to your FHIR server. The emitted resource is a JSON payload your code can post to /Patient on whichever server you target — HAPI, Medplum, Firely, an in-house store. The boundary is deliberate: every customer has a different policy on Patient.active, on identifier.assigner, on which extensions to attach.

Schema-only is enough for most files

The whole flow above runs in schema-only mode. Headers plus three sample rows is enough for the cascade to confirm the mapping; the full file never leaves the customer. If a particular file needs row-level cleanup (split "John A. Doe" into first/middle/last, normalise "M"/"male"/"Männlich"), that is a full-data call routed through PHI Gateway under the inherited BAA.

Want to try it? The patient_demographics template ships with an anonymised fixture; you can run it through the workbench or via MCP in under a minute. See the full template catalog for the other nine.

Try it in 30 seconds

Schema-only mode is free and unlimited. No DPA, no card, no signup required for the MCP free tier.

Importing FHIR Patient resources from CSV — AdaptivMapr