james-kramer commited on
Commit
ca9b74e
Β·
verified Β·
1 Parent(s): 313757c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +520 -0
app.py CHANGED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def map_teams_roof_surface(df):
2
+ # --- Fixed team mapping (stable across all datasets) ---
3
+ team_map = {
4
+ "ARI": 1, "ATL": 2, "BAL": 3, "BUF": 4,
5
+ "CAR": 5, "CHI": 6, "CIN": 7, "CLE": 8,
6
+ "DAL": 9, "DEN": 10, "DET": 11, "GB": 12,
7
+ "HOU": 13, "IND": 14, "JAX": 15, "KC": 16,
8
+ "LV": 17, "LAC": 18, "LAR": 19, "MIA": 20,
9
+ "MIN": 21, "NE": 22, "NO": 23, "NYG": 24,
10
+ "NYJ": 25, "PHI": 26, "PIT": 27, "SEA": 28,
11
+ "SF": 29, "TB": 30, "TEN": 31, "WAS": 32
12
+ }
13
+
14
+ # --- Aliases for older team abbreviations ---
15
+ alias_map = {
16
+ "OAK": "LV",
17
+ "SD": "LAC",
18
+ "STL": "LAR",
19
+ "WSH": "WAS"
20
+ }
21
+
22
+ # --- Surface mapping ---
23
+ surface_map = {
24
+ "a_turf": 1,
25
+ "grass": 2,
26
+ "sportturf": 3,
27
+ "fieldturf": 4,
28
+ "matrixturf": 5,
29
+ "astroturf": 6,
30
+ "0": 0 # unknown/missing
31
+ }
32
+
33
+
34
+ import json
35
+ try:
36
+ with open("receiver_to_player_id.json", 'r') as f:
37
+ receiver_to_player_id = json.load(f)
38
+ with open("passer_to_player_id.json", 'r') as f:
39
+ passer_to_player_id = json.load(f)
40
+ print(f"βœ“ Loaded {len(receiver_to_player_id)} receivers and {len(passer_to_player_id)} passers")
41
+ except FileNotFoundError as e:
42
+ print(f"Error: JSON files not found. Please upload them to the Space.")
43
+ receiver_to_player_id = {}
44
+ passer_to_player_id = {}
45
+
46
+ import requests
47
+ from datetime import datetime, timedelta
48
+
49
+ # Stadium coordinates
50
+ STADIUM_COORDS = {
51
+ "ARI": {"lat": 33.5276, "lon": -112.2626, "name": "State Farm Stadium"},
52
+ "ATL": {"lat": 33.7554, "lon": -84.4008, "name": "Mercedes-Benz Stadium"},
53
+ "BAL": {"lat": 39.2780, "lon": -76.6227, "name": "M&T Bank Stadium"},
54
+ "BUF": {"lat": 42.7738, "lon": -78.7870, "name": "Highmark Stadium"},
55
+ "CAR": {"lat": 35.2258, "lon": -80.8528, "name": "Bank of America Stadium"},
56
+ "CHI": {"lat": 41.8623, "lon": -87.6167, "name": "Soldier Field"},
57
+ "CIN": {"lat": 39.0954, "lon": -84.5160, "name": "Paycor Stadium"},
58
+ "CLE": {"lat": 41.5061, "lon": -81.6995, "name": "FirstEnergy Stadium"},
59
+ "DAL": {"lat": 32.7473, "lon": -97.0945, "name": "AT&T Stadium"},
60
+ "DEN": {"lat": 39.7439, "lon": -105.0201, "name": "Empower Field at Mile High"},
61
+ "DET": {"lat": 42.3400, "lon": -83.0456, "name": "Ford Field"},
62
+ "GB": {"lat": 44.5013, "lon": -88.0622, "name": "Lambeau Field"},
63
+ "HOU": {"lat": 29.6847, "lon": -95.4107, "name": "NRG Stadium"},
64
+ "IND": {"lat": 39.7601, "lon": -86.1639, "name": "Lucas Oil Stadium"},
65
+ "JAX": {"lat": 30.3239, "lon": -81.6373, "name": "EverBank Stadium"},
66
+ "KC": {"lat": 39.0489, "lon": -94.4839, "name": "Arrowhead Stadium"},
67
+ "LV": {"lat": 36.0908, "lon": -115.1833, "name": "Allegiant Stadium"},
68
+ "LAC": {"lat": 33.9535, "lon": -118.3390, "name": "SoFi Stadium"},
69
+ "LAR": {"lat": 33.9535, "lon": -118.3390, "name": "SoFi Stadium"},
70
+ "MIA": {"lat": 25.9580, "lon": -80.2389, "name": "Hard Rock Stadium"},
71
+ "MIN": {"lat": 44.9738, "lon": -93.2577, "name": "U.S. Bank Stadium"},
72
+ "NE": {"lat": 42.0909, "lon": -71.2643, "name": "Gillette Stadium"},
73
+ "NO": {"lat": 29.9511, "lon": -90.0812, "name": "Caesars Superdome"},
74
+ "NYG": {"lat": 40.8128, "lon": -74.0742, "name": "MetLife Stadium"},
75
+ "NYJ": {"lat": 40.8128, "lon": -74.0742, "name": "MetLife Stadium"},
76
+ "PHI": {"lat": 39.9008, "lon": -75.1675, "name": "Lincoln Financial Field"},
77
+ "PIT": {"lat": 40.4468, "lon": -80.0158, "name": "Acrisure Stadium"},
78
+ "SF": {"lat": 37.4032, "lon": -121.9698, "name": "Levi's Stadium"},
79
+ "SEA": {"lat": 47.5952, "lon": -122.3316, "name": "Lumen Field"},
80
+ "TB": {"lat": 27.9759, "lon": -82.5033, "name": "Raymond James Stadium"},
81
+ "TEN": {"lat": 36.1665, "lon": -86.7713, "name": "Nissan Stadium"},
82
+ "WAS": {"lat": 38.9076, "lon": -76.8645, "name": "FedEx Field"}
83
+ }
84
+
85
+ def get_weather_forecast(home_team, game_datetime):
86
+ """
87
+ Get weather forecast for a stadium at game time.
88
+ Uses Open-Meteo API (free, no API key needed)
89
+ """
90
+ coords = STADIUM_COORDS.get(home_team)
91
+ if not coords:
92
+ return None
93
+
94
+ # Open-Meteo API (free, no key required)
95
+ url = "https://api.open-meteo.com/v1/forecast"
96
+ params = {
97
+ "latitude": coords["lat"],
98
+ "longitude": coords["lon"],
99
+ "hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code",
100
+ "temperature_unit": "fahrenheit",
101
+ "wind_speed_unit": "mph",
102
+ "timezone": "America/New_York"
103
+ }
104
+
105
+ try:
106
+ response = requests.get(url, params=params, timeout=10)
107
+ response.raise_for_status()
108
+ data = response.json()
109
+
110
+ # Find the closest time to game time
111
+ hourly = data.get("hourly", {})
112
+ times = hourly.get("time", [])
113
+
114
+ # Find index closest to game time
115
+ game_time_str = game_datetime.strftime("%Y-%m-%dT%H:%M")
116
+ closest_idx = 0
117
+ for i, time_str in enumerate(times):
118
+ if time_str >= game_time_str:
119
+ closest_idx = i
120
+ break
121
+
122
+ temp = hourly["temperature_2m"][closest_idx]
123
+ humidity = hourly["relative_humidity_2m"][closest_idx]
124
+ wind = hourly["wind_speed_10m"][closest_idx]
125
+ weather_code = hourly["weather_code"][closest_idx]
126
+
127
+ # Interpret weather codes
128
+ # https://open-meteo.com/en/docs
129
+ is_rain = weather_code in [51, 53, 55, 61, 63, 65, 80, 81, 82]
130
+ is_snow = weather_code in [71, 73, 75, 77, 85, 86]
131
+ is_clear = weather_code in [0, 1, 2]
132
+
133
+ return {
134
+ "temp_f": temp,
135
+ "humidity_pct": humidity,
136
+ "wind_mph": wind,
137
+ "is_rain": int(is_rain),
138
+ "is_snow": int(is_snow),
139
+ "is_clear": int(is_clear),
140
+ "weather_code": weather_code
141
+ }
142
+
143
+ except Exception as e:
144
+ print(f"Weather API error: {e}")
145
+ return None
146
+
147
+ # Example usage:
148
+ game_time = datetime(2025, 10, 13, 13, 0) # Sunday 1pm ET
149
+ weather = get_weather_forecast("ARI", game_time)
150
+ print(weather)
151
+
152
+ import requests
153
+ from datetime import datetime
154
+
155
+ def get_game_info_espn(home_team, away_team, week):
156
+ """
157
+ Get game time, spread, and total from ESPN API.
158
+ Free, no API key required.
159
+
160
+ Args:
161
+ home_team: Home team abbreviation
162
+ away_team: Away team abbreviation
163
+ week: NFL week number
164
+
165
+ Returns:
166
+ dict with game_datetime, pregame_spread, pregame_total
167
+ """
168
+
169
+ result = {
170
+ "game_datetime": None,
171
+ "pregame_spread": 0,
172
+ "pregame_total": 0
173
+ }
174
+
175
+ try:
176
+ url = f"https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard?seasontype=2&week={week}"
177
+ response = requests.get(url, timeout=10)
178
+ response.raise_for_status()
179
+ data = response.json()
180
+
181
+ for event in data.get('events', []):
182
+ competition = event.get('competitions', [{}])[0]
183
+ competitors = competition.get('competitors', [])
184
+
185
+ if len(competitors) < 2:
186
+ continue
187
+
188
+ # ESPN puts home team first
189
+ h_team = competitors[0]['team']['abbreviation']
190
+ a_team = competitors[1]['team']['abbreviation']
191
+
192
+ if h_team == home_team and a_team == away_team:
193
+ # Get game time
194
+ game_date_str = event.get('date')
195
+ if game_date_str:
196
+ result["game_datetime"] = datetime.fromisoformat(game_date_str.replace('Z', '+00:00'))
197
+
198
+ # Get odds from competition
199
+ odds_data = competition.get('odds', [])
200
+ if odds_data and len(odds_data) > 0:
201
+ spread = odds_data[0].get('spread')
202
+ total = odds_data[0].get('overUnder')
203
+
204
+ # ESPN's spread is from home team perspective
205
+ result["pregame_spread"] = float(spread) if spread is not None else 0
206
+ result["pregame_total"] = float(total) if total is not None else 0
207
+
208
+ print(f"βœ“ Found game info from ESPN")
209
+ print(f" {a_team} @ {h_team}")
210
+ print(f" Game time: {result['game_datetime']}")
211
+ print(f" Spread: {result['pregame_spread']}, Total: {result['pregame_total']}")
212
+ return result
213
+
214
+ print(f"⚠ Game not found: {away_team} @ {home_team} (Week {week})")
215
+
216
+ except Exception as e:
217
+ print(f"⚠ ESPN API error: {e}")
218
+
219
+ return result
220
+
221
+
222
+ def get_all_game_data(home_team, away_team, week):
223
+ """
224
+ Get complete game data: time, odds, and weather forecast.
225
+ Uses ESPN for odds/time and weather API for forecast.
226
+ Completely free, no API keys needed.
227
+
228
+ Args:
229
+ home_team: Home team abbreviation
230
+ away_team: Away team abbreviation
231
+ week: NFL week number
232
+
233
+ Returns:
234
+ Complete game data dictionary
235
+ """
236
+
237
+ # Get game time and odds from ESPN
238
+ game_info = get_game_info_espn(home_team, away_team, week)
239
+
240
+ # Get weather forecast if we have game time
241
+ weather = None
242
+ if game_info["game_datetime"]:
243
+ weather = get_weather_forecast(home_team, game_info["game_datetime"])
244
+
245
+ # Check if dome
246
+ dome_teams = ["ARI", "ATL", "DAL", "DET", "HOU", "IND", "LV", "LAR", "LAC", "MIN", "NO"]
247
+ is_dome = home_team in dome_teams
248
+
249
+ # Combine all data
250
+ if weather:
251
+ game_data = {
252
+ "game_datetime": game_info["game_datetime"],
253
+ "pregame_spread": game_info["pregame_spread"],
254
+ "pregame_total": game_info["pregame_total"],
255
+ "temp_f": weather["temp_f"],
256
+ "humidity_pct": weather["humidity_pct"],
257
+ "wind_mph": weather["wind_mph"],
258
+ "is_dome": int(is_dome),
259
+ "is_rain": weather["is_rain"] if not is_dome else 0,
260
+ "is_snow": weather["is_snow"] if not is_dome else 0,
261
+ "is_clear": weather["is_clear"] if not is_dome else 0
262
+ }
263
+ else:
264
+ # Defaults if no weather available
265
+ game_data = {
266
+ "game_datetime": game_info["game_datetime"],
267
+ "pregame_spread": game_info["pregame_spread"],
268
+ "pregame_total": game_info["pregame_total"],
269
+ "temp_f": 72 if is_dome else 70,
270
+ "humidity_pct": 50,
271
+ "wind_mph": 0 if is_dome else 5,
272
+ "is_dome": int(is_dome),
273
+ "is_rain": 0,
274
+ "is_snow": 0,
275
+ "is_clear": 0 if is_dome else 1
276
+ }
277
+
278
+ print(f"\nβœ“ Complete game data assembled")
279
+ return game_data
280
+
281
+
282
+ # Example usage - no API key needed!
283
+ game_data = get_all_game_data(
284
+ home_team="JAX",
285
+ away_team="KC",
286
+ week=5
287
+ )
288
+
289
+ print("\nFinal game data:")
290
+ for key, value in game_data.items():
291
+ print(f" {key}: {value}")
292
+
293
+ NFL_TEAMS = [
294
+ "ARI", "ATL", "BAL", "BUF", "CAR", "CHI", "CIN", "CLE",
295
+ "DAL", "DEN", "DET", "GB", "HOU", "IND", "JAX", "KC",
296
+ "LV", "LAC", "LAR", "MIA", "MIN", "NE", "NO", "NYG",
297
+ "NYJ", "PHI", "PIT", "SEA", "SF", "TB", "TEN", "WAS"
298
+ ]
299
+
300
+ def create_model_input(home_team, away_team, receiver_on_home_team,
301
+ receiver_name, passer_name, week, season):
302
+ """
303
+ Create model input from user selections.
304
+ """
305
+ try:
306
+ # Determine which team the receiver is on
307
+ receiver_team = home_team if receiver_on_home_team else away_team
308
+ opponent_team = away_team if receiver_on_home_team else home_team
309
+
310
+ # Normalize player names
311
+ def normalize_name(name):
312
+ parts = name.strip().split()
313
+ if len(parts) == 0:
314
+ return ""
315
+ if len(parts) > 1:
316
+ first_initial = parts[0][0].upper() + "."
317
+ last_name = parts[-1]
318
+ return f"{first_initial}{last_name}"
319
+ return parts[0].title()
320
+
321
+ wr_key = normalize_name(receiver_name)
322
+ qb_key = normalize_name(passer_name)
323
+
324
+ # Get player IDs
325
+ receiver_id = receiver_to_player_id.get(wr_key)
326
+ passer_id = passer_to_player_id.get(qb_key)
327
+
328
+ if receiver_id is None:
329
+ return f"Error: Could not find receiver '{wr_key}' in database"
330
+ if passer_id is None:
331
+ return f"Error: Could not find passer '{qb_key}' in database"
332
+
333
+ # Get game data (weather, odds, time)
334
+ game_data = get_all_game_data(home_team, away_team, week)
335
+
336
+ if not game_data.get("game_datetime"):
337
+ return f"Warning: Game not found for Week {week}. Using default values."
338
+
339
+ # Team mappings
340
+ team_map = {
341
+ "ARI": 1, "ATL": 2, "BAL": 3, "BUF": 4,
342
+ "CAR": 5, "CHI": 6, "CIN": 7, "CLE": 8,
343
+ "DAL": 9, "DEN": 10, "DET": 11, "GB": 12,
344
+ "HOU": 13, "IND": 14, "JAX": 15, "KC": 16,
345
+ "LV": 17, "LAC": 18, "LAR": 19, "MIA": 20,
346
+ "MIN": 21, "NE": 22, "NO": 23, "NYG": 24,
347
+ "NYJ": 25, "PHI": 26, "PIT": 27, "SEA": 28,
348
+ "SF": 29, "TB": 30, "TEN": 31, "WAS": 32
349
+ }
350
+
351
+ # Surface mapping
352
+ home_team_surface_map = {
353
+ "ARI": "grass", "ATL": "fieldturf", "BAL": "grass", "BUF": "fieldturf",
354
+ "CAR": "fieldturf", "CHI": "grass", "CIN": "fieldturf", "CLE": "grass",
355
+ "DAL": "fieldturf", "DEN": "grass", "DET": "fieldturf", "GB": "grass",
356
+ "HOU": "fieldturf", "IND": "fieldturf", "JAX": "grass", "KC": "grass",
357
+ "LV": "grass", "LAC": "fieldturf", "LAR": "fieldturf", "MIA": "grass",
358
+ "MIN": "fieldturf", "NE": "fieldturf", "NO": "fieldturf", "NYG": "fieldturf",
359
+ "NYJ": "fieldturf", "PHI": "grass", "PIT": "grass", "SF": "grass",
360
+ "SEA": "fieldturf", "TB": "grass", "TEN": "fieldturf", "WAS": "grass"
361
+ }
362
+
363
+ surface_map = {
364
+ "a_turf": 1, "grass": 2, "sportturf": 3,
365
+ "fieldturf": 4, "matrixturf": 5, "astroturf": 6, "0": 0
366
+ }
367
+
368
+ # Get IDs
369
+ posteam_id = team_map.get(receiver_team, 0)
370
+ defteam_id = team_map.get(opponent_team, 0)
371
+ home_team_id = team_map.get(home_team, 0)
372
+ away_team_id = team_map.get(away_team, 0)
373
+
374
+ # Get surface
375
+ surface_type = home_team_surface_map.get(home_team, "grass")
376
+ surface_id = surface_map.get(surface_type, 2)
377
+
378
+ # Create model input
379
+ model_input = {
380
+ "receiver_player_id": receiver_id,
381
+ "passer_player_id": passer_id,
382
+ "posteam": posteam_id,
383
+ "defteam": defteam_id,
384
+ "surface": surface_id,
385
+ "is_dome": game_data.get("is_dome", 0),
386
+ "is_rain": game_data.get("is_rain", 0),
387
+ "is_snow": game_data.get("is_snow", 0),
388
+ "is_clear": game_data.get("is_clear", 1),
389
+ "temp_f": game_data.get("temp_f", 70),
390
+ "humidity_pct": game_data.get("humidity_pct", 50),
391
+ "wind_mph": game_data.get("wind_mph", 5),
392
+ "pregame_spread": game_data.get("pregame_spread", 0),
393
+ "pregame_total": game_data.get("pregame_total", 0),
394
+ "home_team": home_team_id,
395
+ "away_team": away_team_id,
396
+ }
397
+
398
+ # Format output for display
399
+ output = f"""
400
+ 🏈 Model Input Created Successfully!
401
+
402
+ πŸ“‹ Game Information:
403
+ β€’ Matchup: {away_team} @ {home_team} (Week {week}, {season})
404
+ β€’ Game Time: {game_data.get('game_datetime', 'Unknown')}
405
+ β€’ Venue: {home_team} ({surface_type}, {'Indoor' if game_data.get('is_dome') else 'Outdoor'})
406
+
407
+ πŸ‘€ Players:
408
+ β€’ Receiver: {receiver_name} ({wr_key}) - ID: {receiver_id}
409
+ β€’ Passer: {passer_name} ({qb_key}) - ID: {passer_id}
410
+ β€’ Team: {receiver_team} vs {opponent_team}
411
+
412
+ 🌀️ Weather Conditions:
413
+ β€’ Temperature: {game_data.get('temp_f', 70)}Β°F
414
+ β€’ Humidity: {game_data.get('humidity_pct', 50)}%
415
+ β€’ Wind: {game_data.get('wind_mph', 5)} mph
416
+ β€’ 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'}
417
+
418
+ πŸ’° Betting Lines:
419
+ β€’ Spread: {game_data.get('pregame_spread', 0)}
420
+ β€’ Total: {game_data.get('pregame_total', 0)}
421
+
422
+ πŸ“Š Model Input Dictionary:
423
+ {model_input}
424
+ """
425
+
426
+ return output, model_input
427
+
428
+ except Exception as e:
429
+ return f"Error: {str(e)}", None
430
+
431
+
432
+ # Create Gradio interface
433
+ def gradio_interface(home_team, away_team, receiver_on_home, receiver_name, passer_name, week, season):
434
+ output_text, model_input = create_model_input(
435
+ home_team, away_team, receiver_on_home,
436
+ receiver_name, passer_name, week, season
437
+ )
438
+ return output_text
439
+
440
+ with gr.Blocks(title="NFL Receiver Prediction Input Generator") as app:
441
+ gr.Markdown("# 🏈 NFL Receiver Prediction Input Generator")
442
+ gr.Markdown("Generate model inputs for NFL receiver predictions with automatic weather and odds data.")
443
+
444
+ with gr.Row():
445
+ with gr.Column():
446
+ home_team = gr.Dropdown(
447
+ choices=NFL_TEAMS,
448
+ label="Home Team",
449
+ value="KC"
450
+ )
451
+ away_team = gr.Dropdown(
452
+ choices=NFL_TEAMS,
453
+ label="Away Team",
454
+ value="NO"
455
+ )
456
+
457
+ with gr.Column():
458
+ week = gr.Number(
459
+ label="Week",
460
+ value=6,
461
+ precision=0
462
+ )
463
+ season = gr.Number(
464
+ label="Season",
465
+ value=2025,
466
+ precision=0
467
+ )
468
+
469
+ with gr.Row():
470
+ receiver_on_home = gr.Checkbox(
471
+ label="Receiver is on Home Team",
472
+ value=True
473
+ )
474
+
475
+ with gr.Row():
476
+ with gr.Column():
477
+ receiver_name = gr.Textbox(
478
+ label="Receiver Name",
479
+ placeholder="e.g., Travis Kelce",
480
+ value=""
481
+ )
482
+ with gr.Column():
483
+ passer_name = gr.Textbox(
484
+ label="Passer Name",
485
+ placeholder="e.g., Patrick Mahomes",
486
+ value=""
487
+ )
488
+
489
+ generate_btn = gr.Button("Generate Model Input", variant="primary")
490
+
491
+ output = gr.Textbox(
492
+ label="Model Input",
493
+ lines=25,
494
+ max_lines=30
495
+ )
496
+
497
+ generate_btn.click(
498
+ fn=gradio_interface,
499
+ inputs=[home_team, away_team, receiver_on_home, receiver_name, passer_name, week, season],
500
+ outputs=output
501
+ )
502
+
503
+ gr.Markdown("""
504
+ ### Instructions:
505
+ 1. Select the home and away teams
506
+ 2. Enter the week number and season
507
+ 3. Check the box if the receiver plays for the home team
508
+ 4. Enter the receiver and passer names (First Last format)
509
+ 5. Click "Generate Model Input"
510
+
511
+ The app will automatically fetch:
512
+ - Weather forecast for the game
513
+ - Betting lines (spread and total)
514
+ - Stadium surface information
515
+ - Player IDs from your database
516
+ """)
517
+
518
+ # Launch the app
519
+ if __name__ == "__main__":
520
+ app.launch()