AdaptivMapr

Platform

One engine. Any data in, your schema out.

A stateless, pure-compute mapping cascade that turns any CSV, Excel, JSON, or SQL into your schema — resolving most columns on free deterministic layers before any AI is touched.

Importreshape.pyGenerate codeworkbook.xlsxClean output
  • Headers + ≤3 rows, ≤80 chars a cell
  • HMAC self-contained API keys
  • RLS on the dashboard plane
  • Uploads expire from KV — 24 h by default
  • In-region PHI routing under a BAA

The cascade

Six layers, cost-ordered — the engine stops the moment a column resolves.

Each header runs down a ladder from free and deterministic to metered AI. The instant a layer is confident, the cascade accepts and moves on — so most files are fully mapped before the paid layer is ever touched. That early exit is the single biggest cost lever in the system.

  1. Layer 1

    Statistics

    Auto-accept from confirmed history: a header maps when past confirmations for it agree at a production-tested ratio.

    {minN:100, minRatio:0.95} · {minN:20, minRatio:1.00}free · deterministic

  2. Layer 2

    Heuristic

    normalize() strips accents, punctuation and whitespace, then compares the header against each field’s column, label and every hint (DE/FR/IT/EN/ES).

    exact = 1.00 · substring = 0.85free · pure compute

  3. Layer 3

    Fuzzy

    Token-set ratio plus Levenshtein over the normalized strings — tolerates typos and token-order drift ("Frist Name", "name first").

    FUZZY_AUTO_ACCEPT = 0.80free · pure compute

  4. Layer 3½

    Mapping ranker

    A fine-tuned mDeBERTa cross-encoder we host ourselves scores every still-unresolved column against each template field and against "none", then assigns one-to-one. Only its confident lane resolves; a confident "none" does not. Header text and a locally computed value profile are all it receives — never a cell value.

    lane: execute · RANKER_PROFILE_ROWS = 40self-hosted model · metered per column resolved

  5. Layer 4

    Semantic

    Embeddings cosine over the header against each field’s label + hints. Cached, and silently skipped when no embedding key is configured.

    SEMANTIC_THRESHOLD = 0.78cheap · cached

  6. Layer 5

    LLM

    Every header still unresolved goes into ONE collision-aware batched call, constrained to your column set and told which fields are already claimed.

    1 call per file · llmMatchHeadersBatch()metered

Contests are settled by quality, not column order: layers 1–3 all propose, the engine arbitrates once on (layer rank, confidence, column index), then runs layer 4 only for what is still unresolved. A target field, once assigned, cannot be claimed twice — you can’t emit two source columns into one field. How the cascade works →

Structural understanding

Real-world grids are messy. The engine reads them before it matches.

Before a single header is compared, a deterministic pass (lib/structure.ts) turns spreadsheets-as-humans-made-them into clean logical tables. No LLM, no configuration — just structure recovery.

Headers that aren’t on row 0

Detects the real header row under title banners, logos, and blank lead-in rows — no “skiprows” guesswork required.

Multi-row & grouped headers

Flattens stacked header bands into a single logical name, joining a group and its child — “Instrument › Variable” — so each column reads cleanly.

Unit rows captured

Recognizes a units row beneath the header and carries it as column metadata instead of poisoning the first data row.

Transposed sheets un-pivoted

Auto-detects wide → long layouts where variables run down a column, and unpivots them back into tidy rows — deterministically, no LLM.

Reshape: beyond renaming

When mapping isn’t enough, the engine reshapes — safely.

Some inputs need more than column renaming: a pivot, a split, a join across sheets. A router picks the cheapest strategy that works, and the model — when one is used at all — never sees your data.

direct

The plain cascade. Columns map one-to-one; nothing to reshape. Free and deterministic.

HowNo program is authored at all — the mapping IS the transform.

plan

A declarative JSON DSL run in-process — deterministic and byte-reproducible. Same input, same output, every time. No code execution, no LLM.

HowRe-run a plan and you get identical bytes back.

code

Generated Python / R / SQL run in an isolated sandbox with no network. The model sees only the table shape and a clamped preview — never the full dataset.

HowEvery prompt carrying customer content is fenced; the generated program runs behind network isolation.

Across every generated path the boundary holds: the model is shown a skeleton — headers plus a clamped preview — and writes a transform it never gets to run against your full records. See how reshape works →

Two modes, one boundary

Schema-only, or full-data under a BAA — the boundary is one code path.

Both modes run the same in-process cascade. The only difference is what may leave you, and both are gated at a single clamp — not scattered across routes.

Schema-onlyFull-data
What leaves youHeaders + ≤3 rows, 80 chars a cell.The same clamp, plus the surviving cells on the layer-5 call.
Where it runsEntirely in-process — the deterministic cascade never phones out.In-process, except the one layer-5 call, under X-PHI + X-Region.
What it takesNothing — a DPA is usually not needed (provided on request).A PHI entitlement and a BAA accepted in-app.
The clampclampForSchemaOnly() at the HTTP edge of every route.The same function, the same code path.
Schema-only is a data-minimization mode — only clamped previews leave you. Full-data is gated behind an active PHI entitlement and an accepted BAA; only the layer-5 LLM call routes to a PHI-eligible provider, in-region.

The API

Every surface is a REST route you can read.

