Skip to content

Commit 016739f

Browse files
lp698claude
andcommitted
multi-oracle: distinguish n=1 single-voter from real multi-oracle consensus
The consensus matrix previously rendered a single oracle's direction as "all ↑" / "all ↓" — technically correct (all reporting oracles agree trivially when there's one) but misleading to users who see "all ↑" and assume multiple oracles concurred. v11 content-review audit caught this on the committed SORT1 multi-oracle example, where chrombpnet, legnet, and alphagenome are specialists — each reports a different subset of layers — and the three "all ↑" rows in the matrix were actually single-oracle votes. Change: - _consensus_rows now emits "single_gain" / "single_loss" when exactly one oracle reports a direction, separate from "consensus_gain" / "consensus_loss" which now require ≥2 oracles. - Markdown renders as "only ↑ (n=1)" / "only ↓ (n=1)"; HTML uses "↑ only (n=1)" / "↓ only (n=1)" with a new neutral-grey .agree-single CSS class so users visually distinguish trivial single-voter layers from real cross-oracle consensus. - Existing "all ↑" / "all ↓" labels still fire when 2+ oracles agree. Regenerated SORT1 rs12740374 multi-oracle example (it was the direct case that exposed the bug): the three previously-"all ↑" single-voter layers (TF binding, histone marks, CAGE — all AlphaGenome-only) now correctly read "only ↑ (n=1)". The "disagree" chromatin row (AG vs ChromBPNet) and any future ≥2-oracle consensus rows are unchanged. Tests: +1 regression test (test_single_voter_layer_uses_n1_label_not_all) that verifies both agreement dict values ("single_gain"/"single_loss") and the user-visible strings in MD + HTML. All existing multi-oracle tests still pass (they all use ≥2 oracles per layer, so the consensus_gain / consensus_loss path is untouched). Verified: pytest -m "not integration" → 325 passed (was 324). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f15d926 commit 016739f

5 files changed

Lines changed: 140 additions & 80 deletions

File tree

