Skip to main content

sdc-gdr2omop

Governed Data Records as rows of the OMOP Common Data Model 5.4. A target mapper for sdc-mapper: the core validates the record, resolves every value against its model and reports each one; this package says which OMOP table and column each value goes to, takes each row's concept from the model, and writes one CSV per table. Outbound only. Apache 2.0.

Status: 4.0.0 in development. Models so far, from the FAIR Data Demo: NHANES Participant (xy8upneajsb8vdcmnve01g6g) and NHANES Medication (epbdfmvxc3gbh5f66hhb3o2m).

Use

from sdc_mapper import Engine, load_mapping
from sdc_gdr2omop import OMOPMapper, mapping_path

mapper = OMOPMapper("/path/to/athena")         # or SDC_OMOP_VOCABULARY; without one, every concept_id is 0
result = Engine().map_record(xml, mapper, load_mapping(mapping_path("xy8upneajsb8vdcmnve01g6g")))
result.files            # {"person.csv": ..., "measurement.csv": ..., ...}, the DDL's columns in order
result.report           # every value: lookup, declared, carried or not representable, and why

Through sdc-mapper's CLI and MCP server the mapper is the target omop. Use one mapper for a stream: its row ids are sequences per mapper, so the rows of many records load into one database without colliding. A loader that merges streams assigns final ids.

The target and its terms

OMOP Common Data Model 5.4, from OHDSI's CommonDataModel repository (tag v5.5.0, inst/ddl/5.4/duckdb), licensed Apache License 2.0. This package carries the DuckDB DDL and primary keys unmodified (see NOTICE), and every call loads its rows into an in-memory DuckDB database built from them: a value of the wrong type, a missing NOT NULL column or a duplicate key fails the load and the call.

Vocabularies are never shipped. OMOP's standardized vocabularies come from Athena (https://athena.ohdsi.org), each under its own terms, shown when they are downloaded; some need a license of the user's own. The user gives the directory of their download (CONCEPT.csv, CONCEPT_RELATIONSHIP.csv) at run time. Without one, every concept_id is 0, OMOP's "No matching concept", and the source code stays in the row's source value. The tests use a made-up vocabulary (tests/vocabulary-made-up/), labeled as such, with concept ids no vocabulary uses.

Where values go, and where the concepts come from

