"""Rebuild run-level outcomes from saved run artefacts (no re-simulation): used after metric changes and for threshold-sensitivity grids. Every recomputed number is still traceable to the saved parquet files.""" from __future__ import annotations import json from dataclasses import dataclass from pathlib import Path import pandas as pd from ..experiments.config import RunConfig from ..metrics.outcomes import run_outcomes @dataclass class SavedRun: config: RunConfig ticks: pd.DataFrame trades: pd.DataFrame agents: pd.DataFrame llm: pd.DataFrame manifest: dict run_dir: Path def load_run(run_dir: str | Path) -> SavedRun: rd = Path(run_dir) cfg = RunConfig.from_yaml(rd / "config.yaml") with open(rd / "manifest.json") as f: man = json.load(f) llm_p = rd / "llm_decisions.parquet" return SavedRun(cfg, pd.read_parquet(rd / "ticks.parquet"), pd.read_parquet(rd / "trades.parquet"), pd.read_parquet(rd / "agents.parquet"), pd.read_parquet(llm_p) if llm_p.exists() else pd.DataFrame(columns=["t", "agent_id", "provider", "model", "valid"]), man, rd) def recompute_design(design_dir: str | Path, write: bool = True, **thresholds) -> pd.DataFrame: design_dir = Path(design_dir) rows = [] for man in sorted(design_dir.glob("*/seed*/manifest.json")): r = load_run(man.parent) oc = run_outcomes(r, **thresholds) oc["run_dir"] = str(man.parent) rows.append(oc) df = pd.DataFrame(rows) if write and not thresholds: df.to_parquet(design_dir / "outcomes.parquet", index=False) for r in rows: with open(Path(r["run_dir"]) / "outcomes.json", "w") as f: json.dump(r, f, indent=1, default=float) return df