"""Pydantic data models — the consistent internal representation.""" from __future__ import annotations from pydantic import BaseModel, Field from typing import Optional, Any class RunnerModel(BaseModel): number: int name: str = "" jockey: Optional[str] = None trainer: Optional[str] = None weight: Optional[float] = None barrier: Optional[int] = None age: Optional[int] = None sex: Optional[str] = None career_prize_money: Optional[float] = None form: str = "" last20_starts: str = "" scratched: bool = False stats: dict[str, Any] = Field(default_factory=dict) # extracted helpers data_completeness: float = 1.0 # 0..1 of expected fields present @property def overall(self) -> dict[str, Any]: return self.stats.get("overall", {}) @property def win_percent(self) -> float: return float(self.overall.get("winPercent", 0) or 0) @property def place_percent(self) -> float: return float(self.overall.get("placePercent", 0) or 0) class RaceModel(BaseModel): date: str track: str track_slug: str = "" race_number: int race_name: str = "" distance: Optional[str] = None condition: Optional[str] = None weather: Optional[str] = None race_class: Optional[str] = None abandoned: bool = False start_time: Optional[str] = None timezone: Optional[str] = None prize_money: Optional[str] = None number_of_runners: int = 0 runners: list[RunnerModel] = Field(default_factory=list) source_warnings: list[str] = Field(default_factory=list) @property def active_runners(self) -> list[RunnerModel]: return [r for r in self.runners if not r.scratched] class MeetingModel(BaseModel): track: str slug: str = "" country: str = "" abandoned: bool = False races: list[int] = Field(default_factory=list)