open.gismo · loading your own data · the recommendation

The read, and the bench behind it

The recommendation, before anything else. A user should put the folder on disk exactly as the CRO sent it — wrong column names, three lab vendors, a SAS transport, no define.xml — and run one command: og_read() opens every file it can, changes nothing, and answers with one document that prices each gap in the charts it turns off rather than in the columns it is missing, leaving four or five questions whose answers are single lines of text in the study's own mapping YAML. To make that true we would build three things and no more: the reader and its report; a diagnostic, og doctor, that re-checks every answer on each later transfer, names the file and line that fixes each finding, and refuses to run the pipeline while an identifier does not join; and — as v2, not v1 — a worksheet behind the report for the transfer where four questions are not enough. Nothing here maps data: gsm.mapping stays the engine, and every answer is written as the two spec keys it already reads.

01

The two front-runners are not rivals

Ranked across the three lenses, inspect-first — the intake report — is the only direction that is never below second. Convention is one point behind it. The interesting part is that they do not disagree about anything.

Each direction's rank under each of the three judging lenses
Direction Statistician
never used gsm
Clinical data manager
messy EDC delivery
Engineer
owns the codebase
Inspect-firstog_read(), the intake report 1st 2nd 2nd
Conventionog scan / og doctor 2nd 3rd 1st
Mapping bench — the dense worksheet 4th 1st lower
Guided setup — the wizard 3rd 4th lower

The engineer's judgement named a first and a second and did not separate the remaining two, so those two cells say lower rather than inventing an order. On four-three-two-one points with the unseparated pair split, inspect-first scores 10 and convention 9 — close enough that the ranking is not the argument. What follows is.

Why one path and not two products

The intake report is a document. og doctor is a diagnostic. A document with no diagnostic behind it is true for exactly as long as the folder does not change, which is about a month. A diagnostic with no document in front of it prices its findings in cou0001 and kri0002 — a vocabulary the statistician lens explicitly does not read and should never have to learn. Ship one path: og_read() writes the document, the diagnostic is what keeps it true, and the worksheet is where the document sends you when four questions is not enough.

What each lens actually asked for

Those three are satisfiable together. The first sets the unit of the report, the second sets the vocabulary of an answer, and the third sets what v1 is allowed to be.

02

The path

How a folder becomes a running study The delivery folder is read by og_read, which writes an intake report. The report answers four or five questions; the bench is the optional deeper surface behind it. Answers from either are written into the study's own mapping YAML, config and setup log, which live in the study's git. og doctor reads those files and classifies findings as blocking, costed or ready. og_run refuses while anything is blocking. When it runs, the study site is built and readiness changes from Not run to Ran. input/ the folder exactly as the CRO sent it og_read() reads everything · changes nothing · never fails The intake report 42 displays, priced. Findings ordered by what they cost, never by how sure the machine is. INTAKE/REPORT.HTML The bench OPTIONAL · V2 · DECISION R1 every column, for when four questions is not enough The study's own files WORKFLOWS/1_MAPPINGS/*.YAML CONFIG/DATA-CONFIG.YAML · CONFIG/SETUP-LOG.MD text, in the study's own git — not a product database og doctor every finding: the file, the line, the literal text that fixes it, and the displays it costs og_run() refuses while anything is blocking The study site readiness turns Ran — measured, not counted
Two commands and one document. The dashed box is the only part that is not in v1 — the report is designed to link into it, and whether it ships together is decision R1. Nothing in the chain holds state that is not a file in the study's own repository.

The six things that make it this shape

  1. Read before you complain. og_read() opens the folder as delivered — no renaming, no conversion, no input/Raw_*.csv ritual first. Today og_validate() answers a six-file delivery with twelve lines of file not found and names not one file the user actually sent.
  2. Price everything in displays. Forty-two consumers in a demo-301-shaped study. Every finding says how many of them it costs, and the ordering is by cost — never by how sure the machine is.
  3. "You did not send this" is a legitimate final state. Seven undelivered domains are not a nag that reappears every run. Acknowledge once; the site prints not supplied where the tile would have been.
  4. Nothing is auto-applied, ever. Inference orders candidates. It never fills a blank, and it never shows a confidence number.
  5. Answers are text in the study's git. No app database. The diff is the record, and the person who reads it in December is the audience.
  6. Readiness is empirical. The report says Not run until the mapping has executed against the real files. Never a count of filled boxes.
