File size: 1,898 Bytes
e23172f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
"""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)