Brettapps's picture
Upload folder using huggingface_hub (part 21)
e23172f verified
Raw History Blame Contribute Delete
4.23 kB
"""Hermes/Agent reusable tools.
These wrap the deterministic pipeline so an agent can execute natural-language
commands like "Run tomorrow's Australian gallops analysis" or
"Analyse Randwick Race 7" without re-implementing logic.
Each function is a thin, well-documented wrapper returning plain data. Prediction
math stays deterministic (see model/); the agent adds orchestration + explanation.
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
from .config import settings
from .api.formfav_client import FormFavClient
from .api.validator import validate_race_form
from .data.normalizer import normalise_race
from .data.storage import Storage
from .model.analyse_race import analyse_race
from .model.scoring import load_weights
from .jobs.daily_prediction import tomorrow_australia
from .reporting.dashboard import write_outputs
logger = logging.getLogger("trifecta_bro.tools")
def get_tomorrow_meetings(date: str | None = None) -> list[dict]:
"""Discover tomorrow's AU gallops meetings via FormFav."""
date = date or tomorrow_australia()
client = FormFavClient()
raw = client.get_meetings(date)
meetings = [m for m in raw if m.get("country") == settings.country and not m.get("abandoned")]
return [{"track": m.get("track"), "slug": m.get("slug"), "races": m.get("races", [])}
for m in meetings]
def get_race_form(date: str, track_slug: str, race: int) -> dict:
"""Fetch normalised race form for one race."""
client = FormFavClient()
raw = client.get_race_form(date, track_slug, race)
validate_race_form(raw)
return normalise_race(raw).model_dump()
def analyse_race_tool(date: str, track_slug: str, race: int) -> dict:
"""Full analysis for a single race (deterministic)."""
client = FormFavClient()
raw = client.get_race_form(date, track_slug, race)
validate_race_form(raw)
race_model = normalise_race(raw)
return analyse_race(race_model, weights=load_weights())
def rank_runners(date: str, track_slug: str, race: int) -> list[dict]:
a = analyse_race_tool(date, track_slug, race)
return a.get("ranked", [])
def generate_trifecta(date: str, track_slug: str, race: int) -> dict:
a = analyse_race_tool(date, track_slug, race)
return {"trifecta": a.get("trifecta"), "alternatives": a.get("alternatives"),
"confidence": a.get("confidence")}
def save_prediction(date: str, track_slug: str, race: int) -> dict:
a = analyse_race_tool(date, track_slug, race)
with Storage() as s:
s.save_prediction({
"date": date, "track": a["track"], "track_slug": a["track_slug"],
"race_number": a["race_number"], "model_version": settings.model_version,
"generated_at": datetime.now(timezone.utc).isoformat(),
"top3": a.get("top3"), "trifecta": a.get("trifecta"),
"alternatives": a.get("alternatives"),
"confidence": a["confidence"]["category"],
"confidence_score": a["confidence"]["score"],
"ranked_runners": [{"no": r["number"], "name": r["name"], "score": r["score"]}
for r in a["ranked"]],
"data_completeness": a.get("data_completeness"),
})
return {"saved": True, "track": a["track"], "race": a["race_number"]}
def run_daily_analysis(date: str | None = None, publish: bool = False) -> dict:
"""Run the complete pipeline for a date and return report paths."""
from ..jobs.daily_prediction import run as _run
return _run(date=date)
def why_ranked_first(date: str, track_slug: str, race: int) -> str:
"""Explain in plain language why the top runner ranked first."""
a = analyse_race_tool(date, track_slug, race)
if a.get("skippable"):
return f"Race skipped: {a.get('reason')}"
top = a["ranked"][0]
subs = top["sub_scores"]
factors = sorted(((k, v) for k, v in subs.items() if v > 0), key=lambda x: x[1], reverse=True)
parts = [f"Runner #{top['number']} {top['name']} ranks first (score {top['score']}) because:"
f" strong {', '.join(k for k, _ in factors[:3])}."]
return " ".join(parts)