Use case · Healthcare

Onboard a new lab feed with the LOINC codes checked.

Every laboratory ships a different CSV. Map it once to lab_results_v1, have the LOINC codes checked before anything commits, and emit FHIR Observation — then reuse the mapping on every file that lab sends after.

POST /v1/uploads/:id/validate
LAB_EXPORT_20260214.csv

What you actually get sent

  • PID
  • Analyse
  • LOINC
  • Wert
  • Einheit
  • Entnahme
  • RefMin
  • RefMax

Semicolon-delimited, Windows-1252, a RefMin/RefMax pair the target schema does not have, and a LOINC column that four rows in twelve leave blank.

The job

What you are actually trying to get done

A new laboratory joins the network and starts sending result files. Every lab formats differently, and the one thing you cannot get wrong is the code: a result filed against the wrong LOINC is a clinical error, not a data-quality one. The job is to onboard the feed once, prove the codes are well-formed before anything is written, and never have to think about that lab’s layout again.

Where it lands

Lab results

lab_results_v1 · risk: medium · FHIR Observation

Two templates cover this ground and the difference matters: lab_results_v1 is patient-linked and therefore medium risk; lab_result_catalog_v1 is the test-definition catalogue (LOINC, unit, reference range) with no patient in it and is low risk. Map the catalogue once, the results continuously.

The full field table →

Canonical columns

  • patient_id
  • loinc_code
  • value
  • unit
  • taken_at

5 canonical columns · 4 required · 1 validated

Step by step

The whole run, one call at a time

  1. 1

    Map the catalogue first

    The lab’s test definitions carry no patient data, so this pass is low-risk and commits straight through. It gives you the loinc_code → name → unit → reference-range table the results will be read against.
    template_id: lab_result_catalog_v1
  2. 2

    Map the result feed

    Wert, Einheit and Entnahme are shipped hints on value, unit and taken_at. PID reaches patient_id on the fuzzy layer. LOINC is an exact hit.
    POST /v1/uploads/:id/match
  3. 3

    Check the codes, not just the shape

    The loinc_code validator is one of nineteen built-in types and is checked per row. A malformed or empty code lands in errors with its row index — before a single result is written anywhere.
    POST /v1/uploads/:id/validate
  4. 4

    Decide what a bad row does

    Commit with skip_invalid_rows: true and the good rows land while the rest come back in skipped; leave it off and a single bad code stops the batch. That is your clinical policy, expressed as one boolean.
    skip_invalid_rows
  5. 5

    Emit FHIR Observation

    The template declares fhir_resource: "Observation", so the bundle comes back with status: final and the code as a CodeableConcept — LOINC by default, resolved through CODING_SYSTEM_URIS.
    output: "fhir"
  6. 6

    Watch the layout for drift

    When the lab adds a column, drift comes back with an added/removed/common diff against the last confirmed layout — deterministic, no LLM, before the feed silently starts dropping a field.
    POST /v1/layouts/drift

In code

One shot: file in, validated rows out

POST /v1/transform runs upload, match and validate in one call and returns the transformed grid. Add output to attach a serialized file to the same envelope.

curl
curl https://api.adaptivmapr.com/v1/transform \
  -H "Authorization: Bearer $ADAPTIVMAPR_API_KEY" \
  -F "file=@LAB_EXPORT_20260214.csv" \
  -F "template_id=lab_results_v1" \
  -F "skip_invalid_rows=true" \
  -F "output=fhir"
response
{
  "ok": true,
  "target_schema_id": "lab_results_v1",
  "transformed": {
    "headers": ["patient_id", "loinc_code", "value", "unit", "taken_at"],
    "rows": [["P-4471", "718-7", "13.2", "g/dL", "2026-02-14"]]
  },
  "errors": [
    { "row": 9, "field": "loinc_code", "code": "loinc_code", "message": "not a well-formed LOINC code" }
  ],
  "skipped": 4,
  "file": { "format": "fhir", "filename": "lab_results_v1.fhir.json", "content_type": "application/fhir+json", "data": "…" }
}
→ 8 columns mapped deterministically · 4 rows held back on a bad LOINC · 0 tokens spent on AI
  • loinc_code is one of 19 built-in validator types. SNOMED was removed in 2026-06 — it required a SNOMED International Affiliate License and no template used it. There is no snomed_code validator and there will not be one.
  • lab_result_catalog_v1 is low risk and lab_results_v1 is medium: the difference is the patient link, and it is what decides whether requires_hitl comes back true.
  • A commit records the confirmed mapping in mapping_layouts, keyed by a sha256 fingerprint of the normalized header row. POST /v1/layouts/lookup reads it back — workspace-scoped, never shared across tenants.
  • Reference ranges that the results template does not declare are not silently dropped on the reshape path — POST /v1/reshape can carry pass-through columns into a produced sheet.

