Update app.py
Browse files
app.py
CHANGED
|
@@ -1,56 +1,98 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import requests
|
|
|
|
|
|
|
| 3 |
import pandas as pd
|
| 4 |
import os
|
|
|
|
| 5 |
from datetime import datetime
|
| 6 |
-
from huggingface_hub import snapshot_download
|
| 7 |
from autogluon.tabular import TabularPredictor
|
| 8 |
|
|
|
|
| 9 |
# --- Download Model and Embeddings ---
|
| 10 |
def download_model_and_embeddings(repo_id="SebastianAndreu/2025-24679-NFL-Yards-Predictor", local_dir="nfl_model"):
|
| 11 |
try:
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
repo_id=repo_id,
|
|
|
|
| 14 |
repo_type="model",
|
| 15 |
-
local_dir=
|
| 16 |
-
local_dir_use_symlinks=False
|
| 17 |
)
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
if not os.path.exists(emb_path):
|
| 24 |
-
|
|
|
|
| 25 |
|
|
|
|
| 26 |
emb_df = pd.read_csv(emb_path)
|
| 27 |
-
|
|
|
|
| 28 |
return predictor, emb_df
|
|
|
|
| 29 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
| 30 |
return None, None
|
| 31 |
|
|
|
|
| 32 |
# Load model at startup
|
|
|
|
| 33 |
predictor, player_embeddings = download_model_and_embeddings()
|
| 34 |
|
|
|
|
| 35 |
# Load player and game data
|
| 36 |
try:
|
| 37 |
players_df = pd.read_csv("players.csv")
|
| 38 |
games_df = pd.read_csv("games.csv")
|
|
|
|
| 39 |
|
| 40 |
# Create receiver and passer choices from players.csv
|
|
|
|
| 41 |
receivers_df = players_df[
|
| 42 |
(players_df['position'].isin(['WR', 'TE'])) &
|
| 43 |
(players_df['status'] == 'ACT')
|
| 44 |
].copy()
|
| 45 |
receiver_choices = sorted(receivers_df['display_name'].dropna().unique().tolist())
|
| 46 |
|
|
|
|
| 47 |
passers_df = players_df[
|
| 48 |
(players_df['position'] == 'QB') &
|
| 49 |
(players_df['status'] == 'ACT')
|
| 50 |
].copy()
|
| 51 |
passer_choices = sorted(passers_df['display_name'].dropna().unique().tolist())
|
| 52 |
|
|
|
|
|
|
|
| 53 |
except Exception as e:
|
|
|
|
| 54 |
players_df = pd.DataFrame()
|
| 55 |
games_df = pd.DataFrame()
|
| 56 |
receiver_choices = []
|
|
@@ -76,13 +118,6 @@ STADIUM_COORDS = {
|
|
| 76 |
"TEN": {"lat": 36.1665, "lon": -86.7713}, "WAS": {"lat": 38.9076, "lon": -76.8645}
|
| 77 |
}
|
| 78 |
|
| 79 |
-
def get_player_info(player_name, players_df):
|
| 80 |
-
"""Get player's gsis_id, latest team, and headshot from display_name."""
|
| 81 |
-
player_row = players_df[players_df['display_name'] == player_name]
|
| 82 |
-
if player_row.empty:
|
| 83 |
-
return None, None, None
|
| 84 |
-
return player_row.iloc[0]['gsis_id'], player_row.iloc[0]['latest_team'], player_row.iloc[0].get('headshot', None)
|
| 85 |
-
|
| 86 |
def update_receiver_image(receiver_name):
|
| 87 |
"""Update receiver headshot when selection changes."""
|
| 88 |
if not receiver_name or players_df.empty:
|
|
@@ -96,6 +131,11 @@ def update_passer_image(passer_name):
|
|
| 96 |
return None
|
| 97 |
_, _, headshot = get_player_info(passer_name, players_df)
|
| 98 |
return headshot
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
def get_game_info(receiver_team, season, week, games_df):
|
| 101 |
"""Get game information based on receiver's team, season, and week."""
|
|
@@ -167,6 +207,7 @@ def get_weather_forecast(home_team, game_datetime):
|
|
| 167 |
"is_rain": int(is_rain), "is_snow": int(is_snow), "is_clear": int(is_clear)
|
| 168 |
}
|
| 169 |
except Exception as e:
|
|
|
|
| 170 |
return None
|
| 171 |
|
| 172 |
def get_game_info_espn(home_team, away_team, week):
|
|
@@ -197,7 +238,7 @@ def get_game_info_espn(home_team, away_team, week):
|
|
| 197 |
return result
|
| 198 |
|
| 199 |
except Exception as e:
|
| 200 |
-
|
| 201 |
|
| 202 |
return result
|
| 203 |
|
|
@@ -228,9 +269,16 @@ def predict_yards(model_input_dict, receiver_id, passer_id):
|
|
| 228 |
}
|
| 229 |
|
| 230 |
input_df = pd.DataFrame(input_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
# Merge player embeddings if available
|
| 233 |
if player_embeddings is not None:
|
|
|
|
|
|
|
|
|
|
| 234 |
emb_df = player_embeddings.copy()
|
| 235 |
emb_df['player_id'] = emb_df['player_id'].astype(str)
|
| 236 |
|
|
@@ -243,25 +291,45 @@ def predict_yards(model_input_dict, receiver_id, passer_id):
|
|
| 243 |
|
| 244 |
emb_cols = [c for c in emb_df.columns if c.startswith("emb_")]
|
| 245 |
if input_df[emb_cols].isna().any().any():
|
|
|
|
| 246 |
mean_emb = emb_df[emb_cols].mean()
|
| 247 |
input_df[emb_cols] = input_df[emb_cols].fillna(mean_emb)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
# Make prediction
|
|
|
|
|
|
|
| 250 |
try:
|
|
|
|
| 251 |
prediction = predictor.predict(input_df)
|
| 252 |
yards = float(prediction.values[0])
|
|
|
|
| 253 |
return yards, None
|
| 254 |
-
except Exception:
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
except Exception as e:
|
|
|
|
|
|
|
| 265 |
return None, f"Prediction error: {str(e)}"
|
| 266 |
|
| 267 |
def create_model_input_and_predict(receiver_name, passer_name, week, season):
|
|
@@ -270,17 +338,17 @@ def create_model_input_and_predict(receiver_name, passer_name, week, season):
|
|
| 270 |
# Get receiver info from players.csv
|
| 271 |
receiver_id, receiver_team, receiver_headshot = get_player_info(receiver_name, players_df)
|
| 272 |
if receiver_id is None:
|
| 273 |
-
return
|
| 274 |
|
| 275 |
# Get passer info from players.csv
|
| 276 |
passer_id, passer_team, passer_headshot = get_player_info(passer_name, players_df)
|
| 277 |
if passer_id is None:
|
| 278 |
-
return
|
| 279 |
|
| 280 |
# Get game info from games.csv
|
| 281 |
game_info = get_game_info(receiver_team, season, week, games_df)
|
| 282 |
if game_info is None:
|
| 283 |
-
return
|
| 284 |
|
| 285 |
home_team = game_info['home_team']
|
| 286 |
away_team = game_info['away_team']
|
|
@@ -340,6 +408,17 @@ def create_model_input_and_predict(receiver_name, passer_name, week, season):
|
|
| 340 |
"NYJ": 25, "PHI": 26, "PIT": 27, "SEA": 28, "SF": 29, "TB": 30, "TEN": 31, "WAS": 32
|
| 341 |
}
|
| 342 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
surface_map = {
|
| 344 |
"a_turf": 1, "grass": 2, "sportturf": 3,
|
| 345 |
"fieldturf": 4, "matrixturf": 5, "astroturf": 6, "0": 0
|
|
@@ -350,9 +429,8 @@ def create_model_input_and_predict(receiver_name, passer_name, week, season):
|
|
| 350 |
home_team_id = team_map.get(home_team, 0)
|
| 351 |
away_team_id = team_map.get(away_team, 0)
|
| 352 |
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
surface_id = surface_map.get(surface_type.lower() if surface_type else 'grass', 2)
|
| 356 |
|
| 357 |
# Get betting lines from games.csv or default to 0
|
| 358 |
pregame_spread = game_info.get('spread_line', 0) or 0
|
|
@@ -417,6 +495,8 @@ def create_model_input_and_predict(receiver_name, passer_name, week, season):
|
|
| 417 |
return prediction_text, predicted_value, output, receiver_headshot, passer_headshot
|
| 418 |
|
| 419 |
except Exception as e:
|
|
|
|
|
|
|
| 420 |
return "β Error", f"{str(e)}", f"β Error: {str(e)}", None, None
|
| 421 |
|
| 422 |
# Create Gradio interface
|
|
@@ -475,21 +555,21 @@ with gr.Blocks(title="NFL Receiver Yards Predictor", theme=gr.themes.Soft()) as
|
|
| 475 |
)
|
| 476 |
|
| 477 |
gr.Markdown("""
|
| 478 |
-
### π
|
| 479 |
-
1.
|
| 480 |
-
2.
|
| 481 |
-
3.
|
| 482 |
-
4.
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
-
|
| 486 |
-
-
|
| 487 |
-
-
|
| 488 |
-
-
|
| 489 |
-
-
|
| 490 |
-
-
|
| 491 |
""")
|
| 492 |
|
| 493 |
# Launch the app
|
| 494 |
if __name__ == "__main__":
|
| 495 |
-
app.launch()
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import requests
|
| 3 |
+
import json
|
| 4 |
+
import ast
|
| 5 |
import pandas as pd
|
| 6 |
import os
|
| 7 |
+
import pathlib, zipfile, shutil
|
| 8 |
from datetime import datetime
|
| 9 |
+
from huggingface_hub import hf_hub_download, snapshot_download
|
| 10 |
from autogluon.tabular import TabularPredictor
|
| 11 |
|
| 12 |
+
|
| 13 |
# --- Download Model and Embeddings ---
|
| 14 |
def download_model_and_embeddings(repo_id="SebastianAndreu/2025-24679-NFL-Yards-Predictor", local_dir="nfl_model"):
|
| 15 |
try:
|
| 16 |
+
print(f"Downloading model from {repo_id}...")
|
| 17 |
+
|
| 18 |
+
# --- New Zip-Based Model Loading ---
|
| 19 |
+
ZIP_FILENAME = "autogluon_predictor_dir.zip"
|
| 20 |
+
CACHE_DIR = pathlib.Path(local_dir)
|
| 21 |
+
EXTRACT_DIR = CACHE_DIR / "predictor_native"
|
| 22 |
+
|
| 23 |
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
| 24 |
+
local_zip = hf_hub_download(
|
| 25 |
repo_id=repo_id,
|
| 26 |
+
filename=ZIP_FILENAME,
|
| 27 |
repo_type="model",
|
| 28 |
+
local_dir=str(CACHE_DIR),
|
| 29 |
+
local_dir_use_symlinks=False,
|
| 30 |
)
|
| 31 |
+
|
| 32 |
+
if EXTRACT_DIR.exists():
|
| 33 |
+
shutil.rmtree(EXTRACT_DIR)
|
| 34 |
+
EXTRACT_DIR.mkdir(parents=True, exist_ok=True)
|
| 35 |
+
|
| 36 |
+
with zipfile.ZipFile(local_zip, "r") as zf:
|
| 37 |
+
zf.extractall(str(EXTRACT_DIR))
|
| 38 |
+
|
| 39 |
+
contents = list(EXTRACT_DIR.iterdir())
|
| 40 |
+
predictor_root = contents[0] if (len(contents) == 1 and contents[0].is_dir()) else EXTRACT_DIR
|
| 41 |
+
|
| 42 |
+
print(f"β Extracted model to: {predictor_root}")
|
| 43 |
+
|
| 44 |
+
predictor = TabularPredictor.load(predictor_root, require_py_version_match=False, verbosity=0)
|
| 45 |
+
print("β Successfully loaded AutoGluon predictor from zip")
|
| 46 |
+
|
| 47 |
+
# --- Download embeddings (same as before) ---
|
| 48 |
+
emb_path = os.path.join(local_dir, "player_historical_embeddings.csv")
|
| 49 |
if not os.path.exists(emb_path):
|
| 50 |
+
print(f"Downloading player embeddings from repo...")
|
| 51 |
+
snapshot_download(repo_id=repo_id, repo_type="model", local_dir=local_dir)
|
| 52 |
|
| 53 |
+
emb_path = os.path.join(local_dir, "data", "player_historical_embeddings.csv")
|
| 54 |
emb_df = pd.read_csv(emb_path)
|
| 55 |
+
print(f"β Loaded {len(emb_df)} player embeddings from {emb_path}")
|
| 56 |
+
|
| 57 |
return predictor, emb_df
|
| 58 |
+
|
| 59 |
except Exception as e:
|
| 60 |
+
import traceback
|
| 61 |
+
print(f"Error downloading model or embeddings: {e}")
|
| 62 |
+
traceback.print_exc()
|
| 63 |
return None, None
|
| 64 |
|
| 65 |
+
|
| 66 |
# Load model at startup
|
| 67 |
+
print("Loading NFL Yards Prediction Model...")
|
| 68 |
predictor, player_embeddings = download_model_and_embeddings()
|
| 69 |
|
| 70 |
+
|
| 71 |
# Load player and game data
|
| 72 |
try:
|
| 73 |
players_df = pd.read_csv("players.csv")
|
| 74 |
games_df = pd.read_csv("games.csv")
|
| 75 |
+
print(f"β Loaded {len(players_df)} players and {len(games_df)} games")
|
| 76 |
|
| 77 |
# Create receiver and passer choices from players.csv
|
| 78 |
+
# Filter for receivers (WR, TE) and active players
|
| 79 |
receivers_df = players_df[
|
| 80 |
(players_df['position'].isin(['WR', 'TE'])) &
|
| 81 |
(players_df['status'] == 'ACT')
|
| 82 |
].copy()
|
| 83 |
receiver_choices = sorted(receivers_df['display_name'].dropna().unique().tolist())
|
| 84 |
|
| 85 |
+
# Filter for quarterbacks
|
| 86 |
passers_df = players_df[
|
| 87 |
(players_df['position'] == 'QB') &
|
| 88 |
(players_df['status'] == 'ACT')
|
| 89 |
].copy()
|
| 90 |
passer_choices = sorted(passers_df['display_name'].dropna().unique().tolist())
|
| 91 |
|
| 92 |
+
print(f"β Found {len(receiver_choices)} active receivers and {len(passer_choices)} active QBs")
|
| 93 |
+
|
| 94 |
except Exception as e:
|
| 95 |
+
print(f"β Error loading player/game data: {e}")
|
| 96 |
players_df = pd.DataFrame()
|
| 97 |
games_df = pd.DataFrame()
|
| 98 |
receiver_choices = []
|
|
|
|
| 118 |
"TEN": {"lat": 36.1665, "lon": -86.7713}, "WAS": {"lat": 38.9076, "lon": -76.8645}
|
| 119 |
}
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
def update_receiver_image(receiver_name):
|
| 122 |
"""Update receiver headshot when selection changes."""
|
| 123 |
if not receiver_name or players_df.empty:
|
|
|
|
| 131 |
return None
|
| 132 |
_, _, headshot = get_player_info(passer_name, players_df)
|
| 133 |
return headshot
|
| 134 |
+
"""Get player's gsis_id, latest team, and headshot from display_name."""
|
| 135 |
+
player_row = players_df[players_df['display_name'] == player_name]
|
| 136 |
+
if player_row.empty:
|
| 137 |
+
return None, None, None
|
| 138 |
+
return player_row.iloc[0]['gsis_id'], player_row.iloc[0]['latest_team'], player_row.iloc[0].get('headshot', None)
|
| 139 |
|
| 140 |
def get_game_info(receiver_team, season, week, games_df):
|
| 141 |
"""Get game information based on receiver's team, season, and week."""
|
|
|
|
| 207 |
"is_rain": int(is_rain), "is_snow": int(is_snow), "is_clear": int(is_clear)
|
| 208 |
}
|
| 209 |
except Exception as e:
|
| 210 |
+
print(f"Weather API error: {e}")
|
| 211 |
return None
|
| 212 |
|
| 213 |
def get_game_info_espn(home_team, away_team, week):
|
|
|
|
| 238 |
return result
|
| 239 |
|
| 240 |
except Exception as e:
|
| 241 |
+
print(f"β ESPN API error: {e}")
|
| 242 |
|
| 243 |
return result
|
| 244 |
|
|
|
|
| 269 |
}
|
| 270 |
|
| 271 |
input_df = pd.DataFrame(input_data)
|
| 272 |
+
|
| 273 |
+
print(f"Input DataFrame shape: {input_df.shape}")
|
| 274 |
+
print(f"Input DataFrame dtypes:\n{input_df.dtypes}")
|
| 275 |
+
print(f"Sample input:\n{input_df.head()}")
|
| 276 |
|
| 277 |
# Merge player embeddings if available
|
| 278 |
if player_embeddings is not None:
|
| 279 |
+
print(f"Merging embeddings for receiver: {receiver_id}")
|
| 280 |
+
|
| 281 |
+
# Ensure player_id column in embeddings is string type for matching
|
| 282 |
emb_df = player_embeddings.copy()
|
| 283 |
emb_df['player_id'] = emb_df['player_id'].astype(str)
|
| 284 |
|
|
|
|
| 291 |
|
| 292 |
emb_cols = [c for c in emb_df.columns if c.startswith("emb_")]
|
| 293 |
if input_df[emb_cols].isna().any().any():
|
| 294 |
+
print(f"β Missing embeddings for receiver {receiver_id}. Using global mean.")
|
| 295 |
mean_emb = emb_df[emb_cols].mean()
|
| 296 |
input_df[emb_cols] = input_df[emb_cols].fillna(mean_emb)
|
| 297 |
+
else:
|
| 298 |
+
print(f"β Found embeddings for receiver {receiver_id}")
|
| 299 |
+
else:
|
| 300 |
+
print("β No embeddings loaded")
|
| 301 |
+
|
| 302 |
+
print(f"Final input shape: {input_df.shape}")
|
| 303 |
+
print(f"Final columns: {input_df.columns.tolist()}")
|
| 304 |
|
| 305 |
# Make prediction
|
| 306 |
+
yards = None
|
| 307 |
+
|
| 308 |
try:
|
| 309 |
+
print("Attempting prediction with full ensemble...")
|
| 310 |
prediction = predictor.predict(input_df)
|
| 311 |
yards = float(prediction.values[0])
|
| 312 |
+
print(f"β Success with full ensemble: {yards:.2f} yards")
|
| 313 |
return yards, None
|
| 314 |
+
except Exception as pred_error:
|
| 315 |
+
print(f"β Full ensemble failed: {str(pred_error)[:100]}")
|
| 316 |
+
|
| 317 |
+
try:
|
| 318 |
+
print("Attempting prediction with best model only...")
|
| 319 |
+
leaderboard = predictor.leaderboard(silent=True)
|
| 320 |
+
best_model = leaderboard.iloc[0]['model']
|
| 321 |
+
print(f"Using best model: {best_model}")
|
| 322 |
+
prediction = predictor.predict(input_df, model=best_model)
|
| 323 |
+
yards = float(prediction.values[0])
|
| 324 |
+
print(f"β Success with best model: {yards:.2f} yards")
|
| 325 |
+
return yards, None
|
| 326 |
+
except Exception as best_error:
|
| 327 |
+
print(f"β Best model failed: {str(best_error)[:100]}")
|
| 328 |
+
|
| 329 |
+
return None, "All prediction strategies failed."
|
| 330 |
except Exception as e:
|
| 331 |
+
import traceback
|
| 332 |
+
traceback.print_exc()
|
| 333 |
return None, f"Prediction error: {str(e)}"
|
| 334 |
|
| 335 |
def create_model_input_and_predict(receiver_name, passer_name, week, season):
|
|
|
|
| 338 |
# Get receiver info from players.csv
|
| 339 |
receiver_id, receiver_team, receiver_headshot = get_player_info(receiver_name, players_df)
|
| 340 |
if receiver_id is None:
|
| 341 |
+
return f"β Error: Could not find receiver '{receiver_name}' in database", None, None
|
| 342 |
|
| 343 |
# Get passer info from players.csv
|
| 344 |
passer_id, passer_team, passer_headshot = get_player_info(passer_name, players_df)
|
| 345 |
if passer_id is None:
|
| 346 |
+
return f"β Error: Could not find passer '{passer_name}' in database", None, None
|
| 347 |
|
| 348 |
# Get game info from games.csv
|
| 349 |
game_info = get_game_info(receiver_team, season, week, games_df)
|
| 350 |
if game_info is None:
|
| 351 |
+
return f"β Error: Could not find game for {receiver_team} in Week {week} of {season} season"
|
| 352 |
|
| 353 |
home_team = game_info['home_team']
|
| 354 |
away_team = game_info['away_team']
|
|
|
|
| 408 |
"NYJ": 25, "PHI": 26, "PIT": 27, "SEA": 28, "SF": 29, "TB": 30, "TEN": 31, "WAS": 32
|
| 409 |
}
|
| 410 |
|
| 411 |
+
home_team_surface_map = {
|
| 412 |
+
"ARI": "grass", "ATL": "fieldturf", "BAL": "grass", "BUF": "fieldturf",
|
| 413 |
+
"CAR": "fieldturf", "CHI": "grass", "CIN": "fieldturf", "CLE": "grass",
|
| 414 |
+
"DAL": "fieldturf", "DEN": "grass", "DET": "fieldturf", "GB": "grass",
|
| 415 |
+
"HOU": "fieldturf", "IND": "fieldturf", "JAX": "grass", "KC": "grass",
|
| 416 |
+
"LV": "grass", "LAC": "fieldturf", "LAR": "fieldturf", "MIA": "grass",
|
| 417 |
+
"MIN": "fieldturf", "NE": "fieldturf", "NO": "fieldturf", "NYG": "fieldturf",
|
| 418 |
+
"NYJ": "fieldturf", "PHI": "grass", "PIT": "grass", "SF": "grass",
|
| 419 |
+
"SEA": "fieldturf", "TB": "grass", "TEN": "fieldturf", "WAS": "grass"
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
surface_map = {
|
| 423 |
"a_turf": 1, "grass": 2, "sportturf": 3,
|
| 424 |
"fieldturf": 4, "matrixturf": 5, "astroturf": 6, "0": 0
|
|
|
|
| 429 |
home_team_id = team_map.get(home_team, 0)
|
| 430 |
away_team_id = team_map.get(away_team, 0)
|
| 431 |
|
| 432 |
+
surface_type = game_info.get('surface', home_team_surface_map.get(home_team, "grass"))
|
| 433 |
+
surface_id = surface_map.get(surface_type, 2)
|
|
|
|
| 434 |
|
| 435 |
# Get betting lines from games.csv or default to 0
|
| 436 |
pregame_spread = game_info.get('spread_line', 0) or 0
|
|
|
|
| 495 |
return prediction_text, predicted_value, output, receiver_headshot, passer_headshot
|
| 496 |
|
| 497 |
except Exception as e:
|
| 498 |
+
import traceback
|
| 499 |
+
traceback.print_exc()
|
| 500 |
return "β Error", f"{str(e)}", f"β Error: {str(e)}", None, None
|
| 501 |
|
| 502 |
# Create Gradio interface
|
|
|
|
| 555 |
)
|
| 556 |
|
| 557 |
gr.Markdown("""
|
| 558 |
+
### π Instructions:
|
| 559 |
+
1. Select the receiver from the dropdown (headshot appears automatically)
|
| 560 |
+
2. Select the passer (QB) from the dropdown (headshot appears automatically)
|
| 561 |
+
3. Enter the week number and season
|
| 562 |
+
4. Click **"Predict Yards"** to get the prediction
|
| 563 |
+
|
| 564 |
+
The app will automatically:
|
| 565 |
+
- β
Display player headshots as you select them
|
| 566 |
+
- β
Determine which teams are playing based on the receiver's team
|
| 567 |
+
- β
Find the game in the schedule
|
| 568 |
+
- β
Fetch weather forecast for the game
|
| 569 |
+
- β
Load betting lines from historical data
|
| 570 |
+
- β
Generate AI-powered yards prediction
|
| 571 |
""")
|
| 572 |
|
| 573 |
# Launch the app
|
| 574 |
if __name__ == "__main__":
|
| 575 |
+
app.launch(share=True, debug=True)
|