Omopforge

Public benchmark

Synthea to OMOP CDM 5.4, scored against the official ETL

The public Synthea sample (111 patients, 5,059 encounters, 3,387 conditions) is mapped by a deterministic baseline, compiled to SQL, executed, and compared row by row with OHDSI's own ETL-Synthea SQL run on the same files. No model call anywhere in it.

Run 2026-11-14 · 9.5 s end to end · 0 model calls · node v22.23.3 · latest.json

111 / 111
person rows, every field matches the official ETL
100% · 98.4%
condition concept agreement, code-keyed · label-only (codes stripped)
252 of 256
official DQD check thresholds passed on the produced CDM tables (233 not applicable)
94.5%
visit_occurrence row-count agreement against a 95% gate: the one miss

Table by table

TableReference rowsOur rowsAgreementWhat the number means
person111111100.0%gender, race, ethnicity and birth date match the reference on all 111 persons
visit_occurrence4,7815,05994.5%all 4,781 reference visits are present; the official ETL folds 278 inpatient and same-day ER rows into stays, the baseline spec does not roll up yet
condition_occurrence1,3233,387100% code-keyed
98.4% label-only
concept_id agreement on the 1,323 rows the reference keeps; the reference drops codes missing from the vocabulary shard, the baseline keeps them with concept 0 and lists them

Independent check: of the 70 condition codes in OHDSI's Eunomia Synthea27Nj CDM, 58 also occur in this sample; the baseline picks the same concept_id for 58 of them code-keyed and 57 label-only.

Where it falls short, from the same run

Visit rollup: 94.5% against a 95% gate

5,059 visits against 4,781. The official ETL merges inpatient stays and same-day emergency rows; the baseline mapping spec has one visit per encounter. Every reference visit exists in ours, so nothing is lost: the extra rows are the merge.

2,059 of 3,387 condition rows carry concept 0 (60.8%)

68 of 189 distinct codes cannot be mapped code-keyed (73 label-only): they are absent from the 2,294-concept vocabulary shard used here or sit outside the Condition domain. Each one is listed with its reason in latest.json under code_keyed.unmapped. These are the codes a model or a reviewer would pick up next.

4 data-quality checks fail, and they trace to two causes

What was run

Read this before you quote a number

How many AI calls a 5,000-code list needs

Deterministic tiers (exact code, exact name, token search) settle what they can before any model is asked. On a 5,000-label stress list built by rewriting shard concept names (word order, dropped words, typos, abbreviations, noise words): 3,511 codes mapped without a model, 21 of them to the wrong concept (0.6%), 1,489 left for the model tier, which is 15 calls at 100 codes per call. A list keyed by vocabulary codes (230 Condition codes) leaves 2 codes: 1 call. The stress list is synthetic; a real local list will differ.

Re-run it

Node 22.13 or newer. The command downloads the two public inputs, builds the reference with the official SQL, runs the baseline and prints the scorecard in about 10 seconds.

curl -sSfLO https://omopforge.grain64.com/downloads/omopforge-proof.tar.gz
tar xzf omopforge-proof.tar.gz && cd omopforge-proof
make proof

Results land in proof/results/latest.json. make test runs the test suite (spec validator, compiler golden files for four SQL dialects, mapper, data-quality checks, SQLite, PostgreSQL and DuckDB executing the same compiled spec).

Run it on your own tables

The same engine takes your schema and a synthetic sample, proposes the mappings, writes the ETL for SQLite, PostgreSQL, SQL Server or DuckDB, runs it and scores it. Leave an email for the day it is open.

Tell me when I can run this on my own tables

One email when it opens, nothing else. Privacy · What is stored