govindbalki commited on
Commit
0d64fd9
·
verified ·
1 Parent(s): 0fff343

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. CLAUDE.md +81 -0
  2. web/app/Lab.tsx +90 -56
  3. web/tsconfig.tsbuildinfo +0 -0
CLAUDE.md CHANGED
@@ -1911,6 +1911,87 @@ Built in chunks. Current state:
1911
  strongest immune gene at rank 161 (`|corr| = 0.19`). Verdict:
1912
  **BORDERLINE** — *"A weak single-gene signal remains; a GP run
1913
  might still be informative."*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1914
  - **Next:** survival + unsupervised objectives end-to-end; bigger
1915
  default Lab budget for the full grammar; cross-cohort validation;
1916
  durable run store; mechanism-aware objective; lifting Search's
 
1911
  strongest immune gene at rank 161 (`|corr| = 0.19`). Verdict:
1912
  **BORDERLINE** — *"A weak single-gene signal remains; a GP run
1913
  might still be informative."*
1914
+ - **Chunk 7 follow-up (done — `scripts/tmb_resid_gp.py` synergy
1915
+ check on MSI-residualized TMB):** Second isolated script that
1916
+ answers whether the DSL finds gene COMBINATIONS beating the best
1917
+ single gene on the "leftover TMB" target the diagnostic borderlined.
1918
+ ONE new file under `scripts/` (biology-aware) + a small helper
1919
+ refactor in `validate/tmb_resid_rank.py` to keep the residual
1920
+ byte-identical between the diagnostic and the GP script.
1921
+ - **`validate.tmb_resid_rank.build_residual_cohort()`** —
1922
+ factored-out public helper returning `(X_named, residual_series,
1923
+ group_stats)`. Both `main()` and `scripts/tmb_resid_gp.py` call
1924
+ it, so any tweak to the within-MSI z-scored `log1p(TMB)` target
1925
+ lives in ONE place. Existing diagnostic behaviour byte-for-byte
1926
+ identical (verified by pytest 130/130).
1927
+ - **`scripts/tmb_resid_gp.py`** — reads the existing processed
1928
+ matrix; imports the residual via `build_residual_cohort()`;
1929
+ `anonymise(X_named)` reuses the sealed map; runs
1930
+ `run_v2_pipeline(M, y, objective=TMB_OBJECTIVE, seed, ...)` per
1931
+ seed with `prefilter_n=None`, `scalar_share_override=0.0`
1932
+ (Vector programs only), `coherence_weight=0.0`, diversity on
1933
+ (`tournament_k=2, p_mutate=0.85, immigrant_fraction=0.10`);
1934
+ default budget `--pop 300 --gens 50 --perms 200` on seeds
1935
+ `[1, 7, 13]`. Per-seed grader reveals only the winner's opaque
1936
+ IDs via `airgap.reveal` (bounded — same discipline as
1937
+ `/evaluate`) and computes the SINGLE-GENE CEILING on the SAME
1938
+ held-out test rows via `_test_ids_for(M, y, seed)` (mirrors the
1939
+ pipeline's continuous split: `make_split(stratify=False,
1940
+ random_state=seed)`) + `_spearman_per_column(X_test, y_test)`.
1941
+ `synergy = combined − ceiling`. Prints per-seed
1942
+ program_repr / revealed genes / combined |spearman| (with
1943
+ n_test) / ceiling / synergy / permutation p, ranges across
1944
+ seeds, then an overall verdict:
1945
+ - `median syn ≥ 0.10 && n_sig ≥ ⌈n/2⌉ && median combined ≥ 0.30`
1946
+ → **REAL COMBINATORIAL SIGNAL**.
1947
+ - `|median syn| < 0.05 && median combined < 0.50` →
1948
+ **NO SYNERGY**.
1949
+ - `median combined < 0.20 || range ≥ 0.20` →
1950
+ **THE LEFTOVER IS LARGELY NOISE**.
1951
+ - else **BORDERLINE**.
1952
+ - **Airgap.** `engine_v2` sees only opaque IDs; symbols cross the
1953
+ boundary once per seed via a bounded `reveal(winner_ids)` call.
1954
+ No engine / API / UI / dataset change; writes nothing to disk;
1955
+ deleting the script leaves zero trace.
1956
+ - **Verified live.** Smoke `--seeds 1 --pop 60 --gens 10 --perms 20`
1957
+ prints the full per-seed block + ranges + verdict; scipy's
1958
+ `ConstantInputWarning` from degenerate programs (already floored
1959
+ to `WORST_FITNESS` by the engine) is silenced at script scope.
1960
+ `pytest -q` still 130/130.
1961
+ - **Chunk 7 follow-up (done — Groups-the-engine-explored:
1962
+ ascending/descending sort toggle):** Frontend-only. The Groups
1963
+ table (`<ModuleRankingPanel>` in `web/app/Lab.tsx`) sorted
1964
+ descending only; clicking the active sort key now flips direction,
1965
+ clicking a new key resets to descending, and the active button's
1966
+ label appends `↓` / `↑` so the current direction is legible.
1967
+ Implementation: new `SortDir = "asc" | "desc"` state next to
1968
+ `sortKey`; `sortedModules` refactored to a single `valueOf`
1969
+ switch with null / non-finite modules PINNED to the bottom in
1970
+ BOTH directions (so a missing metric never floats to the top on
1971
+ ascending). Reset effect fires on `sortDir` too — flipping
1972
+ direction resets page 0 and closes any expanded row. `tsc
1973
+ --noEmit` clean; no API / airgap / engine change.
1974
+ - **Chunk 7 follow-up (done — Groups-the-engine-explored: plain-
1975
+ English copy pass on the panel + per-column "?" tooltips):**
1976
+ Presentation-only. Two copy rewrites and four new column-header
1977
+ tooltips in `<ModuleRankingPanel>` (`web/app/Lab.tsx`).
1978
+ - Subtitle rewritten to the four-column story ("Every gene group
1979
+ the engine tried, scored four ways so you can judge whole
1980
+ groups, not just single genes") with a one-liner gloss per
1981
+ column. Fixes a literal `&rsquo;` that was rendering in the
1982
+ old subtitle; straight apostrophes only.
1983
+ - `TIPS.moduleRanking` (the panel-title `?`) rewritten to lead
1984
+ with "Why these columns exist" — the single-gene vs group
1985
+ question the panel is meant to answer — followed by the
1986
+ honest re-scoring caveat.
1987
+ - Each of the four metric column headers (Genetic-programming
1988
+ fitness · Combined AUROC / |ρ| · Coherence · Synergy) now
1989
+ carries a small `<InfoTip>` that says how to READ that column
1990
+ (high vs low), wrapped in
1991
+ `<span className="inline-flex items-center justify-end gap-1">`
1992
+ so it hugs the right edge with the text-right header. Combined
1993
+ header's `metric_kind` conditional preserved inside the wrapper.
1994
+ - `tsc --noEmit` clean; no API / airgap / engine change.
1995
  - **Next:** survival + unsupervised objectives end-to-end; bigger
1996
  default Lab budget for the full grammar; cross-cohort validation;
1997
  durable run store; mechanism-aware objective; lifting Search's
web/app/Lab.tsx CHANGED
@@ -233,28 +233,22 @@ const TIPS = {
233
  "Scalar share = the fraction of programs the engine seeds with a " +
234
  "Scalar root (Associate / Effect) versus a Vector root.",
235
  moduleRanking:
236
- "Every gene group the engine tried during the search. Four sort " +
237
- "lenses:\n\n" +
238
- " Genetic-programming fitness the engine's OWN preference order, what the search " +
239
- "actually picked (its cross-validated objective). The default " +
240
- "sort.\n" +
241
- " Combined AUROC the group's mean across its genes, scored on " +
242
- "patients held out of training. A simpler yardstick.\n" +
243
- " Coherence mean absolute pairwise correlation among the " +
244
- "group's genes on TRAIN. High = the genes move together (a real " +
245
- "module); low = an arbitrary set.\n" +
246
- " Synergy combined held-out minus the best single gene's " +
247
- "score. High = the GROUP beats its strongest constituent (a real " +
248
- "additive effect); low / negative = a “best gene + passengers” " +
249
- "pattern.\n\n" +
250
- "The badged row is the winning program. The Survives flags show " +
251
- "whether each group still separates the label inside the " +
252
- "stratified confound subgroups (HNSC/HPV).\n\n" +
253
- "Caveat: Combined AUROC / Coherence / Synergy re-score ~2,900 " +
254
- "groups on the same small held-out set, so their very top values " +
255
- "are optimistically biased (the luckiest of thousands). The Genetic-programming " +
256
- "fitness order (and the badged winner) is the engine's own, " +
257
- "cross-validated pick — trust it as the reliable choice.",
258
  moduleSurvival:
259
  "What “Survives” checks: whether a group still separates HPV when " +
260
  "you take away a possible confounder — something that travels with " +
@@ -5836,10 +5830,12 @@ function ModuleRankingPanel({
5836
  const [loading, setLoading] = useState<boolean>(false);
5837
  const [error, setError] = useState<string | null>(null);
5838
  type SortKey = "gp_fitness" | "combined" | "coherence" | "synergy";
 
5839
  // Default sort: the engine's own preference order (what the search
5840
  // actually picked). The other three are after-the-fact re-score
5841
  // lenses.
5842
  const [sortKey, setSortKey] = useState<SortKey>("gp_fitness");
 
5843
  const [page, setPage] = useState<number>(0);
5844
  // Symbol cache keyed by opaque ID — accumulates across pages so
5845
  // re-visiting a page is free.
@@ -5878,27 +5874,30 @@ function ModuleRankingPanel({
5878
  const sortedModules = useMemo<RankedModule[]>(() => {
5879
  if (!data) return [];
5880
  const xs = [...data.modules];
5881
- if (sortKey === "coherence") {
5882
- xs.sort((a, b) => (b.coherence ?? -Infinity) - (a.coherence ?? -Infinity));
5883
- } else if (sortKey === "gp_fitness") {
5884
- xs.sort(
5885
- (a, b) => (b.gp_fitness ?? -Infinity) - (a.gp_fitness ?? -Infinity),
5886
- );
5887
- } else if (sortKey === "synergy") {
5888
- xs.sort(
5889
- (a, b) => (synergyOf(b) ?? -Infinity) - (synergyOf(a) ?? -Infinity),
5890
- );
5891
- } else {
5892
- xs.sort(
5893
- (a, b) =>
5894
- (b.combined_holdout ?? -Infinity) - (a.combined_holdout ?? -Infinity),
5895
- );
5896
- }
 
 
 
5897
  return xs;
5898
  // synergyOf depends only on RankedModule's stable per-gene fields,
5899
  // so we leave it out of the dep list.
5900
  // eslint-disable-next-line react-hooks/exhaustive-deps
5901
- }, [data, sortKey]);
5902
 
5903
  // Re-sort or re-load resets the page + collapses any open row so an
5904
  // expanded "rank N" row doesn't strand once the underlying ranking
@@ -5906,7 +5905,7 @@ function ModuleRankingPanel({
5906
  useEffect(() => {
5907
  setPage(0);
5908
  setExpandedIx(null);
5909
- }, [sortKey, data]);
5910
 
5911
  // Winner-set equality: unordered set of opaque IDs equal to the
5912
  // winner's gene_ids. Used to badge that exact module — connects the
@@ -5971,14 +5970,16 @@ function ModuleRankingPanel({
5971
  title="Groups the engine explored"
5972
  titleTip={TIPS.moduleRanking}
5973
  subtitle={
5974
- "Every gene group the engine tried during the search. Sort by " +
5975
- "Genetic-programming fitness (the engine&rsquo;s own picks), " +
5976
- "Combined AUROC (the group&rsquo;s average score on unseen " +
5977
- "patients), Coherence (how tightly its genes move together), " +
5978
- "or Synergy (how much the group beats its single best gene). " +
5979
- "The winning program is badged. The Survives flags show " +
5980
- "whether a group still separates the label under the confound " +
5981
- "checks."
 
 
5982
  }
5983
  >
5984
  {loading && (
@@ -6012,7 +6013,10 @@ function ModuleRankingPanel({
6012
  <button
6013
  key={k}
6014
  type="button"
6015
- onClick={() => setSortKey(k)}
 
 
 
6016
  aria-pressed={sortKey === k}
6017
  className={[
6018
  "rounded-md px-2 py-0.5 text-[11px] transition-colors",
@@ -6021,7 +6025,7 @@ function ModuleRankingPanel({
6021
  : "border border-border bg-card text-ink hover:border-accent/40",
6022
  ].join(" ")}
6023
  >
6024
- {label}
6025
  </button>
6026
  ))}
6027
  </div>
@@ -6068,14 +6072,44 @@ function ModuleRankingPanel({
6068
  <thead className="bg-card text-xs tracking-wide text-muted">
6069
  <tr>
6070
  <th className="px-3 py-2 text-left w-10">#</th>
6071
- <th className="px-3 py-2 text-right">Genetic-programming fitness</th>
6072
  <th className="px-3 py-2 text-right">
6073
- {data.metric_kind === "auroc"
6074
- ? "Combined AUROC"
6075
- : "Combined |ρ|"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6076
  </th>
6077
- <th className="px-3 py-2 text-right">Coherence</th>
6078
- <th className="px-3 py-2 text-right">Synergy</th>
6079
  <th className="px-3 py-2 text-right">Size</th>
6080
  <th className="px-3 py-2 text-left">Genes</th>
6081
  <th className="px-3 py-2 text-left">Tags</th>
 
233
  "Scalar share = the fraction of programs the engine seeds with a " +
234
  "Scalar root (Associate / Effect) versus a Vector root.",
235
  moduleRanking:
236
+ "Why these columns exist: once the engine could detect the trait, " +
237
+ "the real question was whether the signal came from a few genes " +
238
+ "working together or from one strong gene, and single-gene " +
239
+ "rankings can't tell you that. These measures judge whole groups " +
240
+ "instead. Coherence tells a genuine co-expressed module from a " +
241
+ "random bag of genes. Combined AUROC scores the group as a group, " +
242
+ "on patients it never saw. Synergy separates real teamwork from " +
243
+ "one good gene with passengers along for the ride. " +
244
+ "Genetic-programming fitness sits beside them as the engine's own " +
245
+ "verdict, so you can compare what the engine actually preferred " +
246
+ "against these after-the-fact re-scores. Together they answer one " +
247
+ "question: is the result carried by gene teamwork or by lone " +
248
+ "strong genes? Note that the re-scores are computed after the " +
249
+ "run, so they can rank groups differently from the engine's own " +
250
+ "picks, and the very top re-score values are optimistically " +
251
+ "biased because they are the best of thousands.",
 
 
 
 
 
 
252
  moduleSurvival:
253
  "What “Survives” checks: whether a group still separates HPV when " +
254
  "you take away a possible confounder — something that travels with " +
 
5830
  const [loading, setLoading] = useState<boolean>(false);
5831
  const [error, setError] = useState<string | null>(null);
5832
  type SortKey = "gp_fitness" | "combined" | "coherence" | "synergy";
5833
+ type SortDir = "asc" | "desc";
5834
  // Default sort: the engine's own preference order (what the search
5835
  // actually picked). The other three are after-the-fact re-score
5836
  // lenses.
5837
  const [sortKey, setSortKey] = useState<SortKey>("gp_fitness");
5838
+ const [sortDir, setSortDir] = useState<SortDir>("desc");
5839
  const [page, setPage] = useState<number>(0);
5840
  // Symbol cache keyed by opaque ID — accumulates across pages so
5841
  // re-visiting a page is free.
 
5874
  const sortedModules = useMemo<RankedModule[]>(() => {
5875
  if (!data) return [];
5876
  const xs = [...data.modules];
5877
+ const valueOf = (m: RankedModule): number | null | undefined => {
5878
+ switch (sortKey) {
5879
+ case "coherence": return m.coherence;
5880
+ case "gp_fitness": return m.gp_fitness;
5881
+ case "synergy": return synergyOf(m);
5882
+ default: return m.combined_holdout;
5883
+ }
5884
+ };
5885
+ xs.sort((a, b) => {
5886
+ const va = valueOf(a), vb = valueOf(b);
5887
+ const fa = va == null || !Number.isFinite(va);
5888
+ const fb = vb == null || !Number.isFinite(vb);
5889
+ if (fa && fb) return 0;
5890
+ if (fa) return 1;
5891
+ if (fb) return -1;
5892
+ return sortDir === "asc"
5893
+ ? (va as number) - (vb as number)
5894
+ : (vb as number) - (va as number);
5895
+ });
5896
  return xs;
5897
  // synergyOf depends only on RankedModule's stable per-gene fields,
5898
  // so we leave it out of the dep list.
5899
  // eslint-disable-next-line react-hooks/exhaustive-deps
5900
+ }, [data, sortKey, sortDir]);
5901
 
5902
  // Re-sort or re-load resets the page + collapses any open row so an
5903
  // expanded "rank N" row doesn't strand once the underlying ranking
 
5905
  useEffect(() => {
5906
  setPage(0);
5907
  setExpandedIx(null);
5908
+ }, [sortKey, sortDir, data]);
5909
 
5910
  // Winner-set equality: unordered set of opaque IDs equal to the
5911
  // winner's gene_ids. Used to badge that exact module — connects the
 
5970
  title="Groups the engine explored"
5971
  titleTip={TIPS.moduleRanking}
5972
  subtitle={
5973
+ "Every gene group the engine tried, scored four ways so you can " +
5974
+ "judge whole groups, not just single genes. Genetic-programming " +
5975
+ "fitness: how much the engine itself liked the group. Combined " +
5976
+ "AUROC: how well the group separates the two kinds of tumour on " +
5977
+ "patients it never saw. Coherence: how tightly its genes move " +
5978
+ "together (a real module versus a random bag). Synergy: how much " +
5979
+ "the group beats its single best gene (real teamwork versus one " +
5980
+ "strong gene carrying passengers). The winning program is badged; " +
5981
+ "the Survives flags show whether a group still separates the " +
5982
+ "label under the confound checks."
5983
  }
5984
  >
5985
  {loading && (
 
6013
  <button
6014
  key={k}
6015
  type="button"
6016
+ onClick={() => {
6017
+ if (sortKey === k) setSortDir((d) => (d === "desc" ? "asc" : "desc"));
6018
+ else { setSortKey(k); setSortDir("desc"); }
6019
+ }}
6020
  aria-pressed={sortKey === k}
6021
  className={[
6022
  "rounded-md px-2 py-0.5 text-[11px] transition-colors",
 
6025
  : "border border-border bg-card text-ink hover:border-accent/40",
6026
  ].join(" ")}
6027
  >
6028
+ {label}{sortKey === k ? (sortDir === "desc" ? " ↓" : " ↑") : ""}
6029
  </button>
6030
  ))}
6031
  </div>
 
6072
  <thead className="bg-card text-xs tracking-wide text-muted">
6073
  <tr>
6074
  <th className="px-3 py-2 text-left w-10">#</th>
 
6075
  <th className="px-3 py-2 text-right">
6076
+ <span className="inline-flex items-center justify-end gap-1">
6077
+ Genetic-programming fitness
6078
+ <InfoTip
6079
+ text="Higher means the engine liked this group more during the search. The very highest are its actual picks; low values are groups it barely looked at."
6080
+ label="How to read Genetic-programming fitness"
6081
+ />
6082
+ </span>
6083
+ </th>
6084
+ <th className="px-3 py-2 text-right">
6085
+ <span className="inline-flex items-center justify-end gap-1">
6086
+ {data.metric_kind === "auroc"
6087
+ ? "Combined AUROC"
6088
+ : "Combined |ρ|"}
6089
+ <InfoTip
6090
+ text="How well the whole group separates the two kinds of tmour on patients it never saw. Around 0.5 is no better than chance; close to 1.0 is a clean split. For a continuous target this is a correlation instead, where further from 0 is stronger."
6091
+ label="How to read Combined AUROC"
6092
+ />
6093
+ </span>
6094
+ </th>
6095
+ <th className="px-3 py-2 text-right">
6096
+ <span className="inline-flex items-center justify-end gap-1">
6097
+ Coherence
6098
+ <InfoTip
6099
+ text="Higher means the group's genes move together tightly, so it is a real co-expressed module. Low means the genes are largely unrelated, a random bag."
6100
+ label="How to read Coherence"
6101
+ />
6102
+ </span>
6103
+ </th>
6104
+ <th className="px-3 py-2 text-right">
6105
+ <span className="inline-flex items-center justify-end gap-1">
6106
+ Synergy
6107
+ <InfoTip
6108
+ text="Positive and high means the group genuinely beats its best single gene, real teamwork. Around zero or negative means one strong gene is doing the work and the rest are passengers."
6109
+ label="How to read Synergy"
6110
+ />
6111
+ </span>
6112
  </th>
 
 
6113
  <th className="px-3 py-2 text-right">Size</th>
6114
  <th className="px-3 py-2 text-left">Genes</th>
6115
  <th className="px-3 py-2 text-left">Tags</th>
web/tsconfig.tsbuildinfo CHANGED
The diff for this file is too large to render. See raw diff