Seewhatson commited on
Commit
d10c86f
·
verified ·
1 Parent(s): ec66f79

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -82
app.py CHANGED
@@ -2,129 +2,109 @@ import gradio as gr
2
  import pandas as pd
3
  import requests
4
  import joblib
5
- import xgboost # Ensure this is the version your xgb_model.joblib was trained with
 
 
6
 
7
  # --- Model Loading ---
8
  try:
9
  model = joblib.load('xgb_model.joblib')
10
  print("Model loaded successfully.")
11
  except FileNotFoundError:
12
- print("ERROR: Model file 'xgb_model.joblib' not found. The application will not be able to make predictions.")
13
  model = None
14
  except Exception as e:
15
  print(f"Error loading model: {e}")
16
  model = None
17
 
18
- # --- Helper Function for FDR ---
 
19
  def get_next_fdr(team_id, fixtures_df):
20
  """
21
  Finds the next fixture difficulty rating (FDR) for a given team.
22
  """
23
- # Filter for upcoming fixtures for the given team
24
  upcoming = fixtures_df[
25
  ((fixtures_df["team_h"] == team_id) | (fixtures_df["team_a"] == team_id)) &
26
  (fixtures_df["finished"] == False)
27
  ]
28
-
29
  if not upcoming.empty:
30
- # Drop rows where 'event' (gameweek) is NaN before sorting
31
  upcoming = upcoming.dropna(subset=['event'])
32
- if upcoming.empty: # Check again if all upcoming fixtures had NaN events
33
  return None
34
-
35
- # Sort by event (gameweek) to find the very next one
36
  next_fixture = upcoming.sort_values("event").iloc[0]
37
-
38
- # Determine if the team is home or away for the next fixture and return corresponding FDR
39
  if next_fixture["team_h"] == team_id:
40
  return next_fixture["team_h_difficulty"]
41
  else:
42
  return next_fixture["team_a_difficulty"]
43
  else:
44
- return None # No upcoming fixtures found
45
 
46
- # --- Main Prediction Function ---
 
47
  def predict_fpl_points(search_name=""):
48
  """
49
  Fetches FPL player data, calculates their next FDR,
50
  and predicts their FPL points for the next gameweek.
51
  """
52
  if model is None:
53
- # This error will be displayed in the Gradio UI if model loading failed
54
  return pd.DataFrame({"Error": ["Model could not be loaded. Please check server logs."]})
55
 
56
  print(f"Fetching latest FPL data... Search term: '{search_name}'")
57
  try:
58
- # Fetching general player data
59
  players_url = "https://fantasy.premierleague.com/api/bootstrap-static/"
60
  response = requests.get(players_url, timeout=20)
61
- response.raise_for_status() # Raise an HTTPError for bad responses (4XX or 5XX)
62
  players_data = response.json()
63
 
64
  players = pd.DataFrame(players_data["elements"])[
65
  ["id", "web_name", "team", "form", "element_type"]
66
  ]
67
- # Ensure element_type is numeric for isin check, if it's not already
68
  players["element_type"] = pd.to_numeric(players["element_type"], errors='coerce')
69
- players = players[players["element_type"].isin([1, 2, 3, 4])] # Assuming 1:GK, 2:DEF, 3:MID, 4:FWD
70
- players["team"] = players["team"].astype(int) # Team IDs are integers
71
 
72
- # Fetching fixtures data
73
  fixtures_url = "https://fantasy.premierleague.com/api/fixtures/"
74
  fixtures_response = requests.get(fixtures_url, timeout=20)
75
  fixtures_response.raise_for_status()
76
  fixtures_data = fixtures_response.json()
77
  fixtures = pd.DataFrame(fixtures_data)
78
 
79
- # Ensure relevant fixture columns are numeric, coercing errors to NaN
80
  cols_to_numeric = ["team_h", "team_a", "team_h_difficulty", "team_a_difficulty", "event"]
81
  for col in cols_to_numeric:
82
  if col in fixtures.columns:
83
  fixtures[col] = pd.to_numeric(fixtures[col], errors='coerce')
