Skip to content

Commit 7bc67e5

Browse files
lucapinelloclaude
andcommitted
Fix rerender_examples: preserve original filename + canonical oracle order
Two small fixes to the rerender path: - When a dir has one JSON but a non-default HTML filename (e.g. the Enformer variant_analysis report used ``rs12740374_...`` rather than the ``chr1_...`` default), the rerender now matches on oracle_name so we keep the existing name instead of writing an orphan file next to it. - The multi-oracle consolidator loops oracles in the canonical order (specialists → generalist: chrombpnet, legnet, alphagenome) matching scripts/regenerate_multioracle.py, so consensus-matrix columns don't shuffle between a full regen and a pure-JSON rerender. Also synthesises a multi-oracle AnalysisRequest instead of reusing the first per-oracle one — otherwise the rendered prompt block read as a single-oracle run. Re-rendered the multi-oracle example to confirm output matches the full regen (only the timestamp differs). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 12f5297 commit 7bc67e5

3 files changed

Lines changed: 53 additions & 22 deletions

File tree

examples/applications/validation/SORT1_rs12740374_multioracle/example_output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,6 @@
102102
"tracks_requested": "assay_ids as listed in each per-oracle request",
103103
"cell_types": [],
104104
"notes": [],
105-
"generated_at": "2026-04-19 18:35 UTC"
105+
"generated_at": "2026-04-19 19:06 UTC"
106106
}
107107
}

examples/applications/validation/SORT1_rs12740374_multioracle/rs12740374_SORT1_multioracle_report.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ <h3 style="margin:0 0 8px 0;font-size:1.05em;">Analysis Request</h3>
8686
<li><strong>Oracle:</strong> chrombpnet, legnet, alphagenome</li>
8787
<li><strong>Normalizer:</strong> per-oracle chorus per-track v1</li>
8888
<li><strong>Tracks requested:</strong> assay_ids as listed in each per-oracle request</li>
89-
<li><strong>Generated:</strong> 2026-04-19 18:35 UTC</li>
89+
<li><strong>Generated:</strong> 2026-04-19 19:06 UTC</li>
9090
</ul>
9191
</section>
9292
<p class='meta'><b>Variant:</b> chr1:109,274,968 G&gt;T</p>

scripts/rerender_examples.py

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,27 @@ def _rehydrate_variant_report(json_path: Path) -> int:
8080
# When an explicit filename was used before (non-default), keep the
8181
# previous HTML file name if we can unambiguously identify it. This
8282
# preserves link stability across README references.
83-
existing_htmls = sorted(p for p in json_path.parent.glob("*_report.html")
84-
if "multioracle" not in p.name.lower()
85-
and "enformer" not in p.name.lower()
86-
and "_RAW_autoscale" not in p.name)
83+
existing_htmls = sorted(
84+
p for p in json_path.parent.glob("*_report.html")
85+
if "multioracle" not in p.name.lower()
86+
)
8787
if len(existing_htmls) == 1:
8888
out_path = existing_htmls[0]
8989
elif len(existing_htmls) > 1:
90-
# Prefer one that mentions the oracle name in the filename, so we
91-
# don't accidentally overwrite a sibling report from a different
92-
# oracle (e.g. the Enformer/AlphaGenome co-habitation in
93-
# validation/SORT1_rs12740374_with_CEBP/).
90+
# Multiple candidates means the dir holds sibling reports from
91+
# different oracles or runs. Pick the one that belongs to THIS
92+
# JSON by oracle-name match; prefer documented "validation_report"
93+
# names when present.
9494
oracle = report.oracle_name.lower()
95-
oracle_matches = [p for p in existing_htmls if oracle in p.name.lower()]
96-
# And/or "validation_report" for documented canonical filenames.
9795
validation_matches = [p for p in existing_htmls
9896
if "validation_report" in p.name.lower()]
99-
if validation_matches:
97+
oracle_matches = [p for p in existing_htmls if oracle in p.name.lower()]
98+
if validation_matches and oracle in validation_matches[0].name.lower():
10099
out_path = validation_matches[0]
101100
elif len(oracle_matches) == 1:
102101
out_path = oracle_matches[0]
102+
elif validation_matches:
103+
out_path = validation_matches[0]
103104

104105
report.to_html(output_path=out_path)
105106
logger.info(" rerendered %s", out_path.relative_to(REPO_ROOT))
@@ -159,7 +160,22 @@ def _refresh_multioracle(dir_path: Path) -> int:
159160
from chorus.analysis import MultiOracleReport
160161
from chorus.analysis.analysis_request import AnalysisRequest
161162

162-
per_oracle_jsons = sorted(dir_path.glob("*_variant_report.json"))
163+
# Preserve the canonical oracle ordering used by
164+
# scripts/regenerate_multioracle.py (specialists → generalist) so the
165+
# consensus-matrix columns don't shuffle between runs.
166+
_ORACLE_ORDER = ["chrombpnet", "legnet", "alphagenome", "enformer",
167+
"borzoi", "sei"]
168+
all_jsons = {
169+
p.stem.replace("_variant_report", ""): p
170+
for p in dir_path.glob("*_variant_report.json")
171+
}
172+
per_oracle_jsons = [
173+
all_jsons[name] for name in _ORACLE_ORDER if name in all_jsons
174+
]
175+
per_oracle_jsons += [
176+
p for name, p in sorted(all_jsons.items())
177+
if name not in _ORACLE_ORDER
178+
]
163179
if not per_oracle_jsons:
164180
return 0
165181

@@ -170,16 +186,31 @@ def _refresh_multioracle(dir_path: Path) -> int:
170186
if html_candidate.exists():
171187
per_oracle_paths[oracle] = html_candidate.name
172188

173-
# Reuse existing analysis_request if any per-oracle JSON has one.
174-
ar = None
189+
# Synthesise a multi-oracle AnalysisRequest that describes the *combined*
190+
# comparison, not the first per-oracle run — matches what
191+
# scripts/regenerate_multioracle.py writes so the rendered prompt block
192+
# at the top of the page is consistent whether the report is produced
193+
# from a full regen or a pure-JSON rerender.
175194
with per_oracle_jsons[0].open() as fh:
176195
first = json.load(fh)
177-
ar_dict = first.get("analysis_request")
178-
if ar_dict:
179-
try:
180-
ar = AnalysisRequest.from_dict(ar_dict)
181-
except Exception:
182-
ar = None
196+
oracle_names = [
197+
p.stem.replace("_variant_report", "") for p in per_oracle_jsons
198+
]
199+
ar = AnalysisRequest(
200+
user_prompt=(
201+
"Validate rs12740374 (the classic SORT1 LDL-cholesterol causal "
202+
"variant) by scoring it with three independent deep-learning "
203+
"oracles: ChromBPNet for chromatin accessibility, LegNet for MPRA "
204+
"promoter activity, and AlphaGenome as a generalist model "
205+
"covering ChIP, histones and CAGE. A new user should be able to "
206+
"see at a glance whether the three oracles agree on direction, "
207+
"and which assay/cell type drove each call."
208+
),
209+
tool_name="MultiOracleReport",
210+
oracle_name=", ".join(oracle_names),
211+
normalizer_name="per-oracle chorus per-track v1",
212+
tracks_requested="assay_ids as listed in each per-oracle request",
213+
)
183214

184215
moracle = MultiOracleReport.from_json_files(
185216
per_oracle_jsons,

0 commit comments

Comments
 (0)