|
| 1 | +# AlphaGenome code fixes — design proposal |
| 2 | + |
| 3 | +**Date:** 2026-09-17 |
| 4 | +**Code under review:** `alphagenome_weights.py`, `alphagenome_panel_run.py` |
| 5 | + |
| 6 | +## Issues identified |
| 7 | + |
| 8 | +### Issue 1: Indels dropped (992 / 124,841 = 0.79%) |
| 9 | + |
| 10 | +**Current code** (`alphagenome_weights.py:344`): |
| 11 | +```python |
| 12 | +if not ref or not alt or len(ref) != 1 or len(alt) != 1: |
| 13 | + # AVI Atlas SNV table covers SNVs only; skip indels here |
| 14 | + continue |
| 15 | +``` |
| 16 | + |
| 17 | +**Impact:** 992 mutations (Frame_Shift_Del, Frame_Shift_Ins, In_Frame_Del, In_Frame_Ins, etc.) silently dropped. ~2.9% of patients on average lose 1 indel. |
| 18 | + |
| 19 | +**Why:** The Atlas SNV table is SNV-only. Indels have a separate AVIScore scoring track (different genomic coordinate space). The code conservatively drops them rather than mishandle. |
| 20 | + |
| 21 | +**Fix options:** |
| 22 | +- **A: Keep dropping** with explicit count + log message ("dropped 992 indels; no AVI source available for indels") |
| 23 | +- **B: Use proxy weights for indels** (already in `_PROXY_BY_CLASS`: Frame_Shift_Del=29.5, In_Frame_Del=16.0, etc.). Indels contribute to LLR aggregation as proxy-weighted even when other mutations are real AVI-weighted. |
| 24 | +- **C: Fetch AVI for indels** via separate Atlas endpoint (`requested_scorers=["AVIScore"]` requires Interval objects, not Variant — needs different code path). |
| 25 | + |
| 26 | +**Recommendation:** **Option B.** Indels get proxy weights (same class anchors as today). The proxy is honest because it's clearly tagged `source="proxy"`. Real AVI is reserved for SNVs where it actually helps. Combined run will say "real=113833, proxy=992" instead of dropping indels entirely. |
| 27 | + |
| 28 | +### Issue 2: Top-K uses largest LLR contributions, not highest-AVI |
| 29 | + |
| 30 | +**Current code** (`alphagenome_panel_run.py:67`): |
| 31 | +```python |
| 32 | +if top_k is not None and top_k < len(contrib): |
| 33 | + idx = np.argsort(contrib)[::-1][:top_k] |
| 34 | + return float(contrib[idx].sum()) |
| 35 | +``` |
| 36 | + |
| 37 | +**Impact:** Top-K selects based on `weight * LLR` ranking. A high-LLR mutation with low weight still ranks high. A high-AVI mutation with low LLR might rank low (because LLR is the dominant factor at most positions). |
| 38 | + |
| 39 | +**Why:** This is what the CADD Top-K=200 finding (commit `ec16e0d`) showed works best on the 20-patient cohort. The intuition: weighting + selection both look at "does the model think this position is informative?". |
| 40 | + |
| 41 | +**But it conflates two things:** CADD/AVI priority (priors) vs LLR magnitude (likelihood evidence). Selecting top-K by `w * LLR` is a joint criterion. |
| 42 | + |
| 43 | +**Fix options:** |
| 44 | +- **A: Keep as-is** (current behavior). It's what gave us the +0.057 lift. |
| 45 | +- **B: Add `panel_llr_topk_by_avi_{K}`** that selects top-K by raw AVI score alone, then sums their weighted LLR. This is "panel prioritization by prior" — closer to the published CADD Top-K=20 framing. |
| 46 | +- **C: Add `panel_llr_topk_by_llr_{K}`** that selects top-K by raw LLR (uniform selection on likelihood), then weights. |
| 47 | + |
| 48 | +**Recommendation:** **Add Option B as a new method.** The honest comparison is then: |
| 49 | +- `panel_llr_uniform` (baseline) |
| 50 | +- `panel_llr_avi` (weighted, full panel) |
| 51 | +- `panel_llr_topk_{K}` (top-K by `w*LLR`, current) |
| 52 | +- `panel_llr_topk_by_avi_{K}` (top-K by raw AVI, then weight × LLR) ← **NEW** |
| 53 | + |
| 54 | +If Option B (AVI-only Top-K) wins, that's the published CADD Top-K=20 finding re-discovered with AlphaGenome. If current (w*LLR) wins, that's a new finding worth reporting. |
| 55 | + |
| 56 | +### Issue 3: No fallback to top-K-by-AVI |
| 57 | + |
| 58 | +This is the same as Issue 2. Adding Option B addresses both. |
| 59 | + |
| 60 | +## Recommended plan |
| 61 | + |
| 62 | +### Patch 1: `alphagenome_weights.py` — keep indels, give them proxy weights |
| 63 | + |
| 64 | +**File:** `alphagenome_weights.py`, function `load_tcga_mutations_with_ref_alt` |
| 65 | + |
| 66 | +**Before** (lines 344-346): |
| 67 | +```python |
| 68 | +if not ref or not alt or len(ref) != 1 or len(alt) != 1: |
| 69 | + # AVI Atlas SNV table covers SNVs only; skip indels here |
| 70 | + continue |
| 71 | +``` |
| 72 | + |
| 73 | +**After:** |
| 74 | +```python |
| 75 | +if not ref or not alt: |
| 76 | + continue # malformed allele string |
| 77 | +# Note: indels (len != 1) are KEPT — they will receive proxy weights via |
| 78 | +# `_PROXY_BY_CLASS` since the Atlas SNV table is SNV-only. Real AVI is |
| 79 | +# only available for SNVs; indels still contribute via deterministic proxy. |
| 80 | +``` |
| 81 | + |
| 82 | +### Patch 2: `alphagenome_panel_run.py` — add `panel_llr_topk_by_avi_K` method |
| 83 | + |
| 84 | +**File:** `alphagenome_panel_run.py`, function `_panel_score_with_weights` |
| 85 | + |
| 86 | +Add a new helper: |
| 87 | +```python |
| 88 | +def _panel_score_with_weights_by_avi( |
| 89 | + per_pos_llr: np.ndarray, |
| 90 | + avi_norm: np.ndarray, |
| 91 | + weights: np.ndarray, |
| 92 | + top_k: int, |
| 93 | +) -> float: |
| 94 | + """Select top-K by raw AVI priority, then sum weighted LLR.""" |
| 95 | + if len(avi_norm) != len(per_pos_llr): |
| 96 | + raise ValueError("avi_norm length mismatch") |
| 97 | + idx = np.argsort(avi_norm)[::-1][:top_k] |
| 98 | + return float((weights[idx] * per_pos_llr[idx]).sum()) |
| 99 | +``` |
| 100 | + |
| 101 | +Then add the method to the methods list: |
| 102 | +```python |
| 103 | +methods = ["panel_llr_uniform", "panel_llr_avi"] |
| 104 | +methods += [f"panel_llr_topk_{k}" for k in topk_values] # top-K by w*LLR (current) |
| 105 | +methods += [f"panel_llr_topk_by_avi_{k}" for k in topk_values] # top-K by AVI (NEW) |
| 106 | +``` |
| 107 | + |
| 108 | +And the scoring branch: |
| 109 | +```python |
| 110 | +elif method.startswith("panel_llr_topk_by_avi_"): |
| 111 | + k = int(method.split("_")[-1]) |
| 112 | + sp = _panel_score_with_weights_by_avi(lp, patient_avi[p], w, top_k=k) |
| 113 | + sn = _panel_score_with_weights_by_avi(ln, patient_avi[p], w, top_k=k) |
| 114 | +``` |
| 115 | + |
| 116 | +(Need to also track `patient_avi[p]` — the raw AVI norm vector — separately from `patient_weights[p]`.) |
| 117 | + |
| 118 | +### Patch 3: Tests for new behavior |
| 119 | + |
| 120 | +Add to `test/test_alphagenome_weights.py`: |
| 121 | +- Test that indels are now kept (count of loaded mutations > old count) |
| 122 | +- Test that indel keys have `source="proxy"` in weighted output |
| 123 | +- Test that `_panel_score_with_weights_by_avi` returns deterministic output |
| 124 | +- Test that the new method appears in the methods list |
| 125 | + |
| 126 | +### Effort estimate |
| 127 | + |
| 128 | +- Patch 1: 5 lines change + 1 docstring update + 1 test |
| 129 | +- Patch 2: 15 lines change (helper + dispatch) + 1 test |
| 130 | +- Patch 3: 4 new tests |
| 131 | + |
| 132 | +Total: ~25 LOC + ~80 LOC tests. Should land in 1 subagent dispatch. |
| 133 | + |
| 134 | +## Bottom line |
| 135 | + |
| 136 | +3 honest issues, 3 simple fixes. No API key required (proxies handle indels; new method works on whatever weights are present). After fix: |
| 137 | +- Indel count: 0 → 992 (~0.79%) |
| 138 | +- Methods: 5 → 8 (uniform + AVI + 3 top-K-by-w*LLR + 3 top-K-by-AVI) |
| 139 | +- All 3 fixes preserve the "honest proxy, clearly flagged" principle |
0 commit comments