fpl-mcp-server 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fpl-mcp-server
3
- Version: 0.1.7
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
- - **8 Strategy Prompts** - Structured templates for squad analysis, transfer planning, chip strategy, and captain selection
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
@@ -10,26 +10,27 @@ src/rate_limiter.py,sha256=GLk3ZRFFvEZxkZAQd-pZ7UxQdrAAUVch3pxe_aMU-J8,3450
10
10
  src/state.py,sha256=seyygRhlz-K1GtG80os34tnNJ6UkAFA2rVFgupZG2tY,17531
11
11
  src/utils.py,sha256=WhcWQIXpc1vIjU8hyrGDJyKJSlcbVoG938k_3UMDlCM,7340
12
12
  src/validators.py,sha256=aU36TUNYWb26fvZH27Xnryrp8gve9DM2phvy7vEnAi8,6891
13
- src/prompts/__init__.py,sha256=Sj7YgIL46wGrmkJq39rpJilPK3blK6oPI-hE2-lBdxY,535
13
+ src/prompts/__init__.py,sha256=AVJbJtW1A5mxntubqXLj9103WWigBITa7CxCoqAxX_w,572
14
14
  src/prompts/captain_recommendation.py,sha256=1FI69uS9wNkOZZNnenFBW_JXg9HKU4bEUmixTn-6GJ0,5706
15
15
  src/prompts/chips.py,sha256=zzv5bqr8HuUAkvXenonrTXVhwNYGMwH9OPSC-c-1Dtg,5524
16
+ src/prompts/gameweek_analysis.py,sha256=70ieDAhSCPuUK0yrEkEPdpTG9TpjHfnMpszdVDoMDBI,3417
16
17
  src/prompts/league_analysis.py,sha256=bQN-tVC5FmrZEKTIfwM0eLaNc8mia42Qr34o4kaSJ1g,8297
17
18
  src/prompts/player_analysis.py,sha256=7BgF_h0us_vxPC5JrqKPsMs-395xrUvfpW0VJ4Bgon8,5234
18
- src/prompts/squad_analysis.py,sha256=H3COvcHt7uqPyWMm_2SEaN2lgTgwA20lKuotVOdMe3I,5209
19
+ src/prompts/squad_analysis.py,sha256=JIDaGIWasDmbhrMpFwFy6MuKxZJFReo9Io8Kw_Ck98I,4266
19
20
  src/prompts/team_analysis.py,sha256=7ypoaTUvrQQeKsysrhdwbzMzjtI2KldB1ztfSGCZArE,4222
20
21
  src/prompts/team_selection.py,sha256=tDOiyQYTp-hyKlKVAdjGxZsr1xPfMgApWREjbMtNpXM,3847
21
22
  src/prompts/transfers.py,sha256=Gsfey4XmjyYYJcRFfoDl0oNZnAOGsRCt_Ro0ePv43o8,5543
22
23
  src/resources/__init__.py,sha256=i7nlLVSLtiIrLtOnyoMiK3KTFGEnct4LXApB4b6URFM,303
23
24
  src/resources/bootstrap.py,sha256=ViZsGYtr5YqiTtvM_YTkbCr6R6Z9vUBiVSGGI9wwI3s,6970
24
25
  src/tools/__init__.py,sha256=JjoMoMHrhFRMarpgtOS9AoS9604c0p-yFc0PXoITe-E,510
25
- src/tools/fixtures.py,sha256=B5K_jtp4MDqI_Wd3_6blEkDn2Qki_mym2WmDSrhzccs,13155
26
+ src/tools/fixtures.py,sha256=KTIeoETbhq1dBv76moVDviHgjdOnoUD0tm3Zek_xO9M,19725
26
27
  src/tools/gameweeks.py,sha256=7skxUC6HoCj9hFC0YbjsMceVIhvvjFOoSdApe9JDfP0,8126
27
28
  src/tools/leagues.py,sha256=uRUs2gC4Czj-S8qucomI5x4HQ-I7GxRZI4b3E6KObCM,38024
28
29
  src/tools/players.py,sha256=_H2LP9s_yE9mD7zgcgVK__cwc4wdcHo9iUe3R6Llyn0,29691
29
30
  src/tools/teams.py,sha256=DknehKi6HIu2TSlKBqg91VWNONxLP4NIy99cH5TrWqo,7924
30
31
  src/tools/transfers.py,sha256=zpg0ueCRC6MhKiafhU4-gI0SzEzFYGvlZ2EnXqGoJkU,29466
