fpl-mcp-server 0.1.7__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.github/workflows/lint.yml +1 -1
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/PKG-INFO +2 -2
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/README.md +1 -1
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/docs/tool-selection-guide.md +3 -1
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/docs/usage-examples.md +9 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/pyproject.toml +1 -1
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/__init__.py +1 -0
- fpl_mcp_server-0.2.0/src/prompts/gameweek_analysis.py +72 -0
- fpl_mcp_server-0.2.0/src/prompts/squad_analysis.py +106 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/tools/fixtures.py +159 -19
- fpl_mcp_server-0.2.0/tests/test_gameweek_analysis_prompt.py +57 -0
- fpl_mcp_server-0.2.0/tests/test_squad_analysis_prompt.py +70 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_tools_advanced.py +30 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/uv.lock +1 -1
- fpl_mcp_server-0.1.7/src/prompts/squad_analysis.py +0 -136
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.dockerignore +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.env.example +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.github/workflows/publish-docker.yml +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.github/workflows/publish-pypi.yml +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.github/workflows/test.yml +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.gitignore +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/.pre-commit-config.yaml +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/CONTRIBUTING.md +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/Dockerfile +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/LICENSE +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/docs/configuration.md +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/docs/fpl-api-swagger.json +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/docs/fpl-api.md +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/docs/installation.md +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/docs/releasing.md +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/pytest.ini +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/cache.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/client.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/config.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/constants.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/exceptions.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/formatting.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/main.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/models.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/captain_recommendation.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/chips.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/league_analysis.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/player_analysis.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/team_analysis.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/team_selection.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/prompts/transfers.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/rate_limiter.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/resources/__init__.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/resources/bootstrap.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/state.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/tools/__init__.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/tools/gameweeks.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/tools/leagues.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/tools/players.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/tools/teams.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/tools/transfers.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/utils.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/src/validators.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/conftest.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_cache.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_client.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_client_advanced.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_exceptions.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_formatting.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_models.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_rate_limiter.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_resources.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_state.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_state_advanced.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_tools_coverage.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_tools_coverage_extra.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_tools_integration.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_utils.py +0 -0
- {fpl_mcp_server-0.1.7 → fpl_mcp_server-0.2.0}/tests/test_validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fpl-mcp-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Fantasy Premier League MCP Server
|
|
5
5
|
Project-URL: Homepage, https://github.com/nguyenanhducs/fpl-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/nguyenanhducs/fpl-mcp
|
|
@@ -43,7 +43,7 @@ This MCP server provides comprehensive FPL analysis capabilities through:
|
|
|
43
43
|
|
|
44
44
|
- **17 Interactive Tools** - Search players, analyze fixtures, compare managers, track transfers, and more
|
|
45
45
|
- **4 Data Resources** - access to players, teams, and gameweeks bootstrap data
|
|
46
|
-
- **
|
|
46
|
+
- **9 Strategy Prompts** - Structured templates for gameweek analysis, squad analysis, transfer planning, chip strategy, and captain selection
|
|
47
47
|
- **Smart Caching** - 4-hour cache for bootstrap data to minimize API calls while keeping data fresh
|
|
48
48
|
- **Fuzzy Matching** - Find players even with spelling variations or nicknames
|
|
49
49
|
- **Live Transfer Trends** - Track the most transferred in/out players for current gameweek
|
|
@@ -12,7 +12,7 @@ This MCP server provides comprehensive FPL analysis capabilities through:
|
|
|
12
12
|
|
|
13
13
|
- **17 Interactive Tools** - Search players, analyze fixtures, compare managers, track transfers, and more
|
|
14
14
|
- **4 Data Resources** - access to players, teams, and gameweeks bootstrap data
|
|
15
|
-
- **
|
|
15
|
+
- **9 Strategy Prompts** - Structured templates for gameweek analysis, squad analysis, transfer planning, chip strategy, and captain selection
|
|
16
16
|
- **Smart Caching** - 4-hour cache for bootstrap data to minimize API calls while keeping data fresh
|
|
17
17
|
- **Fuzzy Matching** - Find players even with spelling variations or nicknames
|
|
18
18
|
- **Live Transfer Trends** - Track the most transferred in/out players for current gameweek
|
|
@@ -40,7 +40,7 @@ Interactive functions that perform specific FPL analysis tasks. All tools accept
|
|
|
40
40
|
|
|
41
41
|
| Tool Name | Description | Key Parameters |
|
|
42
42
|
|-----------|-------------|----------------|
|
|
43
|
-
| `fpl_get_fixtures_for_gameweek` | All matches in a specific gameweek | `gameweek`, `format` |
|
|
43
|
+
| `fpl_get_fixtures_for_gameweek` | All matches in a specific gameweek (basic or detailed) | `gameweek`, `detailed`, `format` |
|
|
44
44
|
| `fpl_find_fixture_opportunities` | Find teams with easiest upcoming fixtures | `num_gameweeks`, `min_difficulty` |
|
|
45
45
|
|
|
46
46
|
### League & Manager Tools (4 tools)
|
|
@@ -94,6 +94,7 @@ Structured templates that guide analysis workflows. Prompts combine multiple too
|
|
|
94
94
|
| `compare_players` | Side-by-side player comparison | `*player_names` | Choose between transfer targets |
|
|
95
95
|
| `compare_managers` | Manager team comparison | `league_name`, `gameweek`, `*manager_names` | Analyze league rivals' strategies |
|
|
96
96
|
| `find_league_differentials` | Find low-ownership differentials | `league_id`, `max_ownership` | Gain competitive advantage in mini-leagues |
|
|
97
|
+
| `gameweek_analysis` | Detailed match reports & transfer implications | `gameweek` | Review completed gameweek stats and identify trends |
|
|
97
98
|
|
|
98
99
|
---
|
|
99
100
|
|
|
@@ -116,6 +117,7 @@ Structured templates that guide analysis workflows. Prompts combine multiple too
|
|
|
116
117
|
|
|
117
118
|
- **Current gameweek?** → `fpl_get_current_gameweek` - Current or upcoming gameweek details
|
|
118
119
|
- **Gameweek fixtures?** → `fpl_get_fixtures_for_gameweek` - All matches in a specific gameweek
|
|
120
|
+
- **Gameweek review?** → `gameweek_analysis` - Comprehensive match analysis and transfer implications
|
|
119
121
|
|
|
120
122
|
### League & Manager Analysis
|
|
121
123
|
|
|
@@ -17,6 +17,15 @@ Once configured, you can interact with the FPL MCP server through Claude Desktop
|
|
|
17
17
|
"What teams have the best fixtures in the next 5 gameweeks?"
|
|
18
18
|
"Show me Liverpool's upcoming fixtures"
|
|
19
19
|
"Which fixtures are in gameweek 15?"
|
|
20
|
+
"Show me detailed match reports for gameweek 12"
|
|
21
|
+
|
|
22
|
+
## Gameweek Review
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
"Analyze the matches from last gameweek"
|
|
26
|
+
"Who were the key performers in gameweek 5?"
|
|
27
|
+
"Based on gameweek 10 matches, who should I buy?"
|
|
28
|
+
```
|
|
20
29
|
```
|
|
21
30
|
|
|
22
31
|
## League & Manager Insights
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FPL MCP Prompts - Gameweek Analysis.
|
|
3
|
+
|
|
4
|
+
Prompts to analyze detailed match reports and provide transfer advice for a gameweek.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ..state import store
|
|
8
|
+
from ..tools import mcp
|
|
9
|
+
from ..tools.fixtures import (
|
|
10
|
+
FindFixtureOpportunitiesInput,
|
|
11
|
+
GetFixturesForGameweekInput,
|
|
12
|
+
fpl_find_fixture_opportunities,
|
|
13
|
+
fpl_get_fixtures_for_gameweek,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp.prompt()
|
|
18
|
+
async def gameweek_analysis(gameweek: int | None = None) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Analyze detailed match reports for a specific gameweek.
|
|
21
|
+
|
|
22
|
+
Provides a comprehensive analysis of all matches in a gameweek, highlighting
|
|
23
|
+
key performers, tactical insights, and FPL implications based on detailed statistics.
|
|
24
|
+
Also suggests transfer targets based on upcoming fixtures and form.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
gameweek: Gameweek number to analyze (defaults to current gameweek if None)
|
|
28
|
+
"""
|
|
29
|
+
if gameweek is None:
|
|
30
|
+
gw_data = store.get_current_gameweek()
|
|
31
|
+
if not gw_data:
|
|
32
|
+
return "Error: Could not determine current gameweek."
|
|
33
|
+
gameweek = gw_data.id
|
|
34
|
+
|
|
35
|
+
# Fetch detailed fixtures
|
|
36
|
+
fixtures_input = GetFixturesForGameweekInput(gameweek=gameweek, detailed=True)
|
|
37
|
+
match_reports = await fpl_get_fixtures_for_gameweek(fixtures_input)
|
|
38
|
+
|
|
39
|
+
# Fetch simple fixture difficulty context for next 5 gameweeks
|
|
40
|
+
fixture_ops_input = FindFixtureOpportunitiesInput(num_gameweeks=5, max_teams=5)
|
|
41
|
+
fixture_opportunities = await fpl_find_fixture_opportunities(fixture_ops_input)
|
|
42
|
+
|
|
43
|
+
return f"""Analyze the following detailed match reports for Gameweek {gameweek}.
|
|
44
|
+
|
|
45
|
+
**Match Reports:**
|
|
46
|
+
{match_reports}
|
|
47
|
+
|
|
48
|
+
**Upcoming Fixture Context (Next 5 GWs):**
|
|
49
|
+
{fixture_opportunities}
|
|
50
|
+
|
|
51
|
+
**Analysis Objectives:**
|
|
52
|
+
1. **Key Performers:** Identify the standout players based on goals, assists, and bonus points. Who is in top form?
|
|
53
|
+
2. **Tactical Insights:** Are there any noticeable trends? (e.g., high-scoring games, defensive masterclasses, specific teams dominating).
|
|
54
|
+
3. **FPL Implications:**
|
|
55
|
+
- **Buy:** Comparison of assets who performed well.
|
|
56
|
+
- **CRITICAL**: Cross-reference with Upcoming Fixture Context.
|
|
57
|
+
- Generally prioritize players with good upcoming fixtures.
|
|
58
|
+
- **EXCEPTION**: High-form elite players (fixture-proof) can be recommended even with tougher fixtures if their underlying stats are exceptional.
|
|
59
|
+
- Flag 'Trap' assets: players in bad teams who scored once but have terrible fixtures/stats.
|
|
60
|
+
- **Sell:** Notable failures or players who were benched/subbed early. Consider if it's a dip in form or a long-term issue.
|
|
61
|
+
- **Watchlist:** interesting differentials or returning players.
|
|
62
|
+
- **Analysis Depth**: When highlighting players, mention if their performance seems sustainable. Infer xG/xA quality from report descriptions if detailed stats suggest high involvement (e.g. many shots/key passes).
|
|
63
|
+
4. **Upcoming Outlook:** Based on this performance, who looks essential for the next gameweek?
|
|
64
|
+
|
|
65
|
+
## 🔧 Tool Calls Strategy
|
|
66
|
+
|
|
67
|
+
1. **Deep Dive**: Use `fpl_get_player_details(player_name=...)` to check full history and upcoming fixtures for any player who catches your eye.
|
|
68
|
+
2. **Compare Options**: Use `fpl_compare_players(player_names=[...])` to decide between potential transfer targets.
|
|
69
|
+
3. **Check Manager**: If analyzing a specific rival, use `fpl_get_manager_by_team_id`.
|
|
70
|
+
|
|
71
|
+
Provide a concise but deep analysis useful for an FPL manager making transfer decisions.
|
|
72
|
+
"""
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FPL MCP Prompts - Squad Performance Analysis.
|
|
3
|
+
|
|
4
|
+
Prompts guide the LLM in analyzing squad performance over recent gameweeks
|
|
5
|
+
using underlying metrics and regression analysis.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ..tools import mcp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@mcp.prompt()
|
|
12
|
+
def analyze_squad_performance(team_id: int, num_gameweeks: int = 5) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Analyze squad performance using xGI-based metrics and regression analysis.
|
|
15
|
+
|
|
16
|
+
This prompt guides the LLM to identify underperforming/overperforming players
|
|
17
|
+
using underlying stats (xG, xA, xGI) rather than retrospective points.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
team_id: FPL team ID of the manager to analyze
|
|
21
|
+
num_gameweeks: Number of recent gameweeks to analyze (default: 5)
|
|
22
|
+
"""
|
|
23
|
+
return f"""Analyze FPL squad performance for team ID {team_id} over the last {num_gameweeks} gameweeks.
|
|
24
|
+
|
|
25
|
+
**OBJECTIVE: Create a PRO-LEVEL transfer strategy using Underlying Stats (xGI), Fixture Swings, and Chip Strategy.**
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🏗️ **Step 1: Strategic Context (The "Manager's Eye")**
|
|
30
|
+
|
|
31
|
+
Before analyzing players, assess the macro state of the squad:
|
|
32
|
+
1. **Financial Health:**
|
|
33
|
+
- Check `bank` balance. Can we afford luxury upgrades?
|
|
34
|
+
- Check `value` trends.
|
|
35
|
+
2. **Chip Status:**
|
|
36
|
+
- Which chips are available? (Wildcard, Free Hit, Bench Boost, Triple Captain)
|
|
37
|
+
- **Strategy:** If Wildcard is available and squad has >4 "issues", suggest Wildcard.
|
|
38
|
+
3. **Fixture Scan (Next 5 GW):**
|
|
39
|
+
- Identify teams with **Major Fixture Swings** (turning Good → Bad or Bad → Good).
|
|
40
|
+
- *Target:* Players from teams entering a "green run".
|
|
41
|
+
- *Avoid:* Players from teams entering a "red run".
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 📊 **Step 2: Player Performance Analysis (xGI Model)**
|
|
46
|
+
|
|
47
|
+
Analyze each player using the **xGI Regression Model** (Output vs Expected):
|
|
48
|
+
|
|
49
|
+
### **The Regression Framework:**
|
|
50
|
+
- **xGI Delta** = `Actual G+A` - `Expected GI (xG + xA)`
|
|
51
|
+
- **Interpretation:**
|
|
52
|
+
- **Huge Overperformance (+3.0+)**: *Elite Finisher* (e.g., Salah/Son) OR *Luck*? -> **HOLD** unless fixtures turn terrible.
|
|
53
|
+
- **Significant Underperformance (-2.0 to -3.0)**: *Unlucky*. If xGI is high (>0.5/90), **KEEP** or even **CAPTAIN**.
|
|
54
|
+
- **Poor Underlying (xGI < 0.2/90)**: *Ghosting*. Regardless of points, this is a **SELL** priority.
|
|
55
|
+
|
|
56
|
+
### **Categorization:**
|
|
57
|
+
- 🛡️ **Defensive Rocks**: Clean sheet potential + BP system appeal.
|
|
58
|
+
- 🚜 **Workhorses**: Consistent xGI (0.4-0.6), nailed minutes.
|
|
59
|
+
- 💣 **Explosive Differentials**: High xGI but low ownership (<10%).
|
|
60
|
+
- 📉 **Dead Wood**: Low xGI + Bad Fixtures + Rotation Risk.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🔄 **Step 3: Transfer Planning (The "Next 3 Moves")**
|
|
65
|
+
|
|
66
|
+
Instead of just "Buy X", provide a 3-Gameweek Plan:
|
|
67
|
+
|
|
68
|
+
**Scenario A: The Surgery** (3+ Issues)
|
|
69
|
+
- **GW{num_gameweeks + 1}:** Sell [Player A] -> Buy [Target A] (Reason: Fixture Swing)
|
|
70
|
+
- **GW{num_gameweeks + 2}:** Roll Transfer / Sell [Player B]
|
|
71
|
+
- **Long-term:** target [Premium Asset] in GW{num_gameweeks + 3}
|
|
72
|
+
|
|
73
|
+
**Scenario B: The Luxury Move** (Squd is fine)
|
|
74
|
+
- Upgrade specific position or build bank for future premium.
|
|
75
|
+
|
|
76
|
+
**Prioritization Rules:**
|
|
77
|
+
1. **Injuries/Suspensions**: Immediate priority.
|
|
78
|
+
2. **Fixture Cliffs**: Selling players hitting a run of red fixtures.
|
|
79
|
+
3. **xGI Underperformers**: Selling players with low xGI (not just low points).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 💡 **Recommendations & Targets**
|
|
84
|
+
|
|
85
|
+
For each position (GKP, DEF, MID, FWD), recommend **Replacement Targets** based on:
|
|
86
|
+
1. **Fixture Swing**: Teams with Easiest Next 4 Games.
|
|
87
|
+
2. **Underlying Data**: Top xGI performers in those teams.
|
|
88
|
+
3. **Price Structure**: Must fit within budget (Bank + Sale Price).
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🔧 **Tool Calls Strategy**
|
|
93
|
+
|
|
94
|
+
1. **Get Context**: `fpl_get_manager_by_team_id` (Squad, Bank, Rank) AND `fpl_get_manager_chips`.
|
|
95
|
+
2. **Scan Landscape**: `fpl_find_fixture_opportunities` (Identify teams to target).
|
|
96
|
+
3. **Analyze Squad**: `fpl_get_player_details` for current squad loop.
|
|
97
|
+
4. **Find Replacements**: `fpl_get_top_performers` (Cross-reference with Fixture Opportunities).
|
|
98
|
+
5. **Captaincy**: `fpl_get_captain_recommendations` for immediate GW.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ⚠️ **Veteran Advice**
|
|
103
|
+
- **Don't chase last week's points.** Look at who is *about* to score.
|
|
104
|
+
- **Roll transfers** if the squad is healthy. 2 FTs is a superpower.
|
|
105
|
+
- **Value < Points.** Don't hold a falling player just to save 0.1m if they aren't scoring.
|
|
106
|
+
"""
|
|
@@ -22,6 +22,9 @@ class GetFixturesForGameweekInput(BaseModel):
|
|
|
22
22
|
gameweek: int = Field(
|
|
23
23
|
..., description="Gameweek number to get fixtures for (1-38)", ge=1, le=38
|
|
24
24
|
)
|
|
25
|
+
detailed: bool = Field(
|
|
26
|
+
default=False, description="Include detailed match stats (all available metrics)"
|
|
27
|
+
)
|
|
25
28
|
response_format: ResponseFormat = Field(
|
|
26
29
|
default=ResponseFormat.MARKDOWN,
|
|
27
30
|
description="Output format: 'markdown' for human-readable or 'json' for machine-readable",
|
|
@@ -78,6 +81,7 @@ async def fpl_get_fixtures_for_gameweek(params: GetFixturesForGameweekInput) ->
|
|
|
78
81
|
Args:
|
|
79
82
|
params (GetFixturesForGameweekInput): Validated input parameters containing:
|
|
80
83
|
- gameweek (int): Gameweek number between 1-38
|
|
84
|
+
- detailed (bool): Include detailed stats (default: False)
|
|
81
85
|
- response_format (ResponseFormat): 'markdown' or 'json' (default: markdown)
|
|
82
86
|
|
|
83
87
|
Returns:
|
|
@@ -111,26 +115,77 @@ async def fpl_get_fixtures_for_gameweek(params: GetFixturesForGameweekInput) ->
|
|
|
111
115
|
result = {
|
|
112
116
|
"gameweek": params.gameweek,
|
|
113
117
|
"fixture_count": len(gw_fixtures_sorted),
|
|
114
|
-
"fixtures": [
|
|
115
|
-
{
|
|
116
|
-
"home_team": fixture.get("team_h_name"),
|
|
117
|
-
"home_team_short": fixture.get("team_h_short"),
|
|
118
|
-
"away_team": fixture.get("team_a_name"),
|
|
119
|
-
"away_team_short": fixture.get("team_a_short"),
|
|
120
|
-
"kickoff_time": fixture.get("kickoff_time"),
|
|
121
|
-
"finished": fixture.get("finished"),
|
|
122
|
-
"home_score": fixture.get("team_h_score")
|
|
123
|
-
if fixture.get("finished")
|
|
124
|
-
else None,
|
|
125
|
-
"away_score": fixture.get("team_a_score")
|
|
126
|
-
if fixture.get("finished")
|
|
127
|
-
else None,
|
|
128
|
-
"home_difficulty": fixture.get("team_h_difficulty"),
|
|
129
|
-
"away_difficulty": fixture.get("team_a_difficulty"),
|
|
130
|
-
}
|
|
131
|
-
for fixture in gw_fixtures_sorted
|
|
132
|
-
],
|
|
118
|
+
"fixtures": [],
|
|
133
119
|
}
|
|
120
|
+
|
|
121
|
+
for fixture in gw_fixtures_sorted:
|
|
122
|
+
fixture_data = {
|
|
123
|
+
"home_team": fixture.get("team_h_name"),
|
|
124
|
+
"home_team_short": fixture.get("team_h_short"),
|
|
125
|
+
"away_team": fixture.get("team_a_name"),
|
|
126
|
+
"away_team_short": fixture.get("team_a_short"),
|
|
127
|
+
"kickoff_time": fixture.get("kickoff_time"),
|
|
128
|
+
"finished": fixture.get("finished"),
|
|
129
|
+
"home_score": fixture.get("team_h_score") if fixture.get("finished") else None,
|
|
130
|
+
"away_score": fixture.get("team_a_score") if fixture.get("finished") else None,
|
|
131
|
+
"home_difficulty": fixture.get("team_h_difficulty"),
|
|
132
|
+
"away_difficulty": fixture.get("team_a_difficulty"),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if params.detailed and fixture.get("stats"):
|
|
136
|
+
stats = {}
|
|
137
|
+
for stat in fixture.get("stats", []):
|
|
138
|
+
if hasattr(stat, "model_dump"):
|
|
139
|
+
stat_dict = stat.model_dump()
|
|
140
|
+
elif hasattr(stat, "__dict__"):
|
|
141
|
+
stat_dict = stat.__dict__
|
|
142
|
+
else:
|
|
143
|
+
stat_dict = stat
|
|
144
|
+
|
|
145
|
+
identifier = stat_dict.get("identifier")
|
|
146
|
+
if not identifier:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
stats[identifier] = {"h": [], "a": []}
|
|
150
|
+
|
|
151
|
+
# Process home stats
|
|
152
|
+
for item in stat_dict.get("h", []):
|
|
153
|
+
if hasattr(item, "value"):
|
|
154
|
+
value = item.value
|
|
155
|
+
element = item.element
|
|
156
|
+
else:
|
|
157
|
+
value = item.get("value")
|
|
158
|
+
element = item.get("element")
|
|
159
|
+
|
|
160
|
+
stats[identifier]["h"].append(
|
|
161
|
+
{
|
|
162
|
+
"name": store.get_player_name(element),
|
|
163
|
+
"value": value,
|
|
164
|
+
"element": element,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Process away stats
|
|
169
|
+
for item in stat_dict.get("a", []):
|
|
170
|
+
if hasattr(item, "value"):
|
|
171
|
+
value = item.value
|
|
172
|
+
element = item.element
|
|
173
|
+
else:
|
|
174
|
+
value = item.get("value")
|
|
175
|
+
element = item.get("element")
|
|
176
|
+
|
|
177
|
+
stats[identifier]["a"].append(
|
|
178
|
+
{
|
|
179
|
+
"name": store.get_player_name(element),
|
|
180
|
+
"value": value,
|
|
181
|
+
"element": element,
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
fixture_data["stats"] = stats
|
|
186
|
+
|
|
187
|
+
result["fixtures"].append(fixture_data)
|
|
188
|
+
|
|
134
189
|
return format_json_response(result)
|
|
135
190
|
else:
|
|
136
191
|
output = [
|
|
@@ -159,6 +214,91 @@ async def fpl_get_fixtures_for_gameweek(params: GetFixturesForGameweekInput) ->
|
|
|
159
214
|
f"Difficulty: H:{fixture.get('team_h_difficulty')} A:{fixture.get('team_a_difficulty')}"
|
|
160
215
|
)
|
|
161
216
|
|
|
217
|
+
if params.detailed and fixture.get("stats"):
|
|
218
|
+
stats_output = []
|
|
219
|
+
# Filter for interesting stats or show all? Showing all as requested.
|
|
220
|
+
# Common stats order: goals_scored, assists, own_goals, penalties_saved, penalties_missed, yellow_cards, red_cards, bonus, saves, bps
|
|
221
|
+
priority_stats = [
|
|
222
|
+
"goals_scored",
|
|
223
|
+
"assists",
|
|
224
|
+
"bonus",
|
|
225
|
+
"yellow_cards",
|
|
226
|
+
"red_cards",
|
|
227
|
+
"saves",
|
|
228
|
+
]
|
|
229
|
+
other_stats = []
|
|
230
|
+
for s in fixture.get("stats", []):
|
|
231
|
+
ident = s.identifier if hasattr(s, "identifier") else s.get("identifier")
|
|
232
|
+
|
|
233
|
+
if ident and ident not in priority_stats:
|
|
234
|
+
other_stats.append(ident)
|
|
235
|
+
all_stats = priority_stats + other_stats
|
|
236
|
+
|
|
237
|
+
for stat_name in all_stats:
|
|
238
|
+
# Find stat data
|
|
239
|
+
stat_data = None
|
|
240
|
+
for s in fixture.get("stats", []):
|
|
241
|
+
s_ident = (
|
|
242
|
+
s.identifier if hasattr(s, "identifier") else s.get("identifier")
|
|
243
|
+
)
|
|
244
|
+
if s_ident == stat_name:
|
|
245
|
+
stat_data = s
|
|
246
|
+
break
|
|
247
|
+
if not stat_data:
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
# Convert to dict access if needed
|
|
251
|
+
if hasattr(stat_data, "h"):
|
|
252
|
+
h_items = stat_data.h
|
|
253
|
+
a_items = stat_data.a
|
|
254
|
+
else:
|
|
255
|
+
h_items = stat_data.get("h", [])
|
|
256
|
+
a_items = stat_data.get("a", [])
|
|
257
|
+
|
|
258
|
+
if not h_items and not a_items:
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
# Format stat line: Stat: Name (val), Name (val) (H) | Name (val) (A)
|
|
262
|
+
h_strs = []
|
|
263
|
+
for item in h_items:
|
|
264
|
+
val = item.value if hasattr(item, "value") else item.get("value")
|
|
265
|
+
elem = item.element if hasattr(item, "element") else item.get("element")
|
|
266
|
+
h_strs.append(f"{store.get_player_name(elem)} ({val})")
|
|
267
|
+
|
|
268
|
+
a_strs = []
|
|
269
|
+
for item in a_items:
|
|
270
|
+
val = item.value if hasattr(item, "value") else item.get("value")
|
|
271
|
+
elem = item.element if hasattr(item, "element") else item.get("element")
|
|
272
|
+
a_strs.append(f"{store.get_player_name(elem)} ({val})")
|
|
273
|
+
|
|
274
|
+
stat_display = stat_name.replace("_", " ").title()
|
|
275
|
+
if stat_name == "goals_scored":
|
|
276
|
+
stat_display = "⚽ Goals"
|
|
277
|
+
elif stat_name == "assists":
|
|
278
|
+
stat_display = "🅰️ Assists"
|
|
279
|
+
elif stat_name == "bonus":
|
|
280
|
+
stat_display = "💎 Bonus"
|
|
281
|
+
elif stat_name == "yellow_cards":
|
|
282
|
+
stat_display = "🟨 Yellows"
|
|
283
|
+
elif stat_name == "red_cards":
|
|
284
|
+
stat_display = "🟥 Reds"
|
|
285
|
+
elif stat_name == "saves":
|
|
286
|
+
stat_display = "🧤 Saves"
|
|
287
|
+
|
|
288
|
+
if h_strs or a_strs:
|
|
289
|
+
line = f" - **{stat_display}**: "
|
|
290
|
+
if h_strs:
|
|
291
|
+
line += f"{', '.join(h_strs)} ({home_name})"
|
|
292
|
+
if h_strs and a_strs:
|
|
293
|
+
line += " | "
|
|
294
|
+
if a_strs:
|
|
295
|
+
line += f"{', '.join(a_strs)} ({away_name})"
|
|
296
|
+
stats_output.append(line)
|
|
297
|
+
|
|
298
|
+
if stats_output:
|
|
299
|
+
output.extend(stats_output)
|
|
300
|
+
output.append("") # Empty line separator
|
|
301
|
+
|
|
162
302
|
result = "\n".join(output)
|
|
163
303
|
truncated, _ = check_and_truncate(result, CHARACTER_LIMIT)
|
|
164
304
|
return truncated
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from unittest.mock import AsyncMock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from src.prompts.gameweek_analysis import gameweek_analysis
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.asyncio
|
|
9
|
+
async def test_gameweek_analysis_prompt():
|
|
10
|
+
"""Test that gameweek analysis prompt calls dependencies and formats output correctly."""
|
|
11
|
+
|
|
12
|
+
# Mock dependencies
|
|
13
|
+
with (
|
|
14
|
+
patch("src.prompts.gameweek_analysis.store") as mock_store,
|
|
15
|
+
patch(
|
|
16
|
+
"src.prompts.gameweek_analysis.fpl_get_fixtures_for_gameweek", new_callable=AsyncMock
|
|
17
|
+
) as mock_get_fixtures,
|
|
18
|
+
patch(
|
|
19
|
+
"src.prompts.gameweek_analysis.fpl_find_fixture_opportunities", new_callable=AsyncMock
|
|
20
|
+
) as mock_find_opportunities,
|
|
21
|
+
):
|
|
22
|
+
# Setup mock returns
|
|
23
|
+
mock_store.get_current_gameweek.return_value.id = 5
|
|
24
|
+
mock_get_fixtures.return_value = "Detailed Match Reports Content"
|
|
25
|
+
mock_find_opportunities.return_value = "Top Teams: Arsenal, Liverpool"
|
|
26
|
+
|
|
27
|
+
# Call prompt
|
|
28
|
+
result = await gameweek_analysis(gameweek=None)
|
|
29
|
+
|
|
30
|
+
# Verify calls
|
|
31
|
+
mock_store.get_current_gameweek.assert_called_once()
|
|
32
|
+
mock_get_fixtures.assert_called_once()
|
|
33
|
+
mock_find_opportunities.assert_called_once()
|
|
34
|
+
|
|
35
|
+
# Check call arguments
|
|
36
|
+
fixtures_call_arg = mock_get_fixtures.call_args[0][0]
|
|
37
|
+
assert fixtures_call_arg.gameweek == 5
|
|
38
|
+
assert fixtures_call_arg.detailed is True
|
|
39
|
+
|
|
40
|
+
ops_call_arg = mock_find_opportunities.call_args[0][0]
|
|
41
|
+
assert ops_call_arg.num_gameweeks == 5
|
|
42
|
+
assert ops_call_arg.max_teams == 5
|
|
43
|
+
|
|
44
|
+
# Verify output content
|
|
45
|
+
assert "Gameweek 5" in result
|
|
46
|
+
assert "Detailed Match Reports Content" in result
|
|
47
|
+
assert "Upcoming Fixture Context" in result
|
|
48
|
+
assert "Top Teams: Arsenal, Liverpool" in result
|
|
49
|
+
|
|
50
|
+
assert "cross-reference" in result.lower()
|
|
51
|
+
assert "fixture-proof" in result
|
|
52
|
+
assert "trap" in result.lower()
|
|
53
|
+
|
|
54
|
+
# Check for tool strategy
|
|
55
|
+
assert "Tool Calls Strategy" in result
|
|
56
|
+
assert "fpl_get_player_details" in result
|
|
57
|
+
assert "fpl_compare_players" in result
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_via_exec():
|
|
7
|
+
"""Test the prompt content by executing the file content directly, bypassing imports."""
|
|
8
|
+
file_path = os.path.abspath(
|
|
9
|
+
os.path.join(os.path.dirname(__file__), "../src/prompts/squad_analysis.py")
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
with open(file_path) as f:
|
|
13
|
+
content = f.read()
|
|
14
|
+
|
|
15
|
+
# Remove the relative import that causes issues in isolation
|
|
16
|
+
content = re.sub(r"from \.\.tools import mcp", "", content)
|
|
17
|
+
# Remove the decorator
|
|
18
|
+
content = re.sub(r"@mcp\.prompt\(\)", "", content)
|
|
19
|
+
|
|
20
|
+
# Execute in isolated scope
|
|
21
|
+
local_env = {}
|
|
22
|
+
try:
|
|
23
|
+
exec(content, {}, local_env)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"Failed to exec file: {e}")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
if "analyze_squad_performance" not in local_env:
|
|
29
|
+
print("Function analyze_squad_performance not found in executed code")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
analyze_func = local_env["analyze_squad_performance"]
|
|
33
|
+
|
|
34
|
+
# Test generation
|
|
35
|
+
team_id = 999999
|
|
36
|
+
prompt = analyze_func(team_id=team_id, num_gameweeks=5)
|
|
37
|
+
|
|
38
|
+
print("Prompt Preview:")
|
|
39
|
+
print("-" * 20)
|
|
40
|
+
print(prompt[:300] + "...")
|
|
41
|
+
print("-" * 20)
|
|
42
|
+
|
|
43
|
+
# Assertions
|
|
44
|
+
checks = [
|
|
45
|
+
f"team ID {team_id}",
|
|
46
|
+
"PRO-LEVEL transfer strategy",
|
|
47
|
+
"Strategic Context",
|
|
48
|
+
"Financial Health",
|
|
49
|
+
"Chip Status",
|
|
50
|
+
"fpl_get_manager_chips",
|
|
51
|
+
"fpl_find_fixture_opportunities",
|
|
52
|
+
"Scenario A: The Surgery",
|
|
53
|
+
"Scenario B: The Luxury Move",
|
|
54
|
+
"xGI Regression Model",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
failed = []
|
|
58
|
+
for check in checks:
|
|
59
|
+
if check not in prompt:
|
|
60
|
+
failed.append(check)
|
|
61
|
+
|
|
62
|
+
if failed:
|
|
63
|
+
print(f"❌ Verification Failed. Missing phrases: {failed}")
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
else:
|
|
66
|
+
print("✅ Verification Passed! All Pro features present.")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
test_via_exec()
|
|
@@ -321,6 +321,36 @@ class TestAdvancedToolsIntegration:
|
|
|
321
321
|
# Team 1 is Arsenal in mock_teams, short_name is ARS
|
|
322
322
|
assert "ARS" in result
|
|
323
323
|
|
|
324
|
+
async def test_fpl_get_fixtures_for_gameweek_detailed(self, session_store, mock_fixtures_data):
|
|
325
|
+
"""Test getting fixtures for gameweek with detailed stats."""
|
|
326
|
+
params = GetFixturesForGameweekInput(gameweek=2, detailed=True)
|
|
327
|
+
|
|
328
|
+
# Inject fixtures with stats
|
|
329
|
+
from src.models import FixtureData, FixtureStat, FixtureStatValue
|
|
330
|
+
|
|
331
|
+
fixtures = [FixtureData(**f) for f in mock_fixtures_data]
|
|
332
|
+
# Add stats to the first fixture
|
|
333
|
+
# Mocking Salah (ID 1) goal
|
|
334
|
+
fixtures[0].stats = [
|
|
335
|
+
FixtureStat(identifier="goals_scored", h=[FixtureStatValue(value=1, element=1)], a=[])
|
|
336
|
+
]
|
|
337
|
+
session_store.fixtures_data = fixtures
|
|
338
|
+
|
|
339
|
+
with patch(
|
|
340
|
+
"src.tools.fixtures._create_client", new_callable=AsyncMock
|
|
341
|
+
) as mock_create_client:
|
|
342
|
+
mock_client = AsyncMock()
|
|
343
|
+
mock_create_client.return_value = mock_client
|
|
344
|
+
|
|
345
|
+
result = await fpl_get_fixtures_for_gameweek(params)
|
|
346
|
+
|
|
347
|
+
assert "Gameweek 2" in result
|
|
348
|
+
assert "Goals" in result or "goals_scored" in result
|
|
349
|
+
# Should show player name (Salah from mock_players is ID 1)
|
|
350
|
+
# mock_players in conftest usually has ID 1 as Salah.
|
|
351
|
+
# session_store uses mock_players from conftest via fixture.
|
|
352
|
+
assert "Salah" in result
|
|
353
|
+
|
|
324
354
|
async def test_fpl_analyze_team_fixtures_difficulty(self, session_store, mock_fixtures_data):
|
|
325
355
|
"""Test upcoming fixtures difficulty display using analyze_team_fixtures."""
|
|
326
356
|
params = AnalyzeTeamFixturesInput(team_name="Arsenal", num_gameweeks=3)
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
FPL MCP Prompts - Squad Performance Analysis.
|
|
3
|
-
|
|
4
|
-
Prompts guide the LLM in analyzing squad performance over recent gameweeks
|
|
5
|
-
using underlying metrics and regression analysis.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from ..tools import mcp
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@mcp.prompt()
|
|
12
|
-
def analyze_squad_performance(team_id: int, num_gameweeks: int = 5) -> str:
|
|
13
|
-
"""
|
|
14
|
-
Analyze squad performance using xGI-based metrics and regression analysis.
|
|
15
|
-
|
|
16
|
-
This prompt guides the LLM to identify underperforming/overperforming players
|
|
17
|
-
using underlying stats (xG, xA, xGI) rather than retrospective points.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
team_id: FPL team ID of the manager to analyze
|
|
21
|
-
num_gameweeks: Number of recent gameweeks to analyze (default: 5)
|
|
22
|
-
"""
|
|
23
|
-
return f"""Analyze FPL squad performance for team ID {team_id} over the last {num_gameweeks} gameweeks.
|
|
24
|
-
|
|
25
|
-
**OBJECTIVE: Identify transfer targets using xGI-based regression analysis, not retrospective points.**
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 📊 **Performance Analysis Framework**
|
|
30
|
-
|
|
31
|
-
For each player in the squad, analyze:
|
|
32
|
-
|
|
33
|
-
### **1. Underlying Output Metrics (PRIMARY)**
|
|
34
|
-
- **xGI/90 (Expected Goal Involvements per 90 min)**: Total xG + xA over last {num_gameweeks} GW, normalized per 90
|
|
35
|
-
- **Minutes Played**: Total minutes + % of available minutes
|
|
36
|
-
*<60% = rotation risk*
|
|
37
|
-
- **Games Played vs DNP**: Count starts, sub appearances, did not plays
|
|
38
|
-
|
|
39
|
-
### **2. Regression Analysis (CRITICAL)**
|
|
40
|
-
Calculate **xGI Delta** for each player:
|
|
41
|
-
- `Actual G+A (last {num_gameweeks} GW)` MINUS `xG + xA (last {num_gameweeks} GW)`
|
|
42
|
-
|
|
43
|
-
**Interpretation:**
|
|
44
|
-
- **Positive Delta (+2 to +4)**: OVERPERFORMING → Likely to regress (sell candidate)
|
|
45
|
-
- **Negative Delta (-2 to -4)**: UNDERPERFORMING → Due for improvement (keep/monitor)
|
|
46
|
-
- **Near Zero (-1 to +1)**: Performing to expectation (stable)
|
|
47
|
-
|
|
48
|
-
### **3. Player Categorization (xGI-Based)**
|
|
49
|
-
|
|
50
|
-
Instead of arbitrary PPG thresholds, use xGI/90:
|
|
51
|
-
|
|
52
|
-
- ⭐ **Elite Assets** (xGI/90 >0.6): Premium output, essential to keep
|
|
53
|
-
*Even if underperforming actual points (negative delta), underlying stats suggest improvement coming*
|
|
54
|
-
|
|
55
|
-
- ✅ **Strong Contributors** (xGI/90 0.35-0.6): Reliable options, monitor for regression
|
|
56
|
-
*If positive delta >+2, consider selling before decline*
|
|
57
|
-
|
|
58
|
-
- ⚠️ **Moderate Assets** (xGI/90 0.15-0.35): Acceptable for budget slots
|
|
59
|
-
*If negative delta <-2, potential buy-low candidates*
|
|
60
|
-
|
|
61
|
-
- 🚨 **Underperformers** (xGI/90 <0.15): Transfer candidates
|
|
62
|
-
*Low underlying output + poor fixtures = priority sell*
|
|
63
|
-
|
|
64
|
-
**Defenders/Goalkeepers:**
|
|
65
|
-
- Use defensive contribution + clean sheet odds instead of xGI
|
|
66
|
-
- xGC (Expected Goals Conceded) if available
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## 🔍 **Transfer Priority Analysis**
|
|
71
|
-
|
|
72
|
-
### **For Each Underperformer (xGI/90 <0.15 OR injured):**
|
|
73
|
-
|
|
74
|
-
1. **Regression Context:**
|
|
75
|
-
- If negative xGI delta: "Unlucky, but underlying stats still poor → Sell"
|
|
76
|
-
- If positive xGI delta: "Overperforming low xGI → Definitely sell before regression"
|
|
77
|
-
|
|
78
|
-
2. **Availability Check:**
|
|
79
|
-
- Injured/Suspended → 🚨 **URGENT** (transfer immediately)
|
|
80
|
-
- DNP last 2 games → ⚠️ **HIGH** (rotation risk)
|
|
81
|
-
- Minutes <60% last {num_gameweeks} GW → 🟡 **MEDIUM**
|
|
82
|
-
|
|
83
|
-
3. **Fixture Difficulty (Next 4 GW):**
|
|
84
|
-
- Avg FDR >3.5 → Poor fixtures exacerbate low xGI
|
|
85
|
-
- Avg FDR <2.5 → Fixtures can't help poor underlying stats
|
|
86
|
-
|
|
87
|
-
4. **Ownership Context:**
|
|
88
|
-
- If template player (>30% ownership): May need to hold for rank protection
|
|
89
|
-
- If differential (<10% ownership): Easy sell, minimal rank impact
|
|
90
|
-
|
|
91
|
-
5. **Transfer Recommendation:**
|
|
92
|
-
- Provide urgency level: 🚨 URGENT / ⚠️ HIGH / 🟡 MEDIUM / 🟢 LOW
|
|
93
|
-
- Suggest xGI-based replacement targets from `fpl_get_top_performers`
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## 📈 **Squad Health Summary**
|
|
98
|
-
|
|
99
|
-
1. **Player Counts by Category:**
|
|
100
|
-
- Elite Assets (xGI/90 >0.6): [X] players
|
|
101
|
-
- Strong Contributors (xGI/90 0.35-0.6): [X] players
|
|
102
|
-
- Moderate Assets (xGI/90 0.15-0.35): [X] players
|
|
103
|
-
- Underperformers (xGI/90 <0.15): [X] players
|
|
104
|
-
|
|
105
|
-
2. **Regression Risk Summary:**
|
|
106
|
-
- Players with xGI Delta >+2 (overperforming): [List names] → Potential sell targets
|
|
107
|
-
- Players with xGI Delta <-2 (underperforming): [List names] → Monitor for improvement
|
|
108
|
-
|
|
109
|
-
3. **Priority Transfer Target:**
|
|
110
|
-
- **Player Name** (xGI/90: [X.XX], xGI Delta: [+/-X.X], Fixtures: [avg FDR])
|
|
111
|
-
- **Reason:** [injury / low xGI / rotation / regression risk]
|
|
112
|
-
- **Urgency:** [URGENT/HIGH/MEDIUM/LOW]
|
|
113
|
-
|
|
114
|
-
4. **Overall Squad Health:**
|
|
115
|
-
- Healthy (8+ strong contributors) / Moderate (5-7) / Poor (<5)
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## 🔧 **Tool Calls**
|
|
120
|
-
|
|
121
|
-
Use these tools and resources:
|
|
122
|
-
1. `fpl_get_manager_by_team_id(team_id={team_id})` → Current squad composition
|
|
123
|
-
2. `fpl_get_top_performers(num_gameweeks={num_gameweeks})` → Benchmark against top xGI players
|
|
124
|
-
3. For each player:
|
|
125
|
-
- `fpl://player/{{{{player_name}}}}/summary` → xG, xA, xGI, minutes, fixtures
|
|
126
|
-
4. `fpl://bootstrap/players` → Ownership %, price, transfer trends
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## ⚠️ **Critical Rules**
|
|
131
|
-
|
|
132
|
-
1. **NEVER categorize by PPG alone** → Use xGI/90 for attackers, defensive contribution for defenders
|
|
133
|
-
2. **ALWAYS calculate xGI Delta** → Regression context is critical for sell decisions
|
|
134
|
-
3. **Account for ownership** → Template players need more justification to sell
|
|
135
|
-
4. **Prioritize injured/unavailable** → These are auto-sell regardless of xGI
|
|
136
|
-
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|