"""Response validation for FormFav payloads. Guards against shape drift / missing fields. Never fabricates missing data — it records what was present so the scoring model can degrade gracefully. """ from __future__ import annotations from typing import Any class ValidationIssue: def __init__(self, level: str, field: str, message: str) -> None: self.level = level # 'error' | 'warn' self.field = field self.message = message def __repr__(self) -> str: return f"[{self.level}] {self.field}: {self.message}" def validate_meetings(payload: Any) -> list[ValidationIssue]: issues: list[ValidationIssue] = [] if not isinstance(payload, dict): return [ValidationIssue("error", "payload", "meetings payload is not an object")] meetings = payload.get("meetings") if not isinstance(meetings, list): issues.append(ValidationIssue("error", "meetings", "missing 'meetings' array")) return issues for i, m in enumerate(meetings): if not isinstance(m, dict): issues.append(ValidationIssue("error", f"meetings[{i}]", "meeting is not an object")) continue if "slug" not in m and "track" not in m: issues.append(ValidationIssue("warn", f"meetings[{i}]", "no slug/track")) if "races" not in m: issues.append(ValidationIssue("warn", f"meetings[{i}]", "no 'races' array")) return issues def validate_race_form(payload: Any) -> list[ValidationIssue]: issues: list[ValidationIssue] = [] if not isinstance(payload, dict): return [ValidationIssue("error", "payload", "race form payload is not an object")] if "runners" not in payload: issues.append(ValidationIssue("error", "runners", "missing 'runners' array")) else: runners = payload["runners"] if not isinstance(runners, list): issues.append(ValidationIssue("error", "runners", "'runners' is not a list")) else: for i, r in enumerate(runners): if not isinstance(r, dict): issues.append(ValidationIssue("error", f"runners[{i}]", "runner not an object")) continue if "number" not in r and "barrier" not in r: issues.append(ValidationIssue("warn", f"runners[{i}]", "runner missing number/barrier")) return issues