03

What gsm.mapping already solves — and what it does not

Nothing on this page is a second mapping layer. gsm.mapping::ApplySpec() is the engine, it stays the engine, and every answer any surface here collects is written as one of the two spec keys it already reads. What is missing sits beside the engine, not underneath it.

Solved, and not to be rebuilt

  • Renaming. A spec entry's source_col becomes the SELECT … AS …. My column is called something else is already a supported sentence.
  • Typing. type reaches RunQuery(bUseSchema = TRUE), so casts happen in DuckDB rather than in study code.
  • Domain assembly. The mapping workflows turn Raw_* into Mapped_* and carry the joins; Ingest() mapped a hand-built CRO delivery perfectly in the companion session — 14 of 14 columns, 1,000 rows.
  • Everything source_col cannot say. Prefix strips, value recoding and derived grades are ordinary RunQuery steps in the same YAML. The bench direction established this by building them, which is why nothing designed here needs a change from Gilead-BioStats.
  • The column vocabulary. subjid, invid (not siteid), studyid, grouping through strGroupCol. Fixed, and not ours to move.

Never its job, and therefore nobody's

  • It does not discover. It applies a spec you already wrote to a file you already named. The step where a person finds out what they were sent has no home.
  • It does not say when it did nothing. An undelivered column leaves the SELECT silently — measured below, and the reason a readiness screen can be green over a study that produces no rows.
  • It does not read values. Column names are the easy half. Four literal analyte strings decide whether the hepatic explorer draws anything, and no spec key has an opinion about them.
  • It does not price. Nothing in the package knows that a dropped toxgrg_nsv is the Grade 3+ lab-abnormality KRI, or that one identifier convention costs 16 of 42 displays.
  • It does not record why. Nobody collected CTCAE grades on this protocol is the single most valuable sentence in a transfer, and there is nowhere to put it.

M-f · The engine's own missing-column error cannot fire

Re-run against the installed gsm.mapping 1.1.3 while writing this page. Give ApplySpec() a two-column frame and a three-entry spec whose third column was never delivered. purrr::keep() removes that entry before the query is built, so the guard that follows — if (!all(sourceCols %in% names(dfSource))) LogMessage(level = "error", …) — tests a vector from which the offending entries have already been deleted. It is unreachable code.

R — ApplySpec(df[2 cols], spec[3 entries])
spec entries          subjid, invid, timeonstudy
delivered columns     SUBJECT, SITE

columns returned      subjid, invid
timeonstudy present   FALSE

messages / warnings / conditions raised about it   none

Every condition was captured with withCallingHandlers: the only messages emitted are DuckDB connect, query complete and disconnect. This is not an argument against the package — dropping unmatched entries is what makes partial specs work at all. It is the argument for a surface that reports what the engine chose to ignore.

04

What the landscape established, and what it could not

Thirteen platforms, read on 2026-08-27 from public documentation in the companion session and cited rather than re-run here. The gap analysis set the rule this inherits: seen on means documented, never verified. No demos, no trials, no vendor contact, no accounts.

Three things it settles

What it corrected in our own matrix

The July gap analysis scored row D1 — a data-mapping surface — as present on five of the thirteen: safetyGraphics, JReview, Spotfire clinical, tidyCDISC and elluminate. Reading each platform's documentation capsule by capsule does not reproduce that list.

Row D1 re-read, platform by platform
July D1 saidThe August reading
safetyGraphics confirmed — a Mapping tab, per-field dropdowns auto-filled from a detected standard, exported back out as settings
elluminate confirmed — the Mapper, a drag-and-drop transformation builder, described by the vendor and read as a claim
JReview qualified — the mapping lives in the integrated data dictionary and is configured by an administrator, not by the end user
tidyCDISC contradicted — no mapping surface of any kind. Conformance is the contract: a missing required variable rejects the upload outright
Spotfire clinical not established — nothing published at this grain. A statement about the documentation, not about the product
— not listed — three more have one — Medidata Clinical Data Studio, Veeva CDB and Oracle Empirica Signal, all behind a configuring role

