File size: 12,678 Bytes
38af5be
 
 
 
d10c86f
 
 
38af5be
9c61b2d
38af5be
 
 
 
d10c86f
38af5be
 
 
 
 
d10c86f
 
38af5be
9c61b2d
 
 
38af5be
 
 
 
 
 
d10c86f
38af5be
 
 
 
 
 
 
d10c86f
38af5be
d10c86f
 
38af5be
9c61b2d
 
 
 
38af5be
9c61b2d
38af5be
 
 
 
9c61b2d
d10c86f
9c61b2d
 
 
38af5be
 
9c61b2d
d10c86f
 
38af5be
 
9c61b2d
 
 
38af5be
9c61b2d
 
 
 
 
 
 
38af5be
 
 
9c61b2d
d10c86f
38af5be
 
 
9c61b2d
38af5be
 
9c61b2d
 
 
38af5be
 
 
 
 
 
 
 
d10c86f
38af5be
 
 
 
 
 
 
 
 
 
 
 
 
 
9c61b2d
38af5be
 
 
9c61b2d
 
 
38af5be
d10c86f
38af5be
d10c86f
38af5be
9c61b2d
d10c86f
38af5be
 
d10c86f
38af5be
 
d10c86f
38af5be
 
 
 
9c61b2d
38af5be
d10c86f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c61b2d
 
d10c86f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38af5be
9c61b2d
38af5be
d10c86f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c61b2d
d10c86f
 
9c61b2d
d10c86f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c61b2d
d10c86f
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import gradio as gr
import pandas as pd
import requests
import joblib
import xgboost

print(f"Gradio version at script start: {gr.__version__}") # For debugging

# --- Model Loading ---
try:
    model = joblib.load('xgb_model.joblib')
    print("Model loaded successfully.")
except FileNotFoundError:
    print("ERROR: Model file 'xgb_model.joblib' not found. Predictions will fail.")
    model = None
except Exception as e:
    print(f"Error loading model: {e}")
    model = None

# --- Helper Function for FDR (get_next_fdr) ---
# (Your get_next_fdr function code remains the same here)
def get_next_fdr(team_id, fixtures_df):
    """
    Finds the next fixture difficulty rating (FDR) for a given team.
    """
    upcoming = fixtures_df[
        ((fixtures_df["team_h"] == team_id) | (fixtures_df["team_a"] == team_id)) &
        (fixtures_df["finished"] == False)
    ]
    if not upcoming.empty:
        upcoming = upcoming.dropna(subset=['event'])
        if upcoming.empty:
            return None
        next_fixture = upcoming.sort_values("event").iloc[0]
        if next_fixture["team_h"] == team_id:
            return next_fixture["team_h_difficulty"]
        else:
            return next_fixture["team_a_difficulty"]
    else:
        return None

