Skip to content

Commit a9e68b8

Browse files
lucapinelloclaude
andcommitted
Merge fix/2026-04-20-v13-ux-consistency: enriched CHIP labels + shared percentile format
Multi-oracle consensus matrix and causal drill-down now render enriched track descriptions (e.g. CHIP:CEBPA:HepG2) instead of raw AlphaGenome catalog assay_ids, matching the variant-report convention. Causal percentile column now uses _fmt_percentile (≥99th) instead of the raw '+100.0%' that saturated on strong variants. 1 new regression test asserts enriched labels in MD+HTML and absence of raw AlphaGenome IDs in the rendered output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents ba79878 + 5a24072 commit a9e68b8

10 files changed

Lines changed: 503 additions & 415 deletions

File tree

chorus/analysis/causal.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
from .analysis_request import AnalysisRequest
1818
from .normalization import QuantileNormalizer
19-
from .variant_report import TrackScore, VariantReport, build_variant_report, _describe_normalizer
19+
from .variant_report import (
20+
TrackScore, VariantReport, build_variant_report,
21+
_describe_normalizer, _fmt_percentile,
22+
)
2023

2124
logger = logging.getLogger(__name__)
2225

@@ -1007,11 +1010,21 @@ def _build_causal_igv(result: CausalResult) -> str:
10071010
if igv_raw:
10081011
normalizer = None
10091012

1013+
# Build a mapping assay_id → enriched display name so IGV
1014+
# track labels match the table rows ("CHIP:CEBPA:HepG2"
1015+
# instead of "CHIP_TF/EFO:0001187 TF ChIP-seq CEBPA…").
1016+
track_display: dict[str, str] = {}
1017+
for allele_scores in top_s._variant_report.allele_scores.values():
1018+
for ts_disp in allele_scores:
1019+
if ts_disp.description and ts_disp.assay_id:
1020+
track_display[ts_disp.assay_id] = ts_disp.description
1021+
10101022
for aid in assay_ids:
10111023
ref_t = ref_pred[aid]
10121024
alt_t = alt_pred[aid]
10131025
t_start = ref_t.prediction_interval.reference.start
10141026
t_res = ref_t.resolution
1027+
display = track_display.get(aid, aid)
10151028