84
- else:
85
- # Handle missing essential columns if necessary, though FPL API is usually consistent
86
- print(f"Warning: Fixture column '{col}' not found in API response.")
87
- # Depending on severity, you might return an error DataFrame here
88
 
89
  print("FPL data fetched successfully.")
90
 
91
  except requests.exceptions.RequestException as e:
92
  print(f"Error fetching data from FPL API: {e}")
93
  return pd.DataFrame({"Error": [f"Failed to fetch FPL data: {e}. Please try again later."]})
94
- except Exception as e: # Catch other potential errors during data fetching (e.g., JSON parsing)
95
  print(f"An unexpected error occurred during data fetching: {e}")
96
  return pd.DataFrame({"Error": [f"An unexpected error occurred during data fetching: {e}"]})
97
 
98
  print("Calculating next FDR and preparing data for prediction...")
99
  try:
100
  players_with_fdr = players.copy()
101
- # Apply the get_next_fdr function to each player
102
  players_with_fdr["next_fdr"] = players_with_fdr["team"].apply(
103
  lambda team_id: get_next_fdr(team_id, fixtures)
104
  )
105
 
106
- # Clean data: drop players with missing FDR or form, ensure types are correct
107
  players_clean = players_with_fdr.dropna(subset=["next_fdr", "form"])
108
  players_clean["form"] = pd.to_numeric(players_clean["form"], errors='coerce')
109
  players_clean["next_fdr"] = pd.to_numeric(players_clean["next_fdr"], errors='coerce')
110
- # Drop rows where form or next_fdr could not be converted to numeric
111
  players_clean = players_clean.dropna(subset=["form", "next_fdr"])
112
 
113
  if players_clean.empty:
114
  print("No players found after cleaning (missing form or next FDR).")
115
- # Return a DataFrame with a message, ensuring columns match output expectations for consistency
116
- return pd.DataFrame({"Message": ["No eligible players found after data cleaning (missing form or next FDR)."]})
117
 
118
  print(f"Predicting points for {len(players_clean)} players...")
119
- # Define features used by the model
120
  features = ["form", "next_fdr"]
121
  X_next = players_clean[features]
122
-
123
- # Make predictions
124
  players_clean.loc[:, "predicted_points"] = model.predict(X_next)
125
  print("Prediction complete.")
126
 
127
- # Prepare output DataFrame
128
  output_df_base = players_clean[["web_name", "predicted_points", "form", "next_fdr"]].copy()
129
  output_df_base.rename(columns={
130
  "web_name": "Player",
@@ -132,79 +112,154 @@ def predict_fpl_points(search_name=""):
132
  "form": "Form",
133
  "next_fdr": "Next FDR (Lower is easier)"
134
  }, inplace=True)
135
- # Round predicted points for better readability
136
  output_df_base.loc[:, "Predicted Points"] = output_df_base["Predicted Points"].round(2)
137
 
138
- # Handle search functionality
139
  if search_name and search_name.strip():
140
  print(f"Filtering for player name containing: '{search_name}'")
141
- # Case-insensitive search
142
  search_results = output_df_base[
143
  output_df_base['Player'].str.contains(search_name.strip(), case=False, na=False)
144
  ]
145
-
146
  if not search_results.empty:
147
- search_results = search_results.sort_values("Predicted Points", ascending=False)
148
- return search_results
149
  else:
150
- # No player found matching search term
151
- return pd.DataFrame({"Message": [f"No player found matching '{search_name}'. Try a different name or leave blank for top players."]})
152
  else:
153
- # No search term, return top N players
154
  print("No search term provided, returning top 20 predicted players.")
155
- top_players = output_df_base.sort_values("Predicted Points", ascending=False).head(20)
156
- return top_players
157
 
158
  except KeyError as e:
159
- print(f"KeyError during data processing: {e}. This might indicate an unexpected API data structure or a typo in column names.")
160
  import traceback
161
  traceback.print_exc()
162
- return pd.DataFrame({"Error": [f"Data processing error (Missing Key): {e}. Check FPL API structure."]})
163
  except Exception as e:
