SebastianAndreu commited on
Commit
d2d1682
·
verified ·
1 Parent(s): b355641

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +446 -0
app.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import ast
5
+ import pandas as pd
6
+ import os
7
+ from datetime import datetime
8
+ from huggingface_hub import snapshot_download
9
+ from autogluon.tabular import TabularPredictor
10
+
11
+ # --- Download Model and Embeddings ---
12
+ def download_model_and_embeddings(repo_id="SebastianAndreu/2025-24679-NFL-Yards-Predictor", local_dir="nfl_model"):
13
+ try:
14
+ print(f"Downloading model from {repo_id}...")
15
+ model_path = snapshot_download(
16
+ repo_id=repo_id,
17
+ repo_type="model",
18
+ local_dir=local_dir,
19
+ local_dir_use_symlinks=False
20
+ )
21
+ predictor = TabularPredictor.load(os.path.join(local_dir, "model"), verbosity=0)
22
+ emb_df = pd.read_csv(os.path.join(local_dir, "data", "player_historical_embeddings.csv"))
23
+ print(f"✓ Loaded model from {repo_id}/model and embeddings from {repo_id}/data")
24
+ print(f"✓ Loaded {len(emb_df)} player embeddings")
25
+ return predictor, emb_df
26
+ except Exception as e:
27
+ print(f"Error downloading model or embeddings: {e}")
28
+ return None, None
29
+
30
+ # Load model at startup
31
+ print("Loading NFL Yards Prediction Model...")
32
+ predictor, player_embeddings = download_model_and_embeddings()
33
+
34
+ # Load player mappings from the same repo
35
+ def load_player_mappings():
36
+ try:
37
+ # Try to load from downloaded model directory
38
+ with open("nfl_model/receiver_to_player_id.json", 'r') as f:
39
+ content = f.read()
40
+ try:
41
+ receiver_to_player_id = json.loads(content)
42
+ except json.JSONDecodeError:
43
+ receiver_to_player_id = ast.literal_eval(content)
44
+
45
+ with open("nfl_model/passer_to_player_id.json", 'r') as f:
46
+ content = f.read()
47
+ try:
48
+ passer_to_player_id = json.loads(content)
49
+ except json.JSONDecodeError:
50
+ passer_to_player_id = ast.literal_eval(content)
51
+
52
+ print(f"✓ Loaded {len(receiver_to_player_id)} receivers and {len(passer_to_player_id)} passers")
53
+ return receiver_to_player_id, passer_to_player_id
54
+ except Exception as e:
55
+ print(f"Error loading player mappings: {e}")
56
+ return {}, {}
57
+
58
+ receiver_to_player_id, passer_to_player_id = load_player_mappings()
59
+ receiver_choices = sorted(list(receiver_to_player_id.keys()))
60
+ passer_choices = sorted(list(passer_to_player_id.keys()))
61
+
62
+ # Stadium coordinates
63
+ STADIUM_COORDS = {
64
+ "ARI": {"lat": 33.5276, "lon": -112.2626}, "ATL": {"lat": 33.7554, "lon": -84.4008},
65
+ "BAL": {"lat": 39.2780, "lon": -76.6227}, "BUF": {"lat": 42.7738, "lon": -78.7870},
66
+ "CAR": {"lat": 35.2258, "lon": -80.8528}, "CHI": {"lat": 41.8623, "lon": -87.6167},
67
+ "CIN": {"lat": 39.0954, "lon": -84.5160}, "CLE": {"lat": 41.5061, "lon": -81.6995},
68
+ "DAL": {"lat": 32.7473, "lon": -97.0945}, "DEN": {"lat": 39.7439, "lon": -105.0201},
69
+ "DET": {"lat": 42.3400, "lon": -83.0456}, "GB": {"lat": 44.5013, "lon": -88.0622},
70
+ "HOU": {"lat": 29.6847, "lon": -95.4107}, "IND": {"lat": 39.7601, "lon": -86.1639},
71
+ "JAX": {"lat": 30.3239, "lon": -81.6373}, "KC": {"lat": 39.0489, "lon": -94.4839},
72
+ "LV": {"lat": 36.0908, "lon": -115.1833}, "LAC": {"lat": 33.9535, "lon": -118.3390},
73
+ "LAR": {"lat": 33.9535, "lon": -118.3390}, "MIA": {"lat": 25.9580, "lon": -80.2389},
74
+ "MIN": {"lat": 44.9738, "lon": -93.2577}, "NE": {"lat": 42.0909, "lon": -71.2643},
75
+ "NO": {"lat": 29.9511, "lon": -90.0812}, "NYG": {"lat": 40.8128, "lon": -74.0742},
76
+ "NYJ": {"lat": 40.8128, "lon": -74.0742}, "PHI": {"lat": 39.9008, "lon": -75.1675},
77
+ "PIT": {"lat": 40.4468, "lon": -80.0158}, "SF": {"lat": 37.4032, "lon": -121.9698},
78
+ "SEA": {"lat": 47.5952, "lon": -122.3316}, "TB": {"lat": 27.9759, "lon": -82.5033},
79
+ "TEN": {"lat": 36.1665, "lon": -86.7713}, "WAS": {"lat": 38.9076, "lon": -76.8645}
80
+ }
81
+
82
+ def get_weather_forecast(home_team, game_datetime):
83
+ """Get weather forecast for a stadium at game time."""
84
+ coords = STADIUM_COORDS.get(home_team)
85
+ if not coords:
86
+ return None
87
+
88
+ url = "https://api.open-meteo.com/v1/forecast"
89
+ params = {
90
+ "latitude": coords["lat"], "longitude": coords["lon"],
91
+ "hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code",
92
+ "temperature_unit": "fahrenheit", "wind_speed_unit": "mph",
93
+ "timezone": "America/New_York"
94
+ }
95
+
96
+ try:
97
+ response = requests.get(url, params=params, timeout=10)
98
+ response.raise_for_status()
99
+ data = response.json()
100
+
101
+ hourly = data.get("hourly", {})
102
+ times = hourly.get("time", [])
103
+
104
+ game_time_str = game_datetime.strftime("%Y-%m-%dT%H:%M")
105
+ closest_idx = 0
106
+ for i, time_str in enumerate(times):
107
+ if time_str >= game_time_str:
108
+ closest_idx = i
109
+ break
110
+
111
+ temp = hourly["temperature_2m"][closest_idx]
112
+ humidity = hourly["relative_humidity_2m"][closest_idx]
113
+ wind = hourly["wind_speed_10m"][closest_idx]
114
+ weather_code = hourly["weather_code"][closest_idx]
115
+
116
+ is_rain = weather_code in [51, 53, 55, 61, 63, 65, 80, 81, 82]
117
+ is_snow = weather_code in [71, 73, 75, 77, 85, 86]
118
+ is_clear = weather_code in [0, 1, 2]
119
+
120
+ return {
121
+ "temp_f": temp, "humidity_pct": humidity, "wind_mph": wind,
122
+ "is_rain": int(is_rain), "is_snow": int(is_snow), "is_clear": int(is_clear)
123
+ }
124
+ except Exception as e:
125
+ print(f"Weather API error: {e}")
126
+ return None
127
+
128
+ def get_game_info_espn(home_team, away_team, week):
129
+ """Get game time, spread, and total from ESPN API."""
130
+ result = {"game_datetime": None, "pregame_spread": 0, "pregame_total": 0}
131
+
132
+ try:
133
+ for season_type in [2, 3]:
134
+ url = f"https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard?seasontype={season_type}&week={week}"
135
+ response = requests.get(url, timeout=10)
136
+ response.raise_for_status()
137
+ data = response.json()
138
+
139
+ for event in data.get('events', []):
140
+ competition = event.get('competitions', [{}])[0]
141
+ competitors = competition.get('competitors', [])
142
+
143
+ if len(competitors) < 2:
144
+ continue
145
+
146
+ h_team = competitors[0]['team']['abbreviation']
147
+ a_team = competitors[1]['team']['abbreviation']
148
+
149
+ if (h_team == home_team and a_team == away_team) or (h_team == away_team and a_team == home_team):
150
+ actual_home = h_team
151
+ actual_away = a_team
152
+ teams_reversed = (actual_home != home_team)
153
+
154
+ game_date_str = event.get('date')
155
+ if game_date_str:
156
+ result["game_datetime"] = datetime.fromisoformat(game_date_str.replace('Z', '+00:00'))
157
+
158
+ odds_data = competition.get('odds', [])
159
+ if odds_data and len(odds_data) > 0:
160
+ odds = odds_data[0]
161
+ spread = odds.get('spread')
162
+ total = odds.get('overUnder')
163
+
164
+ away_odds = odds.get('awayTeamOdds', {})
165
+ home_odds = odds.get('homeTeamOdds', {})
166
+
167
+ if teams_reversed:
168
+ if away_odds.get('favorite'):
169
+ result["pregame_spread"] = -float(spread) if spread is not None else 0
170
+ elif home_odds.get('favorite'):
171
+ result["pregame_spread"] = float(spread) if spread is not None else 0
172
+ else:
173
+ result["pregame_spread"] = 0
174
+ else:
175
+ if away_odds.get('favorite'):
176
+ result["pregame_spread"] = float(spread) if spread is not None else 0
177
+ elif home_odds.get('favorite'):
178
+ result["pregame_spread"] = -float(spread) if spread is not None else 0
179
+ else:
180
+ result["pregame_spread"] = 0
181
+
182
+ result["pregame_total"] = float(total) if total is not None else 0
183
+
184
+ return result
185
+ except Exception as e:
186
+ print(f"ESPN API error: {e}")
187
+
188
+ return result
189
+
190
+ def get_all_game_data(home_team, away_team, week):
191
+ """Get complete game data: time, odds, and weather forecast."""
192
+ game_info = get_game_info_espn(home_team, away_team, week)
193
+ weather = None
194
+ if game_info["game_datetime"]:
195
+ weather = get_weather_forecast(home_team, game_info["game_datetime"])
196
+
197
+ dome_teams = ["ARI", "ATL", "DAL", "DET", "HOU", "IND", "LV", "LAR", "LAC", "MIN", "NO"]
198
+ is_dome = home_team in dome_teams
199
+
200
+ if weather:
201
+ game_data = {
202
+ "game_datetime": game_info["game_datetime"],
203
+ "pregame_spread": game_info["pregame_spread"],
204
+ "pregame_total": game_info["pregame_total"],
205
+ "temp_f": weather["temp_f"],
206
+ "humidity_pct": weather["humidity_pct"],
207
+ "wind_mph": weather["wind_mph"],
208
+ "is_dome": int(is_dome),
209
+ "is_rain": weather["is_rain"] if not is_dome else 0,
210
+ "is_snow": weather["is_snow"] if not is_dome else 0,
211
+ "is_clear": weather["is_clear"] if not is_dome else 0
212
+ }
213
+ else:
214
+ game_data = {
215
+ "game_datetime": game_info["game_datetime"],
216
+ "pregame_spread": game_info["pregame_spread"],
217
+ "pregame_total": game_info["pregame_total"],
218
+ "temp_f": 72 if is_dome else 70,
219
+ "humidity_pct": 50,
220
+ "wind_mph": 0 if is_dome else 5,
221
+ "is_dome": int(is_dome),
222
+ "is_rain": 0, "is_snow": 0,
223
+ "is_clear": 0 if is_dome else 1
224
+ }
225
+
226
+ return game_data
227
+
228
+ NFL_TEAMS = [
229
+ "ARI", "ATL", "BAL", "BUF", "CAR", "CHI", "CIN", "CLE",
230
+ "DAL", "DEN", "DET", "GB", "HOU", "IND", "JAX", "KC",
231
+ "LV", "LAC", "LAR", "MIA", "MIN", "NE", "NO", "NYG",
232
+ "NYJ", "PHI", "PIT", "SEA", "SF", "TB", "TEN", "WAS"
233
+ ]
234
+
235
+ def predict_yards(model_input_dict, receiver_id, passer_id):
236
+ """Make yards prediction using the loaded model."""
237
+ if predictor is None:
238
+ return None, "Model not loaded"
239
+
240
+ try:
241
+ input_df = pd.DataFrame([model_input_dict])
242
+
243
+ if player_embeddings is not None:
244
+ input_df = input_df.merge(
245
+ player_embeddings,
246
+ left_on="receiver_player_id",
247
+ right_on="player_id",
248
+ how="left"
249
+ ).drop(columns=["player_id"], errors="ignore")
250
+
251
+ emb_cols = [c for c in player_embeddings.columns if c.startswith("emb_")]
252
+ if input_df[emb_cols].isna().any().any():
253
+ mean_emb = player_embeddings[emb_cols].mean()
254
+ input_df[emb_cols] = input_df[emb_cols].fillna(mean_emb)
255
+
256
+ yards = None
257
+
258
+ try:
259
+ leaderboard = predictor.leaderboard(silent=True)
260
+ individual_models = leaderboard[~leaderboard['model'].str.contains('Ensemble', case=False, na=False)]
261
+
262
+ if len(individual_models) > 0:
263
+ for idx, row in individual_models.iterrows():
264
+ model_name = row['model']
265
+ try:
266
+ prediction = predictor.predict(input_df, model=model_name)
267
+ yards = float(prediction.values[0])
268
+ return yards, None
269
+ except Exception:
270
+ continue
271
+ except Exception:
272
+ pass
273
+
274
+ try:
275
+ prediction = predictor.predict(input_df)
276
+ yards = float(prediction.values[0])
277
+ return yards, None
278
+ except Exception:
279
+ pass
280
+
281
+ return None, "All prediction strategies failed."
282
+ except Exception as e:
283
+ return None, f"Prediction error: {str(e)}"
284
+
285
+ def create_model_input_and_predict(home_team, away_team, receiver_on_home_team,
286
+ receiver_name, passer_name, week, season):
287
+ """Create model input from user selections and make prediction."""
288
+ try:
289
+ receiver_team = home_team if receiver_on_home_team else away_team
290
+ opponent_team = away_team if receiver_on_home_team else home_team
291
+
292
+ def normalize_name(name):
293
+ parts = name.strip().split()
294
+ if len(parts) == 0:
295
+ return ""
296
+ if len(parts) > 1:
297
+ first_initial = parts[0][0].upper() + "."
298
+ last_name = parts[-1]
299
+ return f"{first_initial}{last_name}"
300
+ return parts[0].title()
301
+
302
+ wr_key = normalize_name(receiver_name)
303
+ qb_key = normalize_name(passer_name)
304
+
305
+ receiver_id = receiver_to_player_id.get(wr_key)
306
+ passer_id = passer_to_player_id.get(qb_key)
307
+
308
+ if receiver_id is None:
309
+ return f"Error: Could not find receiver '{wr_key}' in database"
310
+ if passer_id is None:
311
+ return f"Error: Could not find passer '{qb_key}' in database"
312
+
313
+ game_data = get_all_game_data(home_team, away_team, week)
314
+
315
+ team_map = {
316
+ "ARI": 1, "ATL": 2, "BAL": 3, "BUF": 4, "CAR": 5, "CHI": 6, "CIN": 7, "CLE": 8,
317
+ "DAL": 9, "DEN": 10, "DET": 11, "GB": 12, "HOU": 13, "IND": 14, "JAX": 15, "KC": 16,
318
+ "LV": 17, "LAC": 18, "LAR": 19, "MIA": 20, "MIN": 21, "NE": 22, "NO": 23, "NYG": 24,
319
+ "NYJ": 25, "PHI": 26, "PIT": 27, "SEA": 28, "SF": 29, "TB": 30, "TEN": 31, "WAS": 32
320
+ }
321
+
322
+ home_team_surface_map = {
323
+ "ARI": "grass", "ATL": "fieldturf", "BAL": "grass", "BUF": "fieldturf",
324
+ "CAR": "fieldturf", "CHI": "grass", "CIN": "fieldturf", "CLE": "grass",
325
+ "DAL": "fieldturf", "DEN": "grass", "DET": "fieldturf", "GB": "grass",
326
+ "HOU": "fieldturf", "IND": "fieldturf", "JAX": "grass", "KC": "grass",
327
+ "LV": "grass", "LAC": "fieldturf", "LAR": "fieldturf", "MIA": "grass",
328
+ "MIN": "fieldturf", "NE": "fieldturf", "NO": "fieldturf", "NYG": "fieldturf",
329
+ "NYJ": "fieldturf", "PHI": "grass", "PIT": "grass", "SF": "grass",
330
+ "SEA": "fieldturf", "TB": "grass", "TEN": "fieldturf", "WAS": "grass"
331
+ }
332
+
333
+ surface_map = {
334
+ "a_turf": 1, "grass": 2, "sportturf": 3,
335
+ "fieldturf": 4, "matrixturf": 5, "astroturf": 6, "0": 0
336
+ }
337
+
338
+ posteam_id = team_map.get(receiver_team, 0)
339
+ defteam_id = team_map.get(opponent_team, 0)
340
+ home_team_id = team_map.get(home_team, 0)
341
+ away_team_id = team_map.get(away_team, 0)
342
+
343
+ surface_type = home_team_surface_map.get(home_team, "grass")
344
+ surface_id = surface_map.get(surface_type, 2)
345
+
346
+ model_input = {
347
+ "receiver_player_id": receiver_id,
348
+ "passer_player_id": passer_id,
349
+ "posteam": posteam_id,
350
+ "defteam": defteam_id,
351
+ "surface": surface_id,
352
+ "is_dome": game_data.get("is_dome", 0),
353
+ "is_rain": game_data.get("is_rain", 0),
354
+ "is_snow": game_data.get("is_snow", 0),
355
+ "is_clear": game_data.get("is_clear", 1),
356
+ "temp_f": game_data.get("temp_f", 70),
357
+ "humidity_pct": game_data.get("humidity_pct", 50),
358
+ "wind_mph": game_data.get("wind_mph", 5),
359
+ "pregame_spread": game_data.get("pregame_spread", 0),
360
+ "pregame_total": game_data.get("pregame_total", 0),
361
+ "home_team": home_team_id,
362
+ "away_team": away_team_id,
363
+ }
364
+
365
+ predicted_yards, error = predict_yards(model_input, receiver_id, passer_id)
366
+
367
+ if error:
368
+ prediction_text = f"Error: {error}"
369
+ elif predicted_yards is not None:
370
+ prediction_text = f"PREDICTED YARDS: {predicted_yards:.1f}"
371
+ else:
372
+ prediction_text = "Prediction unavailable"
373
+
374
+ output = f"""
375
+ {prediction_text}
376
+
377
+ Game Information:
378
+ • Matchup: {away_team} @ {home_team} (Week {week}, {season})
379
+ • Game Time: {game_data.get('game_datetime', 'TBD - Game not scheduled yet')}
380
+ • Venue: {home_team} ({surface_type}, {'Indoor' if game_data.get('is_dome') else 'Outdoor'})
381
+
382
+ Players:
383
+ • Receiver: {receiver_name} ({wr_key}) - ID: {receiver_id}
384
+ • Passer: {passer_name} ({qb_key}) - ID: {passer_id}
385
+ • Team: {receiver_team} vs {opponent_team}
386
+
387
+ Weather Conditions:
388
+ • Temperature: {game_data.get('temp_f', 70)}°F
389
+ • Humidity: {game_data.get('humidity_pct', 50)}%
390
+ • Wind: {game_data.get('wind_mph', 5)} mph
391
+ • Conditions: {'Dome' if game_data.get('is_dome') else 'Rain' if game_data.get('is_rain') else 'Snow' if game_data.get('is_snow') else 'Clear'}
392
+
393
+ Betting Lines:
394
+ • Spread: {game_data.get('pregame_spread', 'N/A') if game_data.get('pregame_spread') != 0 else 'Not available yet'}
395
+ • Total: {game_data.get('pregame_total', 'N/A') if game_data.get('pregame_total') != 0 else 'Not available yet'}
396
+ """
397
+
398
+ return output
399
+
400
+ except Exception as e:
401
+ return f"Error: {str(e)}"
402
+
403
+ with gr.Blocks(title="NFL Receiver Yards Predictor", theme=gr.themes.Soft()) as app:
404
+ gr.Markdown("# NFL Receiver Yards Predictor")
405
+ gr.Markdown("Predict receiving yards with AI-powered analysis using weather, odds, and player data.")
406
+
407
+ with gr.Row():
408
+ with gr.Column():
409
+ home_team = gr.Dropdown(choices=NFL_TEAMS, label="Home Team", value="JAX")
410
+ away_team = gr.Dropdown(choices=NFL_TEAMS, label="Away Team", value="KC")
411
+
412
+ with gr.Column():
413
+ week = gr.Number(label="Week", value=5, precision=0)
414
+ season = gr.Number(label="Season", value=2024, precision=0)
415
+
416
+ receiver_on_home = gr.Checkbox(label="Receiver is on Home Team", value=True)
417
+
418
+ with gr.Row():
419
+ with gr.Column():
420
+ receiver_name = gr.Dropdown(choices=receiver_choices, label="Receiver Name", value="")
421
+ with gr.Column():
422
+ passer_name = gr.Dropdown(choices=passer_choices, label="Passer Name", value="")
423
+
424
+ predict_btn = gr.Button("Predict Yards", variant="primary", size="lg")
425
+
426
+ output = gr.Textbox(label="Prediction Results", lines=20, max_lines=25)
427
+
428
+ predict_btn.click(
429
+ fn=create_model_input_and_predict,
430
+ inputs=[home_team, away_team, receiver_on_home, receiver_name, passer_name, week, season],
431
+ outputs=output
432
+ )
433
+
434
+ gr.Markdown("""
435
+ ### Instructions:
436
+ 1. Select the home and away teams
437
+ 2. Enter the week number and season
438
+ 3. Check the box if the receiver plays for the home team
439
+ 4. Select the receiver and passer from the dropdown
440
+ 5. Click **"Predict Yards"** to get the prediction
441
+
442
+ The app automatically fetches weather forecast, betting lines, and stadium information.
443
+ """)
444
+
445
+ if __name__ == "__main__":
446
+ app.launch()