chorus/analysis/multi_oracle_report.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,16 @@ def _consensus_rows(self) -> list[dict]:
238238
"quantile_score": best.quantile_score,
239239
}
240240
directions.append(1 if best.raw_score > 0 else -1)
241+
# "Consensus" requires at least two voting oracles; a single
242+
# voter is recorded as ``single_gain`` / ``single_loss`` so the
243+
# rendered label doesn't read "all ↑" when only one oracle
244+
# actually scored the layer.
241245
if directions:
242246
pos = sum(1 for d in directions if d > 0)
243247
neg = len(directions) - pos
244-
if pos == len(directions):
248+
if len(directions) == 1:
249+
entry["agreement"] = "single_gain" if pos == 1 else "single_loss"
250+
elif pos == len(directions):
245251
entry["agreement"] = "consensus_gain"
246252
elif neg == len(directions):
247253
entry["agreement"] = "consensus_loss"
@@ -308,6 +314,8 @@ def to_markdown(self) -> str:
308314
agree = {
309315
"consensus_gain": "all ↑",
310316
"consensus_loss": "all ↓",
317+
"single_gain": "only ↑ (n=1)",
318+
"single_loss": "only ↓ (n=1)",
311319
"disagree": "disagree",
312320
"no_data": "—",
313321
}[row["agreement"]]
@@ -328,6 +336,10 @@ def to_markdown(self) -> str:
328336
font-weight: 600; }
329337
.consensus-table .agree-mixed { background: #fff3cd; color: #856404;
330338
font-weight: 600; }
339+
/* single-voter — neutral grey so users see that only one oracle
340+
reported a direction (no real consensus possible). */
341+
.consensus-table .agree-single { background: #f3f4f6; color: #4b5563;
342+
font-weight: 500; }
331343
.consensus-table .agree-none { color: #adb5bd; }
332344
.consensus-table td.effect-cell { font-family: ui-monospace, SFMono-Regular,
333345
Menlo, monospace; font-size: 0.85rem; }
@@ -447,6 +459,8 @@ def _build_multioracle_html(report: "MultiOracleReport") -> str:
447459
agree_label, agree_cls = {
448460
"consensus_gain": ("✅ all ↑", "agree-gain"),
449461
"consensus_loss": ("✅ all ↓", "agree-loss"),
462+
"single_gain": ("↑ only (n=1)", "agree-single"),
463+
"single_loss": ("↓ only (n=1)", "agree-single"),
450464
"disagree": ("⚠ disagree", "agree-mixed"),
451465
"no_data": ("—", "agree-none"),
452466
}[agree]

examples/applications/validation/SORT1_rs12740374_multioracle/example_output.json

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
},
1111
"gene_name": "SORT1",
1212
"oracles": [
13-
"alphagenome",
1413
"chrombpnet",
15-
"legnet"
14+
"legnet",
15+
"alphagenome"
1616
],
1717
"per_oracle_report_paths": {
1818
"legnet": "rs12740374_SORT1_legnet_report.html"
@@ -21,87 +21,87 @@
2121
{
2222
"layer": "chromatin_accessibility",
2323
"oracles": {
24-
"alphagenome": {
25-
"raw_score": 0.4525551981440325,
26-
"assay_id": "DNASE/EFO:0001187 DNase-seq/.",
27-
"cell_type": "HepG2",
28-
"quantile_score": 1.0
29-
},
3024
"chrombpnet": {
3125
"raw_score": -0.11089156202257182,
3226
"assay_id": "ATAC:HepG2",
3327
"cell_type": "HepG2",
3428
"quantile_score": 0.96721823290665
3529
},
36-
"legnet": null
30+
"legnet": null,
31+
"alphagenome": {
32+
"raw_score": 0.4525551981440325,
33+
"assay_id": "DNASE/EFO:0001187 DNase-seq/.",
34+
"cell_type": "HepG2",
35+
"quantile_score": 1.0
36+
}
3737
},
3838
"agreement": "disagree"
3939
},
40+
{
41+
"layer": "promoter_activity",
42+
"oracles": {
43+
"chrombpnet": null,
44+
"legnet": {
45+
"raw_score": -0.0278623104095459,
46+
"assay_id": "LentiMPRA:HepG2",
47+
"cell_type": "HepG2",
48+
"quantile_score": null
49+
},
50+
"alphagenome": null
51+
},
52+
"agreement": "single_loss"
53+
},
4054
{
4155
"layer": "tf_binding",
4256
"oracles": {
57+
"chrombpnet": null,
58+
"legnet": null,
4359
"alphagenome": {
4460
"raw_score": 0.3806839182959419,
4561
"assay_id": "CHIP_TF/EFO:0001187 TF ChIP-seq CEBPA genetically modified (insertion) using CRISPR targeting H. sapiens CEBPA/.",
4662
"cell_type": "HepG2",
4763
"quantile_score": 1.0
48-
},
49-
"chrombpnet": null,
50-
"legnet": null
64+
}
5165
},
52-
"agreement": "consensus_gain"
66+
"agreement": "single_gain"
5367
},
5468
{
5569
"layer": "histone_marks",
5670
"oracles": {
71+
"chrombpnet": null,
72+
"legnet": null,
5773
"alphagenome": {
5874
"raw_score": 0.17998971913566533,
5975
"assay_id": "CHIP_HISTONE/EFO:0001187 Histone ChIP-seq H3K27ac/.",
6076
"cell_type": "HepG2",
6177
"quantile_score": 1.0
62-
},
63-
"chrombpnet": null,
64-
"legnet": null
78+
}
6579
},
66-
"agreement": "consensus_gain"
80+
"agreement": "single_gain"
6781
},
6882
{
6983
"layer": "tss_activity",
7084
"oracles": {
85+
"chrombpnet": null,
86+
"legnet": null,
7187
"alphagenome": {
7288
"raw_score": 0.2541795926080432,
7389
"assay_id": "CAGE/hCAGE EFO:0001187/-",
7490
"cell_type": "HepG2",
7591
"quantile_score": 1.0
76-
},
77-
"chrombpnet": null,
78-
"legnet": null
79-
},
80-
"agreement": "consensus_gain"
81-
},
82-
{
83-
"layer": "promoter_activity",
84-
"oracles": {
85-
"alphagenome": null,
86-
"chrombpnet": null,
87-
"legnet": {
88-
"raw_score": -0.0278623104095459,
89-
"assay_id": "LentiMPRA:HepG2",
90-
"cell_type": "HepG2",
91-
"quantile_score": null
9292
}
9393
},
94-
"agreement": "consensus_loss"
94+
"agreement": "single_gain"
9595
}
9696
],
9797
"analysis_request": {
98-
"user_prompt": "Analyze rs12740374 (chr1:109274968 G>T) in HepG2 liver cells using DNASE, CEBPA/CEBPB ChIP, H3K27ac, and CAGE tracks. Gene is SORT1.",
99-
"tool_name": "analyze_variant_multilayer",
100-
"oracle_name": "alphagenome",
101-
"normalizer_name": "per-track background CDFs",
102-
"tracks_requested": "6 HepG2 tracks",
98+
"user_prompt": "Validate rs12740374 (the classic SORT1 LDL-cholesterol causal variant) by scoring it with three independent deep-learning oracles: ChromBPNet for chromatin accessibility, LegNet for MPRA promoter activity, and AlphaGenome as a generalist model covering ChIP, histones and CAGE. A new user should be able to see at a glance whether the three oracles agree on direction, and which assay/cell type drove each call.",
99+
"tool_name": "MultiOracleReport",
100+
"oracle_name": "chrombpnet, legnet, alphagenome",
101+
"normalizer_name": "per-oracle chorus per-track v1",
102+
"tracks_requested": "assay_ids as listed in each per-oracle request",
103103
"cell_types": [],
104104
"notes": [],
105-
"generated_at": "2026-04-17 19:45 UTC"
105+
"generated_at": "2026-04-19 18:35 UTC"
106106
}
107107
}

