Session

No shell read yet

Guide
Gate 1 · ReadingNot read
Gate 2 · DerivationNot derived
ProgramNot generated
Review · SignNo review

Study list

The sessions started in this browser, newest first, with where each stands. Sessions are private to the browser that started them; open any other by its link.

loading…

Disagreeing cell

The trace names both sides. It does not say which one is wrong — a misread photo looks exactly like a wrong program.

Continue on another device

Open this session in Chrome on a PC, or send a PC session to a phone. The code is live for 30 minutes.

On the other device, open the app and choose Open a session, then type
······
or open this link
Open a session from another device

Health

What is true about this build right now, in the order it matters while testing. Every panel below either answers or says it could not — none of them shows green by default.

1 · Safety checks

The gate suite — the tests that must pass before this tool is believed.

2 · What this run decided for you

Choices the generated program made because your shell did not say. These are the ones to argue with.

3 · Is this build current

Whether the app you are clicking is the code in the repository.

4 · Model and providers

Which model reads your photographs, and what it falls back to.

PROTOCOL —SESSION — Population —Data —Build —

Photograph the pages

The shell is required. A spec page and an old program change the result: population and treatment variable from the spec, house style from the program.

Many shells in one document

A binder — a CSR PDF, an RTF or a Word document holding many outputs. It is split into the shells it contains; you tick the ones you want and each one runs as its own session, so a shell that fails does not touch the others.

A CRO shell prints its treatment columns as <Treatment xx>. Declare the arms once here, in the order the columns appear; Total is never declared. Tables already held must be re-run.
No batch is running. Split a document and tick the shells to start one.

Tables from documents uploaded here

Every output split out of a multi-table document you uploaded in this session — open it, download it, or send it to the PC for SAS. Specs, programs and data you add to this session are copied into each of them; a table built before you added one is marked and can be re-run.

Read the photos

A vision model transcribes each page. Nothing is generated yet.

model —

Gate 1Check what it read

This is the gate. The shell is set below the way the output will be set. Fix any misread name or digit before generating.

Gate 2Check the derivation

The second gate. The first one asked whether the model read the page correctly; this one asks whether it is counting the right thing. Every row shows the dataset and variable it will be counted from — change any of them and the program changes with it.

Generate and run

R is generated and executed here. SAS is generated as a mirror and never executed — there is no SAS licence on this server.

No ADaM to hand? Declare what it holds instead

Paste PROC CONTENTS output, a define extract, or lines like ADSL: USUBJID, AGE, SEX, SAFFL. No data leaves your machine. It is used for exactly what an uploaded dataset is used for: judging each derivation above, and putting a PLACEHOLDER in the program for every analysis variable your ADaM does not have — so you get a real program and a real gap list now, and swap in the numbers when the data exists.

Sponsor ADaM spec — read it back before anything is built

Upload the sponsor's spec as .xlsx, .csv, define.xml, .docx, .pdf, or a PROC CONTENTS listing (.txt/.lst). The tool shows every variable it read. Anything it could not read cleanly, and any variable this table needs that the spec lacks, blocks — it is never replaced by a plausible-looking name. Nothing is generated until you confirm.

Shell against output

Left: the shell as the model understood it. Right: the table the program produced. Same layout, so a wrong reading is visible before any number is.

Generate and run first.

Programs and files

Is this output wrong?

The gap report knows what the tool could not derive. It cannot know what it derived confidently and got wrong. That is what this records, and what the next version learns from.

Ask about this table

Answers come from this table — its rows, their derivations, the code that ran — with the house skills and the PHUSE and PharmaSUG papers behind them. Every source used is listed.

Sign and download

The package carries the programs, the output, the reconciliation, the photos, and a hash-chained audit trail.

Reviewer comments

What you think of this run, in your words — what was right, what was wrong, what you had to fix by hand. It is stored with the session and is what the next version is improved from.

Review, then sign

The programmer runs, a different person reviews, and the signer is neither. Names are typed, so this catches a slip, not an impostor. Sign-off without a review is still possible; the record then says no independent review stood behind it.

Tools

Integration panels for this session — each one is injected by the service that provides it and owns its own controls.

No integration panel is installed in this build. Nothing here is hidden or pending — when a service registers a panel it appears above, with its own controls.

How this works

Read it once. The last two parts keep you out of trouble.

In plain words

