SebastianAndreu's picture
Update app.py
c14d3b4 verified
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
# Team name mappings
# ESPN uses different abbreviations than NFL data
ESPN_TEAM_MAP = {
"WAS": "WSH", # Washington
"LA": "LAR", # LA Rams
"LAR": "LAR", # LA Rams (if already normalized)
}
# Reverse mapping for ESPN to internal
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)
# --- Download Model and Embeddings ---
def download_model_and_embeddings(MODEL_REPO_ID="SebastianAndreu/2025-24679-NFL-Yards-Predictor"):
try:
# Configuration
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"
# Download & load the native predictor
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)
# Download model and embeddings
try:
# Download the model and data using snapshot_download
model_path = snapshot_download(
repo_id=MODEL_REPO_ID,
repo_type="model",
local_dir=MODEL_LOCAL_DIR,
local_dir_use_symlinks=False
)
# Load the predictor
PREDICTOR_DIR = _prepare_predictor_dir()
PREDICTOR = TabularPredictor.load(PREDICTOR_DIR, require_py_version_match=False)
# Load embeddings
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
# Load model at startup
predictor, player_embeddings = download_model_and_embeddings()
# Load player and game data
try:
players_df = pd.read_csv("players.csv")
games_df = pd.read_csv("games.csv")
# Normalize team names in games_df to match internal format
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'})
# Normalize team names in players_df
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 coordinates
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."""
# Ensure receiver_team is normalized (e.g., LAR not LA)
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."""
# Convert internal team names to ESPN format
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:
# Try both regular season (2) and postseason (3)
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 puts home team at index 0, away team at index 1
espn_home = competitors[0]['team']['abbreviation']
espn_away = competitors[1]['team']['abbreviation']
print(f" Checking ESPN game: {espn_away} @ {espn_home}")
# Match using ESPN format
if espn_home == home_team_espn and espn_away == away_team_espn:
print(f" โœ“ MATCH FOUND!")
# Get game time
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']}")
# Get odds from competition
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')
# ESPN's spread is from home team perspective
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)}")
# Check what columns the predictor expects
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)}")
# Check for missing embeddings
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 prediction
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 with specific model
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:
# Get receiver info (posteam from latest_team)
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
# Get passer info
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}")
# Get game info from games.csv using receiver's team
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']
# Get game info from ESPN (including spread and total)
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)
# Use spread and total from ESPN API
# ESPN spread is from HOME team perspective
# MODEL ALWAYS USES AWAY TEAM PERSPECTIVE
espn_spread_home = espn_info["pregame_spread"]
pregame_total = espn_info["pregame_total"]
# Convert to away team perspective for the model
# If home is -7.5, away is +7.5
# If home is +3, away is -3
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"
# Calculate receiver's team spread for display purposes
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
# Create Gradio interface
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)