examples/applications/validation/SORT1_rs12740374_multioracle/example_output.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
- **Variant:** chr1:109,274,968 G>T
44
- **Gene:** SORT1
5-
- **Oracles:** alphagenome, chrombpnet, legnet
5+
- **Oracles:** chrombpnet, legnet, alphagenome
66

77
## Cross-oracle consensus
88

9-
| Layer | alphagenome | chrombpnet | legnet | Agreement |
9+
| Layer | chrombpnet | legnet | alphagenome | Agreement |
1010
|---|---|---|---|---|
11-
| Chromatin accessibility (DNASE/ATAC) (log2fc) | +0.453 · DNASE/EFO:0001187 DNase-seq/. · HepG2 | -0.111 · ATAC:HepG2 · HepG2 | | disagree |
12-
| Transcription factor binding (ChIP-TF) (log2fc) | +0.381 · CHIP_TF/EFO:0001187 TF ChIP-seq CEBPA genetically modified (insertion) using CRISPR targeting H. sapiens CEBPA/. · HepG2 || | all ↑ |
13-
| Histone modifications (ChIP-Histone) (log2fc) | +0.180 · CHIP_HISTONE/EFO:0001187 Histone ChIP-seq H3K27ac/. · HepG2 | || all ↑ |
14-
| TSS activity (CAGE/PRO-CAP) (log2fc) | +0.254 · CAGE/hCAGE EFO:0001187/- · HepG2 | || all ↑ |
15-
| Promoter activity (MPRA) (diff) ||| -0.028 · LentiMPRA:HepG2 · HepG2 | all ↓ |
11+
| Chromatin accessibility (DNASE/ATAC) (log2fc) | -0.111 · ATAC:HepG2 · HepG2 | | +0.453 · DNASE/EFO:0001187 DNase-seq/. · HepG2 | disagree |
12+
| Promoter activity (MPRA) (diff) | | -0.028 · LentiMPRA:HepG2 · HepG2 || only ↓ (n=1) |
13+
| Transcription factor binding (ChIP-TF) (log2fc) | || +0.381 · CHIP_TF/EFO:0001187 TF ChIP-seq CEBPA genetically modified (insertion) using CRISPR targeting H. sapiens CEBPA/. · HepG2 | only ↑ (n=1) |
14+
| Histone modifications (ChIP-Histone) (log2fc) | || +0.180 · CHIP_HISTONE/EFO:0001187 Histone ChIP-seq H3K27ac/. · HepG2 | only ↑ (n=1) |
15+
| TSS activity (CAGE/PRO-CAP) (log2fc) ||| +0.254 · CAGE/hCAGE EFO:0001187/- · HepG2 | only ↑ (n=1) |

0 commit comments

Comments
 (0)