No SDK required and no bespoke verbs: the engine is a dozen routes under /v1, each doing one thing. These are the paths as they exist in the codebase.

  • POST/v1/uploadsStage a CSV, Excel, JSON or SQL export. Returns an upload id; the bytes live in KV for 24 hours.
  • POST/v1/uploads/{id}/matchRun the six-layer cascade over the file’s headers against a template.
  • POST/v1/uploads/{id}/match/streamThe same run as Server-Sent Events — a frame per cascade layer.streams
  • PATCH/v1/uploads/{id}/mappingsOverride a proposed mapping. Medium- and high-risk templates come back with requires_hitl.
  • POST/v1/uploads/{id}/commitConfirm the mapping. Feeds layer 1 and records the layout fingerprint for one-click reuse.
  • POST/v1/matchHeaders plus ≤3 clamped sample rows, no key required. Layers 1–4 only — an anonymous call can never reach the metered layer.public
  • POST/v1/reshapePivot, split, join across sheets. The router picks direct, plan or code, and echoes which it chose.
  • POST/v1/convertAny-to-any import: images, PDF, audio, HTML and URLs into structured rows.
  • POST/v1/layouts/lookupReuse the last confirmed mapping for an identical header row — a fingerprint cache hit, no AI.
  • POST/v1/layouts/driftDiff a new field set against the newest confirmed layout: stable, first_seen, or drift with an added/removed diff.
  • GET/v1/templates33 templates across 7 packs, with their fields, multilingual hints and validators.
  • GET/v1/usageMetered usage for the workspace; ?phi=1 adds token consumption and cost.

The public host is api.adaptivmapr.com. Dashboard-session routes live under /v1/me/* and are CSRF-gated. Full API reference →

For developers

One call, and the layer is in the answer.

Every match carries the layer that produced it and the confidence it produced it at. You can prove which columns cost nothing — and exactly which ones reached the metered layer.

  • REST /v1 — uploads, match, mappings, commit, reshape, convert, layouts, templates, usage
  • HMAC self-contained keys — a key verifies without a database lookup; revocation is a check layered on top
  • SSE progress — /match/stream and /me/reshape/stream emit a frame per phase, then the same terminal JSON
  • MCP — adaptivmapr.match_headers calls POST /v1/match, clamped to the schema-only contract at the edge; free layers 1–3 with no key, plus the ranker and AI (billed to the wallet) when a key is set
POST /v1/match · bash
curl -s https://api.adaptivmapr.com/v1/match \
  -H 'content-type: application/json' \
  -d '{
    "template_id": "lab_results_v1",
    "headers": ["Patient-ID","LOINC","Wert","Einheit","Entnahme"],
    "sample_rows": [["P-1042","2345-7","5.4","mmol/L","2026-03-11"]]
  }'
200 OK
{
  "template_id": "lab_results_v1",
  "matches": [
    { "source_col": "Patient-ID", "target_field": "patient_id", "confidence": 1,    "source": "heuristic" },
    { "source_col": "LOINC",      "target_field": "loinc_code", "confidence": 0.85, "source": "heuristic" },
    { "source_col": "Wert",       "target_field": "value",      "confidence": 1,    "source": "heuristic" },
    { "source_col": "Einheit",    "target_field": "unit",       "confidence": 1,    "source": "heuristic" },
    { "source_col": "Entnahme",   "target_field": "taken_at",   "confidence": 1,    "source": "heuristic" }
  ],
  "auto_accept_threshold": [
    { "minN": 100, "minRatio": 0.95 },
    { "minN": 20,  "minRatio": 1 }
  ],
  "cascade_layers": ["statistics","heuristic","fuzzy","ranker","semantic","ai"],
  "unmapped": []
}
→ 5 of 5 headers mapped on layer 2 · 0 sent to an LLM · 0 unmapped

Built for regulated data

Compliance isn’t a mode — it’s the substrate.

The cascade is the product, but the deployed engine is a full stateful SaaS with the controls regulated teams have to answer for. These are wired in, not roadmap.

Hash-chained audit trail

Every commit and confirmation lands in the audit_logs table, where a trigger assigns a per-tenant chain sequence, hash and prev_hash — a tamper-evident, replayable record of who mapped what. The external Chainlog emit is the database-less fallback.

Howmapping.commit carries review_approved and the attempt count.

Uploads expire on a TTL you set

Uploaded files live in Cloudflare KV under a TTL your workspace sets — 24 hours by default, or 0, 168 or 720 — and then vanish. 0 means the upload is destroyed on commit; the response carries the actual expires_at, which is the value to trust.

Howlib/uploads.ts — createUpload writes retention_hours * 3600 into the KV expirationTtl, mirrored to Supabase.

KEK-encrypted secrets

Connector credentials and other secrets are envelope-encrypted under a key-encryption key, scoped per tenant and never returned in plaintext.

HowGET /v1/connectors/{id}/secret-meta returns metadata, never the secret.

Strict CSP, per-request nonce

Every page ships a strict Content-Security-Policy with a fresh nonce minted per request — inline scripts that aren’t ours simply don’t run.

Howmiddleware.ts mints x-nonce; every executable inline script carries it.

Fail-closed PHI allowlist

Full-data PHI traffic only reaches BAA-covered hosts on an explicit allowlist. If it’s unset in production, PHI routing refuses rather than leaks.

HowMAPR_PHI_ALLOWED_HOSTS — /api/health reports the component down when unset.

HIPAA-ready, GDPR & nFADP posture

Regulated-data-first by design: schema-only ships headers plus three clamped rows and nothing else, full-data routes in-region under a BAA, and residency is enforced at the edge. SOC 2 Type II is in progress.

HowBAA + confidentiality terms are accepted in-app and recorded in tenant_agreements.

One key unlocks the whole engine.

Schema-only mode runs the same cascade on your prepaid wallet — every map is a few tokens; only files that need the paid layer add AI token cost.

How AdaptivMapr works — the mapping engine — AdaptivMapr