sdtm-check

Four deterministic conformance checks over a CDISC SDTM delivery — structural completeness, cross-field consistency, visit & date coherence, and controlled terminology. No model, no API key, no per-run cost.

Download v0.1.0 (tar.gz) SHA-256

Released 2026-08-04 · Apache-2.0-compatible · requires R with haven, jsonlite and p21bridge

Why deterministic

A finding has to be reproducible to be usable as regulatory evidence. The same delivery plus the same controlled-terminology release always produces the same findings, so a reviewer can re-run a check and get the same answer. That is the property a language model cannot offer, and it is why these are plain R scripts rather than agents.

What each check sees

CheckSeesBlocking findings
structuraldelivery completeness, readability, encoding missing DM, missing expected domain, unreadable file
consistencywho — values individually legal but mutually contradictory contradicted USUBJID composition, orphan USUBJID, duplicate DM subject
temporalwhen — contradictory dates, disagreeing visit identifiers start after end, death before reference start, one VISITNUM with two VISIT labels
terminologyCDISC controlled terminology non-extensible codelist violation

The scopes are deliberately separate, and each split was forced by evidence rather than design taste. Running terminology alone over a set of deliberately corrupted sample deliveries scored two of them identically to a clean one; adding the structural check still scored a third one identically. Each gap was a real blind spot in the checks that existed, not a bug in them.

Extensible vs non-extensible — the distinction that matters

CodelistExampleVerdict
Non-extensibleDM.SEX = "X" against Sex (C66731) Blocking — a sponsor cannot extend this list, so the value is simply wrong
ExtensibleLB.LBTESTCD = "BUN" against Laboratory Test Code Review item — a legitimate sponsor extension

Flagging extensible extensions would make every real study fail; ignoring non-extensible violations would miss the ones that matter.

Usage

tar xzf sdtm-check-0.1.0.tar.gz
cd sdtm-check-0.1.0
chmod +x bin/sdtm-check scripts/*.R

./bin/sdtm-check /path/to/sdtm --expect DM,AE,EX,LB,VS
./bin/sdtm-check /path/to/sdtm --strict     # exit 1 if anything blocks (CI gating)
./bin/sdtm-check --selftest                 # run the 20 bundled checks

It runs all four checks, writes one JSON report each, and prints a consolidated summary:

CHECK            FINDINGS   BLOCKING   DETAIL
structural              0          0   structural-findings.json
consistency             1          1   consistency-findings.json
temporal                0          0   temporal-findings.json
terminology             4          1   ct-findings.json

2 blocking finding(s) — a reviewer must adjudicate before the delivery is accepted:
  [sdtm_consistency] orphan_usubjid  domain=AE  usubjid=ORPHAN-99
  [cdisc_ct_conformance] ct_nonconformant  domain=DM  variable=SEX  value=X

Exit codes report whether the check ran, not what it found

All four scripts exit 0 whenever the check itself completed, even with blocking findings present. A blocking finding is a successful result — the check did its job, and a human decides what it means. Pass --strict, or read .blocking_count from the JSON, when you want a non-zero exit for CI.

Note on --demo. The bundled --demo mode generates a delivery with known defects from a sample study that ships with the development tree, not with this tarball. Outside that tree it will report that the sample is missing. Point the tool at your own SDTM directory instead.

Running it as a workflow step

The distribution includes clincoder-ct-check.wd.json, a workflow definition that runs the four checks as deterministic script steps followed by a human review gate, on Mediforce. Each step is attributed in the audit trail as executed by a script; the review gate records the reviewer's verdict, the reason the transition was taken, and their verbatim comment.

upload-sdtm (human)
  → structural-check   (script)
  → consistency-check  (script)
  → temporal-check     (script)
  → ct-conformance     (script)
  → review-findings    (human)
  → done

container/Dockerfile and scripts/build-image.sh build the step image.

Known limits