Spaces:
Configuration error
Configuration error
Download trifecta_bro/data/normalizer.py from Brettapps/Trifecta-Lab: direct link, hf CLI and curl.
- Browser
- Download file 3.71 kB
-
https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/trifecta_bro/data/normalizer.py
- Command line
-
hf download hf://spaces/Brettapps/Trifecta-Lab/trifecta_bro/data/normalizer.py
-
curl -L -o normalizer.py https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/trifecta_bro/data/normalizer.py
3.71 kB
| """Normalise raw FormFav payloads into internal models. | |
| Key responsibilities: | |
| - drop scratched runners from the active set (but keep record) | |
| - mark abandoned races | |
| - never invent missing values; track data_completeness instead | |
| - tolerate missing `form` (fall back to `last20Starts`) | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from typing import Any | |
| from .models import RaceModel, RunnerModel, MeetingModel | |
| logger = logging.getLogger("trifecta_bro.normalizer") | |
| EXPECTED_RUNNER_FIELDS = [ | |
| "name", "jockey", "trainer", "weight", "barrier", "age", | |
| "careerPrizeMoney", "form", "stats", | |
| ] | |
| def _to_float(value: Any) -> float | None: | |
| if value is None: | |
| return None | |
| try: | |
| return float(str(value).replace("$", "").replace(",", "")) | |
| except (ValueError, TypeError): | |
| return None | |
| def _normalise_runner(raw: dict[str, Any]) -> RunnerModel: | |
| present = sum(1 for f in EXPECTED_RUNNER_FIELDS if raw.get(f) not in (None, "", {})) | |
| completeness = present / len(EXPECTED_RUNNER_FIELDS) | |
| return RunnerModel( | |
| number=int(raw.get("number", 0)), | |
| name=raw.get("name", "") or "", | |
| jockey=raw.get("jockey"), | |
| trainer=raw.get("trainer"), | |
| weight=_to_float(raw.get("weight")), | |
| barrier=int(raw["barrier"]) if raw.get("barrier") not in (None, "") else None, | |
| age=int(raw["age"]) if raw.get("age") not in (None, "") else None, | |
| sex=raw.get("sex"), | |
| career_prize_money=_to_float(raw.get("careerPrizeMoney")), | |
| form=raw.get("form") or raw.get("last20Starts") or "", | |
| last20_starts=raw.get("last20Starts") or raw.get("form") or "", | |
| scratched=bool(raw.get("scratched", False)), | |
| stats=raw.get("stats", {}) or {}, | |
| data_completeness=round(completeness, 3), | |
| ) | |
| def normalise_race(payload: dict[str, Any]) -> RaceModel: | |
| warnings: list[str] = [] | |
| raw_runners = payload.get("runners", []) or [] | |
| runners = [_normalise_runner(r) for r in raw_runners] | |
| active = [r for r in runners if not r.scratched] | |
| if len(active) < 3: | |
| warnings.append(f"only {len(active)} active runners after scratches") | |
| # distance may be like "1200m" | |
| dist = payload.get("distance") | |
| nrun = payload.get("numberOfRunners") | |
| if nrun is None: | |
| nrun = len(active) | |
| return RaceModel( | |
| date=payload.get("date", ""), | |
| track=payload.get("track", ""), | |
| track_slug=payload.get("trackSlug") or payload.get("slug") or payload.get("track", "").lower(), | |
| race_number=int(payload.get("raceNumber", payload.get("race", 0))), | |
| race_name=payload.get("raceName", "") or "", | |
| distance=str(dist) if dist is not None else None, | |
| condition=payload.get("condition"), | |
| weather=payload.get("weather"), | |
| race_class=payload.get("raceClass"), | |
| abandoned=bool(payload.get("abandoned", False)), | |
| start_time=payload.get("startTime"), | |
| timezone=payload.get("timezone"), | |
| prize_money=str(payload.get("prizeMoney")) if payload.get("prizeMoney") is not None else None, | |
| number_of_runners=int(nrun) if nrun is not None else 0, | |
| runners=runners, | |
| source_warnings=warnings, | |
| ) | |
| def normalise_meeting(raw: dict[str, Any]) -> MeetingModel: | |
| races = [] | |
| for r in raw.get("races", []) or []: | |
| rn = r.get("raceNumber") if isinstance(r, dict) else None | |
| if rn is not None: | |
| races.append(int(rn)) | |
| return MeetingModel( | |
| track=raw.get("track", ""), | |
| slug=raw.get("slug", "") or raw.get("track", "").lower(), | |
| country=raw.get("country", ""), | |
| abandoned=bool(raw.get("abandoned", False)), | |
| races=sorted(set(races)), | |
| ) | |