The count survives — five platforms with a documented column-mapping surface, out of thirteen — but three of the five names change. That matters here because the two the July row got right are also the two whose shape this design borrows from, and the one it contradicts, tidyCDISC, is the closest thing in the survey to og_validate() today: conformance or nothing.

What it could not establish, stated as such

The honest summary of the whole survey: it establishes what vendors and maintainers chose to publish about their front doors, which flatters. It is strong enough to say what shape of surface exists in this market and where every one of them stops. It is not evidence that any of them works.

05

Ten grafts, each one named

What the three runners-up contributed, and which lens demanded it. Nine come from a sibling direction; one is an addition none of the four made.

From convention over configuration

G1Every finding names the file, the line, and the literal text that fixes itconvention

The intake report ends a finding with a question. og doctor ends one with a patch: workflows/1_mappings/SUBJ.yaml:23, the three lines to replace, and the metrics it costs if you do not. Grafted whole. A finding that says what is wrong and not where to type is a report; a finding that says where to type is work, and work is what the user came for.

Demanded by the statistician lens, which called og doctor the best-designed single piece in the session.

G2The mapping is a study document, edited as text and re-parsed before savingconvention

Answers write source_col: lines into the study's own workflows/1_mappings/*.yaml, in the study's own git history, reviewed as a diff. The writer edits the file as text and re-parses, refusing to save unless the parsed structure matches what it intended — because open.gismo already ships the naive version and it destroys the study's own explanations. See measurement M-c.

Demanded by the engineer lens, which found the defect at R/og_app_helpers.R before any of this was designed.

G3The key-overlap check is blocking, not advisoryconvention

The intake direction detects broken joins and reports them. Convention refuses to run. Take the refusal. A wrong key does not crash — it flatters: every column is present, every type checks, the run completes, and sixteen of forty-two displays quietly report zero while the site risk score reweights over the ones that survived. Roughly sixty lines, and it is the cheapest item on any build list here.

Measured independently in this session on demo-301's own data — M-d and M-e.

From the mapping bench

G4Four dispositions are the vocabulary of every answermapping bench

Bound to a source column · Derived from a generated step · Constant for the whole study · Declined, with a reason and a printed price. The intake report's findings offered two buttons each; the bench's four dispositions generalise them, and the fourth is the one no other direction has.

Demanded by the data-manager lens: today her spreadsheet says N/A in a cell and that judgement dies there.

G5Declining is a recorded judgement with a price, not a blankmapping bench

Decline Raw_LB.toxgrg_nsv and the product prints the two displays that stop, takes the reason in the user's own words, and writes both into the YAML. This is the only place in the whole session where a person can tell the product something it never thought to ask.

G6Readiness says Not run until the mapping has executedmapping bench

Never a count of filled boxes. Today's og_validate() makes the opposite bargain and gets it wrong in both directions at once — see M-b.

G7The worksheet is the room behind the report, reachable from any findingmapping bench

Every column the pipeline declares beside every column the study delivered, on one screen. The report is the front door and is where a first-time user should stay; the bench is where the fortieth transfer gets worked, and where somebody who knows their data can answer a question the four findings never raised.

Demanded by the data-manager lens, whose one killer objection to the intake report was that it hands you back to nothing once the four findings are answered.

From guided setup

G8Inference orders candidates, never fills a blank, and shows no confidence numberguided setup

That session built a name-blind value profiler, scored it 9 of 9 on a CRO lab extract where the published name detector scored 3 of 9 — and then ran it on demo-301's own Raw_LB.csv, where it picked visnum as the lab result and toxgrg_nsv as the lower limit, scoring 1.000: higher than the 0.849 it scored on the answer it got right. There is no threshold that admits one and rejects the other. So the number is never shown and the guess is never applied.

G9Candidates are re-ranked on structure, not on name or distributionguided setup

Does some categorical column give exactly one reference range per level? That test puts the correct result-and-limits triple first on both lab files and demotes the impostor to purity 0.00. It is the difference between an ordering worth reading and one worth ignoring.

G10A repair is offered as a rule, and the plain-English reason is kept beside itguided setup

Not "we fixed 765 identifiers" but "strip this constant prefix from this column in these domains" — a rule a person can read, argue with, and reuse next month. Beside it, config/setup-log.md: the user's own sentence about why, sitting next to the machine-readable spec, for whoever opens the diff in December.

One addition none of the four made

