major-domo-v2/services/team_service.py
Cal Corum 620fa0ef2d CLAUDE: Initial commit for discord-app-v2 rebuild
Complete rebuild of the Discord bot with modern architecture including:
- Modular API client with proper error handling
- Clean separation of models, services, and commands
- Comprehensive test coverage with pytest
- Structured logging and configuration management
- Organized command structure for scalability

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 00:04:50 -05:00

266 lines
8.6 KiB
Python

"""
Team service for Discord Bot v2.0
Handles team-related operations with roster management and league queries.
"""
import logging
from typing import Optional, List, Dict, Any
from services.base_service import BaseService
from models.team import Team
from constants import SBA_CURRENT_SEASON
from exceptions import APIException
logger = logging.getLogger(f'{__name__}.TeamService')
class TeamService(BaseService[Team]):
"""
Service for team-related operations.
Features:
- Team retrieval by ID, abbreviation, and season
- Manager-based team queries
- Division and league organization
- Roster management with position counts and player lists
- Season-specific team data
- Standings integration
"""
def __init__(self):
"""Initialize team service."""
super().__init__(Team, 'teams')
logger.debug("TeamService initialized")
async def get_team(self, team_id: int) -> Optional[Team]:
"""
Get team by ID with error handling.
Args:
team_id: Unique team identifier
Returns:
Team instance or None if not found
"""
try:
return await self.get_by_id(team_id)
except APIException:
logger.error(f"Failed to get team {team_id}")
return None
except Exception as e:
logger.error(f"Unexpected error getting team {team_id}: {e}")
return None
async def get_team_by_abbrev(self, abbrev: str, season: Optional[int] = None) -> Optional[Team]:
"""
Get team by abbreviation for a specific season.
Args:
abbrev: Team abbreviation (e.g., 'NYY', 'BOS')
season: Season number (defaults to current season)
Returns:
Team instance or None if not found
"""
try:
season = season or SBA_CURRENT_SEASON
params = [
('abbrev', abbrev.upper()),
('season', str(season))
]
teams = await self.get_all_items(params=params)
if teams:
team = teams[0] # Should be unique per season
logger.debug(f"Found team {abbrev} for season {season}: {team.lname}")
return team
logger.debug(f"No team found for abbreviation '{abbrev}' in season {season}")
return None
except Exception as e:
logger.error(f"Error getting team by abbreviation '{abbrev}': {e}")
return None
async def get_teams_by_season(self, season: int) -> List[Team]:
"""
Get all teams for a specific season.
Args:
season: Season number
Returns:
List of teams in the season
"""
try:
params = [('season', str(season))]
teams = await self.get_all_items(params=params)
logger.debug(f"Retrieved {len(teams)} teams for season {season}")
return teams
except Exception as e:
logger.error(f"Failed to get teams for season {season}: {e}")
return []
async def get_teams_by_manager(self, manager_id: int, season: Optional[int] = None) -> List[Team]:
"""
Get teams managed by a specific manager.
Uses 'manager_id' query parameter which supports multiple manager matching.
Args:
manager_id: Manager identifier
season: Season number (optional)
Returns:
List of teams managed by the manager
"""
try:
params = [('manager_id', str(manager_id))]
if season:
params.append(('season', str(season)))
teams = await self.get_all_items(params=params)
logger.debug(f"Found {len(teams)} teams for manager {manager_id}")
return teams
except Exception as e:
logger.error(f"Failed to get teams for manager {manager_id}: {e}")
return []
async def get_teams_by_division(self, division_id: int, season: int) -> List[Team]:
"""
Get teams in a specific division for a season.
Args:
division_id: Division identifier
season: Season number
Returns:
List of teams in the division
"""
try:
params = [
('division_id', str(division_id)),
('season', str(season))
]
teams = await self.get_all_items(params=params)
logger.debug(f"Retrieved {len(teams)} teams for division {division_id} in season {season}")
return teams
except Exception as e:
logger.error(f"Failed to get teams for division {division_id}: {e}")
return []
async def get_team_roster(self, team_id: int, roster_type: str = 'current') -> Optional[Dict[str, Any]]:
"""
Get the roster for a team with position counts and player lists.
Returns roster data with active, shortil (minor league), and longil (injured list)
rosters. Each roster contains position counts and players sorted by descending WARa.
Args:
team_id: Team identifier
roster_type: 'current' or 'next' roster
Returns:
Dictionary with roster structure:
{
'active': {
'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0,
'players': [<Player objects>]
},
'shortil': { ... },
'longil': { ... }
}
"""
try:
client = await self.get_client()
data = await client.get(f'teams/{team_id}/roster/{roster_type}')
if data:
logger.debug(f"Retrieved {roster_type} roster for team {team_id}")
return data
logger.debug(f"No roster data found for team {team_id}")
return None
except Exception as e:
logger.error(f"Failed to get roster for team {team_id}: {e}")
return None
async def update_team(self, team_id: int, updates: dict) -> Optional[Team]:
"""
Update team information.
Args:
team_id: Team ID to update
updates: Dictionary of fields to update
Returns:
Updated team instance or None
"""
try:
return await self.update(team_id, updates)
except Exception as e:
logger.error(f"Failed to update team {team_id}: {e}")
return None
async def get_team_standings_position(self, team_id: int, season: int) -> Optional[dict]:
"""
Get team's standings information.
Calls /standings/team/{team_id} endpoint which returns a Standings object.
Args:
team_id: Team identifier
season: Season number
Returns:
Standings object data for the team
"""
try:
client = await self.get_client()
data = await client.get(f'standings/team/{team_id}', params=[('season', str(season))])
if data:
logger.debug(f"Retrieved standings for team {team_id}")
return data
return None
except Exception as e:
logger.error(f"Failed to get standings for team {team_id}: {e}")
return None
async def is_valid_team_abbrev(self, abbrev: str, season: Optional[int] = None) -> bool:
"""
Check if a team abbreviation is valid for a season.
Args:
abbrev: Team abbreviation to validate
season: Season number (defaults to current)
Returns:
True if the abbreviation is valid
"""
team = await self.get_team_by_abbrev(abbrev, season)
return team is not None
async def get_current_season_teams(self) -> List[Team]:
"""
Get all teams for the current season.
Returns:
List of teams in current season
"""
return await self.get_teams_by_season(SBA_CURRENT_SEASON)
# Global service instance
team_service = TeamService()