Every claim on the other two pages traces to something here: a contract read out of the workflow YAMLs, a matcher run against a hand-authored delivery, a dependency graph traced through the metric workflows' own specs, and a survey slice read from public documentation. The failures were executed rather than reasoned about.
Read on 2026-08-27 from demo-301/workflows/1_mappings/*.yaml — the study configuration that carries both the RBQM lens and the Safety charts, which is the realistic target for “bring your own study.” Every Raw_* domain in every spec: block, sentinel keys (names beginning _) dropped, merged across mappings.
og_init()'s default snapshot is smaller — 13 mapping workflows, 12 raw domains, 90 columns — because it omits ECG, inclusion/exclusion and the safety-chart mappings. Both numbers are correct; they describe different studies.
Two properties of the contract that shape any design:
Seven files, 105 source columns, authored from the CDISC ADaM IG and SDTMIG variable lists plus EDC-vendor export conventions — written before the alias table, so the matcher gets no help from having been fitted to it. Both were written by the same author, which is the honest limitation of this measurement and is stated again at the bottom.
Exact case-insensitive name matching across the whole delivery settles 44 of 126. That is higher than the “a conformant CDISC package matches zero” framing suggests, and the reason matters: most of the 44 are studyid, subjid, country, sex, race, arm repeated across domains, plus the EDC vendor's City/State/Country, which are not CDISC at all.
Raw_SUBJ.subjid ← SUBJID is one of those 44. In ADaM, SUBJID is the site-level subject number and USUBJID is the study-unique one; adae and adlb carry only USUBJID, so those domains resolve subjid to a different identifier. Case-insensitive exact matching is the highest-confidence rule available and here it is the one that breaks the study. Any design that ranks matches by confidence will rank this one first.
| Domain | settled | of | Left open |
|---|---|---|---|
| Raw_ENROLL | 7 | 7 | ✔ complete |
| Raw_QUERY | 5 | 5 | ✔ complete |
| Raw_SITE | 9 | 9 | ✔ complete |
| Raw_AE | 14 | 16 | aeongo, mincreated_dts |
| Raw_LB | 11 | 14 | lbstnrlo, lbstnrhi, toxgrg_nsv |
| Raw_SUBJ | 11 | 15 | subject_nsv, timeonstudy, firstparticipantdate, mincreated_dts |
| Raw_PD | 5 | 6 | deemedimportant |
| Raw_STUDCOMP | 5 | 6 | mincreated_dts |
| Raw_DATACHG | 3 | 4 | n_changes |
| Raw_DATAENT | 3 | 4 | data_entry_lag |
| Raw_EG | 0 | 10 | ✖ nothing delivered |
| Raw_IE | 0 | 10 | ✖ nothing delivered |
| Raw_SDRGCOMP | 0 | 5 | ✖ nothing delivered |
| Raw_STUDY | 0 | 15 | ✖ no CTMS extract |
Three domains complete themselves entirely, and the two biggest holes are domains nobody sent rather than columns nobody matched. That shape is what makes “decline it, and here is what it costs” the right primitive: a third of the outstanding work is not mapping at all, it is a study telling the product what it does not have.
| How the 73 were settled | columns | Reliability |
|---|---|---|
| Exact, case-insensitive name match | 27 | ⚠ includes the error above |
| EDC-vendor export conventions (hand) | 16 | ✔ strong within a vendor |
| CDISC IG variable index (hand) | 13 | ✔ strong |
| safetyCharts::meta_aes | 6 | ✔ per-standard defaults |
| safetyCharts::meta_labs | 4 | ✔ per-standard defaults |
| Semantically near, not equal (hand, flagged weak) | 6 | ⚠ must be reviewed |
| safetyCharts::meta_dm | 1 | ✔ per-standard defaults |
The six flagged weak are the interesting ones and they are why a matcher must be able to say “near”: Raw_SUBJ.enrollyn ← ITTFL, Raw_ENROLL.enroll_dt ← RANDDT, Raw_STUDCOMP.compyn ← COMPLFL, Raw_STUDCOMP.compreas ← DCSREAS, Raw_LB.battrnam ← PARCAT1. Each is defensible and each is a study-specific judgement — an intent-to-treat flag is not an enrolment flag, and randomisation is not enrolment. These are exactly the lines a reviewer needs to see in a diff, and exactly the ones a wizard would let a user click past.
Traced through the metric workflows' own spec: blocks in demo-301/workflows/2_metrics/: 30 workflows, 136 spec entries, 47 distinct Mapped_* columns. The dependency is heavily concentrated.
| column | metric workflows that name it |
|---|---|
| Mapped_SUBJ.subjid | 24 |
| Mapped_SUBJ.invid | 12 |
| Mapped_SUBJ.country | 11 |
| Mapped_SUBJ.timeonstudy | 8 |
| Mapped_AE.subjid | 5 |
| Mapped_PD.subjid | 4 |
| Mapped_PD.deemedimportant | 4 |
| Mapped_QUERY.querystatus | 4 |
| Mapped_LB.subjid | 3 |
| Mapped_AE.aeser | 3 |
Only 34 of the 126 required columns are named by any metric at all. The other 92 feed reporting, labels and the study masthead. That ratio is the load-bearing fact under this whole direction: the mapping job a user must actually get right is a quarter of the size the contract implies, and a diagnostic that orders findings by cost turns a 53-line chore into a 12-line one. Get the ordering wrong and the direction fails on volume — that is W4 on the design page.
Mapped_LB and Mapped_AE arrive with zero rows. Metrics that go to zero at every site: cou0001 cou0002 cou0005 kri0001 kri0002 kri0005 saf0001 saf0003 — 8 of 29. Metrics that still report normally: 21. No error, no warning, no missing column.
srs0001 declares no spec at all, so it weights whichever kri* workflows ran. A partial mapping therefore changes the site risk score's denominator instead of failing — the silent-divisor defect, arriving through the data-loading door.
Raw_LB.toxgrg_nsv has no source in SDTM or ADaM. It costs exactly cou0005 and kri0005, and one input to srs0001. Today the user is told “missing column: toxgrg_nsv” and is given no way to tell whether that is fatal or cosmetic, and nowhere to say so if it is permanent.
safetyCharts ships nine metadata tables, 82 rows, each row carrying standard_adam and standard_sdtm defaults. It is MIT-licensed and @jwildfire is its author and maintainer. It contains two kinds of row, and the second kind is the more interesting one:
| type | rows | What it maps | gsm.mapping equivalent |
|---|---|---|---|
| column | 68 | A chart role to a standard's variable name — value_col → AVAL / LBSTRESN | source_col exists; the defaults do not |
| field | 14 | A chart's literal value to a standard's value — measure_values--ALT → "Alanine Aminotransferase (U/L)" in ADaM, "Alanine Aminotransferase" in SDTM | ✖ no seam at all |
The 14 field rows are a value-level metadata layer that safetyGraphics has had for years and that gsm has nowhere — which is why hep_explorer.yaml ends up carrying four bare literal strings in lSettings.measure_values that a study must happen to match. The ADaM defaults in those rows even carry the units inside the string, "Alanine Aminotransferase (U/L)", which is exactly the unit problem showing through the vocabulary.
Bridging chart roles to gsm columns takes a hand-written table of 29 pairs. Doing that yields 52 alias rows covering 26 gsm columns for free.
meta_labs gives the ADaM reference range as A1LO / A1HI. The delivery used ANRLO / ANRHI. Both are legitimate ADaM spellings for an analysis reference range, so the alias missed a column it should have caught, and Raw_LB.lbstnrlo / lbstnrhi landed in the unsettled pile — with lbstnrhi being one of the 34 columns a metric names.
The lesson is a design requirement, not a complaint: aliases must be many-to-one, with several accepted spellings per standard, and the table needs a regression test per standard rather than a one-time import. That is decision C6.
The thirteen-platform survey is not repeated here — the gap analysis holds it and the sibling artifact re-visited it for the screen-based direction. This slice asks one question of the same sources: when a platform's mapping is finished, is there a file, and does the study own it?
| Platform | How a user's data gets in | Mapping is a file? | In the study's own VCS? |
|---|---|---|---|
| teal / teal.modules.clinical | You hand it ADaM. There is no mapping surface — the mapping is your ADaM programming. | ✔ your ADaM code | ✔ yes |
| clinDataReview | YAML configuration per report, run from the console. | ✔ YAML | ✔ yes |
| DaVinci dv.* | Modules take pre-shaped R data frames; shaping is your code. | ✔ your code | ✔ yes |
| safetyGraphics | Both: detectStandard() pre-fills an interactive mapping tab, and safetyGraphicsApp() also accepts a mapping argument programmatically. | ⚠ if you use the R path | ⚠ if you save it |
| tidyCDISC | Upload; a missing required variable is rejected. No mapping surface. | ✖ none | ✖ n/a |
| JMP Clinical | Points at SDTM / ADaM libraries with a domain-detection step on load. | ⚠ project state | ✖ no |
| Medidata Clinical Data Studio | Platform-side ingestion and standardisation, configured in the product. | ✖ platform state | ✖ no |
| Veeva CDB | Platform-side ingestion; conversion to a canonical model is the product's job. | ✖ platform state | ✖ no |
| elluminate | A data-mapping surface inside the platform, typically run with vendor services. | ✖ platform state | ✖ no |
| CluePoints | Ingestion and mapping delivered as part of the engagement. | ✖ vendor-side | ✖ no |
| Oracle Empirica Signal | Configured data ingestion into the platform's own model. | ✖ platform state | ✖ no |
Documented, not verified: each row reflects what the platform's public documentation, package reference index or user guide describes. Nothing was tested, no demo requested, no vendor contacted, and vendor documentation flatters. Where a row is a judgement about the shape of the product rather than a documented feature, it is written as such.
Both halves were written by the same author. The delivery came from the CDISC variable lists and the alias table came from safetyCharts plus the same lists, so a shared assumption inflates the score in a way a real CRO delivery would not. The number to trust is the shape, not the value: name matching alone gets roughly a third, a standards-aware alias table roughly another quarter, and the residue is dominated by domains nobody sent rather than by columns nobody matched. A second delivery from a different EDC vendor would move the vendor-convention 16 more than anything else. Running this against a real, non-synthetic delivery is the first thing that should happen if this direction is picked.