You photograph, paste or upload a table shell — the blank layout a clinical study report table has to take — and, if you have them, the analysis plan and an old program for the same table. The tool reads the pages, shows you what it read, and only after you have agreed does it write two programs (one in R, one in SAS, the two languages trial statisticians use), run the R one here against the study's analysis datasets, and put the finished table beside your photo so every number that differs is marked. Everything you did, in order, comes back in one download together with a tamper-evident record of it.

1Gate 1 — check the reading. Before anything is written, each page comes back as an editable table set out the way the output will be. You fix any misread word or digit and press confirm. The confirmation is yours, not the model's; every correction is kept with its before and after. Nothing moves on until you have passed this gate.
2Gate 2 — check the derivation. Each row and column then shows the rule it will be counted by: which dataset, which records, which population. You accept it or change it. No program is generated until you have saved this.
3The demo. Run demo table takes a bundled photograph of a filled table from a made-up study through every stage, so you see a real table, real numbers and a real verdict before you upload anything. Its data is invented; nothing in it is from a real trial.
4Moving between phone and PC. Continue on another device gives a six-character code or a QR. The session, photos, data and output all live on the server; nothing lives only in the phone.

What it will not pretend

The SAS is written, never run. There is no SAS on this server. Every number you see came from the R; run the SAS yourself and compare, or use the optional PC check, whose label stays R-EXECUTED. NOT PC-TESTED. until your PC reports back.
House macros are recorded, not called. Your old program's macro names are noted as style; their definitions are not here, so the generated program never calls them.
Rows it cannot derive are named, not filled. A row whose rule could not be resolved prints a placeholder and is listed in the gap report. Read the gap report, not only the table.
Demo data means another study's photo always mismatches. Until you upload the study's own analysis datasets the R runs on invented demo data, so a photo from any real study will disagree with it, and that disagreement means nothing.
A disagreement with the photo means the program and the page differ. It does not say which is wrong.
The sign-off is an application record, not a qualified electronic signature, unless your own identity controls sit in front of this app.
Your column headings are never rewritten. The heading you confirmed is what prints, even when the data words that arm differently; the mapping is reported as a gap. A heading that matches no treatment value prints empty rather than borrowing another arm's numbers.

How it works — technical

Seven stages, each a call the page makes against api/ under this path. Session ids are 16 hex characters minted by the server; anything else never reaches a URL.

01Photograph. POST /api/session mints the sid. Inputs go to POST /api/session/{sid}/photo (any slot, image or pasted screenshot), /shell-file, /shell-text, /source-file and /source-text (spec or program), /data (ADaM upload), /sponsor_spec (+ /confirm, DELETE). A document of many shells goes to POST /api/batch/upload; its children are read from GET /api/batch/{bid} and GET /api/session/{sid}/batches. The page re-reads GET /api/session/{sid}/state after every change. Every input is stored exactly as received and listed with its SHA-256 in inputs.json; a removed photo moves to photos/_removed/; a duplicate upload is recorded, not read twice.
02Read. POST /api/session/{sid}/extract {kind}. Images go to the vision model, pasted text and office files to the text model, a shell file to the deterministic parser. The extraction audit event records which (model, read_as, attempts, usage) and the names of the verbatim model replies kept as raw_<kind>_<n>.txt, write-once.
03Check the reading (Gate 1). POST /api/session/{sid}/confirm {kind, edits} and POST .../sponsor_spec/confirm. Each edit is an audit event with before and after. The reader version that produced the reading is stamped on the session.
04Check the derivation (Gate 2). GET|POST /api/session/{sid}/annotate builds the per-row binding (dataset, filter, population), POST .../annotate/check runs the annotation checks, POST .../annotate/save records the human's accepted version, GET .../annotation returns it, POST .../inventory declares datasets nobody uploaded. /generate answers 400 until this save exists.
05Generate and run. POST /api/session/{sid}/generate writes program.R and program.SAS into a job dir and returns the job_id — or refuses: 400 with no confirmed shell or saved derivation, 409 when the shell was read by an older reader version, when the reader left cells uncertain, or when the sponsor spec check is blocking; POST .../execute runs the R; the page polls GET .../job and GET .../rlog. Artifacts are served by GET .../file/{name} from a fixed allow-list. The R announces any silent loss on stdout as a marker line (UNBOUNDROW, POPFILTER, ARMCONFLICT, NESTVIOLATION, …); the job JSON carries them as decisions.
06Shell against output. GET /api/session/{sid}/grid returns the confirmed shell and the executed table as a stacked pair; reference.csv holds the photographed values, verification.json / GET .../verdict the cell-by-cell reconciliation keyed on row label with a tolerance. POST .../feedback marks an output wrong. The optional PC leg: GET .../kit downloads the SAS kit, GET .../kit/status reports whether the PC has posted its comparison. POST .../ask answers a question grounded in this session's confirmed shell, annotations, code and output.
07Sign and download. POST .../comment, POST .../review, POST .../sign {name, role, meaning} each append an audit event; GET .../audit returns the chain; GET .../package builds the zip. POST .../handoff + GET .../handoff.svg issue the device code, GET /api/handoff/{code} resolves it. Also used by the page: GET /api/health, GET /api/sessions, GET /api/build, POST /api/demo.

