TL;DR
EHR2Trace converts hospital EHR exports into OMOP CDM 5.4 and MEDS 0.4 from one canonical layer of patient events. Every published row links back to the source rows it came from, event time and information-availability time are stored separately, and 55 checks run on the saved outputs. Where the data can't answer a question, the converter stops and says so instead of guessing. It is built for preparing patient histories for patient world models, clinical agents and offline reinforcement learning.
中文简介:EHR2Trace 是一个开源工具,把医院 EHR 导出数据从同一个规范事件层同时转换为 OMOP CDM 5.4 和 MEDS 0.4。每条输出记录都能追溯到来源行;事件发生时间与信息可得时间分开存储,避免时间泄漏;55 项检查直接校验落盘结果;数据回答不了的问题会阻断运行,不做猜测。面向患者世界模型、临床智能体和离线强化学习的训练数据准备。
What it does
- One canonical layer, two targets. OMOP and MEDS are each derived from the same canonical events, not one from the other, so identity, time and unit decisions are made once.
- Row-level lineage. Every canonical, OMOP and MEDS record carries a
source_row_id; OMOP adds anetl_auditlineage table.ehr2trace trace --patient <key>follows one patient through every layer. - Two clocks per event. Clinical time and the time the information became available are stored separately, so a downstream episode builder can avoid temporal leakage.
- Medication actions stay distinct. Orders, pharmacy dispensing and administration are separate records.
- No guessing.
inspectlists blockers such as an unknown source timezone and exits non-zero until they're answered in configuration. Uncertain values go to quarantine or a review queue. - Reproducible by hash. Outputs are content-addressed by input, configuration and code version; four concurrency settings produce one digest.
- Validation on what ships. 55 checks, including cross-layer checks (MEDS concepts against OMOP concepts, published deaths against canonical deaths, merged rows against source rows). Skipped checks are reported as skipped, not passed.
- Configuration, not code, per site. Everything dataset-specific lives in one YAML file; a test fails the build if a hospital-specific string appears in the core code.
- Language models propose, people decide. A model never parses data, invents a concept ID or writes an output layer; that rule is enforced by a test.
How it works
raw export (read-only) delimited text, spreadsheets, or prepared Parquet
↓ ingest deterministic parsing, one lineage record per row
source/ one Parquet per logical source, original values kept
↓ identity, canonical patient identity, time semantics, values, units, dedup
canonical/ the single event store both targets are derived from
↓ ↓
omop/ (DuckDB) meds/ (Parquet shards + metadata)
↓ validate 55 checks on what was written, with lineage
Results
All numbers come from the aggregate records committed in experiments/results/.
- Fault detection. 28 silent faults, each drawn from a real incident, are injected into a PHI-free fixture. The current 55-check suite detects all 28 and localises 21. The earlier 34-check suite detected 13; that gap is what the cross-layer checks were added to close. This runs in CI on every push.
- What CDM-level checks see. The OHDSI Data Quality Dashboard (v2.8.9, 2,374 checks) was run on the OMOP output for the 17 faults it could be pointed at. It flagged 5. Eight of the 17 never reach the OMOP tables at all; they live in the canonical layer, the identities or the MEDS shards. DQD checks CDM conformance and plausibility, and it does that job; these faults need checks outside the CDM.
- Reproducibility. Four concurrency settings gave byte-identical results across 60 compared artifacts, with no false alarms from the 55 checks on the correct build.
- Why the second clock matters. On full MIMIC-IV (131,007 subjects, in-hospital death at 6 hours after admission), a model given diagnosis codes timestamped at admission scores AUROC 0.965 on held-out data, against 0.829 when availability is respected. Scored under honest timing, that inflated model drops to 0.642.
Comparison with other EHR converters
I wrote EHR2Trace, so read this table with that in mind. It is based on each project's public README and repository as of September 2026. If something is wrong or out of date, please open an issue and I'll correct it.
| EHR2Trace | MEDS-Extract | meds_etl | ehr2meds | OHDSI/MIMIC | |
|---|---|---|---|---|---|
| Input | Any delimited or spreadsheet export, via one YAML file | Any timestamped tables, via a YAML event config | MIMIC-IV 2.2, OMOP 5.3/5.4, MEDS Unsorted | Raw EHR dumps via preMEDS configs (Danish codes: SKS, ATC, NPU) | MIMIC-IV only |
| Output | OMOP CDM 5.4 and MEDS 0.4 | MEDS 0.4 | MEDS 0.3.3 | MEDS (via MEDS-Transforms) | OMOP CDM |
| Runs on | Python, DuckDB, Parquet | Python, Polars, Hydra | Python (Polars) or C++ backend | Python, MEDS-Transforms | SQL on Google BigQuery |
| Source link in published output | Row-level source_row_ids; OMOP etl_audit table | Config block (source_block) | Not documented | row_idx column in the default pipeline | Row IDs in intermediate tables; removed at unload |
| Availability time stored apart from event time | Yes, on every event | No dedicated field (an extra column could be configured) | No CDM field for it | ||
| Order / dispense / administration kept distinct | Yes, by design | Depends on configuration | Drug type concept per row | ||
| Checks on the published output | 55, incl. cross-layer; 28 injected faults gated in CI | Unit and integration tests of the pipeline | Unit tests | Not documented | Unit tests and QA metrics; DQD-compatible |
| Stops on unanswered questions | Yes (inspect blockers) | — | — | — | — |
| Maturity | New (v0.7.0), one team, not yet on PyPI | Established: on PyPI, docs site, Zenodo DOI | On PyPI | Research group tool | Official OHDSI ETL, works with ATLAS and DQD |
| License | Apache-2.0 | MIT | Apache-2.0 | MIT | Apache-2.0 |
Measured on the same data: MIMIC-IV demo (100 patients)
The feature table above comes from documentation. This one comes from reading the files. For the two baselines I used the conversions their own maintainers published for the open MIMIC-IV demonstration subset, so nothing depends on me running someone else's tool well. The script is run_converter_comparison.py.
| EHR2Trace (MEDS + OMOP) | Published demo MEDS MEDS-Transforms 0.0.9, MEDS 0.3.3 | Published demo OMOP OHDSI/MIMIC, CDM 5.3.1 | |
|---|---|---|---|
| Events (MEDS) / clinical rows (OMOP) | 971,545 / 897,487 | 916,166 | 423,057 |
| Events that name their source table and row | 971,545 (100%) | 0; 872,430 (95.2%) carry a foreign key such as hadm_id or emar_id | 0 (lineage fields are removed at unload) |
| Events with a later availability time | 693,360 (71.4%) | No availability column | No availability field |
| Medication actions | 17,498 orders, 15,225 dispenses, 81,638 administrations | 63,696 MEDICATION events; action inferable from keys (35,835 administration, 27,721 order) | 18,229 drug rows, all typed as prescription |
| OMOP rows mapped to a concept | 169,717 of 897,487 (18.9%) | — | 415,661 of 423,057 (98.3%) |
The last row is where EHR2Trace is clearly behind. The OHDSI ETL ships curated custom mappings for MIMIC-specific codes. EHR2Trace maps a code only when an exact or unambiguous vocabulary lookup, or a confirmed human decision, resolves it; the rest stay at concept 0 and go to the review queue. If concept coverage for OHDSI analytics on MIMIC-IV is what you need today, use the OHDSI ETL.
Which one should you use?
- MIMIC-IV into OMOP for ATLAS or cohort studies: OHDSI/MIMIC.
- MEDS for foundation-model training, from MIMIC-IV or any timestamped tables: MEDS-Extract, the most established MEDS extraction pipeline.
- MEDS from an existing OMOP database: meds_etl.
- Danish registry and hospital data into MEDS: ehr2meds.
- A local hospital export into both OMOP and MEDS, where you must be able to audit any row back to its source and avoid temporal leakage: EHR2Trace.
Try it on open data
The MIMIC-IV demonstration subset is openly licensed and needs no credential. This takes a few minutes on a laptop.
git clone https://github.com/Yangxinyee/ehr2trace && cd ehr2trace
python3 -m venv .venv
.venv/bin/pip install -r requirements.lock -e ".[dev]"
wget -r -N -c -np -nH --cut-dirs=1 -P data https://physionet.org/files/mimic-iv-demo/2.2/
python3 tools/prepare_mimiciv.py --mimic-root data/mimic-iv-demo/2.2 --out prepared/mimiciv
export MIMICIV_DATA_ROOT=$PWD/prepared EHR_WORK_ROOT=$PWD/work OFFLINE_MODE=1
for step in inspect ingest identity canonical omop meds validate; do
.venv/bin/ehr2trace $step -d datasets/mimiciv.yaml
done
.venv/bin/ehr2trace trace -d datasets/mimiciv.yaml --patient <key>
Limitations
- Research software from one team, at v0.7.0. Not yet on PyPI; install from source.
- OMOP concept coverage depends on the vocabulary you install from Athena and on reviewed decisions, and starts low on a new site (see above).
- Full MIMIC-IV converts on 48 cores and 251 GB of memory; the demo subset runs on a laptop.
- Episodes, rewards and models are downstream of EHR2Trace and not included.
- The companion paper is forthcoming, so the method hasn't been peer reviewed yet.
Citation
Xinye Yang, Yuli Wang, Cheng Ting Lin, Harrison Bai. EHR2Trace: a deterministic converter from local EHR exports to OMOP CDM 5.4 and MEDS, with row-level lineage and an executable correctness contract. Version 0.7.0, 2026. https://github.com/Yangxinyee/ehr2trace
@software{yang2026ehr2trace,
author = {Yang, Xinye and Wang, Yuli and Lin, Cheng Ting and Bai, Harrison},
title = {{EHR2Trace}: a deterministic converter from local {EHR} exports to {OMOP CDM} 5.4 and {MEDS}, with row-level lineage and an executable correctness contract},
version = {0.7.0},
year = {2026},
url = {https://github.com/Yangxinyee/ehr2trace},
license = {Apache-2.0}
}
Companion paper: EHR2Trace: Auditable EHR Data Infrastructure for Patient World Models and Clinical Agents (forthcoming).
FAQ
What is EHR2Trace?
Open-source research software (Apache-2.0) that converts hospital EHR exports into OMOP CDM 5.4 and MEDS 0.4 from one canonical event layer, with row-level source lineage, separate event and availability times, and 55 validation checks on the saved outputs.
How is it different from MEDS-Extract or meds_etl?
MEDS-Extract and meds_etl produce MEDS only. EHR2Trace produces both OMOP and MEDS from one canonical layer, links every published event to its source rows, stores availability time next to event time, keeps medication actions distinct, and validates the outputs against each other. MEDS-Extract is the more mature and widely used MEDS pipeline.
How is it different from the OHDSI MIMIC-IV to OMOP ETL?
The OHDSI ETL converts MIMIC-IV only, into OMOP, on Google BigQuery, and ships curated custom mappings, so it maps far more rows to concepts. EHR2Trace is configured for any delimited or spreadsheet export, writes OMOP and MEDS, and keeps row-level lineage in the published output.
Can I try it without credentialed data access?
Yes. It runs unchanged on the openly licensed MIMIC-IV demonstration subset (100 patients) in a few minutes on a laptop, and the same conversion runs in CI on every push.
Does it use large language models to convert data?
No. A language model never parses dates, numbers or table structure, never invents a concept ID and never writes to an output layer. An optional local model can rank terminology candidates for a human reviewer, and whether that helps is measured against confirmed decisions.
How do I cite it?
Use the BibTeX above or CITATION.cff in the repository. A companion paper is forthcoming.