164
  print(f"An error occurred during prediction/processing: {e}")
165
  import traceback
166
  traceback.print_exc()
167
  return pd.DataFrame({"Error": [f"Prediction or data processing failed: {e}"]})
168
 
169
- # --- Gradio Interface Definition ---
170
- iface = gr.Interface(
171
- fn=predict_fpl_points,
172
- inputs=gr.Textbox(
173
- label="Search for Player (Optional)",
174
- placeholder="E.g., Salah, Haaland, Saka... Leave blank for Top 20",
175
- mcp_label="player_search_input" # MCP label for this input component
176
- ),
177
- outputs=gr.DataFrame(
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  label="Predicted Player Data (Next Gameweek)",
179
  wrap=True,
180
- mcp_label="prediction_output_dataframe" # MCP label for this output component
181
- ),
182
- title="Fantasy Premier League Player Point Predictor (MCP Enabled)",
183
- description="Predicts FPL points for the *next* gameweek based on current form and upcoming fixture difficulty (FDR). "
184
- "Enter a player name to search, or leave blank to see the top 20 predicted players. Fetches live data. "
185
- "MCP server enabled for model card data collection.",
186
- allow_flagging='never', # Flagging not used for MCP data collection here
187
- theme=gr.themes.Soft(),
188
- examples=[[""], ["Son"], ["Watkins"], ["Palmer"]] # Examples for user convenience
189
- )
 
 
 
 
 
 
 
190
 
191
  # --- Application Launch ---
192
  if __name__ == "__main__":
193
- print("Attempting to launch Gradio app with MCP server...")
194
- # CRITICAL: For mcp_server=True to work, 'gradio[mcp]' must be correctly
195
- # installed in your Hugging Face Space environment via requirements.txt.
196
- # If you still get 'unexpected keyword argument mcp_server',
197
- # it means the MCP extensions for Gradio are not active.
198
- # Double-check requirements.txt and ensure your Space has rebuilt.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  try:
200
- iface.launch(mcp_server=True)
 
201
  except TypeError as e:
202
- if 'mcp_server' in str(e):
203
- print("\nERROR: Failed to launch with mcp_server=True. ")
204
- print("This usually means the 'gradio[mcp]' extras are not installed correctly.")
205
- print("Please ensure 'gradio[mcp]' is in your requirements.txt and your Hugging Face Space has been rebuilt.\n")
206
- # Fallback: Launch without mcp_server if it fails, so the app still runs
207
- print("Attempting to launch without MCP server as a fallback...")
208
- iface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  else:
210
- raise e # Re-raise other TypeErrors
 
 
 
 
 
2
  import pandas as pd
3
  import requests
4
  import joblib
5
+ import xgboost
6
+
7
+ print(f"Gradio version at script start: {gr.__version__}") # For debugging
8
 
9
  # --- Model Loading ---
10
  try:
11
  model = joblib.load('xgb_model.joblib')
12
  print("Model loaded successfully.")
13
  except FileNotFoundError:
14
+ print("ERROR: Model file 'xgb_model.joblib' not found. Predictions will fail.")
15
  model = None
16
  except Exception as e:
17
  print(f"Error loading model: {e}")
18
  model = None
19
 
20
+ # --- Helper Function for FDR (get_next_fdr) ---
21
+ # (Your get_next_fdr function code remains the same here)
22
  def get_next_fdr(team_id, fixtures_df):
23
  """
24
  Finds the next fixture difficulty rating (FDR) for a given team.
25
  """
 
26
  upcoming = fixtures_df[
27
  ((fixtures_df["team_h"] == team_id) | (fixtures_df["team_a"] == team_id)) &
28
  (fixtures_df["finished"] == False)
29
  ]
 
30
  if not upcoming.empty:
 
31
  upcoming = upcoming.dropna(subset=['event'])
32
+ if upcoming.empty:
33
  return None
 
 
34
  next_fixture = upcoming.sort_values("event").iloc[0]
 
 
35
  if next_fixture["team_h"] == team_id:
