"""Persistence for human-review queue and decisions within a job output dir.""" import json import os from typing import Dict, List from backend.review.schemas import validate_decision class ReviewStore: def __init__(self, job_out_dir: str, create: bool = True) -> None: self.review_dir = os.path.join(job_out_dir, "review") if create: os.makedirs(self.review_dir, exist_ok=True) def _path(self, name: str) -> str: return os.path.join(self.review_dir, name) def _write_json(self, name: str, value) -> None: path = self._path(name) tmp = f"{path}.tmp" with open(tmp, "w", encoding="utf-8") as f: json.dump(value, f, indent=2) os.replace(tmp, path) def write_queue(self, queue: List[dict]) -> None: self._write_json("review_queue.json", queue) def read_queue(self) -> List[dict]: try: with open(self._path("review_queue.json"), encoding="utf-8") as f: value = json.load(f) return value if isinstance(value, list) else [] except (OSError, json.JSONDecodeError): return [] def append_decision(self, decision: dict) -> None: valid = validate_decision(decision) if not valid or not valid["review_item_id"]: raise ValueError("invalid review decision") decisions = self.read_decisions() decisions[valid["review_item_id"]] = valid self._write_json("review_decisions.json", decisions) def read_decisions(self) -> Dict[str, dict]: try: with open(self._path("review_decisions.json"), encoding="utf-8") as f: value = json.load(f) return value if isinstance(value, dict) else {} except (OSError, json.JSONDecodeError): return {} def progress(self, queue: List[dict]) -> dict: decisions = self.read_decisions() required = [item for item in queue if item.get("blocking")] completed = [item for item in required if item.get("review_item_id") in decisions] return { "required": len(required), "completed": len(completed), "remaining": len(required) - len(completed), "total": len(queue), }