# --- Main Prediction Function (predict_fpl_points) ---
# (Your predict_fpl_points function code remains the same here)
def predict_fpl_points(search_name=""):
    """
    Fetches FPL player data, calculates their next FDR,
    and predicts their FPL points for the next gameweek.
    """
    if model is None:
        return pd.DataFrame({"Error": ["Model could not be loaded. Please check server logs."]})

    print(f"Fetching latest FPL data... Search term: '{search_name}'")
    try:
        players_url = "https://fantasy.premierleague.com/api/bootstrap-static/"
        response = requests.get(players_url, timeout=20)
        response.raise_for_status() 
        players_data = response.json()
        
        players = pd.DataFrame(players_data["elements"])[
            ["id", "web_name", "team", "form", "element_type"]
        ]
        players["element_type"] = pd.to_numeric(players["element_type"], errors='coerce')
        players = players[players["element_type"].isin([1, 2, 3, 4])] 
        players["team"] = players["team"].astype(int)

        fixtures_url = "https://fantasy.premierleague.com/api/fixtures/"
        fixtures_response = requests.get(fixtures_url, timeout=20)
        fixtures_response.raise_for_status()
        fixtures_data = fixtures_response.json()
        fixtures = pd.DataFrame(fixtures_data)

        cols_to_numeric = ["team_h", "team_a", "team_h_difficulty", "team_a_difficulty", "event"]
        for col in cols_to_numeric:
            if col in fixtures.columns:
                fixtures[col] = pd.to_numeric(fixtures[col], errors='coerce')
        
        print("FPL data fetched successfully.")

    except requests.exceptions.RequestException as e:
        print(f"Error fetching data from FPL API: {e}")
        return pd.DataFrame({"Error": [f"Failed to fetch FPL data: {e}. Please try again later."]})
    except Exception as e:
         print(f"An unexpected error occurred during data fetching: {e}")
         return pd.DataFrame({"Error": [f"An unexpected error occurred during data fetching: {e}"]})

    print("Calculating next FDR and preparing data for prediction...")
    try:
        players_with_fdr = players.copy()
        players_with_fdr["next_fdr"] = players_with_fdr["team"].apply(
            lambda team_id: get_next_fdr(team_id, fixtures)
        )

        players_clean = players_with_fdr.dropna(subset=["next_fdr", "form"])
        players_clean["form"] = pd.to_numeric(players_clean["form"], errors='coerce')
        players_clean["next_fdr"] = pd.to_numeric(players_clean["next_fdr"], errors='coerce')
        players_clean = players_clean.dropna(subset=["form", "next_fdr"])

        if players_clean.empty:
             print("No players found after cleaning (missing form or next FDR).")
             return pd.DataFrame({"Message": ["No eligible players found after data cleaning."]})

        print(f"Predicting points for {len(players_clean)} players...")
        features = ["form", "next_fdr"]
        X_next = players_clean[features]
        players_clean.loc[:, "predicted_points"] = model.predict(X_next)
        print("Prediction complete.")

        output_df_base = players_clean[["web_name", "predicted_points", "form", "next_fdr"]].copy()
        output_df_base.rename(columns={
            "web_name": "Player",
            "predicted_points": "Predicted Points",
            "form": "Form",
            "next_fdr": "Next FDR (Lower is easier)"
        }, inplace=True)
        output_df_base.loc[:, "Predicted Points"] = output_df_base["Predicted Points"].round(2)

        if search_name and search_name.strip():
            print(f"Filtering for player name containing: '{search_name}'")
            search_results = output_df_base[
                output_df_base['Player'].str.contains(search_name.strip(), case=False, na=False)
            ]
            if not search_results.empty:
                return search_results.sort_values("Predicted Points", ascending=False)
            else:
                return pd.DataFrame({"Message": [f"No player found matching '{search_name}'."]})
        else:
            print("No search term provided, returning top 20 predicted players.")
            return output_df_base.sort_values("Predicted Points", ascending=False).head(20)

    except KeyError as e:
        print(f"KeyError during data processing: {e}.")
        import traceback
        traceback.print_exc()
        return pd.DataFrame({"Error": [f"Data processing error (Missing Key): {e}."]})
    except Exception as e:
        print(f"An error occurred during prediction/processing: {e}")
        import traceback
        traceback.print_exc()
        return pd.DataFrame({"Error": [f"Prediction or data processing failed: {e}"]})


# --- Gradio UI Definition using gr.Blocks ---
with gr.Blocks(theme=gr.themes.Soft(), title="Fantasy Premier League Player Point Predictor") as demo:
    gr.Markdown(
        """
        # Fantasy Premier League Player Point Predictor
        Predicts FPL points for the *next* gameweek based on current form and upcoming fixture difficulty (FDR). 
        Enter a player name to search, or leave blank to see the top 20 predicted players. Fetches live data.
        """
    ) # You can add "(MCP Enabled)" to the title/markdown if MCP features load successfully

    with gr.Row():
        player_search_input = gr.Textbox(
            label="Search for Player (Optional)",
            placeholder="E.g., Salah, Haaland, Saka... Leave blank for Top 20",
            # Add mcp_label if MCP features are correctly installed and working
            # mcp_label="player_search_input_blocks" 
        )

    predict_button = gr.Button("Predict Points")

    prediction_output_dataframe = gr.DataFrame(
        label="Predicted Player Data (Next Gameweek)",
        wrap=True,
        # Add mcp_label if MCP features are correctly installed and working
        # mcp_label="prediction_output_dataframe_blocks"
    )

    gr.Examples(
        examples=[[""], ["Son"], ["Watkins"], ["Palmer"]],
        inputs=player_search_input,
        # You could also have outputs and fn here if examples should pre-run,
        # but for dynamic input, just setting input is common.
    )

    # Define the action for the button click
    predict_button.click(
        fn=predict_fpl_points,
        inputs=player_search_input,
        outputs=prediction_output_dataframe
    )