G11The answers replay on the next transfer, and the report shows only the deltanew here

The bench deferred reuse explicitly and the data-manager lens called that the worst call in the session — month two of the same study on the same EDC costing what month one cost. The answers are already files in the study's git, so replay is not a new store: the second read applies what is there, re-measures every assumption it rests on, and reports only what changed. A new analyte appearing in the lab feed is a finding. Nothing else is.

Not designed by any of the four directions. It is the graft with the least evidence behind it and the first thing to challenge.

06

What was rejected

The wizard as the primary surface
Its measurements were the strongest work in the session and every one of them is kept (G8, G9, G10). Its shape is rejected: linear, one decision at a time, optimised for somebody who does this once. Both the statistician and the data manager load a study monthly — forty times before database lock. The wizard wins hour one and then dies.
Auto-applying anything, at any confidence
Upheld from the intake direction and reinforced by the guided-setup measurement above. A data manager who finds one wrong pre-filled row stops trusting the other 125, and re-checking 126 rows by hand is worse than filling them.
Showing a confidence score
A number that reads 1.000 on the wrong answer and 0.849 on the right one is worse than no number, because it transfers the machine's certainty to the reader.
An application database
Every disposition is a line in a file the study owns. There is no state anywhere that survives git checkout.
Proposing a recode: key to gsm.mapping
Outside this program's write scope, and the bench established it is unnecessary: source_col plus generated RunQuery steps already express value recoding. Nothing here needs anything from Gilead-BioStats.
A second mapping layer beside gsm.mapping
ApplySpec() is the engine and stays the engine. Everything designed here writes its two keys and nothing else — see M-a.
og_validate() in its current form
Replaced rather than extended. It compares target names against file columns and never reads source_col, so it fails projects the engine maps perfectly and passes projects that produce zero rows. Both errors are in the report today.
07

What this session measured itself

This artifact synthesises four sessions that each measured a great deal; it re-ran six things rather than trusting the summaries, because three of the grafts above depend on them. Five are below; the sixth is M-f, in What gsm.mapping already solves above. R 4.3, gsm.mapping 1.1.3, open.gismo @ dev v0.2.0, demo-301 @ main. Everything else on this page is cited to the session that measured it.

M-a · The whole mapping seam is two lines

gsm.mapping::ApplySpec, deparsed from the installed 1.1.3:

R — deparse(gsm.mapping::ApplySpec)
mapping$source <- spec$source_col %||% name
mapping$type   <- spec$type %||% NULL
...
%>% purrr::keep(~ .x$source %in% colnames(dfSource))

Two keys, and a spec entry whose source column is absent is dropped from the generated SELECT with no message at any verbosity level. All four directions write into exactly this, which is why none of them needs a change upstream — and why a silent partial map is the failure mode the whole design has to defend against.

M-b · The validator checks the wrong side of the mapping

open.gismo/R/og_validate.R:201 is missing_cols <- setdiff(required, info$cols) — the target names the pipeline wants, set against the column names in the file. It never reads source_col. A correct spec that ApplySpec() maps perfectly still produces the full missing-column error report; a spec whose join yields nothing passes clean.

M-c · The YAML writer eats the study's own explanation

R/og_app_helpers.R ends a settings write with yaml::write_yaml(y, target). Round-tripping demo-301's own workflows/1_mappings/LB.yaml through read_yaml, setting one source_col, and writing it back:

R — read_yaml → set source_col → write_yaml
before lines: 80  comment lines: 13
after  lines: 70  comment lines: 0

