Spaces:
Configuration error
Configuration error
Download trifecta_bro/model/features.py from Brettapps/Trifecta-Lab: direct link, hf CLI and curl.
- Browser
- Download file 6.19 kB
-
https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/trifecta_bro/model/features.py
- Command line
-
hf download hf://spaces/Brettapps/Trifecta-Lab/trifecta_bro/model/features.py
-
curl -L -o features.py https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/trifecta_bro/model/features.py
6.19 kB
| """Comprehensive feature engineering from existing FormFav data.""" | |
| from __future__ import annotations | |
| import json | |
| from pathlib import Path | |
| from typing import Optional | |
| from trifecta_bro.data.models import RunnerModel, RaceModel | |
| def extract_features(runner: RunnerModel, race: RaceModel) -> dict: | |
| """Extract all possible features from runner data for ML model.""" | |
| features = {} | |
| # Basic features | |
| features['number'] = runner.number | |
| features['barrier'] = runner.barrier or 0 | |
| features['weight'] = runner.weight or 57.0 | |
| features['age'] = runner.age or 4 | |
| # Sex encoding | |
| sex = (runner.sex or '').lower() | |
| features['is_gelding'] = 1 if 'gelding' in sex else 0 | |
| features['is_mare'] = 1 if 'mare' in sex else 0 | |
| features['is_filly'] = 1 if 'filly' in sex else 0 | |
| features['is_colt'] = 1 if 'colt' in sex else 0 | |
| # Career stats | |
| overall = runner.stats.get('overall', {}) | |
| features['career_starts'] = overall.get('starts', 0) or 0 | |
| features['career_wins'] = overall.get('wins', 0) or 0 | |
| features['career_places'] = overall.get('places', 0) or 0 | |
| features['career_win_pct'] = overall.get('winPercent', 0) or 0 | |
| features['career_place_pct'] = overall.get('placePercent', 0) or 0 | |
| # Track stats | |
| track = runner.stats.get('track', {}) | |
| features['track_starts'] = track.get('starts', 0) or 0 | |
| features['track_wins'] = track.get('wins', 0) or 0 | |
| features['track_win_pct'] = track.get('winPercent', 0) or 0 | |
| features['track_place_pct'] = track.get('placePercent', 0) or 0 | |
| # Distance stats | |
| distance = runner.stats.get('distance', {}) | |
| features['distance_starts'] = distance.get('starts', 0) or 0 | |
| features['distance_wins'] = distance.get('wins', 0) or 0 | |
| features['distance_win_pct'] = distance.get('winPercent', 0) or 0 | |
| features['distance_place_pct'] = distance.get('placePercent', 0) or 0 | |
| # Track + Distance combo | |
| track_dist = runner.stats.get('trackDistance', {}) | |
| features['track_distance_starts'] = track_dist.get('starts', 0) or 0 | |
| features['track_distance_wins'] = track_dist.get('wins', 0) or 0 | |
| features['track_distance_win_pct'] = track_dist.get('winPercent', 0) or 0 | |
| features['track_distance_place_pct'] = track_dist.get('placePercent', 0) or 0 | |
| # Condition stats (match by race condition) | |
| cond = (race.condition or '').lower() | |
| cond_stats = runner.stats.get('conditions', {}) | |
| cond_key = None | |
| for key in cond_stats: | |
| if cond and cond[:4] in key.lower(): | |
| cond_key = key | |
| break | |
| if cond_key: | |
| cs = cond_stats[cond_key] | |
| features['cond_starts'] = cs.get('starts', 0) or 0 | |
| features['cond_wins'] = cs.get('wins', 0) or 0 | |
| features['cond_win_pct'] = cs.get('winPercent', 0) or 0 | |
| features['cond_place_pct'] = cs.get('placePercent', 0) or 0 | |
| else: | |
| features['cond_starts'] = 0 | |
| features['cond_wins'] = 0 | |
| features['cond_win_pct'] = 0 | |
| features['cond_place_pct'] = 0 | |
| # First-up / Second-up stats | |
| first_up = runner.stats.get('firstUp', {}) | |
| second_up = runner.stats.get('secondUp', {}) | |
| features['first_up_starts'] = first_up.get('starts', 0) or 0 | |
| features['first_up_wins'] = first_up.get('wins', 0) or 0 | |
| features['first_up_win_pct'] = first_up.get('winPercent', 0) or 0 | |
| features['second_up_starts'] = second_up.get('starts', 0) or 0 | |
| features['second_up_wins'] = second_up.get('wins', 0) or 0 | |
| features['second_up_win_pct'] = second_up.get('winPercent', 0) or 0 | |
| # Form analysis | |
| form = runner.form or '' | |
| features['form_length'] = len(form) | |
| # Recent form (last 5) | |
| recent = form[-5:] if len(form) >= 5 else form | |
| features['recent_wins'] = sum(1 for c in recent if c == '1') | |
| features['recent_places'] = sum(1 for c in recent if c in ['1', '2', '3']) | |
| features['recent_win_pct'] = features['recent_wins'] / len(recent) if recent else 0 | |
| features['recent_place_pct'] = features['recent_places'] / len(recent) if recent else 0 | |
| # Very recent (last 3) | |
| very_recent = form[-3:] if len(form) >= 3 else form | |
| features['very_recent_wins'] = sum(1 for c in very_recent if c == '1') | |
| features['very_recent_win_pct'] = features['very_recent_wins'] / len(very_recent) if very_recent else 0 | |
| # Last start result | |
| features['last_start_pos'] = _parse_last_position(form[-1]) if form else 0 | |
| # Career prize money | |
| prize = runner.career_prize_money | |
| if prize: | |
| try: | |
| features['career_prize'] = float(str(prize).replace('$', '').replace(',', '')) | |
| except: | |
| features['career_prize'] = 0 | |
| else: | |
| features['career_prize'] = 0 | |
| # Prize money per start | |
| features['prize_per_start'] = features['career_prize'] / features['career_starts'] if features['career_starts'] > 0 else 0 | |
| # Race features | |
| features['num_runners'] = race.number_of_runners or 10 | |
| # Distance in metres | |
| distance_str = race.distance or '1200m' | |
| try: | |
| features['distance_metres'] = int(''.join(filter(str.isdigit, str(distance_str)))) | |
| except: | |
| features['distance_metres'] = 1200 | |
| # Barrier advantage (relative to field) | |
| features['barrier_central'] = abs(features['barrier'] - features['num_runners'] / 2) | |
| # Weight relative to field | |
| features['weight_relative'] = features['weight'] - 57.0 # 57kg is average | |
| return features | |
| def _parse_last_position(char: str) -> int: | |
| """Parse a form character to a finishing position.""" | |
| if char == '1': | |
| return 1 | |
| elif char == '2': | |
| return 2 | |
| elif char == '3': | |
| return 3 | |
| elif char == '4': | |
| return 4 | |
| elif char == '5': | |
| return 5 | |
| elif char == '6': | |
| return 6 | |
| elif char == '7': | |
| return 7 | |
| elif char == '8': | |
| return 8 | |
| elif char == '9': | |
| return 9 | |
| elif char == '0': | |
| return 10 # spell | |
| elif char in ['x', 'X']: | |
| return 0 # spell | |
| elif char in ['f', 'F']: | |
| return 0 # fell | |
| elif char in ['p', 'P']: | |
| return 0 # pulled up | |
| else: | |
| return 0 | |