Repository navigation
Risk model: Table 1, each risk group's clinical and genomic makeup - #41
Merged
Merged
Conversation
…members Two internal changes Table 1 will build on, with no change to any result: - keepRiskRows(rows, spec) is the fit's own loop that decides who is modelled and counts who is left out and why, moved out of fitRiskmodel. A second reader of the same rows (Table 1) gets the same patients in the same order through it rather than through a copy. - groupRiskmodel also returns `members`: each group's patients as positions in the fit's subjects (no identifiers). It is not added to the view's result. Verified: test/riskmodel-groups.test.ts passes; the riskmodel result for msk_chord_2024 NSCLC (runs 2, covariate GENDER, stratified by SMOKING_PREDICTIONS_3_CLASSES; 7,809 patients) hashes identically (sha1 44e5f901…) before and after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UCpBeF9VMrjXyddFKUfiSX
A riskmodel view with `describe: {attributes?, genes?}` adds `table1` to its
result: per risk group, a categorical attribute's patients per value, a
numeric one's median and IQR, a gene's mutated patients over those whose
panels cover it — each tested across groups, with Benjamini–Hochberg q.
Where and how it is counted
- In the engine, from the fit's own rows query (riskmodelRowsSql) with the
attributes added (folded value, its spelling, and how many values the
patient's samples hold), kept by keepRiskRows exactly as the fit kept
them, and refused unless they match the fit's patients one by one (time,
entry, event). Group membership is groupRiskmodel's `members`. Only counts
leave; no identifier is projected (boundary suite passes).
- Gene rows are panel-aware: profiled = the patient's mutation panels cover
the gene (WES covers all); a patient whose panels do not is "not
profiled", never unmutated. Mutated follows the model's `measure`.
- Tests reuse lib/stats.ts: chiSquareTest (categories; genes as mutated vs
not among the profiled), kruskalWallis + quantilesFromHistogram (numbers),
benjaminiHochberg.
- Rows the groups differ in by construction are marked: genes the model
kept in any run and covariates "in the model", the endpoint's columns
"outcome". They keep p but get no q and stay out of the BH family. The
stratum is marked "stratum" and stays in (the score has no stratum term).
- Defaults: attributes = stratum, covariates, then the study's attributes
by priority without survival columns; of the first 20, those with no
value, one value, or >12 categories are left out (listed in `omitted`
with why), first 8 kept. Genes = the 15 kept in the most runs. Named
lists: ≤20 each (refused past that); an unknown attribute, a category
with >100 values, or a gene on no panel and mutated in nobody is set aside
by name. Case variants of one gene are one row.
- Not in the fit's cache key (test/riskmodel-groups.test.ts): changing
Table 1 refits nothing.
Invariants: riskmodelMethod gains Table 1's steps and a script recomputing
every p, q and median from the counts (scipy chi2_contingency without
correction, kruskal, numpy percentile, statsmodels fdr_bh over the same
family); lib/verification.ts names Table 1 under χ² and Kruskal–Wallis (no
new statistic); DSL reference + docs/dsl.md regenerated; the gallery's
riskmodel example names a Table 1; two planted bugs in check-mutants
(denominator ignores the panel; model rows in the FDR family).
Verified
- npm test: 1258 pass. test/riskmodel-table1.test.ts covers the
denominator (panel lacking the gene, WES, a group with nobody covered),
roles and the BH family, folding, numbers, defaults, set-aside rows, and
the refusal when rows do not match the fit. Planting either mutant by hand
fails it.
- Against ClickHouse, independently: msk_chord_2024 NSCLC, validate, runs 3
— a hand-written query over the validation half gives 3867 modelled
patients, SMARCA2 22/1093, ZFHX3 185/3710, KEAP1 535/3867; Table 1's
per-group counts sum to exactly these.
- check:methods on the riskmodel example: the script reproduces all 21
numbers. Ad-hoc runs with defaults, named lists, strata/covariates and
validation all reproduced (29–32 numbers each); the check caught a
duplicate-gene bug, fixed above.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCpBeF9VMrjXyddFKUfiSX
The figure does not draw Table 1 (the page shows it in a dialog), so the caption the server SVG carries — MCP, exports — says it in words when the view has `describe`: the groups and their sizes, how many rows and how many are in the q-value family, then each row's headline per group (a category's commonest value as a share of patients with a value, a number's median, a gene's share mutated among patients whose panels cover it) with its q, in three parts: differing at q < 0.05, not, and different by construction (in the model / outcome, with p and no q). Defaults passed over and set-aside rows are named with why. A stress document (riskmodel-table1-full: 20 attributes, 20 genes, five groups, a stratum and a covariate, including outcome columns and an unknown gene) joins scripts/figure-stress.json. Verified: FIGURE_CHECK_ONLY=risk check:figures — the gallery example and the stress document lint clean at 560, 936 and 1352px (6 figures, 0 issues); the 936px caption was rendered and read; npm test passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UCpBeF9VMrjXyddFKUfiSX
A "Table 1" button in the riskmodel card's toolbar (its own zone, HTML
beside the figure) opens every risk group's makeup side by side: a
category's patients per value (top 8, the rest pooled as a disclosed
"other"; not recorded shown), a number's median [IQR], a gene's mutated
over profiled with the not-profiled count; p, q, and the row's role
("in the model", "outcome", "stratum", each titled with what it means)
and a "sparse" flag below an expected count of 5. Clicking a group's
header marks its column.
The dialog owns its draft `describe` (attributes from the study's catalog
in a PickMenu, genes typed in and removed as chips, "defaults" to go
back): a change re-runs only this request — the view's one-view document
with the draft, through /api/analysis, so the model is the cached fit —
and the page re-renders nothing. While it runs the table is covered
("updating Table 1…") and the controls stay usable; a failure says why
with retry. When the draft is what the document holds, the view's own
result is shown with no request. "Add to document" writes the resolved
lists into the view (patchView), so a link and the caption carry it;
"Remove from document" takes them out.
e2e (riskmodel Table 1 block): one button, aligned (misalignedControls on
the toolbar and the dialog); groups side by side, a header click marks a
column; the model's genes marked with no q; adding SMARCA2 with the
request held covers the table and leaves the controls usable, then is one
request and shows not-profiled counts; "Add to document" puts describe in
the URL and "Table 1 (not drawn; in the result)" in the caption (caption
lines joined as read, since it wraps); reopening is served from the
view's result with no request; no JS errors.
Verified: the block passes against this folder's dev server on cold seeds
(13; and 11, which first exposed the caption-wrap flaw in the check);
screenshots of the toolbar and dialog at 2x read and compared with the
risk-groups dialog; tsc and eslint clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCpBeF9VMrjXyddFKUfiSX
The groups button reads just "groups"; what the groups are now is drawn
in the figure, and said in the button's title ("tertiles: equal shares of
patients by risk score, tied scores kept together — click to change", or
"cut at the 80th percentile of the risk score"). "equal groups" said
nothing about what was equal. "table 1" moves beside it, lowercase like
the toolbar's other controls, and its own "groups" zone (a second
"groups" beside the first) is gone.
e2e: the Apply check reads the cut from the button's title instead of its
text. Verified: both risk-model e2e blocks (18 checks, alignment
included) pass against this folder's dev server; the toolbar screenshot
read at 2x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCpBeF9VMrjXyddFKUfiSX
…ctions The card's toolbar had four sections (data, adjust, model, view) and about ten controls, wrapping onto two lines with the sections hard to tell apart. It is now three, on one line at 1400px: data genes · stratify by · covariates · clock from model settings · groups · table 1 view label genes kept in ≥ "settings" is the view settings menu the sequence and course views use (GearMenu, Settings2 icon, setrow label + Choice, a chk checkbox), with the padding the oncoprint's display menu gives form rows. It holds what is set once and rarely changed — penalty, runs, validate on held-out half — each explained in its title. A runs value the menu does not offer (the e2e document's 2) now shows instead of a blank select. Covariates and the clock join "data"; the "adjust" section is gone. e2e: a check that the menu holds the three settings and shows the document's runs. Verified: both risk-model e2e blocks (19 checks, alignment included) pass against this folder's dev server; a run changed through the menu reaches the document; toolbar and open menu screenshots read at 2x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UCpBeF9VMrjXyddFKUfiSX
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Request #4 from the analyst's review of the
riskmodelview: show the clinical and genomic makeup of each risk group — stage in the low-risk group, whether a group is enriched or depleted for KEAP1 or TP53 — with a test across groups. Built on the risk-model work merged in #37.Preview
Open this on the PR's preview deployment (or any instance with msk_chord_2024): the gallery's NSCLC model. Click table 1 in the card's toolbar; add a gene such as
SMARCA2(not every panel covers it); try Add to document./lab?tab=analysis&doc=eyJkc2wiOiIyLjYiLCJzdHVkeSI6Im1za19jaG9yZF8yMDI0IiwiY29ob3J0cyI6eyJOU0NMQyI6eyJhbGwiOlt7ImtpbmQiOiJjbGluaWNhbCIsImF0dHJpYnV0ZSI6IkNBTkNFUl9UWVBFIiwidmFsdWVzIjpbIk5vbi1TbWFsbCBDZWxsIEx1bmcgQ2FuY2VyIl0sInBlciI6InNhbXBsZSJ9XX19LCJ2aWV3cyI6W3sia2luZCI6InJpc2ttb2RlbCIsImNvaG9ydHMiOlsiTlNDTEMiXSwicnVucyI6NX1dfQWhat changes for a reader
validate, the validation half):describe: {attributes?, genes?}— new view option. "Add to document" writes the table's resolved lists into the view, so a link reproduces it and the figure's caption (MCP, exports) states Table 1 in words. Without it the table is the dialog's alone.Commit by commit
Risk model: share the modelled-patient filter, and keep each group's members— refactor, no behaviour change:keepRiskRows(who is modelled, every exclusion counted) out offitRiskmodel;groupRiskmodelalso returns each group's members as positions. The riskmodel result hashes identically before and after.Risk model: Table 1, each risk group's clinical and genomic makeup— the engine. Table 1 re-reads the fit's own rows query with its attributes added, keeps them throughkeepRiskRows, and refuses unless they match the fit's patients one by one (time, entry, event), so each column is the patients its curve drew. Counting is pure (lib/views/riskmodel-table1.ts); tests reuselib/stats.ts(chiSquareTest,kruskalWallis,quantilesFromHistogram,benjaminiHochberg).describejoinsRISK_PRESENTATION_KEYS, so a Table 1 change never refits. Method record (steps + a script recomputing every p, q and median from the counts), verification wording (no new statistic), DSL reference +docs/dsl.md, the gallery example, two planted bugs incheck-mutants.Risk model: the figure's caption states Table 1— and a stress document (20 attributes, 20 genes, five groups) forcheck:figures.Risk model: a Table 1 dialog, with rows to add and "Add to document"Risk model: "groups" and "table 1" side by side in the model zoneRisk model: the fitting settings in a settings menu, three toolbar sectionsHow it was verified
scripts/gate.sh): build, lint, unit tests, Python suite,check:methods, docs, figures, e2e — green on this branch rebased onto master.validate, runs 3 — a hand-written query over the validation half gives 3,867 modelled patients, SMARCA2 22/1,093, ZFHX3 185/3,710, KEAP1 535/3,867; Table 1's per-group counts sum to exactly these.chi2_contingency(correction=False),kruskal, numpy percentiles, statsmodelsfdr_bhover the same family) reproduces every number on the gallery example (21) and on ad-hoc runs with defaults, named lists, strata/covariates and validation (29–32 each). It caught a duplicate-gene bug before commit.test/riskmodel-table1.test.ts): the denominator (panel lacking the gene, WES, a group nobody covers), roles and the BH family, folding, numbers, defaults, set-aside rows, and the refusal when rows do not match the fit. Planting either mutant by hand fails them.describein the URL and Table 1 in the caption; reopening is served from the view's result with no request; the settings menu shows the document's runs.Review notes
DOC_VERSIONbump:describeis an optional view setting, ascutsandlabelAbovewere in Risk model: OncoCast's 0–10 score, volcano, custom cuts, and a cached fit #37; an older engine shows no Table 1 rather than a wrong number.🤖 Generated with Claude Code
https://claude.ai/code/session_01UCpBeF9VMrjXyddFKUfiSX