| """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(): |
| |
| 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", {}), |
| ) |
|
|