"""Load case YAML into typed dataclasses.""" from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path import yaml @dataclass(frozen=True) class Witness: id: str name: str portrait_ref: str persona: str secret_knowledge: str reliability: float independence_group: str | None cost_days: int is_physical_evidence: bool target_mass: dict[frozenset[str], float] @dataclass(frozen=True) class GroundTruth: culprit: str accomplice: str | None @dataclass(frozen=True) class CaseData: case_id: str title: str setting_blurb: str victim: str dock: list[str] court_days: int threshold: float methods_available: list[str] witnesses: list[Witness] ground_truth: GroundTruth intended_path: str reveal_expectations: dict def _parse_mass(raw: dict | None, dock: list[str]) -> dict[frozenset[str], float]: if not raw: return {} dock_set = set(dock) out: dict[frozenset[str], float] = {} for key, val in raw.items(): # keys are comma-separated suspect names, e.g. "Gardener,Maid" names = [s.strip() for s in str(key).split(",")] for n in names: if n not in dock_set: raise ValueError(f"unknown suspect in target_mass: {n!r}") out[frozenset(names)] = float(val) return out def load_case(path: str | Path) -> CaseData: raw = yaml.safe_load(Path(path).read_text()) dock = list(raw["dock"]) witnesses = [ Witness( id=w["id"], name=w["name"], portrait_ref=w.get("portrait_ref", ""), persona=w["persona"], secret_knowledge=w["secret_knowledge"], reliability=float(w.get("reliability", 1.0)), independence_group=w.get("independence_group"), cost_days=int(w.get("cost_days", 1)), is_physical_evidence=bool(w.get("is_physical_evidence", False)), target_mass=_parse_mass(w.get("target_mass"), dock), ) for w in raw["witnesses"] ] gt = raw["ground_truth"] return CaseData( case_id=raw["case_id"], title=raw["title"], setting_blurb=raw["setting_blurb"], victim=raw["victim"], dock=dock, court_days=int(raw["court_days"]), threshold=float(raw["threshold"]), methods_available=list(raw["methods_available"]), witnesses=witnesses, ground_truth=GroundTruth(culprit=gt["culprit"], accomplice=gt.get("accomplice")), intended_path=raw.get("intended_path", ""), reveal_expectations=raw.get("reveal_expectations", {}), )