Major Features Added: • Admin Management System: Complete admin command suite with user moderation, system control, and bot maintenance tools • Enhanced Player Commands: Added batting/pitching statistics with concurrent API calls and improved embed design • League Standings: Full standings system with division grouping, playoff picture, and wild card visualization • Game Schedules: Comprehensive schedule system with team filtering, series organization, and proper home/away indicators New Admin Commands (12 total): • /admin-status, /admin-help, /admin-reload, /admin-sync, /admin-clear • /admin-announce, /admin-maintenance • /admin-timeout, /admin-untimeout, /admin-kick, /admin-ban, /admin-unban, /admin-userinfo Enhanced Player Display: • Team logo positioned beside player name using embed author • Smart thumbnail priority: fancycard → headshot → team logo fallback • Concurrent batting/pitching stats fetching for performance • Rich statistics display with team colors and comprehensive metrics New Models & Services: • BattingStats, PitchingStats, TeamStandings, Division, Game models • StatsService, StandingsService, ScheduleService for data management • CustomCommand system with CRUD operations and cleanup tasks Bot Architecture Improvements: • Admin commands integrated into bot.py with proper loading • Permission checks and safety guards for moderation commands • Enhanced error handling and comprehensive audit logging • All 227 tests passing with new functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
154 lines
5.3 KiB
Python
154 lines
5.3 KiB
Python
"""
|
|
Statistics service for Discord Bot v2.0
|
|
|
|
Handles batting and pitching statistics retrieval and processing.
|
|
"""
|
|
import logging
|
|
from typing import Optional, List
|
|
|
|
from services.base_service import BaseService
|
|
from models.batting_stats import BattingStats
|
|
from models.pitching_stats import PitchingStats
|
|
from exceptions import APIException
|
|
|
|
logger = logging.getLogger(f'{__name__}.StatsService')
|
|
|
|
|
|
class StatsService:
|
|
"""
|
|
Service for player statistics operations.
|
|
|
|
Features:
|
|
- Batting statistics retrieval
|
|
- Pitching statistics retrieval
|
|
- Season-specific filtering
|
|
- Error handling and logging
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize stats service."""
|
|
# We don't inherit from BaseService since we need custom endpoints
|
|
from api.client import get_global_client
|
|
self._get_client = get_global_client
|
|
logger.debug("StatsService initialized")
|
|
|
|
async def get_client(self):
|
|
"""Get the API client."""
|
|
return await self._get_client()
|
|
|
|
async def get_batting_stats(self, player_id: int, season: int) -> Optional[BattingStats]:
|
|
"""
|
|
Get batting statistics for a player in a specific season.
|
|
|
|
Args:
|
|
player_id: Player ID
|
|
season: Season number
|
|
|
|
Returns:
|
|
BattingStats instance or None if not found
|
|
"""
|
|
try:
|
|
client = await self.get_client()
|
|
|
|
# Call the batting stats view endpoint
|
|
params = [
|
|
('player_id', str(player_id)),
|
|
('season', str(season))
|
|
]
|
|
|
|
response = await client.get('views/season-stats/batting', params=params)
|
|
|
|
if not response or 'stats' not in response:
|
|
logger.debug(f"No batting stats found for player {player_id}, season {season}")
|
|
return None
|
|
|
|
stats_list = response['stats']
|
|
if not stats_list:
|
|
logger.debug(f"Empty batting stats for player {player_id}, season {season}")
|
|
return None
|
|
|
|
# Take the first (should be only) result
|
|
stats_data = stats_list[0]
|
|
|
|
batting_stats = BattingStats.from_api_data(stats_data)
|
|
logger.debug(f"Retrieved batting stats for player {player_id}: {batting_stats.avg:.3f} AVG")
|
|
return batting_stats
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting batting stats for player {player_id}: {e}")
|
|
return None
|
|
|
|
async def get_pitching_stats(self, player_id: int, season: int) -> Optional[PitchingStats]:
|
|
"""
|
|
Get pitching statistics for a player in a specific season.
|
|
|
|
Args:
|
|
player_id: Player ID
|
|
season: Season number
|
|
|
|
Returns:
|
|
PitchingStats instance or None if not found
|
|
"""
|
|
try:
|
|
client = await self.get_client()
|
|
|
|
# Call the pitching stats view endpoint
|
|
params = [
|
|
('player_id', str(player_id)),
|
|
('season', str(season))
|
|
]
|
|
|
|
response = await client.get('views/season-stats/pitching', params=params)
|
|
|
|
if not response or 'stats' not in response:
|
|
logger.debug(f"No pitching stats found for player {player_id}, season {season}")
|
|
return None
|
|
|
|
stats_list = response['stats']
|
|
if not stats_list:
|
|
logger.debug(f"Empty pitching stats for player {player_id}, season {season}")
|
|
return None
|
|
|
|
# Take the first (should be only) result
|
|
stats_data = stats_list[0]
|
|
|
|
pitching_stats = PitchingStats.from_api_data(stats_data)
|
|
logger.debug(f"Retrieved pitching stats for player {player_id}: {pitching_stats.era:.2f} ERA")
|
|
return pitching_stats
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting pitching stats for player {player_id}: {e}")
|
|
return None
|
|
|
|
async def get_player_stats(self, player_id: int, season: int) -> tuple[Optional[BattingStats], Optional[PitchingStats]]:
|
|
"""
|
|
Get both batting and pitching statistics for a player.
|
|
|
|
Args:
|
|
player_id: Player ID
|
|
season: Season number
|
|
|
|
Returns:
|
|
Tuple of (batting_stats, pitching_stats) - either can be None
|
|
"""
|
|
try:
|
|
# Get both types of stats concurrently
|
|
batting_task = self.get_batting_stats(player_id, season)
|
|
pitching_task = self.get_pitching_stats(player_id, season)
|
|
|
|
batting_stats = await batting_task
|
|
pitching_stats = await pitching_task
|
|
|
|
logger.debug(f"Retrieved stats for player {player_id}: "
|
|
f"batting={'yes' if batting_stats else 'no'}, "
|
|
f"pitching={'yes' if pitching_stats else 'no'}")
|
|
|
|
return batting_stats, pitching_stats
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting player stats for {player_id}: {e}")
|
|
return None, None
|
|
|
|
|
|
# Global service instance
|
|
stats_service = StatsService() |