1"""Rebuild run-level outcomes from saved run artefacts (no re-simulation): used after metric changes and for2threshold-sensitivity grids. Every recomputed number is still traceable to the saved parquet files."""3from __future__ import annotations45import json6from dataclasses import dataclass7from pathlib import Path89import pandas as pd1011from ..experiments.config import RunConfig12from ..metrics.outcomes import run_outcomes131415@dataclass16class SavedRun:17 config: RunConfig18 ticks: pd.DataFrame19 trades: pd.DataFrame20 agents: pd.DataFrame21 llm: pd.DataFrame22 manifest: dict23 run_dir: Path242526def load_run(run_dir: str | Path) -> SavedRun:27 rd = Path(run_dir)28 cfg = RunConfig.from_yaml(rd / "config.yaml")29 with open(rd / "manifest.json") as f:30 man = json.load(f)31 llm_p = rd / "llm_decisions.parquet"32 return SavedRun(cfg, pd.read_parquet(rd / "ticks.parquet"), pd.read_parquet(rd / "trades.parquet"), pd.read_parquet(rd / "agents.parquet"),33 pd.read_parquet(llm_p) if llm_p.exists() else pd.DataFrame(columns=["t", "agent_id", "provider", "model", "valid"]), man, rd)343536def recompute_design(design_dir: str | Path, write: bool = True, **thresholds) -> pd.DataFrame:37 design_dir = Path(design_dir)38 rows = []39 for man in sorted(design_dir.glob("*/seed*/manifest.json")):40 r = load_run(man.parent)41 oc = run_outcomes(r, **thresholds)42 oc["run_dir"] = str(man.parent)43 rows.append(oc)44 df = pd.DataFrame(rows)45 if write and not thresholds:46 df.to_parquet(design_dir / "outcomes.parquet", index=False)47 for r in rows:48 with open(Path(r["run_dir"]) / "outcomes.json", "w") as f:49 json.dump(r, f, indent=1, default=float)50 return df51