36
  return next_fixture["team_h_difficulty"]
37
  else:
38
  return next_fixture["team_a_difficulty"]
39
  else:
40
+ return None
41
 
42
+ # --- Main Prediction Function (predict_fpl_points) ---
43
+ # (Your predict_fpl_points function code remains the same here)
44
  def predict_fpl_points(search_name=""):
45
  """
46
  Fetches FPL player data, calculates their next FDR,
47
  and predicts their FPL points for the next gameweek.
48
  """
49
  if model is None:
 
50
  return pd.DataFrame({"Error": ["Model could not be loaded. Please check server logs."]})
51
 
52
  print(f"Fetching latest FPL data... Search term: '{search_name}'")
53
  try:
 
54
  players_url = "https://fantasy.premierleague.com/api/bootstrap-static/"
55
  response = requests.get(players_url, timeout=20)
56
+ response.raise_for_status()
57
  players_data = response.json()
58
 
59
  players = pd.DataFrame(players_data["elements"])[
60
  ["id", "web_name", "team", "form", "element_type"]
61
  ]
 
62
  players["element_type"] = pd.to_numeric(players["element_type"], errors='coerce')
63
+ players = players[players["element_type"].isin([1, 2, 3, 4])]
64
+ players["team"] = players["team"].astype(int)
65
 
 
66
  fixtures_url = "https://fantasy.premierleague.com/api/fixtures/"
67
  fixtures_response = requests.get(fixtures_url, timeout=20)
68
  fixtures_response.raise_for_status()
69
  fixtures_data = fixtures_response.json()
70
  fixtures = pd.DataFrame(fixtures_data)
71
 
 
72
  cols_to_numeric = ["team_h", "team_a", "team_h_difficulty", "team_a_difficulty", "event"]
73
  for col in cols_to_numeric:
74
  if col in fixtures.columns:
75
  fixtures[col] = pd.to_numeric(fixtures[col], errors='coerce')
 
 
 
 
76
 
77
  print("FPL data fetched successfully.")
78
 
79
  except requests.exceptions.RequestException as e:
80
  print(f"Error fetching data from FPL API: {e}")
81
  return pd.DataFrame({"Error": [f"Failed to fetch FPL data: {e}. Please try again later."]})
82
+ except Exception as e:
83
  print(f"An unexpected error occurred during data fetching: {e}")
84
  return pd.DataFrame({"Error": [f"An unexpected error occurred during data fetching: {e}"]})
85
 
86
  print("Calculating next FDR and preparing data for prediction...")
87
  try:
88
  players_with_fdr = players.copy()
 
89
  players_with_fdr["next_fdr"] = players_with_fdr["team"].apply(
90
  lambda team_id: get_next_fdr(team_id, fixtures)
91
  )
92
 
 
93
  players_clean = players_with_fdr.dropna(subset=["next_fdr", "form"])
94
  players_clean["form"] = pd.to_numeric(players_clean["form"], errors='coerce')
95
  players_clean["next_fdr"] = pd.to_numeric(players_clean["next_fdr"], errors='coerce')
 
96
  players_clean = players_clean.dropna(subset=["form", "next_fdr"])
97
 
98
  if players_clean.empty:
99
  print("No players found after cleaning (missing form or next FDR).")
100
+ return pd.DataFrame({"Message": ["No eligible players found after data cleaning."]})
 
101
 
102
  print(f"Predicting points for {len(players_clean)} players...")
 
103
  features = ["form", "next_fdr"]
104
  X_next = players_clean[features]
 
 
105
  players_clean.loc[:, "predicted_points"] = model.predict(X_next)
106
  print("Prediction complete.")
107
 
 
108
  output_df_base = players_clean[["web_name", "predicted_points", "form", "next_fdr"]].copy()
109
  output_df_base.rename(columns={
110
  "web_name": "Player",
 
112
  "form": "Form",
113
  "next_fdr": "Next FDR (Lower is easier)"
114
  }, inplace=True)
 
115
  output_df_base.loc[:, "Predicted Points"] = output_df_base["Predicted Points"].round(2)