A mapping file, one per model (src/sdc_gdr2omop/mappings/), says which table and column each value goes to: +MEASUREMENT/value_as_number (a row of its own), PERSON/gender_concept_id (the record's one PERSON row), RECORD/person_id and RECORD/event_date (what every row of the record takes). Defaults by type keep it short: a quantity is a MEASUREMENT row, an answer or a count an OBSERVATION row.

The concepts come from the model, not the mapping:

  • the row's concept is the one the component links to: a LOINC code for each vital sign and laboratory result, a SNOMED CT concept for each condition question;
  • an answer's value_as_concept_id is the code its enumerated value is defined by (a LOINC answer, a SNOMED CT or NCI Thesaurus concept);
  • a unit is its UCUM symbol;
  • a value that is itself a code is looked up in the code system the component names (ICD-10-CM) and followed through "Maps to" to its standard concept.

Each is looked up in the user's vocabulary. A value whose concept comes from the model is a lookup; one the mapping places without a concept from the model is declared; one written only as source text, with no vocabulary OMOP carries, is carried.

What the mapping declares, because NHANES does not say it:

  • Dates. NHANES publishes no exam or interview dates. Every row of a 2017-2018 record takes the cycle's first day, 2017-01-01, and each person's observation period is the cycle (2017-01-01 to 2018-12-31).
  • Year of birth is 2017 less the age at screening, by a table in the mapping (NHANES top-codes age at 80).
  • Gender, race and ethnicity go to OMOP's Gender, Race and Ethnicity vocabularies by tables in the mapping. Two race answers have no code in OMOP's Race vocabulary ("Hispanic, Latino, or Spanish"; "Middle Eastern or North African"): the concept is 0 and the answer is carried as race_source_value.

OMOP's conventions the mapper applies: a NOT NULL concept column with no concept is 0; type concepts are 0, for a site to set in its own profile of the mapping; the component's identifier is the row's source value when the value is not itself a code; a drug exposure with no end date ends the day it starts.

Coverage, measured

python tools/measure.py maps every sample record (200 of each model, a seeded sample of the FAIR Data Demo's NHANES 2017-2018 records) and counts each value's class. Lookups here mean the model names the concept; whether the user's vocabulary holds each code is the vocabulary's.

Every value of every record, the record's provenance and audit included:

Model Records Values Lookup Declared Carried Not representable
NHANES Participant 200 19226 9045 (47.0%) 2023 (10.5%) 44 (0.2%) 8114 (42.2%)
NHANES Medication 200 6991 400 (5.7%) 591 (8.5%) 200 (2.9%) 5800 (83.0%)
All 400 26217 9445 (36.0%) 2614 (10.0%) 244 (0.9%) 13914 (53.1%)

The record's data alone (provenance and audit left out):

Model Records Values Lookup Declared Carried Not representable
NHANES Participant 200 13826 9045 (65.4%) 2023 (14.6%) 44 (0.3%) 2714 (19.6%)
NHANES Medication 200 1591 400 (25.1%) 591 (37.1%) 200 (12.6%) 400 (25.1%)
All 400 15417 9445 (61.3%) 2614 (17.0%) 244 (1.6%) 3114 (20.2%)

What OMOP cannot hold here: the record's provenance and audit; the survey's design (weights, strata, primary sampling units, release, interview status), which describes the sample rather than the person; a blood pressure reading's body site and status (MEASUREMENT has no column for either); a second statement of gender (PERSON holds one); a drug's name and a diagnosis code's display text (OMOP keeps the code; the name is the vocabulary's). The NHANES generic drug codes are Multum Lexicon codes, which OMOP's vocabularies do not carry, so each drug exposure's concept is 0 and its code is carried.

Exceptional Values

OMOP has no null flavors and no column that says why a value is missing; concept 0 means "No matching concept", not a reason. So every ISO 21090 code is carried: the code is written as the row's source value for the column the value would have filled (value_source_value for a measurement or an answer, gender_source_value for gender, the code column itself for a value that is a code), beside the row's concept. Where the column has no source value (year_of_birth, a date), the Exceptional Value is lost, and where OMOP requires that column the row cannot be written: every value in it is reported as not representable, with the reason (OMOP requires PERSON.year_of_birth, and the record has no value for it). A mapping file may declare a code lost; it cannot declare one preserved.

The sample records hold no Exceptional Values: the FAIR Data Demo's generators leave NHANES refusals and don't-knows out of the records and count them. The tests put them in.

Tests

pip install -e ".[dev]"
python -m pytest

The suite maps every sample record and loads all of them into one CDM built from the OHDSI DDL, checks lookups against the made-up vocabulary, and exercises Exceptional Values and refused mappings.

Metadata

Release files for sdc-gdr2omop 4.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sdc-gdr2omop 4.0.0
File Size Uploaded Provenance
sdc_gdr2omop-4.0.0.tar.gz 28.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sdc-gdr2omop 4.0.0
File Interpreter ABI Platform
sdc_gdr2omop-4.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 55.8 kB

Release files / sdc_gdr2omop-4.0.0.tar.gz

Download URL sdc_gdr2omop-4.0.0.tar.gz
Size 28.7 kB
Tags Source
SHA-256 checksum
How to use checksums
558802e57efac5ce1d0a2fb29cbaf8c52ff22d2e577ba29482e6c439c86f5398
BLAKE2b-256 checksum
How to use checksums
38fa5cedad413ad55c151d7a31da4b0c74a003def06e6b4ed27a7c36f045262b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Release files / sdc_gdr2omop-4.0.0-py3-none-any.whl

Download URL sdc_gdr2omop-4.0.0-py3-none-any.whl
Size 27.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5ca32ac84dc549289dbbe8bcd9bff8703accbfc89b6c4aafa96bdebbe2719040
BLAKE2b-256 checksum
How to use checksums
02487b8b1c7379b300145357a102dd57f5294b34a01616f040d359d6b7699aa0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Security for sdc-gdr2omop 4.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sdc_gdr2omop-4.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page