CAST(toxgrg_nsv present   before: TRUE   after: TRUE

The thirteen destroyed lines are the study's note explaining that toxgrg_nsv must be cast to character or the Grade 3+ KRI's WHERE toxgrg_nsv IN ('3','4') matches nothing. The CAST step survives, because it is a value in the document; the reason it exists does not, because it is a comment. This is a defect open.gismo already owns, and it is the reason G2 specifies a text editor rather than a serialiser.

M-d · The join that returns nothing, on demo-301's own data

Package the subject file the way a CRO does — USUBJID built as studyid-subjid — while the lab file keeps the bare identifier:

R — key overlap on demo-301 input/
Raw_SUBJ rows: 1005   enrolled (enrollyn=='Y'): 765
Raw_LB   rows: 57200

subject file delivered as  AA-AA-000-0000-S384
lab file delivered as      S1000

overlap                                    0 of 765
overlap after stripping "AA-AA-000-0000-"  765 of 765

inner_join rows with the ADaM key  0
inner_join rows with the bare key  57,200  participants: 765

Both columns are present, both are character, both pass every type check open.gismo runs today. This independently reproduces the guided-setup session's M3b and the convention session's blocking finding, from a third direction.

M-e · What that silence costs, resolved from the workflows

All 42 consumers in demo-301/workflows/ — 30 metric and 12 module workflows — resolved back through their Mapped_* dependencies to the raw domains that feed them:

Raw domainConsumersWhich
Raw_SUBJ36 of 42 Nearly everything. It is the join target, so it is also the single point of failure.
Raw_LB9 cou0005 · kri0005 · saf0001 · hep_explorer · safety_delta_delta · safety_histogram · safety_outlier_explorer · safety_results_over_time · safety_shift_plot
Raw_AE7 cou0001 · cou0002 · kri0001 · kri0002 · saf0003 · ae_explorer · ae_timelines
— none —1 srs0001 is the only consumer that declares no spec: at all. It does not fail with the others; it stacks whatever kri* produced results and re-normalises over them.

One wrong identifier convention across a delivery takes out 16 of 42 displays — and moves the site risk score's denominator rather than failing it. Nothing errors. The study looks exemplary.

08

Where this is weakest

  1. W1 — It is two surfaces, which is more than any single direction proposed. The report plus the bench is a bigger v1 than anything the four sessions costed. The mitigation is a staircase, and it needs to be a real one: og_read() plus the diagnostic is v1 and is useful with no bench at all; the bench is v2. The honest risk is that v1 ships with the report linking to a room that does not exist, which is exactly the objection the data-manager lens raised in the first place.
  2. W2 — The whole product rests on one cost resolver. Every number on the ledger, and therefore the ordering of every finding, comes out of a graph built once, by one session, against one study's workflow files. If it mis-attributes a column, the report is confidently wrong about priority in a way no user can detect. It needs a test suite of its own before it is allowed to sort anything.
  3. W3 — srs0001 is a hole none of this closes. It declares no spec, so no resolver can price it and no diagnostic can block on it. Declining a domain moves its denominator instead of failing. The best available response is a printed warning, which is what all four directions settled for, and it is not enough.
  4. W4 — Answering still writes machine-generated SQL into a clinical document. A source_col: line is one line and an exercised feature. Prefix strips, value recoding and derived grades have no declarative home in the spec, so they arrive as generated RunQuery steps — code the data manager cannot fully audit and the statistician did not write. G10's setup log records the human sentence beside it; it does not make the SQL reviewable.
  5. W5 — Nothing in this session has met a real sponsor delivery. Every fixture, including the ones behind the measurements above, is demo-301's own data re-cut. A real measurement of an invented study is the strongest claim available here, and it is not the same as a claim about a real one. Running this against one genuine CRO transfer is the first thing that should happen if the direction is picked.
09

What this is built on

Competitor research is read-only and from public sources only — product documentation, package reference indexes, user guides and conference papers. No demos, no trials, no vendor contact. Seen on means documented, never verified.

10

What needs deciding

Six questions this page cannot answer for itself. Each carries the answer the page argues for, so a nod is enough and only a disagreement needs writing.

#QuestionThe recommendation on this page
R1Two surfaces in v1, or report-plus-diagnostic first and the bench in v2?Staircase. v1 is the read and the diagnostic; the bench is v2 and the report is designed to link into it.
R2Does a blocking finding refuse og_run(), or only warn?Refuse. A key with zero overlap is not a preference.
R3Does answering write into the study's own mapping YAML, or into a file open.gismo owns?The study's own, edited as text — with the writer fixed first (M-c).
R4Do answers replay across transfers, and is the scope per study or per sponsor?Replay, per study. Per-sponsor reuse is the obvious next ask and the obvious leakage risk.
R5Which formats does the door accept on day one?Add haven. It is already installed in this stack and never called, which is why a six-file delivery gets twelve file not found lines today.
R6Do lossy substitutions get carried onto the study site?Yes, on the display itself. A chart built on severity standing in for toxicity grade should say so where somebody reads it.