Where files land. The session dir (P2P_ROOT/<sid>/, default /root/shellforge/p2p_sessions/) holds photos/, photos/_removed/, inputs/ + inputs.json, the per-stage state JSONs, the raw_*.txt model replies and audit.jsonl. The job dir (jobs/<job_id>/) holds program.sas, program.R, table.rtf, table.pdf, annotated.pdf (every row's derivation in red, population top right, treatment variable on each column), annotations.json, rlog.txt, reference.csv, environment.json, cells.json, verification.json. The package zip adds README.md, reconciliation.md, RUN_IN_RSTUDIO.md, a portable program.R beside program_as_run_on_server.R, data/ (parquet + csv when bundled), armds.csv/armds.json, traceability.md, programming_notes.json, the photos/ and inputs/ as uploaded, and audit.jsonl.

What the audit chain hashes. audit.jsonl is one JSON object per line: {ts, event, prev, detail}, keys sorted. prev is the first 16 hex characters of the SHA-256 of the previous line's bytes (empty on the first line). Editing or deleting any line breaks the prev of the one after it, so tampering is detectable, not merely discouraged. The chain hashes events; the files themselves are hashed into the events' detail (photo and input SHA-256s) and into inputs.json.

Tools. The Tools view is a plug-in registry. The page calls GET /api/tools/registry for the list of installed tools (slug, title, panel, error), then GET /api/tools/panel/{slug} for each one's HTML, which it injects and whose scripts it re-executes. A tool that registered but failed to load is shown as broken with its error, never dropped; a tool with a backend and no panel says so. Nothing in that view is hard-coded into this page.

The limits above, with their cause

SAS rendered, never executed. program.sas is produced by the SAS code generator and passed through the house formatter; no SAS licence exists on this host, so there is no execution path. The only route to executed SAS is the PC kit (GET .../kit); until the PC posts its report, kit/status keeps the label R-EXECUTED. NOT PC-TESTED.
House macros recorded, not called. The program reader extracts macro names and style from the uploaded .sas; macro definitions are not uploaded, so an emitted call would be an undefined-macro error at run time. They are kept as provenance in the reading, and the generated code is self-contained.
Underivable rows are named. The annotator binds every DATA row to a dataset and filter; a row with no resolvable binding prints PLACEHOLDER, the R log carries one UNBOUNDROW rows=N of=M line per table, and reconciliation.md lists it as a gap. The count against the total says how hollow the body is.
Demo data mismatches any other study. With no /data upload, the R binds to the bundled synthetic ADaM (study HEME-303); reference.csv then holds another study's photographed values, so reconciliation reports differences by construction. The package README.md states that its data is synthetic.
A disagreement is not a verdict. Reconciliation joins executed cells to photographed cells on the row label and compares within tolerance; it has no third source, so it can say the two differ and nothing about which is right.
Sign-off is an application record. POST .../sign appends a signed event with the typed name, role and meaning. No identity provider authenticates the signer, so it is evidence of a decision made in this tool, not a Part 11 signature.
Headings are never rewritten. The confirmed heading is the printed heading; when its filter resolves to a differently worded arm the substitution is announced as a gap (column-substitution choke point), and a heading matching no treatment value prints empty.
Adverse-event nesting. SOC and PT rows are counted from the same records; if a printed SOC count is ever lower than one of its PTs the log says NESTVIOLATION with the row and column — flagged, never repaired. Every treatment column prints its (N=..) even when the shell did not.

starting