open.gismo v0.2.0 runs the whole pipeline against a plain project folder. The forkable demo proves it. What neither answers is the step before all of it: a person has an EDC export, and between that export and a running study sits work this product currently makes invisible.
The engine for this already exists and is not the problem. gsm.mapping maps raw data into the standard domains, and its spec already accepts a source_col: key that renames a source column onto a target one — an existing, exercised feature that no user of open.gismo would ever discover. What is missing is a surface, and three specific things nobody has named.
Reading it closely also changes the shape of the problem. The gsm raw layer is not CDISC: findings and events use lower-case SDTM-adjacent names, while subject, site and enrolment use operational-EDC names such as invid, agerep and subject_nsv. A sponsor with immaculate SDTM still has to map. The mapping surface is not a concession to messy data; it is the front door for everybody.
Counted on 2026-08-27 from demo-301/workflows/1_mappings/ — the fourteen Raw_* domains those specs read and the distinct columns they require. The two spec keys are what gsm.mapping::ApplySpec() reads; the third number is the finding below.
Each was established by reading the code and the shipped demo study, not inferred from the shape of the product.
The mapping workflows join every findings and events domain to the subject spine with an inner join: LB.yaml and AE.yaml both end in dplyr::inner_join on subjid. That join is deliberate and documented as the data-cleaning step. But nothing anywhere checks that the two sides share any identifiers.
A lab vendor that writes "0301-S036" where the demographics extract writes "S036" passes today’s validation completely — the column is present, the type is right, no values are missing — and then produces an empty mapped domain. In demo-301 that would silently remove six of the nine safety charts, the Hy’s Law candidate metric and the Grade 3+ Lab Abnormality Rate. og_validate() reads one file at a time and never compares keys across files, so there is no moment at which anyone is told.
A spec column accepts exactly two keys, type and source_col, and ApplySpec() turns them into a SELECT ... AS .... That covers renaming and nothing else. Every genuinely hard case in a real export is a value problem rather than a name problem: an identifier with a site prefix, a baseline flag spelled BASELINE instead of Y, a lab result in different units, a toxicity grade written as Grade 3 rather than 3.
Those can be expressed today only by hand-writing a steps: entry — arbitrary R function calls in YAML — which is a far higher bar than the mapping it accompanies. This is the one place where a surface alone is not enough and the spec needs a decision.
When an input file is absent, og_run() raises an R warning and continues (R/og_run.R:397). The pipeline runs, the site builds, and the metrics that needed that domain are quietly not there. Most studies will legitimately lack several of the fourteen: Raw_DATACHG, Raw_DATAENT and Raw_QUERY come from an EDC audit trail that many sponsors will never extract.
Going without should be a choice a user makes with the cost in front of them, recorded in the study config, and printed on the site wherever the missing metric would have appeared — not a warning in a console that a scheduled run throws away.
Three of the four seams are already in the engine. Nothing here proposes a second mapping layer beside gsm.mapping — the question is what surface a person touches.
| The seam | Where it lives today | Status | What is missing |
|---|---|---|---|
| Which filedomain → path | config/data-config.yaml | ✓Exists | A surface that writes it, and formats other than CSV at the door |
| Which columntarget ← source | source_col: in each mapping spec | ✓Exists, exercisedgsm.mapping’s own workflows use it for Rave names | Everything user-facing. No detection, no proposal, no confirmation, no writer |
| Which typecoercion | type: in each mapping spec | ✓Existsog_validate checks numeric coercion on a 5,000-row sample | Reported to the user before a run rather than after |
| Which valuerecode, reformat, derive | Nowhere declarative — only a hand-written steps: entry | ○No seam | A spec key, and an upstream conversation with gsm.mapping |
| Do the keys joincross-file | Nowhere. Every check reads one file in isolation | ○No check | An overlap measurement, and a run that refuses rather than warns |
| Going withoutdeclining a domain | An R warning, then the run continues | ○Not a concept | A recorded decision the site can print |
Four screens, clickable, built against real column names and real downstream dependencies from demo-301. One of them is live: change a mapping and the readiness line, the preview and the config diff all move.
Drop a folder of CSV, SAS transport, SAS datasets or Excel. Everything is converted once, at import, into plain CSV under input/, so the project folder stays the diffable, forkable thing the product rests on. Each file is placed against a domain with a stated reason.
The successor to og_validate(), with the two columns it does not have: whether the identifiers join, and what each absent domain costs in named metrics and named charts. Six ready, three needing a decision, five going without.
A grid, not a wizard. Required column, its type and its downstream consumer on the left; your column on the right with a proposed match and the reason for it. Three rows carry the argument: an identifier that maps cleanly and fails the join, a flag that needs its values recoded, and a column nobody supplied.
A diff against the study’s own configuration, to be committed and reviewed. Renames use the existing source_col: key and are real today. Value transforms are drawn in a different colour because they are a proposal, and that is the decision below.
Thirteen platforms, re-read against a narrower question than the gap analysis asked: not “is there a mapping surface” but “what does a person actually do”. The full survey is Part 1.
The gap analysis scored row D1 as seen on five platforms: safetyGraphics, JReview, Spotfire clinical, tidyCDISC and elluminate. On this closer reading the count survives and the membership does not. tidyCDISC has no mapping surface of any kind — a missing required variable rejects the upload outright — and should not have been credited. Spotfire clinical could not be established either way from public documentation. JReview’s mapping is real but lives in an administrator-configured data dictionary rather than anywhere a reviewer touches. And three platforms the row did not credit do have one: Medidata, Veeva and Empirica. Five of thirteen cells in that row were wrong in one direction or the other, which is roughly what a survey scored at sixty-three-row grain should be expected to get wrong, and is why this one asks a narrower question.
Seven, each with a recommendation rather than a menu. D4 is the only one that reaches outside this portfolio, and it is the one everything else waits on.
A separate setup wizard, or part of the study site that already exists?
Considered: a standalone initialiser in the shape of safetyGraphicsInit(), which is our ancestor’s answer and works, but splits the product in two.
A diff the user commits, or state the app keeps for itself?
Considered: a sidecar mapping file the app owns. Simpler, and it quietly turns the study repository into something that is no longer the whole record.
CSV only, as today, or the formats people actually have?
Considered: leave conversion to the user. It is one more undocumented step at the exact moment someone decides whether this product is worth the afternoon.
Value recoding, identifier reformatting and derivation have nowhere declarative to live. Where should they go?
Considered: steps: forever. It works, and it means the generated mapping is a program rather than a declaration, which is the wrong artifact to ask a reviewer to approve. gsm.mapping is a Gilead-BioStats repository, so this is a draft handed over rather than an issue filed.
Should declining be a recorded decision or stay a warning?
Considered: keep the warning. It is the current behaviour and it is how a study ships a hole nobody can explain six months later.
Auto-detection is either a great convenience or a great way to be confidently wrong.
Considered: no detection at all. Fourteen domains and 126 columns is a long afternoon of dropdowns if nothing is pre-filled.
Central labs and local labs. One Raw_LB.
Considered: one file per domain, and the user concatenates. Which they will do in Excel, unversioned, and nobody will ever know which vendor a row came from.
In the order that removes the most risk per day of work. The first two are not the mockups at all.
Steps one and two together are the difference between a study that fails loudly and one that publishes a site full of empty charts. They are worth doing whether or not any of the rest is ever built.