Trifecta-Lab / trifecta_bro /model /pace_analysis.py
Brettapps's picture
Upload folder using huggingface_hub (part 21)
e23172f verified
Raw History Blame Contribute Delete
2.87 kB
"""Pace / race-shape estimation.
Where FormFav supplies a pace scenario we use it; otherwise we derive a proxy
from the field's historical speed profiles. The engine then classifies SLOW /
MODERATE / FAST / VERY FAST and tags each runner's likely role
(leader / on-speed / midfield / backmarker) using barrier + historical
front-running tendency when available.
All adjustments are evidence-based and never universal rules.
"""
from __future__ import annotations
from typing import Optional
from ..data.models import RaceModel, RunnerModel
def classify_pace(race: RaceModel, runners: list[RunnerModel]) -> dict:
"""Estimate race shape from available evidence.
Uses:
- FormFav raceClass / distance as coarse tempo priors (short races + lower
classes tend to be faster)
- a derived 'speed_pressure' from how many runners have strong front-running
or high win% (proxy when no explicit pace field)
"""
n = len(runners) or 1
pressure = 0.0
leaders = []
for r in runners:
wp = r.win_percent
# high win% + low barrier => more likely to press forward
barrier = r.barrier or 5
fwd = max(0.0, (wp / 100.0) - 0.05) + (0.5 if barrier <= 3 else 0.0)
pressure += fwd
if fwd > 0.12:
leaders.append(r.number)
pressure_norm = pressure / n # ~0..0.5
if pressure_norm >= 0.30:
label = "VERY FAST"
elif pressure_norm >= 0.20:
label = "FAST"
elif pressure_norm >= 0.11:
label = "MODERATE"
else:
label = "SLOW"
roles = {}
for r in runners:
barrier = r.barrier or 5
wp = r.win_percent / 100.0
if barrier <= 3 and wp >= 0.10:
roles[r.number] = "leader/on-speed"
elif barrier <= 6:
roles[r.number] = "midfield"
else:
roles[r.number] = "backmarker"
return {
"label": label,
"pressure": round(pressure_norm, 3),
"likely_leaders": leaders,
"roles": roles,
}
def pace_adjustment(runner: RunnerModel, pace: dict) -> float:
"""Return an additive 0..100 sub-score adjustment (can be negative).
Logic:
- SLOW race: a confirmed leader/on-speed runner is advantaged (can control).
- FAST/VERY FAST: a leader is penalised (likely to fold); midfield/strong
finishers advantaged.
- Backmarkers get a small negative in SLOW (hard to make ground).
"""
role = pace["roles"].get(runner.number, "midfield")
label = pace["label"]
if label == "SLOW":
if role == "leader/on-speed":
return 6.0
if role == "backmarker":
return -4.0
return 1.0
if label in ("FAST", "VERY FAST"):
if role == "leader/on-speed":
return -4.0
if role == "backmarker":
return 3.0
return 2.0
return 0.0