""" Roster service for Discord Bot v2.0 Handles roster operations and validation. """ import logging from typing import Optional, List, Dict from models.roster import TeamRoster from models.player import Player from models.transaction import RosterValidation from exceptions import APIException logger = logging.getLogger(f"{__name__}.RosterService") class RosterService: """Service for roster operations and validation.""" def __init__(self): """Initialize roster service.""" from api.client import get_global_client self._get_client = get_global_client logger.debug("RosterService initialized") async def get_client(self): """Get the API client.""" return await self._get_client() async def get_team_roster( self, team_id: int, week_type: str = "current" ) -> Optional[TeamRoster]: """ Get team roster for current or next week. Args: team_id: Team ID from database week_type: "current" or "next" Returns: TeamRoster object or None if not found """ try: client = await self.get_client() # Use the team roster endpoint roster_data = await client.get(f"teams/{team_id}/roster/{week_type}") if not roster_data: logger.warning( f"No roster data found for team {team_id}, week {week_type}" ) return None # Add team metadata if not present if "team_id" not in roster_data: roster_data["team_id"] = team_id # Determine week number (this might need adjustment based on API) roster_data.setdefault("week", 0) # Will need current week info roster_data.setdefault("season", 12) # Will need current season info roster = TeamRoster.from_api_data(roster_data) logger.debug(f"Retrieved roster for team {team_id}, {week_type} week") return roster except Exception as e: logger.error(f"Error getting roster for team {team_id}: {e}") raise APIException(f"Failed to retrieve roster: {e}") async def get_current_roster(self, team_id: int) -> Optional[TeamRoster]: """Get current week roster.""" return await self.get_team_roster(team_id, "current") async def get_next_roster(self, team_id: int) -> Optional[TeamRoster]: """Get next week roster.""" return await self.get_team_roster(team_id, "next") async def validate_roster(self, roster: TeamRoster) -> RosterValidation: """ Validate roster for legality according to league rules. Args: roster: TeamRoster to validate Returns: RosterValidation with results """ try: validation = RosterValidation( is_legal=True, total_players=roster.total_players, active_players=roster.active_count, il_players=roster.il_count, minor_league_players=roster.minor_league_count, total_sWAR=roster.total_wara, ) # Validate active roster size (typical limits) if roster.active_count > 25: # Adjust based on league rules validation.is_legal = False validation.errors.append( f"Too many active players: {roster.active_count}/25" ) elif roster.active_count < 20: # Minimum active roster validation.warnings.append( f"Low active player count: {roster.active_count}" ) # Validate total roster size if roster.total_players > 50: # Adjust based on league rules validation.is_legal = False validation.errors.append( f"Total roster too large: {roster.total_players}/50" ) # Position requirements validation position_counts = self._count_positions(roster.active_players) # Check catcher requirement (at least 2 catchers) if position_counts.get("C", 0) < 2: validation.warnings.append("Fewer than 2 catchers on active roster") # Check pitcher requirements (at least 10 pitchers) pitcher_count = ( position_counts.get("SP", 0) + position_counts.get("RP", 0) + position_counts.get("P", 0) ) if pitcher_count < 10: validation.warnings.append( f"Fewer than 10 pitchers on active roster: {pitcher_count}" ) # WARA validation (if there are limits) if validation.total_sWAR > 100: # Adjust based on league rules validation.warnings.append( f"High WARA total: {validation.total_sWAR:.2f}" ) elif validation.total_sWAR < 20: validation.warnings.append( f"Low WARA total: {validation.total_sWAR:.2f}" ) logger.debug( f"Validated roster: legal={validation.is_legal}, {len(validation.errors)} errors, {len(validation.warnings)} warnings" ) return validation except Exception as e: logger.error(f"Error validating roster: {e}") return RosterValidation( is_legal=False, errors=[f"Validation error: {str(e)}"] ) def _count_positions(self, players: List[Player]) -> Dict[str, int]: """Count players by position.""" position_counts = {} for player in players: pos = player.primary_position position_counts[pos] = position_counts.get(pos, 0) + 1 return position_counts async def get_roster_summary(self, roster: TeamRoster) -> Dict[str, any]: """ Get a summary of roster composition. Args: roster: TeamRoster to summarize Returns: Dictionary with roster summary information """ try: position_counts = self._count_positions(roster.active_players) # Group positions catchers = position_counts.get("C", 0) infielders = sum( position_counts.get(pos, 0) for pos in ["1B", "2B", "3B", "SS", "IF"] ) outfielders = sum( position_counts.get(pos, 0) for pos in ["LF", "CF", "RF", "OF"] ) pitchers = sum(position_counts.get(pos, 0) for pos in ["SP", "RP", "P"]) dh = position_counts.get("DH", 0) summary = { "total_active": roster.active_count, "total_il": roster.il_count, "total_minor": roster.minor_league_count, "total_wara": roster.total_wara, "positions": { "catchers": catchers, "infielders": infielders, "outfielders": outfielders, "pitchers": pitchers, "dh": dh, }, "detailed_positions": position_counts, } return summary except Exception as e: logger.error(f"Error creating roster summary: {e}") return {} # Global service instance roster_service = RosterService()