31
- fpl_mcp_server-0.1.7.dist-info/METADATA,sha256=m53_QwzJv3nkfvfsUgzE_urTaFhsX8jq4egiXr5bR8w,4788
32
- fpl_mcp_server-0.1.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
33
- fpl_mcp_server-0.1.7.dist-info/entry_points.txt,sha256=b3R5hBUMTLVnCGl07NfK7kyq9NCKtpn5Q8OsY79pMek,49
34
- fpl_mcp_server-0.1.7.dist-info/licenses/LICENSE,sha256=HCDOcdX83voRU2Eip214yj6P_tEyjVjCsCW_sixZFPw,1071
35
- fpl_mcp_server-0.1.7.dist-info/RECORD,,
32
+ fpl_mcp_server-0.2.0.dist-info/METADATA,sha256=dVb06qo0fSl5Op3sCAryWeTQUE69-kTUdDvURarciwY,4807
33
+ fpl_mcp_server-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
+ fpl_mcp_server-0.2.0.dist-info/entry_points.txt,sha256=b3R5hBUMTLVnCGl07NfK7kyq9NCKtpn5Q8OsY79pMek,49
35
+ fpl_mcp_server-0.2.0.dist-info/licenses/LICENSE,sha256=HCDOcdX83voRU2Eip214yj6P_tEyjVjCsCW_sixZFPw,1071
36
+ fpl_mcp_server-0.2.0.dist-info/RECORD,,
src/prompts/__init__.py CHANGED
@@ -8,6 +8,7 @@ from ..tools import mcp
8
8
  from . import (
9
9
  captain_recommendation, # noqa: F401
10
10
  chips, # noqa: F401
11
+ gameweek_analysis, # noqa: F401
11
12
  league_analysis, # noqa: F401
12
13
  player_analysis, # noqa: F401
13
14
  squad_analysis, # noqa: F401
@@ -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
+ """
@@ -22,115 +22,85 @@ def analyze_squad_performance(team_id: int, num_gameweeks: int = 5) -> str:
22
22
  """
23
23
  return f"""Analyze FPL squad performance for team ID {team_id} over the last {num_gameweeks} gameweeks.
24
24
 
25
- **OBJECTIVE: Identify transfer targets using xGI-based regression analysis, not retrospective points.**
25
+ **OBJECTIVE: Create a PRO-LEVEL transfer strategy using Underlying Stats (xGI), Fixture Swings, and Chip Strategy.**
26
26
 
27
27
  ---
28
28
 
29
- ## 📊 **Performance Analysis Framework**
29
+ ## 🏗️ **Step 1: Strategic Context (The "Manager's Eye")**
30
30
 
31
- For each player in the squad, analyze:
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".
32
42
 
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*
43
+ ---
54
44
 
55
- - **Strong Contributors** (xGI/90 0.35-0.6): Reliable options, monitor for regression
56
- *If positive delta >+2, consider selling before decline*
45
+ ## 📊 **Step 2: Player Performance Analysis (xGI Model)**
57
46
 
58
- - ⚠️ **Moderate Assets** (xGI/90 0.15-0.35): Acceptable for budget slots
59
- *If negative delta <-2, potential buy-low candidates*
47
+ Analyze each player using the **xGI Regression Model** (Output vs Expected):
60
48
 
61
- - 🚨 **Underperformers** (xGI/90 <0.15): Transfer candidates
62
- *Low underlying output + poor fixtures = priority sell*
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.
63
55
 
64
- **Defenders/Goalkeepers:**
65
- - Use defensive contribution + clean sheet odds instead of xGI
66
- - xGC (Expected Goals Conceded) if available
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.
67
61
 
68
62
  ---
69
63
 
70
- ## 🔍 **Transfer Priority Analysis**
64
+ ## 🔄 **Step 3: Transfer Planning (The "Next 3 Moves")**
71
65
 
72
- ### **For Each Underperformer (xGI/90 <0.15 OR injured):**
66
+ Instead of just "Buy X", provide a 3-Gameweek Plan:
73
67
 
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"
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}
77
72
 
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**
73
+ **Scenario B: The Luxury Move** (Squd is fine)
74
+ - Upgrade specific position or build bank for future premium.
82
75
 
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`
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).
94
80
 
95
81
  ---
96
82
 
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
83
+ ## 💡 **Recommendations & Targets**
104
84
 
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)
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).
116
89
 
117
90
  ---
118
91
 
119
- ## 🔧 **Tool Calls**
92
+ ## 🔧 **Tool Calls Strategy**
120
93
 
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
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.
127
99
 
128
100
  ---
129
101
 
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
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.
136
106
  """
src/tools/fixtures.py CHANGED
@@ -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