get_league_analytics
Analyze Fantasy Premier League mini-leagues to visualize performance trends, team compositions, and decision patterns for strategic insights.
Instructions
Get rich analytics for a Fantasy Premier League mini-league
Returns visualization-optimized data for various types of league analysis.
Args:
league_id: ID of the league to analyze
analysis_type: Type of analysis to perform:
- "overview": General league overview (default)
- "historical": Historical performance analysis
- "team_composition": Team composition analysis
- "decisions": Captain and transfer decision analysis
- "fixtures": Fixture difficulty comparison
start_gw: Starting gameweek (defaults to 1 or use "current-N" format)
end_gw: Ending gameweek (defaults to current)
Returns:
Rich analytics data structured for visualization
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| league_id | Yes | ||
| analysis_type | No | overview | |
| start_gw | No | ||
| end_gw | No |
Implementation Reference
- Main tool handler function 'get_league_analytics' decorated with @mcp.tool(). Includes type annotations serving as input schema and comprehensive docstring describing parameters and analysis types. Delegates to core implementation.async def get_league_analytics( league_id: int, analysis_type: str = "overview", start_gw: Optional[int] = None, end_gw: Optional[int] = None ) -> Dict[str, Any]: """Get rich analytics for a Fantasy Premier League mini-league Returns visualization-optimized data for various types of league analysis. Args: league_id: ID of the league to analyze analysis_type: Type of analysis to perform: - "overview": General league overview (default) - "historical": Historical performance analysis - "team_composition": Team composition analysis - "decisions": Captain and transfer decision analysis - "fixtures": Fixture difficulty comparison start_gw: Starting gameweek (defaults to 1 or use "current-N" format) end_gw: Ending gameweek (defaults to current) Returns: Rich analytics data structured for visualization """ return await _get_league_analytics(league_id, analysis_type, start_gw, end_gw)
- Core helper function '_get_league_analytics' containing the primary execution logic: input validation, gameweek processing, league data fetching, and routing to specialized analysis based on analysis_type.async def _get_league_analytics( league_id: int, analysis_type: str = "overview", start_gw: Optional[int] = None, end_gw: Optional[int] = None ) -> Dict[str, Any]: """ Get rich analytics for a Fantasy Premier League mini-league Returns visualization-optimized data for various types of league analysis. Args: league_id: ID of the league to analyze analysis_type: Type of analysis to perform: - "overview": General league overview (default) - "historical": Historical performance analysis - "team_composition": Team composition analysis - "decisions": Captain and transfer decision analysis - "fixtures": Fixture difficulty comparison start_gw: Starting gameweek (defaults to 1) end_gw: Ending gameweek (defaults to current) Returns: Rich analytics data structured for visualization """ # Add logging for debugging logger.info(f"Starting league analytics: {analysis_type} for league {league_id}") # Validate analysis type valid_types = ["overview", "historical", "team_composition", "decisions", "fixtures"] if analysis_type not in valid_types: return { "error": f"Invalid analysis type: {analysis_type}", "valid_types": valid_types } # Get current gameweek try: current_gw_data = await api.get_current_gameweek() current_gw = current_gw_data.get("id", 1) logger.info(f"Current gameweek: {current_gw}") except Exception as e: logger.error(f"Error getting current gameweek: {e}") current_gw = 1 # Use the configured limit for all analysis types logger.info(f"Using configured limit of {LEAGUE_RESULTS_LIMIT} teams for {analysis_type} analysis") # Process gameweek range to ensure it's not too large effective_start_gw = start_gw effective_end_gw = end_gw # Handle start gameweek - using a consistent default (last 5 gameweeks) for all analysis types DEFAULT_GW_LOOKBACK = 5 if effective_start_gw is None: effective_start_gw = max(1, current_gw - DEFAULT_GW_LOOKBACK + 1) logger.info(f"Using default start gameweek: {effective_start_gw}") elif isinstance(effective_start_gw, str) and effective_start_gw.startswith("current-"): try: offset = int(effective_start_gw.split("-")[1]) effective_start_gw = max(1, current_gw - offset) logger.info(f"Parsed relative start gameweek: {effective_start_gw}") except ValueError: effective_start_gw = max(1, current_gw - DEFAULT_GW_LOOKBACK + 1) logger.info(f"Invalid relative start gameweek, using default: {effective_start_gw}") # Handle end gameweek if effective_end_gw is None or effective_end_gw == "current": effective_end_gw = current_gw logger.info(f"Using current end gameweek: {effective_end_gw}") elif isinstance(effective_end_gw, str) and effective_end_gw.startswith("current-"): try: offset = int(effective_end_gw.split("-")[1]) effective_end_gw = max(1, current_gw - offset) logger.info(f"Parsed relative end gameweek: {effective_end_gw}") except ValueError: effective_end_gw = current_gw logger.info(f"Invalid relative end gameweek, using current: {effective_end_gw}") # Convert to integers if necessary try: effective_start_gw = int(effective_start_gw) effective_end_gw = int(effective_end_gw) except (ValueError, TypeError): logger.error(f"Invalid gameweek values: start={effective_start_gw}, end={effective_end_gw}") return {"error": "Invalid gameweek values"} # Ensure the range is valid and not too large if effective_start_gw < 1: effective_start_gw = 1 if effective_end_gw > current_gw: effective_end_gw = current_gw if effective_start_gw > effective_end_gw: effective_start_gw, effective_end_gw = effective_end_gw, effective_start_gw # Apply consistent gameweek range limit to prevent performance issues gw_range = effective_end_gw - effective_start_gw + 1 # MAX_GW_RANGE = 5 # Use a consistent max range for all analysis types # if gw_range > MAX_GW_RANGE: # logger.info(f"Reducing gameweek range from {gw_range} to {MAX_GW_RANGE}") # effective_start_gw = max(1, effective_end_gw - MAX_GW_RANGE + 1) # logger.info(f"Final gameweek range: {effective_start_gw} to {effective_end_gw}") # Get league standings first logger.info(f"Fetching league standings for league {league_id}") try: # Don't check size limit, just fetch all and filter league_data = await _get_league_standings(league_id) # Check for errors if "error" in league_data: logger.error(f"Error getting league standings: {league_data['error']}") return league_data logger.info(f"Successfully fetched standings for {len(league_data['standings'])} teams") except Exception as e: logger.error(f"Exception getting league standings: {e}") return {"error": f"Failed to get league standings: {str(e)}"} # Route to the appropriate analysis function (with timeout protection) try: if analysis_type == "overview" or analysis_type == "historical": # For overview analysis, use the regular function but with reduced range return await _get_league_historical_performance( league_id, effective_start_gw, effective_end_gw ) elif analysis_type == "team_composition": # For team composition, use specified gameweek # Previously this only used end_gw, but we'll now pass both for consistency return await _get_league_team_composition( league_id, effective_end_gw ) elif analysis_type == "decisions": # For decisions, use our new simplified analysis function return await get_simplified_league_decision_analysis( league_id, effective_start_gw, effective_end_gw, _get_league_standings, get_teams_historical_data, league_data=league_data # Pass league data to avoid fetching again ) elif analysis_type == "fixtures": # Call the league fixture analysis function return await _get_league_fixture_analysis( league_id, effective_start_gw, effective_end_gw ) except Exception as e: logger.error(f"Error in league analytics: {e}") return { "error": f"Analysis failed: {str(e)}", "league_info": league_data["league_info"], "standings": league_data["standings"], "status": "error" } # This shouldn't happen due to earlier validation return {"error": "Unknown analysis type"}
- src/fpl_mcp/fpl/tools/leagues.py:1115-1156 (registration)Module-level registration function 'register_tools(mcp)' that defines and registers the 'get_league_analytics' tool (and get_league_standings) using the @mcp.tool() decorator. This function is imported and called from tools/__init__.py.def register_tools(mcp): """Register league analytics tools with the MCP server""" @mcp.tool() async def get_league_standings(league_id: int) -> Dict[str, Any]: """Get standings for a specified FPL league Args: league_id: ID of the league to fetch Returns: League information with standings and team details """ # When directly using the tool, enforce size check return await _get_league_standings(league_id) @mcp.tool() async def get_league_analytics( league_id: int, analysis_type: str = "overview", start_gw: Optional[int] = None, end_gw: Optional[int] = None ) -> Dict[str, Any]: """Get rich analytics for a Fantasy Premier League mini-league Returns visualization-optimized data for various types of league analysis. Args: league_id: ID of the league to analyze analysis_type: Type of analysis to perform: - "overview": General league overview (default) - "historical": Historical performance analysis - "team_composition": Team composition analysis - "decisions": Captain and transfer decision analysis - "fixtures": Fixture difficulty comparison start_gw: Starting gameweek (defaults to 1 or use "current-N" format) end_gw: Ending gameweek (defaults to current) Returns: Rich analytics data structured for visualization """ return await _get_league_analytics(league_id, analysis_type, start_gw, end_gw)