EHR / practice-management export
CSV or XLSX, one row per patient
Header row in German, French or Italian; dates as DD.MM.YYYY; gender as M / W / F or 1 / 2; the AHV number formatted 756.xxxx.xxxx.xx in one system and unformatted in the next.
Solution · Healthcare
An EHR export, a lab CSV and a payer file all arrive in different shapes. They land in your schema — or as FHIR R4 resources — without the records leaving your control.
Healthcare
The job
What changes
What actually arrives
One export per source system, none of them alike: an EHR writes Geburtsdatum, a lab writes LOINC, a payer writes CPT. Mapping each to FHIR R4 by hand is slow and error-prone — and it is exactly the data you least want to hand to a generic model. Schema-only mapping never lets a record leave.
Four shapes cover most of what arrives. None of them is wrong — they are each correct for the system that wrote them, and each one is a different mapping problem.
CSV or XLSX, one row per patient
Header row in German, French or Italian; dates as DD.MM.YYYY; gender as M / W / F or 1 / 2; the AHV number formatted 756.xxxx.xxxx.xx in one system and unformatted in the next.
CSV, one row per result
LOINC present, absent, or hidden in a column called Analyse; the unit baked into the header — “Hb (g/L)” — instead of its own column; the collection timestamp named Entnahmedatum, Prélèvement or just Datum.
CSV or fixed-width, one row per line item
CPT and ICD-10 in adjacent, similarly named columns; amounts with a comma decimal separator; service dates in a second format from the same vendor’s other feed.
XLSX, often with a banner row above the headers
ATC, GTIN and Pharmacode all present but under vendor SKU names; the real header row sitting three rows down under a title and a logo.
Healthcare — every field carries DE / FR / IT / EN / ES hints.
4 medium + 0 high — PATCH /mappings returns requires_hitl: true.
Patient, Observation, Medication, Claim.item, Appointment, Coverage, Practitioner
How it works
Upload, cascade, confirm, commit. The deterministic layers do most of the work before anything metered runs, and nothing but headers and a few clamped sample rows leaves your system on the way.
A CSV or Excel file straight out of your EHR, LIS or claims system. The response gives you the detected columns and up to three sample rows, each clamped to 80 characters — that clamp is the whole of what schema-only sends.
Six layers, cheapest first. Statistics, heuristic and fuzzy are pure compute and cost nothing; DE / FR / IT / EN / ES hints on every field mean Geburtsdatum, date de naissance and fecha de nacimiento all land on date_of_birth. Only what falls through reaches a metered model.
Field-level validators run before a value is accepted: loinc_code on lab codes, icd10_code on diagnoses, atc_code and gtin on the formulary, cpt_code on claim lines, npi and gln on providers. A malformed code fails at map time with its own error code, not on ingest downstream.
Commit emits FHIR R4 from the template’s fhir_resource mapping — Patient, Observation, Medication, Claim.item, Appointment, Coverage, Practitioner — or writes the mapped rows to your warehouse, a signed webhook or a saved database connector instead.
Field level
This is not a description of the template — it is the template. Every row below is a field object the catalogue actually ships, with the validator ids that run on it and the header vocabulary the free heuristic layer already recognises.
lab_results_v1mediumFHIR R4 · Observation5 fields · 4 required · 1 validated| Field | Type | Required | Validators | Header vocabulary it already knows |
|---|---|---|---|---|
patient_id | string | required | — | patient · patient_id · pid |
loinc_code | string | required | loinc_code | loinc |
value | number | required | — | wert · valeur · valor |
unit | string | — | — | einheit · unité · unidad |
taken_at | date | required | — | entnahme · prélèvement · fecha |
GET /v1/templates/lab_results_v1 — not a description of one. The hint lists are what the heuristic layer compares a header against after normalize() strips accents, punctuation and whitespace, which is why adding a hint is the highest-leverage change in the whole engine: it costs nothing to run and removes a model call.Why AdaptivMapr
What the healthcare pack gives you that a generic importer does not — and, under each card, the mechanism that does it.
Eight of the ten healthcare templates carry a fhir_resource mapping, across seven distinct R4 resources, so a confirmed mapping emits FHIR directly. The canonical model is part of the catalogue, not a post-processing step you write.
Howpatient_demographics_v1 → Patient · lab_results_v1 and lab_result_catalog_v1 → Observation · drug_formulary_v1 → Medication · claims_line_items_v1 → Claim.item · appointment_log_v1 → Appointment · insurance_contracts_v1 → Coverage · provider_directory_v1 → Practitioner.
Validators run per field at map time, so a transposed LOINC code or an ICD-10 value in a CPT column is caught before anything is committed — with a specific error code you can route on.
Howloinc_code, icd10_code, atc_code, cpt_code, npi (Luhn), gln and gtin (GS1 checksum). SNOMED is deliberately absent: it needs a SNOMED International Affiliate License and no template uses it.
Schema-only ships no records at all. When you need full-data mapping, PHI routing is an explicit opt-in that pins the run in-region and is locked until your workspace accepts the BAA in the app. We offer a BAA and hold a HIPAA security risk assessment; SOC 2 is in progress.
GateAn explicit PHI ask without an acceptance returns 403 agreement_required with a settings_url — never a silent downgrade. PHI-routed runs carry a +20% surcharge on the whole map charge.
Every field carries hints in five languages, and the heuristic layer compares against them at zero cost — so vocabulary drift across sites and vendors is absorbed by the free layers instead of billed to a model.
Howdate_of_birth alone carries geburtsdatum, gebdatum, date de naissance, data di nascita, fecha de nacimiento, dob, birthday and birth date. normalize() strips accents and punctuation before comparing.
The frame
Each frame below is paired with a mechanism, not an adjective. Where we hold nothing, the row says so — a compliance page that overstates is worse than one that is short.
PHI may only be handled by a business associate under a signed BAA, with access controls, an audit trail and a documented risk assessment.
Art. 5(1)(c): process the minimum personal data necessary for the purpose. A processor that ingests whole clinical files to infer a mapping is doing more than the purpose requires.
Clinical data frequently may not leave a jurisdiction, and “usually in-region” is not a control.
Downstream systems expect canonical resources, not your column names.
What we do not claim: AdaptivMapr is not HIPAA certified (no such certificate exists), SOC 2 is in progress, and this is not a clinical integration engine — we do not replace Redox or an interface engine, and we are not a hosted onboarding UI in place of Flatfile. AdaptivMapr is the mapping layer: the cascade, the template catalogue, the validators and the delivery, under your own product.
Risk & review
Every template this vertical ships, with the risk level that decides whether a commit gates on human review, and the FHIR R4 resource it emits.
| Template id | Risk | Commit gate | FHIR R4 resource |
|---|---|---|---|
employee_roster_v1 | low | requires_hitl: false | — |
patient_demographics_v1 | medium | requires_hitl: true | Patient |
lab_result_catalog_v1 | low | requires_hitl: false | Observation |
drug_formulary_v1 | medium | requires_hitl: true | Medication |
claims_line_items_v1 | medium | requires_hitl: true | Claim.item |
appointment_log_v1 | low | requires_hitl: false | Appointment |
insurance_contracts_v1 | low | requires_hitl: false | Coverage |
supplier_inventory_v1 | low | requires_hitl: false | — |
provider_directory_v1 | low | requires_hitl: false | Practitioner |
lab_results_v1 | medium | requires_hitl: true | Observation |
GET /v1/templates returns. Nothing on this table is typed by hand.There is no setting for it and no per-id list to maintain. A template rated medium or high makes PATCH /mappings return requires_hitl: true and hitl_status: "pending_review" — on every call, for every workspace, derived in one line of the route from the risk field. AdaptivMapr sets the flag; your workflow owns the queue. A native review queue is on the roadmap, not in v1.
In this vertical, out of 10 templates.
Across all 7 packs the engine serves on GET /v1/templates.
[
{ "source_col": "Patient",
"target_field": "patient_id",
"user_confirmed": true }
]{
"upload_id": "upl_7a41c2…",
"mappings": [ … ],
"requires_hitl": true,
"hitl_status": "pending_review"
}In code
A German lab export against lab_results_v1. Five headers, five hits on the free heuristic layer, nothing sent to a model — and only the headers plus three clamped sample rows ever left the building.
/v1/uploadsparse the file, return its headers + ≤3 clamped sample rows/v1/uploads/:id/matchrun the six-layer cascade against one template/v1/uploads/:id/mappingsrecord confirmations; returns requires_hitl/v1/uploads/:id/validaterun the field validators over every row, indexed/v1/uploads/:id/commitemit and deliver — inline, webhook or DB connectorAuthentication is one header. Keys are HMAC-signed and carry their own tenant_id, so verifying one needs no database lookup — and a key can be scoped so the service that reads mappings is not the one allowed to commit them.
# 1 · upload — schema-only: headers + <=3 rows, 80 chars each
curl -s https://api.adaptivmapr.com/v1/uploads \
-H "Authorization: Bearer $MAPR_KEY" \
-F file=@laborwerte.csv
# 2 · run the cascade against the FHIR template
curl -s https://api.adaptivmapr.com/v1/uploads/$ID/match \
-H "Authorization: Bearer $MAPR_KEY" \
-H "Content-Type: application/json" \
-d '{"target_schema":{"schema_id":"lab_results_v1"},
"mode":"schema-only"}'
# 3 · confirm a column, then commit (medium risk = gated)
curl -s https://api.adaptivmapr.com/v1/uploads/$ID/mappings -X PATCH \
-H "Authorization: Bearer $MAPR_KEY" \
-H "Content-Type: application/json" \
-d '[{"source_col":"Entnahmedatum",
"target_field":"taken_at","user_confirmed":true}]'{
"template_id": "lab_results_v1",
"mode": "schema-only",
"matches": [
{ "source_col": "PID", "target_field": "patient_id", "confidence": 1, "source": "heuristic" },
{ "source_col": "LOINC", "target_field": "loinc_code", "confidence": 1, "source": "heuristic" },
{ "source_col": "Wert", "target_field": "value", "confidence": 1, "source": "heuristic" },
{ "source_col": "Einheit", "target_field": "unit", "confidence": 1, "source": "heuristic" },
{ "source_col": "Entnahmedatum", "target_field": "taken_at", "confidence": 0.85, "source": "heuristic" }
],
"unmapped": [],
"cascade_layers": ["statistics","heuristic","fuzzy","ranker","semantic","ai"]
}Rollout
Four phases, none of which requires the previous one to be finished. Most teams stop after the third and only wire the fourth when a customer starts sending the same file every month.
Take a file you already received, run it against a healthcare template in schema-only mode, and read the per-column confidence and the layer each match came from. No integration, no schema work, no commitment beyond a $10 wallet.
uploads → match → mappings → commit, driven by a scoped API key. Keys are HMAC-signed and carry their own tenant_id, so verification needs no lookup and a key can be scoped to just the verbs your service uses.
PATCH /mappings returns requires_hitl on every medium and high template. Send that flag to whatever already approves clinical data in your organisation — we set the flag, you own the queue. A native queue is roadmap, not v1.
Every confirmed mapping is cached whole against a fingerprint of the normalized header row, and every confirmation teaches the statistics layer. The next month’s file from the same site is a lookup, and drift in its columns is a deterministic diff rather than a surprise.
FAQ
Start on schema-only — headers and a few clamped sample rows, a few tokens per map. Turn on PHI routing when you need it: in-region, under a BAA you accept in the app.