10161029
layer = classify_track_layer(ref_t)
10171030
floor_ok, ref_vals, alt_vals = apply_floor_rescale(
@@ -1036,20 +1049,20 @@ def _build_causal_igv(result: CausalResult) -> str:
10361049

10371050
rank_label = _TOP_VARIANT_COLORS[vi]["label"] if vi < len(_TOP_VARIANT_COLORS) else f"#{vi+1}"
10381051
tracks.append({
1039-
"name": f"{aid} ({rank_label} {top_s.variant_id})",
1052+
"name": f"{display} ({rank_label} {top_s.variant_id})",
10401053
"type": "merged",
10411054
"height": 60,
10421055
"tracks": [
10431056
{
10441057
"type": "wig",
1045-
"name": f"{aid} ref",
1058+
"name": f"{display} ref",
10461059
"color": f"rgb({_REF_COLOR})",
10471060
**scale_cfg,
10481061
"features": ref_feats,
10491062
},
10501063
{
10511064
"type": "wig",
1052-
"name": f"{aid} alt",
1065+
"name": f"{display} alt",
10531066
"color": f"rgb({alt_rgb})",
10541067
**scale_cfg,
10551068
"features": alt_feats,
@@ -1295,11 +1308,18 @@ def _build_causal_html(result: CausalResult) -> str:
12951308
ct_str = top_info.get("cell_type") or "—"
12961309
desc = top_info.get("description") or ""
12971310
asm = top_info.get("assay_id") or ""
1311+
# Show enriched description (e.g. "CHIP:CEBPA:HepG2") as the
1312+
# primary track label; keep the raw assay_id in a secondary
1313+
# <code> tag for traceability. Matches the variant-report
1314+
# convention and avoids showing the raw AlphaGenome catalog
1315+
# ID as the user-facing track name.
1316+
primary_label = desc if desc else asm
12981317
p.append('<p class="top-track-line">'
12991318
f'<b>Strongest track:</b> {html_mod.escape(layer_label)} '
1300-
f'&middot; <code>{html_mod.escape(asm)}</code> '
1319+
f'&middot; <b>{html_mod.escape(primary_label)}</b> '
13011320
f'&middot; cell type: <b>{html_mod.escape(ct_str)}</b>'
1302-
+ (f' &middot; {html_mod.escape(desc)}' if desc else '')
1321+
+ (f' &middot; <code>{html_mod.escape(asm)}</code>'
1322+
if desc and asm and asm != desc else '')
13031323
+ (f' &middot; <span class="formula-chip">{formula}</span>' if formula else '')
13041324
+ '</p>')
13051325

@@ -1327,17 +1347,23 @@ def _build_causal_html(result: CausalResult) -> str:
13271347
else ("#dc3545" if score < 0 else "#6c757d"))
13281348
info = s.per_layer_top_track.get(layer, {}) or {}
13291349
assay = info.get("assay_id") or "—"
1350+
desc = info.get("description") or ""
1351+
# Prefer enriched description ("CHIP:CEBPA:HepG2") over
1352+
# raw assay_id so the table matches the variant-report
1353+
# convention. Fall back to the raw id when description
1354+
# is absent (older snapshots).
1355+
assay_display = desc if desc else assay
13301356
ct_val = info.get("cell_type") or "—"
13311357
ref_v = info.get("ref_value")
13321358
alt_v = info.get("alt_value")
13331359
ref_str = f"{ref_v:.3g}" if ref_v is not None else "—"
13341360
alt_str = f"{alt_v:.3g}" if alt_v is not None else "—"
13351361
q = info.get("quantile_score")
1336-
q_str = f"{q * 100:+.1f}%" if q is not None else "—"
1362+
q_str = _fmt_percentile(q) if q is not None else "—"
13371363
p.append(f'<tr>'
13381364
f'<td>{html_mod.escape(name)}</td>'
13391365
f'<td><span class="formula-chip">{formula}</span></td>'
1340-
f'<td><code>{html_mod.escape(str(assay))}</code></td>'
1366+
f'<td>{html_mod.escape(str(assay_display))}</td>'
13411367
f'<td>{html_mod.escape(str(ct_val))}</td>'
13421368
f'<td>{ref_str}</td>'
13431369
f'<td>{alt_str}</td>'

chorus/analysis/multi_oracle_report.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ def _consensus_rows(self) -> list[dict]:
236236
"assay_id": best.assay_id,
237237
"cell_type": best.cell_type,
238238
"quantile_score": best.quantile_score,
239+
# Prefer the enriched display name (e.g.
240+
# ``CHIP:CEBPA:HepG2``) over the raw AlphaGenome
241+
# assay_id (``CHIP_TF/EFO:0001187 TF ChIP-seq CEBPA
242+
# genetically modified…``) so the consensus matrix
243+
# matches what the per-variant reports show.
244+
"description": best.description or best.assay_id,
239245
}
240246
directions.append(1 if best.raw_score > 0 else -1)
241247
# "Consensus" requires at least two voting oracles; a single
@@ -307,9 +313,13 @@ def to_markdown(self) -> str:
307313
cells.append("—")
308314
else:
309315
sign = "+" if entry["raw_score"] >= 0 else ""
316+
# Prefer enriched description (e.g. CHIP:CEBPA:HepG2)
317+
# over raw assay_id so the matrix matches per-variant
318+
# reports.
319+
track_label = entry.get("description") or entry["assay_id"]
310320
cells.append(
311321
f"{sign}{entry['raw_score']:.3f} · "
312-
f"{entry['assay_id']} · {entry['cell_type'] or '—'}"
322+
f"{track_label} · {entry['cell_type'] or '—'}"
313323
)
314324
agree = {
315325
"consensus_gain": "all ↑",
@@ -445,11 +455,15 @@ def _build_multioracle_html(report: "MultiOracleReport") -> str:
445455
score = entry["raw_score"]
446456
sign_char = "+" if score >= 0 else ""
447457
cls = "effect-pos" if score >= 0 else "effect-neg"
458+
# Use the same percentile display helper as every other
459+
# chorus report so "≥99th" / "≤1st" / "near-zero" are used
460+
# uniformly (not "+100.0%").
448461
q = entry.get("quantile_score")
449-
q_str = (f" · %ile {q*100:+.1f}%"
450-
if isinstance(q, (int, float)) else "")
451-
track_line = f"{entry['assay_id']}"
452-
if entry.get("cell_type"):
462+
q_str = f" · {_fmt_percentile(q)}" if q is not None else ""
463+
# Prefer enriched label (CHIP:CEBPA:HepG2) over raw assay_id
464+
# so the consensus matrix matches per-variant reports.
465+
track_line = entry.get("description") or entry["assay_id"]
466+
if entry.get("cell_type") and entry["cell_type"] not in track_line:
453467
track_line += f" · {entry['cell_type']}"
454468
p.append(
455469
f"<td class='effect-cell {cls}'>{sign_char}{score:.3f}{q_str}"
@@ -524,9 +538,14 @@ def _build_multioracle_html(report: "MultiOracleReport") -> str:
524538
alt_s = f"{ts.alt_value:.3g}" if ts.alt_value is not None else "—"
525539
q = ts.quantile_score
526540
q_str = _fmt_percentile(q) if q is not None else "—"
541+
# Prefer enriched description (e.g. "CHIP:CEBPA:HepG2")
542+
# over the raw AlphaGenome assay_id so the per-oracle
543+
# drill-down table matches what the variant-report pages
544+
# show.
545+
track_display = ts.description or ts.assay_id
527546
p.append(
528547
f"<tr><td>{esc(layer_label)} {chip}</td>"
529-
f"<td><code>{esc(ts.assay_id)}</code></td>"
548+
f"<td>{esc(track_display)}</td>"
530549
f"<td>{esc(ts.cell_type or '—')}</td>"
531550
f"<td>{ref_s}</td><td>{alt_s}</td>"
532551
f"<td class='effect-cell {cls}'>"

0 commit comments

Comments
 (0)