|
|
import gradio as gr |
|
|
import requests |
|
|
import pandas as pd |
|
|
import os |
|
|
import pathlib |
|
|
import zipfile |
|
|
import shutil |
|
|
from datetime import datetime |
|
|
from huggingface_hub import hf_hub_download, snapshot_download |
|
|
from autogluon.tabular import TabularPredictor |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ESPN_TEAM_MAP = { |
|
|
"WAS": "WSH", |
|
|
"LA": "LAR", |
|
|
"LAR": "LAR", |
|
|
} |
|
|
|
|
|
|
|
|
ESPN_TO_INTERNAL = { |
|
|
"WSH": "WAS", |
|
|
"LAR": "LAR", |
|
|
} |
|
|
|
|
|
def normalize_team_for_espn(team_abbr): |
|
|
"""Convert internal team abbreviation to ESPN format.""" |
|
|
return ESPN_TEAM_MAP.get(team_abbr, team_abbr) |
|
|
|
|
|
def normalize_team_from_espn(team_abbr): |
|
|
"""Convert ESPN team abbreviation to internal format.""" |
|
|
return ESPN_TO_INTERNAL.get(team_abbr, team_abbr) |
|
|
|
|
|
|
|
|
|
|
|
def download_model_and_embeddings(MODEL_REPO_ID="SebastianAndreu/2025-24679-NFL-Yards-Predictor"): |
|
|
try: |
|
|
|
|
|
|
|
|
MODEL_REPO_ID = "SebastianAndreu/2025-24679-NFL-Yards-Predictor" |
|
|
ZIP_FILENAME = "autogluon_predictor_dir.zip" |
|
|
CACHE_DIR = pathlib.Path("hf_assets") |
|
|
EXTRACT_DIR = CACHE_DIR / "predictor_native" |
|
|
MODEL_LOCAL_DIR = "nfl_model" |
|
|
|
|
|
|
|
|
def _prepare_predictor_dir() -> str: |
|
|
CACHE_DIR.mkdir(parents=True, exist_ok=True) |
|
|
local_zip = hf_hub_download( |
|
|
repo_id=MODEL_REPO_ID, |
|
|
filename=ZIP_FILENAME, |
|
|
repo_type="model", |
|
|
local_dir=str(CACHE_DIR), |
|
|
local_dir_use_symlinks=False, |
|
|
) |
|
|
if EXTRACT_DIR.exists(): |
|
|
shutil.rmtree(EXTRACT_DIR) |
|
|
EXTRACT_DIR.mkdir(parents=True, exist_ok=True) |
|
|
with zipfile.ZipFile(local_zip, "r") as zf: |
|
|
zf.extractall(str(EXTRACT_DIR)) |
|
|
contents = list(EXTRACT_DIR.iterdir()) |
|
|
predictor_root = contents[0] if (len(contents) == 1 and contents[0].is_dir()) else EXTRACT_DIR |
|
|
return str(predictor_root) |
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
model_path = snapshot_download( |
|
|
repo_id=MODEL_REPO_ID, |
|
|
repo_type="model", |
|
|
local_dir=MODEL_LOCAL_DIR, |
|
|
local_dir_use_symlinks=False |
|
|
) |
|
|
|
|
|
|
|
|
PREDICTOR_DIR = _prepare_predictor_dir() |
|
|
PREDICTOR = TabularPredictor.load(PREDICTOR_DIR, require_py_version_match=False) |
|
|
|
|
|
|
|
|
emb_df = pd.read_csv(os.path.join(MODEL_LOCAL_DIR, "data", "player_historical_embeddings.csv")) |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error loading model or embeddings: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
PREDICTOR = None |
|
|
emb_df = None |
|
|
|
|
|
return PREDICTOR, emb_df |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error loading model: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
return None, None |
|
|
|
|
|
|
|
|
predictor, player_embeddings = download_model_and_embeddings() |
|
|
|
|
|
|
|
|
try: |
|
|
players_df = pd.read_csv("players.csv") |
|
|
games_df = pd.read_csv("games.csv") |
|
|
|
|
|
|
|
|
if 'home_team' in games_df.columns: |
|
|
games_df['home_team'] = games_df['home_team'].replace({'LA': 'LAR'}) |
|
|
if 'away_team' in games_df.columns: |
|
|
games_df['away_team'] = games_df['away_team'].replace({'LA': 'LAR'}) |
|
|
|
|
|
|
|
|
if 'latest_team' in players_df.columns: |
|
|
players_df['latest_team'] = players_df['latest_team'].replace({'LA': 'LAR'}) |
|
|
|
|
|
receivers_df = players_df[ |
|
|
(players_df['position'].isin(['WR', 'TE', 'RB'])) & |
|
|
(players_df['ngs_status'] == 'ACT') |
|
|
].copy() |
|
|
receiver_choices = sorted(receivers_df['display_name'].dropna().unique().tolist()) |
|
|
|
|
|
passers_df = players_df[ |
|
|
(players_df['position'] == 'QB') & |
|
|
(players_df['status'] == 'ACT') |
|
|
].copy() |
|
|
passer_choices = sorted(passers_df['display_name'].dropna().unique().tolist()) |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error loading player/game data: {e}") |
|
|
players_df = pd.DataFrame() |
|
|
games_df = pd.DataFrame() |
|
|
receiver_choices = [] |
|
|
passer_choices = [] |
|
|
|
|
|
|
|
|
STADIUM_COORDS = { |
|
|
"ARI": {"lat": 33.5276, "lon": -112.2626}, "ATL": {"lat": 33.7554, "lon": -84.4008}, |
|
|
"BAL": {"lat": 39.2780, "lon": -76.6227}, "BUF": {"lat": 42.7738, "lon": -78.7870}, |
|
|
"CAR": {"lat": 35.2258, "lon": -80.8528}, "CHI": {"lat": 41.8623, "lon": -87.6167}, |
|
|
"CIN": {"lat": 39.0954, "lon": -84.5160}, "CLE": {"lat": 41.5061, "lon": -81.6995}, |
|
|
"DAL": {"lat": 32.7473, "lon": -97.0945}, "DEN": {"lat": 39.7439, "lon": -105.0201}, |
|
|
"DET": {"lat": 42.3400, "lon": -83.0456}, "GB": {"lat": 44.5013, "lon": -88.0622}, |
|
|
"HOU": {"lat": 29.6847, "lon": -95.4107}, "IND": {"lat": 39.7601, "lon": -86.1639}, |
|
|
"JAX": {"lat": 30.3239, "lon": -81.6373}, "KC": {"lat": 39.0489, "lon": -94.4839}, |
|
|
"LV": {"lat": 36.0908, "lon": -115.1833}, "LAC": {"lat": 33.9535, "lon": -118.3390}, |
|
|
"LAR": {"lat": 33.9535, "lon": -118.3390}, "MIA": {"lat": 25.9580, "lon": -80.2389}, |
|
|
"MIN": {"lat": 44.9738, "lon": -93.2577}, "NE": {"lat": 42.0909, "lon": -71.2643}, |
|
|
"NO": {"lat": 29.9511, "lon": -90.0812}, "NYG": {"lat": 40.8128, "lon": -74.0742}, |
|
|
"NYJ": {"lat": 40.8128, "lon": -74.0742}, "PHI": {"lat": 39.9008, "lon": -75.1675}, |
|
|
"PIT": {"lat": 40.4468, "lon": -80.0158}, "SF": {"lat": 37.4032, "lon": -121.9698}, |
|
|
"SEA": {"lat": 47.5952, "lon": -122.3316}, "TB": {"lat": 27.9759, "lon": -82.5033}, |
|
|
"TEN": {"lat": 36.1665, "lon": -86.7713}, "WAS": {"lat": 38.9076, "lon": -76.8645} |
|
|
} |
|
|
|
|
|
def get_player_info(player_name, players_df): |
|
|
"""Get player's gsis_id, latest team, and headshot from display_name.""" |
|
|
player_row = players_df[players_df['display_name'] == player_name] |
|
|
if player_row.empty: |
|
|
return None, None, None |
|
|
return player_row.iloc[0]['gsis_id'], player_row.iloc[0]['latest_team'], player_row.iloc[0].get('headshot', None) |
|
|
|
|
|
def update_receiver_image(receiver_name): |
|
|
"""Update receiver headshot when selection changes.""" |
|
|
if not receiver_name or players_df.empty: |
|
|
return None |
|
|
_, _, headshot = get_player_info(receiver_name, players_df) |
|
|
return headshot |
|
|
|
|
|
def update_passer_image(passer_name): |
|
|
"""Update passer headshot when selection changes.""" |
|
|
if not passer_name or players_df.empty: |
|
|
return None |
|
|
_, _, headshot = get_player_info(passer_name, players_df) |
|
|
return headshot |
|
|
|
|
|
def get_game_info(receiver_team, season, week, games_df): |
|
|
"""Get game information based on receiver's team, season, and week.""" |
|
|
|
|
|
receiver_team_normalized = receiver_team.replace('LA', 'LAR') if receiver_team == 'LA' else receiver_team |
|
|
|
|
|
game_row = games_df[ |
|
|
(games_df['season'] == season) & |
|
|
(games_df['week'] == week) & |
|
|
((games_df['home_team'] == receiver_team_normalized) | (games_df['away_team'] == receiver_team_normalized)) |
|
|
] |
|
|
|
|
|
if game_row.empty: |
|
|
print(f"โ No game found for team '{receiver_team_normalized}' in season {season}, week {week}") |
|
|
return None |
|
|
|
|
|
game = game_row.iloc[0] |
|
|
is_home = game['home_team'] == receiver_team_normalized |
|
|
|
|
|
print(f"โ Found game: {game['away_team']} @ {game['home_team']}") |
|
|
print(f" Receiver team '{receiver_team_normalized}' is {'HOME' if is_home else 'AWAY'}") |
|
|
|
|
|
return { |
|
|
'home_team': game['home_team'], |
|
|
'away_team': game['away_team'], |
|
|
'receiver_is_home': is_home, |
|
|
'opponent_team': game['away_team'] if is_home else game['home_team'], |
|
|
'surface': game.get('surface', 'grass'), |
|
|
'roof': game.get('roof', 'outdoors'), |
|
|
'gameday': game.get('gameday'), |
|
|
'gametime': game.get('gametime') |
|
|
} |
|
|
|
|
|
def get_weather_forecast(home_team, game_datetime): |
|
|
"""Get weather forecast for a stadium at game time.""" |
|
|
coords = STADIUM_COORDS.get(home_team) |
|
|
if not coords: |
|
|
return None |
|
|
|
|
|
url = "https://api.open-meteo.com/v1/forecast" |
|
|
params = { |
|
|
"latitude": coords["lat"], "longitude": coords["lon"], |
|
|
"hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code", |
|
|
"temperature_unit": "fahrenheit", "wind_speed_unit": "mph", |
|
|
"timezone": "America/New_York" |
|
|
} |
|
|
|
|
|
try: |
|
|
response = requests.get(url, params=params, timeout=10) |
|
|
response.raise_for_status() |
|
|
data = response.json() |
|
|
|
|
|
hourly = data.get("hourly", {}) |
|
|
times = hourly.get("time", []) |
|
|
|
|
|
game_time_str = game_datetime.strftime("%Y-%m-%dT%H:%M") |
|
|
closest_idx = 0 |
|
|
for i, time_str in enumerate(times): |
|
|
if time_str >= game_time_str: |
|
|
closest_idx = i |
|
|
break |
|
|
|
|
|
temp = hourly["temperature_2m"][closest_idx] |
|
|
humidity = hourly["relative_humidity_2m"][closest_idx] |
|
|
wind = hourly["wind_speed_10m"][closest_idx] |
|
|
weather_code = hourly["weather_code"][closest_idx] |
|
|
|
|
|
is_rain = weather_code in [51, 53, 55, 61, 63, 65, 80, 81, 82] |
|
|
is_snow = weather_code in [71, 73, 75, 77, 85, 86] |
|
|
is_clear = weather_code in [0, 1, 2] |
|
|
|
|
|
return { |
|
|
"temp_f": temp, "humidity_pct": humidity, "wind_mph": wind, |
|
|
"is_rain": int(is_rain), "is_snow": int(is_snow), "is_clear": int(is_clear) |
|
|
} |
|
|
except Exception as e: |
|
|
print(f"โ Weather API error: {e}") |
|
|
return None |
|
|
|
|
|
def get_game_info_espn(home_team, away_team, week): |
|
|
"""Get game time, spread, and total from ESPN API.""" |
|
|
|
|
|
home_team_espn = normalize_team_for_espn(home_team) |
|
|
away_team_espn = normalize_team_for_espn(away_team) |
|
|
|
|
|
print(f"\n=== Querying ESPN API ===") |
|
|
print(f"Internal format: {away_team} @ {home_team}") |
|
|
print(f"ESPN format: {away_team_espn} @ {home_team_espn}") |
|
|
|
|
|
result = { |
|
|
"game_datetime": None, |
|
|
"pregame_spread": 0, |
|
|
"pregame_total": 0 |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
for season_type in [2, 3]: |
|
|
url = f"https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard?seasontype={season_type}&week={week}" |
|
|
response = requests.get(url, timeout=10) |
|
|
response.raise_for_status() |
|
|
data = response.json() |
|
|
|
|
|
for event in data.get('events', []): |
|
|
competition = event.get('competitions', [{}])[0] |
|
|
competitors = competition.get('competitors', []) |
|
|
|
|
|
if len(competitors) < 2: |
|
|
continue |
|
|
|
|
|
|
|
|
espn_home = competitors[0]['team']['abbreviation'] |
|
|
espn_away = competitors[1]['team']['abbreviation'] |
|
|
|
|
|
print(f" Checking ESPN game: {espn_away} @ {espn_home}") |
|
|
|
|
|
|
|
|
if espn_home == home_team_espn and espn_away == away_team_espn: |
|
|
print(f" โ MATCH FOUND!") |
|
|
|
|
|
|
|
|
game_date_str = event.get('date') |
|
|
if game_date_str: |
|
|
result["game_datetime"] = datetime.fromisoformat(game_date_str.replace('Z', '+00:00')) |
|
|
print(f" Game time: {result['game_datetime']}") |
|
|
|
|
|
|
|
|
odds_data = competition.get('odds', []) |
|
|
if odds_data and len(odds_data) > 0: |
|
|
spread = odds_data[0].get('spread') |
|
|
total = odds_data[0].get('overUnder') |
|
|
|
|
|
|
|
|
result["pregame_spread"] = float(spread) if spread is not None else 0 |
|
|
result["pregame_total"] = float(total) if total is not None else 0 |
|
|
|
|
|
print(f" Spread: {result['pregame_spread']} (home perspective)") |
|
|
print(f" Total: {result['pregame_total']}") |
|
|
else: |
|
|
print(f" โ No odds data available") |
|
|
|
|
|
return result |
|
|
|
|
|
print(f" โ No matching game found in ESPN API") |
|
|
|
|
|
except Exception as e: |
|
|
print(f"โ ESPN API error: {e}") |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
|
|
|
return result |
|
|
|
|
|
def predict_yards(model_input_dict, receiver_id, passer_id): |
|
|
"""Make yards prediction using the loaded model.""" |
|
|
if predictor is None: |
|
|
print("ERROR: Predictor is None") |
|
|
return None, "Model not loaded" |
|
|
|
|
|
try: |
|
|
print(f"\n=== Starting Prediction ===") |
|
|
print(f"Receiver ID: {receiver_id}") |
|
|
print(f"Passer ID: {passer_id}") |
|
|
print(f"Model input dict: {model_input_dict}") |
|
|
|
|
|
input_data = { |
|
|
"receiver_player_id": [str(receiver_id)], |
|
|
"defteam": [int(model_input_dict["defteam"])], |
|
|
"posteam": [int(model_input_dict["posteam"])], |
|
|
"surface": [int(model_input_dict["surface"])], |
|
|
"is_dome": [int(model_input_dict["is_dome"])], |
|
|
"is_rain": [int(model_input_dict["is_rain"])], |
|
|
"is_snow": [int(model_input_dict["is_snow"])], |
|
|
"is_clear": [int(model_input_dict["is_clear"])], |
|
|
"temp_f": [float(model_input_dict["temp_f"])], |
|
|
"humidity_pct": [float(model_input_dict["humidity_pct"])], |
|
|
"wind_mph": [float(model_input_dict["wind_mph"])], |
|
|
"home_team": [int(model_input_dict["home_team"])], |
|
|
"away_team": [int(model_input_dict["away_team"])], |
|
|
"pregame_spread": [float(model_input_dict["pregame_spread"])], |
|
|
"pregame_total": [float(model_input_dict["pregame_total"])], |
|
|
"passer_player_id": [str(passer_id)] |
|
|
} |
|
|
|
|
|
input_df = pd.DataFrame(input_data) |
|
|
print(f"\nInitial DataFrame shape: {input_df.shape}") |
|
|
print(f"Initial columns: {list(input_df.columns)}") |
|
|
|
|
|
|
|
|
try: |
|
|
print(f"\nPredictor feature metadata:") |
|
|
print(f"Features: {predictor.feature_metadata}") |
|
|
except: |
|
|
print("Could not get feature metadata") |
|
|
|
|
|
if player_embeddings is not None: |
|
|
print(f"\nProcessing embeddings...") |
|
|
emb_df = player_embeddings.copy() |
|
|
emb_df['player_id'] = emb_df['player_id'].astype(str) |
|
|
|
|
|
print(f"Embeddings shape: {emb_df.shape}") |
|
|
print(f"Looking for receiver '{receiver_id}' in embeddings") |
|
|
print(f"Receiver ID in embeddings: {str(receiver_id) in emb_df['player_id'].values}") |
|
|
|
|
|
input_df = input_df.merge( |
|
|
emb_df, |
|
|
left_on="receiver_player_id", |
|
|
right_on="player_id", |
|
|
how="left" |
|
|
).drop(columns=["player_id"], errors="ignore") |
|
|
|
|
|
print(f"After merge shape: {input_df.shape}") |
|
|
|
|
|
emb_cols = [c for c in emb_df.columns if c.startswith("emb_")] |
|
|
print(f"Number of embedding columns: {len(emb_cols)}") |
|
|
|
|
|
|
|
|
missing_emb = input_df[emb_cols].isna().any().any() |
|
|
print(f"Has missing embeddings: {missing_emb}") |
|
|
|
|
|
if missing_emb: |
|
|
print("Filling missing embeddings with mean...") |
|
|
mean_emb = emb_df[emb_cols].mean() |
|
|
input_df[emb_cols] = input_df[emb_cols].fillna(mean_emb) |
|
|
|
|
|
print(f"\nFinal DataFrame info:") |
|
|
print(f"Shape: {input_df.shape}") |
|
|
print(f"Columns ({len(input_df.columns)}): {list(input_df.columns)}") |
|
|
print(f"Data types:\n{input_df.dtypes}") |
|
|
print(f"\nFirst row values:") |
|
|
for col in input_df.columns: |
|
|
print(f" {col}: {input_df[col].iloc[0]}") |
|
|
|
|
|
|
|
|
try: |
|
|
print("\n--- Attempting prediction with default model ---") |
|
|
prediction = predictor.predict(input_df) |
|
|
yards = float(prediction.values[0]) |
|
|
print(f"โ SUCCESS! Predicted yards: {yards}") |
|
|
return yards, None |
|
|
|
|
|
except Exception as e: |
|
|
print(f"\nโ Default prediction FAILED") |
|
|
print(f"Error type: {type(e).__name__}") |
|
|
print(f"Error message: {str(e)}") |
|
|
import traceback |
|
|
print("Full traceback:") |
|
|
traceback.print_exc() |
|
|
|
|
|
|
|
|
try: |
|
|
print("\n--- Attempting prediction with best model from leaderboard ---") |
|
|
leaderboard = predictor.leaderboard(silent=True) |
|
|
print(f"Available models:\n{leaderboard[['model', 'score_val']]}") |
|
|
best_model = leaderboard.iloc[0]['model'] |
|
|
print(f"Using model: {best_model}") |
|
|
|
|
|
prediction = predictor.predict(input_df, model=best_model) |
|
|
yards = float(prediction.values[0]) |
|
|
print(f"โ SUCCESS with {best_model}! Predicted yards: {yards}") |
|
|
return yards, None |
|
|
|
|
|
except Exception as e2: |
|
|
print(f"\nโ Leaderboard prediction ALSO FAILED") |
|
|
print(f"Error type: {type(e2).__name__}") |
|
|
print(f"Error message: {str(e2)}") |
|
|
import traceback |
|
|
print("Full traceback:") |
|
|
traceback.print_exc() |
|
|
return None, f"Both prediction attempts failed: {str(e2)}" |
|
|
|
|
|
except Exception as e: |
|
|
print(f"\nโโโ OUTER EXCEPTION โโโ") |
|
|
print(f"Error type: {type(e).__name__}") |
|
|
print(f"Error message: {str(e)}") |
|
|
import traceback |
|
|
print("Full traceback:") |
|
|
traceback.print_exc() |
|
|
return None, f"Prediction setup error: {str(e)}" |
|
|
|
|
|
def create_model_input_and_predict(receiver_name, passer_name, week, season): |
|
|
"""Create model input from user selections and make prediction.""" |
|
|
try: |
|
|
|
|
|
receiver_id, receiver_team, receiver_headshot = get_player_info(receiver_name, players_df) |
|
|
if receiver_id is None: |
|
|
return "โ Prediction Failed", f"Could not find receiver '{receiver_name}'", f"โ Error: Could not find receiver '{receiver_name}' in database", None, None |
|
|
|
|
|
|
|
|
passer_id, passer_team, passer_headshot = get_player_info(passer_name, players_df) |
|
|
if passer_id is None: |
|
|
return "โ Prediction Failed", f"Could not find passer '{passer_name}'", f"โ Error: Could not find passer '{passer_name}' in database", None, None |
|
|
|
|
|
print(f"\n=== Game Lookup ===") |
|
|
print(f"Receiver: {receiver_name} (Team: {receiver_team})") |
|
|
print(f"Season: {season}, Week: {week}") |
|
|
|
|
|
|
|
|
game_info = get_game_info(receiver_team, season, week, games_df) |
|
|
if game_info is None: |
|
|
return "โ Prediction Failed", "Game not found", f"โ Error: Could not find game for {receiver_team} in Week {week} of {season} season", None, None |
|
|
|
|
|
home_team = game_info['home_team'] |
|
|
away_team = game_info['away_team'] |
|
|
opponent_team = game_info['opponent_team'] |
|
|
receiver_is_home = game_info['receiver_is_home'] |
|
|
|
|
|
|
|
|
espn_info = get_game_info_espn(home_team, away_team, week) |
|
|
|
|
|
if espn_info["game_datetime"]: |
|
|
game_datetime = espn_info["game_datetime"] |
|
|
elif game_info.get('gameday') and game_info.get('gametime'): |
|
|
try: |
|
|
game_datetime = datetime.strptime( |
|
|
f"{game_info['gameday']} {game_info['gametime']}", |
|
|
"%Y-%m-%d %H:%M:%S" |
|
|
) |
|
|
except: |
|
|
game_datetime = None |
|
|
else: |
|
|
game_datetime = None |
|
|
|
|
|
weather = None |
|
|
if game_datetime: |
|
|
weather = get_weather_forecast(home_team, game_datetime) |
|
|
|
|
|
dome_teams = ["ARI", "ATL", "DAL", "DET", "HOU", "IND", "LV", "LAR", "LAC", "MIN", "NO"] |
|
|
is_dome = home_team in dome_teams or game_info.get('roof') == 'dome' |
|
|
|
|
|
if weather: |
|
|
game_data = { |
|
|
"temp_f": weather["temp_f"], |
|
|
"humidity_pct": weather["humidity_pct"], |
|
|
"wind_mph": weather["wind_mph"], |
|
|
"is_dome": int(is_dome), |
|
|
"is_rain": weather["is_rain"] if not is_dome else 0, |
|
|
"is_snow": weather["is_snow"] if not is_dome else 0, |
|
|
"is_clear": weather["is_clear"] if not is_dome else 0 |
|
|
} |
|
|
else: |
|
|
game_data = { |
|
|
"temp_f": 72 if is_dome else 70, |
|
|
"humidity_pct": 50, |
|
|
"wind_mph": 0 if is_dome else 5, |
|
|
"is_dome": int(is_dome), |
|
|
"is_rain": 0, "is_snow": 0, |
|
|
"is_clear": 0 if is_dome else 1 |
|
|
} |
|
|
|
|
|
team_map = { |
|
|
"ARI": 1, "ATL": 2, "BAL": 3, "BUF": 4, "CAR": 5, "CHI": 6, "CIN": 7, "CLE": 8, |
|
|
"DAL": 9, "DEN": 10, "DET": 11, "GB": 12, "HOU": 13, "IND": 14, "JAX": 15, "KC": 16, |
|
|
"LV": 17, "LAC": 18, "LAR": 19, "MIA": 20, "MIN": 21, "NE": 22, "NO": 23, "NYG": 24, |
|
|
"NYJ": 25, "PHI": 26, "PIT": 27, "SEA": 28, "SF": 29, "TB": 30, "TEN": 31, "WAS": 32 |
|
|
} |
|
|
|
|
|
surface_map = { |
|
|
"a_turf": 1, "grass": 2, "sportturf": 3, |
|
|
"fieldturf": 4, "matrixturf": 5, "astroturf": 6, "0": 0 |
|
|
} |
|
|
|
|
|
posteam_id = team_map.get(receiver_team, 0) |
|
|
defteam_id = team_map.get(opponent_team, 0) |
|
|
home_team_id = team_map.get(home_team, 0) |
|
|
away_team_id = team_map.get(away_team, 0) |
|
|
|
|
|
surface_type = game_info.get('surface', 'grass') |
|
|
surface_id = surface_map.get(surface_type.lower() if surface_type else 'grass', 2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
espn_spread_home = espn_info["pregame_spread"] |
|
|
pregame_total = espn_info["pregame_total"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pregame_spread = -espn_spread_home |
|
|
|
|
|
print(f"\n=== Spread Conversion ===") |
|
|
print(f"ESPN spread (home team {home_team} perspective): {espn_spread_home}") |
|
|
print(f"Converted to away team ({away_team}) perspective: {pregame_spread}") |
|
|
print(f"Receiver team: {receiver_team} ({'HOME' if receiver_is_home else 'AWAY'})") |
|
|
print(f"Model Input Spread: {pregame_spread} (always from {away_team}'s perspective)") |
|
|
|
|
|
model_input = { |
|
|
"receiver_player_id": receiver_id, |
|
|
"defteam": defteam_id, |
|
|
"posteam": posteam_id, |
|
|
"surface": surface_id, |
|
|
"is_dome": game_data["is_dome"], |
|
|
"is_rain": game_data["is_rain"], |
|
|
"is_snow": game_data["is_snow"], |
|
|
"is_clear": game_data["is_clear"], |
|
|
"temp_f": game_data["temp_f"], |
|
|
"humidity_pct": game_data["humidity_pct"], |
|
|
"wind_mph": game_data["wind_mph"], |
|
|
"home_team": home_team_id, |
|
|
"away_team": away_team_id, |
|
|
"pregame_spread": pregame_spread, |
|
|
"pregame_total": pregame_total, |
|
|
"passer_player_id": passer_id |
|
|
} |
|
|
|
|
|
predicted_yards, error = predict_yards(model_input, receiver_id, passer_id) |
|
|
|
|
|
if error: |
|
|
prediction_text = "โ Prediction Failed" |
|
|
predicted_value = f"Error: {error}" |
|
|
elif predicted_yards is not None: |
|
|
prediction_text = "๐ฏ PREDICTION" |
|
|
predicted_value = f"{predicted_yards:.1f} yards" |
|
|
else: |
|
|
prediction_text = "โ ๏ธ Unavailable" |
|
|
predicted_value = "Prediction unavailable" |
|
|
|
|
|
|
|
|
if receiver_is_home: |
|
|
receiver_team_spread = espn_spread_home |
|
|
else: |
|
|
receiver_team_spread = pregame_spread |
|
|
|
|
|
output = f""" |
|
|
๐ **Game Information:** |
|
|
โข Matchup: {away_team} @ {home_team} (Week {week}, {season}) |
|
|
โข Game Time: {game_datetime if game_datetime else 'TBD'} |
|
|
โข Venue: {home_team} ({surface_type}, {'Indoor' if is_dome else 'Outdoor'}) |
|
|
๐ค **Players:** |
|
|
โข Receiver: {receiver_name} (ID: {receiver_id}) - Team: {receiver_team} |
|
|
โข Passer: {passer_name} (ID: {passer_id}) - Team: {passer_team} |
|
|
โข Opponent: {opponent_team} |
|
|
โข Playing {'Home' if receiver_is_home else 'Away'} |
|
|
๐ค๏ธ **Weather Conditions:** |
|
|
โข Temperature: {game_data['temp_f']}ยฐF |
|
|
โข Humidity: {game_data['humidity_pct']}% |
|
|
โข Wind: {game_data['wind_mph']} mph |
|
|
โข Conditions: {'Dome' if is_dome else 'Rain' if game_data['is_rain'] else 'Snow' if game_data['is_snow'] else 'Clear'} |
|
|
๐ฐ **Betting Lines:** |
|
|
โข Spread: {receiver_team} {receiver_team_spread if receiver_team_spread != 0 else 'N/A'} |
|
|
โข Total: {pregame_total if pregame_total != 0 else 'N/A'} |
|
|
""" |
|
|
|
|
|
return prediction_text, predicted_value, output, receiver_headshot, passer_headshot |
|
|
|
|
|
except Exception as e: |
|
|
import traceback |
|
|
traceback.print_exc() |
|
|
return "โ Error", f"{str(e)}", f"โ Error: {str(e)}", None, None |
|
|
|
|
|
|
|
|
with gr.Blocks(title="NFL Receiver Yards Predictor", theme=gr.themes.Soft()) as app: |
|
|
gr.Markdown("# ๐ NFL Receiver Yards Predictor") |
|
|
gr.Markdown("Predict receiving yards with AI-powered analysis. Just select the player, QB, week, and season!") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=4): |
|
|
receiver_name = gr.Dropdown(choices=receiver_choices, label="๐ฏ Receiver Name", value="", allow_custom_value=False) |
|
|
with gr.Column(scale=1): |
|
|
receiver_img = gr.Image(label="", show_label=False, height=120, show_download_button=False, container=False) |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=4): |
|
|
passer_name = gr.Dropdown(choices=passer_choices, label="๐ Passer Name", value="", allow_custom_value=False) |
|
|
with gr.Column(scale=1): |
|
|
passer_img = gr.Image(label="", show_label=False, height=120, show_download_button=False, container=False) |
|
|
|
|
|
with gr.Row(): |
|
|
week = gr.Number(label="๐
Week", value=6, precision=0) |
|
|
season = gr.Number(label="๐
Season", value=2025, precision=0) |
|
|
|
|
|
predict_btn = gr.Button("๐ฎ Predict Yards", variant="primary", size="lg") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
prediction_label = gr.Textbox(label="", show_label=False, interactive=False, |
|
|
container=False, lines=1, max_lines=1, |
|
|
text_align="center") |
|
|
with gr.Column(): |
|
|
prediction_value = gr.Textbox(label="", show_label=False, interactive=False, |
|
|
container=False, lines=1, max_lines=1, |
|
|
text_align="center") |
|
|
|
|
|
output = gr.Textbox(label="๐ Detailed Results", lines=15, max_lines=20) |
|
|
|
|
|
receiver_name.change( |
|
|
fn=update_receiver_image, |
|
|
inputs=[receiver_name], |
|
|
outputs=[receiver_img] |
|
|
) |
|
|
|
|
|
passer_name.change( |
|
|
fn=update_passer_image, |
|
|
inputs=[passer_name], |
|
|
outputs=[passer_img] |
|
|
) |
|
|
|
|
|
predict_btn.click( |
|
|
fn=create_model_input_and_predict, |
|
|
inputs=[receiver_name, passer_name, week, season], |
|
|
outputs=[prediction_label, prediction_value, output, receiver_img, passer_img] |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
### ๐ How It Works: |
|
|
1. **Select Receiver** โ Headshot appears instantly |
|
|
2. **Select Passer (QB)** โ Headshot appears instantly |
|
|
3. **Enter Week & Season** |
|
|
4. **Click "Predict Yards"** โ Get your AI-powered prediction! |
|
|
### โก What Happens Automatically: |
|
|
- ๐ผ๏ธ Player headshots load in real-time as you select them |
|
|
- ๐๏ธ Determines matchup and venue based on receiver's team schedule |
|
|
- ๐ค๏ธ Fetches live weather forecast for game time |
|
|
- ๐ฐ Loads real-time betting lines (spread & total) from ESPN API |
|
|
- ๐ค Generates AI prediction using advanced machine learning model |
|
|
- ๐ Displays comprehensive game analysis and prediction results |
|
|
|
|
|
### ๐ง Technical Notes: |
|
|
- Team abbreviations are automatically normalized (WAS/WSH, LA/LAR) |
|
|
- Home/away teams correctly identified from games.csv |
|
|
- ESPN API queries use proper team format for accurate odds retrieval |
|
|
""") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
app.launch(share=True, debug=True) |