# --- Application Launch ---
if __name__ == "__main__":
    print(f"Gradio version before launch: {gr.__version__}")
    
    # Attempt to add MCP labels dynamically if the Gradio version seems to support it
    # This is a bit of a workaround due to the persistent environment issues.
    # Ideally, you'd just define them directly if the env was correct.
    mcp_is_likely_available = False
    try:
        # A simple test: does Textbox accept mcp_label?
        # This is a rough check and might not be perfectly reliable for all Gradio versions/setups.
        # The real check is whether 'gradio[mcp]' was correctly installed.
        _ = gr.Textbox(mcp_label="test") 
        mcp_is_likely_available = True
        print("MCP features (like mcp_label) seem available in this Gradio version.")
    except TypeError:
        print("WARNING: MCP features (like mcp_label) are NOT available in this Gradio version. MCP server might fail or not collect specific data.")
        print(f"This is likely due to an issue with 'gradio[mcp]' installation or an incompatible Gradio version ({gr.__version__}) in the environment.")

    if mcp_is_likely_available:
        # If MCP seems available, re-define components within Blocks with mcp_label
        # This is done by re-creating the demo object if we want to add them now.
        # For simplicity in this example, I'll just note that you would have defined them above directly.
        # The `mcp_label`s in the Blocks definition above are commented out;
        # you'd uncomment them if your environment was fixed.
        # For now, we'll proceed, and launch will attempt mcp_server=True
        print("Proceeding with mcp_server=True. If it fails, it confirms environment issues.")
        if hasattr(player_search_input, 'mcp_label'): # This check is illustrative
            player_search_input.mcp_label = "player_search_input_blocks"
            prediction_output_dataframe.mcp_label = "prediction_output_dataframe_blocks"
            demo.title = "Fantasy Premier League Player Point Predictor (MCP Enabled)"
            demo.blocks[0].value = demo.blocks[0].value.replace("Player Point Predictor", "Player Point Predictor (MCP Enabled)")


    print("Attempting to launch Gradio app...")
    try:
        # Try to launch with mcp_server=True
        demo.launch(mcp_server=True)
    except TypeError as e:
        if 'mcp_server' in str(e) or 'mcp_label' in str(e):
            print("\nERROR: Failed to launch with MCP features (mcp_server or mcp_label not recognized).")
            print(f"Gradio version being used: {gr.__version__}")
            print("This confirms that 'gradio[mcp]' extras are not correctly installed or it's an incompatible Gradio version.")
            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")
            print(f"Error details: {e}")
            
            print("Attempting to launch Gradio app WITHOUT MCP server as a fallback...")
            # Fallback launch:
            # Create a new Blocks instance without attempting MCP features if the first one is problematic
            with gr.Blocks(theme=gr.themes.Soft(), title="Fantasy Premier League Player Point Predictor (MCP Fallback)") as fallback_demo:
                gr.Markdown(
                    """
                    # Fantasy Premier League Player Point Predictor (MCP Features Failed to Load)
                    Predicts FPL points for the *next* gameweek based on current form and upcoming fixture difficulty (FDR). 
                    Enter a player name to search, or leave blank to see the top 20 predicted players. Fetches live data.
                    """
                )
                with gr.Row():
                    player_search_input_fb = gr.Textbox( # no mcp_label
                        label="Search for Player (Optional)",
                        placeholder="E.g., Salah, Haaland, Saka... Leave blank for Top 20"
                    )
                predict_button_fb = gr.Button("Predict Points")
                prediction_output_dataframe_fb = gr.DataFrame( # no mcp_label
                    label="Predicted Player Data (Next Gameweek)",
                    wrap=True
                )
                gr.Examples(
                    examples=[[""], ["Son"], ["Watkins"], ["Palmer"]],
                    inputs=player_search_input_fb,
                )
                predict_button_fb.click(
                    fn=predict_fpl_points,
                    inputs=player_search_input_fb,
                    outputs=prediction_output_dataframe_fb
                )
            fallback_demo.launch()
        else:
            # Re-raise other TypeErrors not related to MCP
            raise e
    except Exception as general_e:
        print(f"A general error occurred during launch: {general_e}")
        # Potentially try the fallback launch here too if it's a critical launch failure