Where the work lands

Which layer resolves this file

LayerWhat it does hereAuto-accepts atCost
1 · statisticsEvery header this lab has sent before, once you have confirmed ~20 of them≥100 @ 95% · ≥20 @ 100%Free · deterministic
2 · heuristic“Wert”, “Einheit”, “Entnahme”, “LOINC” — shipped hints, resolved at 1.00≥ 0.85Free · deterministic
3 · fuzzy“PID” → patient_id, “RefMin” → ref_low on the catalogue template≥ 0.80Free · pure compute
4 · semanticAn assay-specific label with no shared vocabulary≥ 0.78Cheap, cached · off without an embedding key
5 · aiOne batched call over anything left, constrained to unclaimed fieldsModel pick, constrained to the unclaimed column setMetered · the only paid layer

Layers run in strict cost order and short-circuit — the first layer that resolves a column claims the target field and no later, dearer layer sees it. Layers 4 and 5 are config-gated (MAPR_EMBEDDING_API_KEY, MAPR_LLM_API_KEY) and fail soft to OFF, so an unconfigured deployment still maps on the deterministic layers rather than erroring.

Before you commit

Review, cost and where it routes

It comes back flagged for review

lab_results_v1 is a medium-risk template, so PATCH /v1/uploads/:id/mappings returns requires_hitl: true and hitl_status: "pending_review" — automatically, from the template’s own risk field.

Honest limit · The flag is advisory in v1: it tells your importer to gate, it does not block the commit server-side. Native AgentGate queue integration is roadmap Q3 2026.

Usually just the flat fee

When every column resolves on layers 1–3 — the common case for a file you receive regularly — no model runs, so there are no AI tokens to bill. A small flat per-map fee ($0.0010) is drawn on every map — a fully deterministic one and a layout-cache re-map included. There is no free tier and no subscription; you top up a prepaid wallet from $10 and it draws down.

Rate card · AI tokens are billed only when a model actually ran, at provider cost ×2 (×0.5 with your own LLM key). A PHI-routed run multiplies the whole charge — flat fee included — by 1.2. See lib/pricing.ts and GET /v1/pricing.

PHI routing is opt-in, and gated

Standard is what an unconfigured workspace gets. Switch a run to PHI · enterprise and the layer-5 call goes to phi-cloud with X-PHI and a region pin, so a PHI-eligible in-region model handles it under the BAA.

Gate · Locked until the workspace accepts the BAA in-app (Settings → Security & Data, POST /v1/me/agreements). An explicit PHI ask without one is 403 agreement_required with a settings_url pointer — never a silent downgrade. PHI adds 20% to the whole map charge.

Questions

The things people actually ask

The built-in loinc_code validator is a well-formedness check on the code, not a lookup against a licensed LOINC release. It catches transposed digits, missing check digits and free text in a code column. If you need existence checking against a specific LOINC version, keep that in your own catalogue and map against it — lab_result_catalog_v1 exists for exactly that.
lab_result_catalog_v1 holds test definitions — LOINC, name, unit, reference range — with no patient in it, so nothing about it needs a human before it commits. lab_results_v1 carries patient_id alongside a clinical value, which is why its risk is medium and why every mapping comes back with requires_hitl: true.
Nothing silently. POST /v1/layouts/drift takes the new header row, fingerprints it, and compares it against the newest confirmed layout for that (workspace, template) pair — returning stable, first_seen, or drift with an added/removed/common diff. It is deterministic and uses no LLM, so you can run it on every inbound file.
Yes — save the lab as a connector (https, url_import, s3, gcs, azure_blob, sql_http, google_sheets and others) and a sync lands the parsed grid as an upload and runs the deterministic cascade over it when the connector names a template_id. Incremental pull works by modified-time on object storage and by If-Modified-Since on URL sources.

Ready when you are

Stop hand-mapping this file. Map it once.

Start with a $10 prepaid wallet. In schema-only mode only headers and up to three clamped sample rows ever leave you.

$10 minimum to start · pay only for what you map · PHI under BAA coverage
Lab result (LOINC) onboarding & mapping — AdaptivMapr — AdaptivMapr