116
 
 
117
  if search_name and search_name.strip():
118
  print(f"Filtering for player name containing: '{search_name}'")
 
119
  search_results = output_df_base[
120
  output_df_base['Player'].str.contains(search_name.strip(), case=False, na=False)
121
  ]
 
122
  if not search_results.empty:
123
+ return search_results.sort_values("Predicted Points", ascending=False)
 
124
  else:
125
+ return pd.DataFrame({"Message": [f"No player found matching '{search_name}'."]})
 
126
  else:
 
127
  print("No search term provided, returning top 20 predicted players.")
128
+ return output_df_base.sort_values("Predicted Points", ascending=False).head(20)
 
129
 
130
  except KeyError as e:
131
+ print(f"KeyError during data processing: {e}.")
132
  import traceback
133
  traceback.print_exc()
134
+ return pd.DataFrame({"Error": [f"Data processing error (Missing Key): {e}."]})
135
  except Exception as e:
136
  print(f"An error occurred during prediction/processing: {e}")
137
  import traceback
138
  traceback.print_exc()
139
  return pd.DataFrame({"Error": [f"Prediction or data processing failed: {e}"]})
140
 
141
+
142
+ # --- Gradio UI Definition using gr.Blocks ---
143
+ with gr.Blocks(theme=gr.themes.Soft(), title="Fantasy Premier League Player Point Predictor") as demo:
144
+ gr.Markdown(
145
+ """
146
+ # Fantasy Premier League Player Point Predictor
147
+ Predicts FPL points for the *next* gameweek based on current form and upcoming fixture difficulty (FDR).
148
+ Enter a player name to search, or leave blank to see the top 20 predicted players. Fetches live data.
149
+ """
150
+ ) # You can add "(MCP Enabled)" to the title/markdown if MCP features load successfully
151
+
152
+ with gr.Row():
153
+ player_search_input = gr.Textbox(
154
+ label="Search for Player (Optional)",
155
+ placeholder="E.g., Salah, Haaland, Saka... Leave blank for Top 20",
156
+ # Add mcp_label if MCP features are correctly installed and working
157
+ # mcp_label="player_search_input_blocks"
158
+ )
159
+
160
+ predict_button = gr.Button("Predict Points")
161
+
162
+ prediction_output_dataframe = gr.DataFrame(
163
  label="Predicted Player Data (Next Gameweek)",
164
  wrap=True,
165
+ # Add mcp_label if MCP features are correctly installed and working
166
+ # mcp_label="prediction_output_dataframe_blocks"
167
+ )
168
+
169
+ gr.Examples(
170
+ examples=[[""], ["Son"], ["Watkins"], ["Palmer"]],
171
+ inputs=player_search_input,
172
+ # You could also have outputs and fn here if examples should pre-run,
173
+ # but for dynamic input, just setting input is common.
174
+ )
175
+
176
+ # Define the action for the button click
177
+ predict_button.click(
178
+ fn=predict_fpl_points,
179
+ inputs=player_search_input,
180
+ outputs=prediction_output_dataframe
181
+ )
182
 
183
  # --- Application Launch ---
184
  if __name__ == "__main__":
185
+ print(f"Gradio version before launch: {gr.__version__}")
186
+
187
+ # Attempt to add MCP labels dynamically if the Gradio version seems to support it
188
+ # This is a bit of a workaround due to the persistent environment issues.
189
+ # Ideally, you'd just define them directly if the env was correct.
190
+ mcp_is_likely_available = False
191
+ try:
192
+ # A simple test: does Textbox accept mcp_label?
193
+ # This is a rough check and might not be perfectly reliable for all Gradio versions/setups.
194
+ # The real check is whether 'gradio[mcp]' was correctly installed.
195
+ _ = gr.Textbox(mcp_label="test")
196
+ mcp_is_likely_available = True
197
+ print("MCP features (like mcp_label) seem available in this Gradio version.")
198
+ except TypeError:
199
+ print("WARNING: MCP features (like mcp_label) are NOT available in this Gradio version. MCP server might fail or not collect specific data.")
200
+ print(f"This is likely due to an issue with 'gradio[mcp]' installation or an incompatible Gradio version ({gr.__version__}) in the environment.")
201
+
202
+ if mcp_is_likely_available:
203
+ # If MCP seems available, re-define components within Blocks with mcp_label
204
+ # This is done by re-creating the demo object if we want to add them now.
205
+ # For simplicity in this example, I'll just note that you would have defined them above directly.
206
+ # The `mcp_label`s in the Blocks definition above are commented out;
207
+ # you'd uncomment them if your environment was fixed.
208
+ # For now, we'll proceed, and launch will attempt mcp_server=True
209
+ print("Proceeding with mcp_server=True. If it fails, it confirms environment issues.")
210
+ if hasattr(player_search_input, 'mcp_label'): # This check is illustrative
211
+ player_search_input.mcp_label = "player_search_input_blocks"
212
+ prediction_output_dataframe.mcp_label = "prediction_output_dataframe_blocks"
213
+ demo.title = "Fantasy Premier League Player Point Predictor (MCP Enabled)"
214
+ demo.blocks[0].value = demo.blocks[0].value.replace("Player Point Predictor", "Player Point Predictor (MCP Enabled)")
215
+
216
+
217
+ print("Attempting to launch Gradio app...")
218
  try:
219
+ # Try to launch with mcp_server=True
220
+ demo.launch(mcp_server=True)
221
  except TypeError as e:
222
+ if 'mcp_server' in str(e) or 'mcp_label' in str(e):
223
+ print("\nERROR: Failed to launch with MCP features (mcp_server or mcp_label not recognized).")
224
+ print(f"Gradio version being used: {gr.__version__}")
225
+ print("This confirms that 'gradio[mcp]' extras are not correctly installed or it's an incompatible Gradio version.")
226
+ print("If you are on Hugging Face Spaces, ensure your environment (requirements.txt, Dockerfile if used, or SDK settings) correctly installs a recent 'gradio[mcp]'.\n")
227
+ print(f"Error details: {e}")
228
+
229
+ print("Attempting to launch Gradio app WITHOUT MCP server as a fallback...")
230
+ # Fallback launch:
231
+ # Create a new Blocks instance without attempting MCP features if the first one is problematic
232
+ with gr.Blocks(theme=gr.themes.Soft(), title="Fantasy Premier League Player Point Predictor (MCP Fallback)") as fallback_demo:
233
+ gr.Markdown(
234
+ """
235
+ # Fantasy Premier League Player Point Predictor (MCP Features Failed to Load)
236
+ Predicts FPL points for the *next* gameweek based on current form and upcoming fixture difficulty (FDR).
237
+ Enter a player name to search, or leave blank to see the top 20 predicted players. Fetches live data.
238
+ """
239
+ )
240
+ with gr.Row():
241
+ player_search_input_fb = gr.Textbox( # no mcp_label
242
+ label="Search for Player (Optional)",
243
+ placeholder="E.g., Salah, Haaland, Saka... Leave blank for Top 20"
244
+ )
245
+ predict_button_fb = gr.Button("Predict Points")
246
+ prediction_output_dataframe_fb = gr.DataFrame( # no mcp_label
247
+ label="Predicted Player Data (Next Gameweek)",
248
+ wrap=True
249
+ )
250
+ gr.Examples(
251
+ examples=[[""], ["Son"], ["Watkins"], ["Palmer"]],
252
+ inputs=player_search_input_fb,
253
+ )
254
+ predict_button_fb.click(
255
+ fn=predict_fpl_points,
256
+ inputs=player_search_input_fb,
257
+ outputs=prediction_output_dataframe_fb
258
+ )
259
+ fallback_demo.launch()
260
  else:
261
+ # Re-raise other TypeErrors not related to MCP
262
+ raise e
263
+ except Exception as general_e:
264
+ print(f"A general error occurred during launch: {general_e}")
265
+ # Potentially try the fallback launch here too if it's a critical launch failure