fix: update roster labels to use Minor League and Injured List (#59)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m10s
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m10s
- Add section headers (Active Roster, Minor League, Injured List) to the main summary embed in _create_roster_embeds so each roster section is clearly labeled for users - Fix incorrect docstring in team_service.get_team_roster that had the shortil/longil mapping backwards Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f7a65706a1
commit
346e36bfc6
@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Team roster commands for Discord Bot v2.0
|
Team roster commands for Discord Bot v2.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@ -18,144 +19,173 @@ from views.embeds import EmbedTemplate, EmbedColors
|
|||||||
|
|
||||||
class TeamRosterCommands(commands.Cog):
|
class TeamRosterCommands(commands.Cog):
|
||||||
"""Team roster command handlers."""
|
"""Team roster command handlers."""
|
||||||
|
|
||||||
def __init__(self, bot: commands.Bot):
|
def __init__(self, bot: commands.Bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.logger = get_contextual_logger(f'{__name__}.TeamRosterCommands')
|
self.logger = get_contextual_logger(f"{__name__}.TeamRosterCommands")
|
||||||
self.logger.info("TeamRosterCommands cog initialized")
|
self.logger.info("TeamRosterCommands cog initialized")
|
||||||
|
|
||||||
@discord.app_commands.command(name="roster", description="Display team roster")
|
@discord.app_commands.command(name="roster", description="Display team roster")
|
||||||
@discord.app_commands.describe(
|
@discord.app_commands.describe(
|
||||||
abbrev="Team abbreviation (e.g., BSG, DEN, WV, etc.)",
|
abbrev="Team abbreviation (e.g., BSG, DEN, WV, etc.)",
|
||||||
roster_type="Roster week: current or next (defaults to current)"
|
roster_type="Roster week: current or next (defaults to current)",
|
||||||
|
)
|
||||||
|
@discord.app_commands.choices(
|
||||||
|
roster_type=[
|
||||||
|
discord.app_commands.Choice(name="Current Week", value="current"),
|
||||||
|
discord.app_commands.Choice(name="Next Week", value="next"),
|
||||||
|
]
|
||||||
)
|
)
|
||||||
@discord.app_commands.choices(roster_type=[
|
|
||||||
discord.app_commands.Choice(name="Current Week", value="current"),
|
|
||||||
discord.app_commands.Choice(name="Next Week", value="next")
|
|
||||||
])
|
|
||||||
@requires_team()
|
@requires_team()
|
||||||
@logged_command("/roster")
|
@logged_command("/roster")
|
||||||
async def team_roster(self, interaction: discord.Interaction, abbrev: str,
|
async def team_roster(
|
||||||
roster_type: str = "current"):
|
self,
|
||||||
|
interaction: discord.Interaction,
|
||||||
|
abbrev: str,
|
||||||
|
roster_type: str = "current",
|
||||||
|
):
|
||||||
"""Display team roster with position breakdowns."""
|
"""Display team roster with position breakdowns."""
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
|
|
||||||
# Get team by abbreviation
|
# Get team by abbreviation
|
||||||
team = await team_service.get_team_by_abbrev(abbrev, get_config().sba_season)
|
team = await team_service.get_team_by_abbrev(abbrev, get_config().sba_season)
|
||||||
|
|
||||||
if team is None:
|
if team is None:
|
||||||
self.logger.info("Team not found", team_abbrev=abbrev)
|
self.logger.info("Team not found", team_abbrev=abbrev)
|
||||||
embed = EmbedTemplate.error(
|
embed = EmbedTemplate.error(
|
||||||
title="Team Not Found",
|
title="Team Not Found",
|
||||||
description=f"No team found with abbreviation '{abbrev.upper()}'"
|
description=f"No team found with abbreviation '{abbrev.upper()}'",
|
||||||
)
|
)
|
||||||
await interaction.followup.send(embed=embed)
|
await interaction.followup.send(embed=embed)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get roster data
|
# Get roster data
|
||||||
roster_data = await team_service.get_team_roster(team.id, roster_type)
|
roster_data = await team_service.get_team_roster(team.id, roster_type)
|
||||||
|
|
||||||
if not roster_data:
|
if not roster_data:
|
||||||
embed = EmbedTemplate.error(
|
embed = EmbedTemplate.error(
|
||||||
title="Roster Not Available",
|
title="Roster Not Available",
|
||||||
description=f"No {roster_type} roster data available for {team.abbrev}"
|
description=f"No {roster_type} roster data available for {team.abbrev}",
|
||||||
)
|
)
|
||||||
await interaction.followup.send(embed=embed)
|
await interaction.followup.send(embed=embed)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create roster embeds
|
# Create roster embeds
|
||||||
embeds = await self._create_roster_embeds(team, roster_data, roster_type)
|
embeds = await self._create_roster_embeds(team, roster_data, roster_type)
|
||||||
|
|
||||||
# Send first embed and follow up with others if needed
|
# Send first embed and follow up with others if needed
|
||||||
await interaction.followup.send(embed=embeds[0])
|
await interaction.followup.send(embed=embeds[0])
|
||||||
for embed in embeds[1:]:
|
for embed in embeds[1:]:
|
||||||
await interaction.followup.send(embed=embed)
|
await interaction.followup.send(embed=embed)
|
||||||
|
|
||||||
async def _create_roster_embeds(self, team: Team, roster_data: Dict[str, Any],
|
async def _create_roster_embeds(
|
||||||
roster_type: str) -> List[discord.Embed]:
|
self, team: Team, roster_data: Dict[str, Any], roster_type: str
|
||||||
|
) -> List[discord.Embed]:
|
||||||
"""Create embeds for team roster data."""
|
"""Create embeds for team roster data."""
|
||||||
embeds = []
|
embeds = []
|
||||||
|
|
||||||
# Main roster embed
|
# Main roster embed
|
||||||
embed = EmbedTemplate.create_base_embed(
|
embed = EmbedTemplate.create_base_embed(
|
||||||
title=f"{team.abbrev} - {roster_type.title()} Week",
|
title=f"{team.abbrev} - {roster_type.title()} Week",
|
||||||
description=f"{team.lname} Roster Breakdown",
|
description=f"{team.lname} Roster Breakdown",
|
||||||
color=int(team.color, 16) if team.color else EmbedColors.PRIMARY
|
color=int(team.color, 16) if team.color else EmbedColors.PRIMARY,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Position counts for active roster
|
# Position counts for active roster
|
||||||
for key in ['active', 'longil', 'shortil']:
|
roster_titles = {
|
||||||
if key in roster_data:
|
"active": "Active Roster",
|
||||||
|
"longil": "Minor League",
|
||||||
|
"shortil": "Injured List",
|
||||||
|
}
|
||||||
|
for key in ["active", "longil", "shortil"]:
|
||||||
|
if key in roster_data:
|
||||||
this_roster = roster_data[key]
|
this_roster = roster_data[key]
|
||||||
|
|
||||||
players = this_roster.get('players')
|
embed.add_field(name=roster_titles[key], value="\u200b", inline=False)
|
||||||
if len(players) > 0:
|
|
||||||
this_team = players[0].get("team", {"id": "Unknown", "sname": "Unknown"})
|
|
||||||
|
|
||||||
embed.add_field(
|
players = this_roster.get("players")
|
||||||
name='Team (ID)',
|
if len(players) > 0:
|
||||||
value=f'{this_team.get("sname")} ({this_team.get("id")})',
|
this_team = players[0].get(
|
||||||
inline=True
|
"team", {"id": "Unknown", "sname": "Unknown"}
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Player Count',
|
name="Team (ID)",
|
||||||
value=f'{len(players)} Players'
|
value=f'{this_team.get("sname")} ({this_team.get("id")})',
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="Player Count", value=f"{len(players)} Players"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Total WAR
|
# Total WAR
|
||||||
total_war = this_roster.get('WARa', 0)
|
total_war = this_roster.get("WARa", 0)
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Total sWAR",
|
name="Total sWAR",
|
||||||
value=f"{total_war:.2f}" if isinstance(total_war, (int, float)) else str(total_war),
|
value=(
|
||||||
inline=True
|
f"{total_war:.2f}"
|
||||||
|
if isinstance(total_war, (int, float))
|
||||||
|
else str(total_war)
|
||||||
|
),
|
||||||
|
inline=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name='Position Counts',
|
name="Position Counts",
|
||||||
value=self._position_code_block(this_roster),
|
value=self._position_code_block(this_roster),
|
||||||
inline=False
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
embeds.append(embed)
|
embeds.append(embed)
|
||||||
|
|
||||||
# Create detailed player list embeds if there are players
|
# Create detailed player list embeds if there are players
|
||||||
for roster_name, roster_info in roster_data.items():
|
for roster_name, roster_info in roster_data.items():
|
||||||
if roster_name in ['active', 'longil', 'shortil'] and 'players' in roster_info:
|
if (
|
||||||
players = sorted(roster_info['players'], key=lambda player: player.get('wara', 0), reverse=True)
|
roster_name in ["active", "longil", "shortil"]
|
||||||
|
and "players" in roster_info
|
||||||
|
):
|
||||||
|
players = sorted(
|
||||||
|
roster_info["players"],
|
||||||
|
key=lambda player: player.get("wara", 0),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
if players:
|
if players:
|
||||||
player_embed = self._create_player_list_embed(
|
player_embed = self._create_player_list_embed(
|
||||||
team, roster_name, players
|
team, roster_name, players
|
||||||
)
|
)
|
||||||
embeds.append(player_embed)
|
embeds.append(player_embed)
|
||||||
|
|
||||||
return embeds
|
|
||||||
|
|
||||||
def _position_code_block(self, roster_data: dict) -> str:
|
|
||||||
return f'```\n C 1B 2B 3B SS\n' \
|
|
||||||
f' {roster_data.get("C", 0)} {roster_data.get("1B", 0)} {roster_data.get("2B", 0)} ' \
|
|
||||||
f'{roster_data.get("3B", 0)} {roster_data.get("SS", 0)}\n\nLF CF RF SP RP\n' \
|
|
||||||
f' {roster_data.get("LF", 0)} {roster_data.get("CF", 0)} {roster_data.get("RF", 0)} ' \
|
|
||||||
f'{roster_data.get("SP", 0)} {roster_data.get("RP", 0)}\n```'
|
|
||||||
|
|
||||||
def _create_player_list_embed(self, team: Team, roster_name: str,
|
return embeds
|
||||||
players: List[Dict[str, Any]]) -> discord.Embed:
|
|
||||||
|
def _position_code_block(self, roster_data: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"```\n C 1B 2B 3B SS\n"
|
||||||
|
f' {roster_data.get("C", 0)} {roster_data.get("1B", 0)} {roster_data.get("2B", 0)} '
|
||||||
|
f'{roster_data.get("3B", 0)} {roster_data.get("SS", 0)}\n\nLF CF RF SP RP\n'
|
||||||
|
f' {roster_data.get("LF", 0)} {roster_data.get("CF", 0)} {roster_data.get("RF", 0)} '
|
||||||
|
f'{roster_data.get("SP", 0)} {roster_data.get("RP", 0)}\n```'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _create_player_list_embed(
|
||||||
|
self, team: Team, roster_name: str, players: List[Dict[str, Any]]
|
||||||
|
) -> discord.Embed:
|
||||||
"""Create an embed with detailed player list."""
|
"""Create an embed with detailed player list."""
|
||||||
roster_titles = {
|
roster_titles = {
|
||||||
'active': 'Active Roster',
|
"active": "Active Roster",
|
||||||
'longil': 'Minor League',
|
"longil": "Minor League",
|
||||||
'shortil': 'Injured List'
|
"shortil": "Injured List",
|
||||||
}
|
}
|
||||||
|
|
||||||
embed = EmbedTemplate.create_base_embed(
|
embed = EmbedTemplate.create_base_embed(
|
||||||
title=f"{team.abbrev} - {roster_titles.get(roster_name, roster_name.title())}",
|
title=f"{team.abbrev} - {roster_titles.get(roster_name, roster_name.title())}",
|
||||||
color=int(team.color, 16) if team.color else EmbedColors.PRIMARY
|
color=int(team.color, 16) if team.color else EmbedColors.PRIMARY,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Group players by position for better organization
|
# Group players by position for better organization
|
||||||
batters = []
|
batters = []
|
||||||
pitchers = []
|
pitchers = []
|
||||||
|
|
||||||
for player in players:
|
for player in players:
|
||||||
try:
|
try:
|
||||||
this_player = Player.from_api_data(player)
|
this_player = Player.from_api_data(player)
|
||||||
@ -166,8 +196,11 @@ class TeamRosterCommands(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
batters.append(player_line)
|
batters.append(player_line)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Failed to create player from data: {e}", player_id=player.get('id'))
|
self.logger.warning(
|
||||||
|
f"Failed to create player from data: {e}",
|
||||||
|
player_id=player.get("id"),
|
||||||
|
)
|
||||||
|
|
||||||
# Add player lists to embed
|
# Add player lists to embed
|
||||||
if batters:
|
if batters:
|
||||||
# Split long lists into multiple fields if needed
|
# Split long lists into multiple fields if needed
|
||||||
@ -175,18 +208,18 @@ class TeamRosterCommands(commands.Cog):
|
|||||||
for i, chunk in enumerate(batter_chunks):
|
for i, chunk in enumerate(batter_chunks):
|
||||||
field_name = "Batters" if i == 0 else f"Batters (cont.)"
|
field_name = "Batters" if i == 0 else f"Batters (cont.)"
|
||||||
embed.add_field(name=field_name, value="\n".join(chunk), inline=True)
|
embed.add_field(name=field_name, value="\n".join(chunk), inline=True)
|
||||||
embed.add_field(name='', value='', inline=False)
|
embed.add_field(name="", value="", inline=False)
|
||||||
|
|
||||||
if pitchers:
|
if pitchers:
|
||||||
pitcher_chunks = self._chunk_list(pitchers, 16)
|
pitcher_chunks = self._chunk_list(pitchers, 16)
|
||||||
for i, chunk in enumerate(pitcher_chunks):
|
for i, chunk in enumerate(pitcher_chunks):
|
||||||
field_name = "Pitchers" if i == 0 else f"Pitchers (cont.)"
|
field_name = "Pitchers" if i == 0 else f"Pitchers (cont.)"
|
||||||
embed.add_field(name=field_name, value="\n".join(chunk), inline=False)
|
embed.add_field(name=field_name, value="\n".join(chunk), inline=False)
|
||||||
|
|
||||||
embed.set_footer(text=f"Total players: {len(players)}")
|
embed.set_footer(text=f"Total players: {len(players)}")
|
||||||
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
def _chunk_list(self, lst: List[str], chunk_size: int) -> List[List[str]]:
|
def _chunk_list(self, lst: List[str], chunk_size: int) -> List[List[str]]:
|
||||||
"""Split a list into chunks of specified size."""
|
"""Split a list into chunks of specified size."""
|
||||||
return [lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)]
|
return [lst[i : i + chunk_size] for i in range(0, len(lst), chunk_size)]
|
||||||
|
|||||||
@ -3,6 +3,7 @@ Team service for Discord Bot v2.0
|
|||||||
|
|
||||||
Handles team-related operations with roster management and league queries.
|
Handles team-related operations with roster management and league queries.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|
||||||
@ -12,13 +13,13 @@ from models.team import Team, RosterType
|
|||||||
from exceptions import APIException
|
from exceptions import APIException
|
||||||
from utils.decorators import cached_single_item
|
from utils.decorators import cached_single_item
|
||||||
|
|
||||||
logger = logging.getLogger(f'{__name__}.TeamService')
|
logger = logging.getLogger(f"{__name__}.TeamService")
|
||||||
|
|
||||||
|
|
||||||
class TeamService(BaseService[Team]):
|
class TeamService(BaseService[Team]):
|
||||||
"""
|
"""
|
||||||
Service for team-related operations.
|
Service for team-related operations.
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Team retrieval by ID, abbreviation, and season
|
- Team retrieval by ID, abbreviation, and season
|
||||||
- Manager-based team queries
|
- Manager-based team queries
|
||||||
@ -27,12 +28,12 @@ class TeamService(BaseService[Team]):
|
|||||||
- Season-specific team data
|
- Season-specific team data
|
||||||
- Standings integration
|
- Standings integration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize team service."""
|
"""Initialize team service."""
|
||||||
super().__init__(Team, 'teams')
|
super().__init__(Team, "teams")
|
||||||
logger.debug("TeamService initialized")
|
logger.debug("TeamService initialized")
|
||||||
|
|
||||||
@cached_single_item(ttl=1800) # 30-minute cache
|
@cached_single_item(ttl=1800) # 30-minute cache
|
||||||
async def get_team(self, team_id: int) -> Optional[Team]:
|
async def get_team(self, team_id: int) -> Optional[Team]:
|
||||||
"""
|
"""
|
||||||
@ -57,12 +58,12 @@ class TeamService(BaseService[Team]):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Unexpected error getting team {team_id}: {e}")
|
logger.error(f"Unexpected error getting team {team_id}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_teams_by_owner(
|
async def get_teams_by_owner(
|
||||||
self,
|
self,
|
||||||
owner_id: int,
|
owner_id: int,
|
||||||
season: Optional[int] = None,
|
season: Optional[int] = None,
|
||||||
roster_type: Optional[str] = None
|
roster_type: Optional[str] = None,
|
||||||
) -> List[Team]:
|
) -> List[Team]:
|
||||||
"""
|
"""
|
||||||
Get teams owned by a specific Discord user.
|
Get teams owned by a specific Discord user.
|
||||||
@ -80,10 +81,7 @@ class TeamService(BaseService[Team]):
|
|||||||
Allows caller to distinguish between "no teams" vs "error occurred"
|
Allows caller to distinguish between "no teams" vs "error occurred"
|
||||||
"""
|
"""
|
||||||
season = season or get_config().sba_season
|
season = season or get_config().sba_season
|
||||||
params = [
|
params = [("owner_id", str(owner_id)), ("season", str(season))]
|
||||||
('owner_id', str(owner_id)),
|
|
||||||
('season', str(season))
|
|
||||||
]
|
|
||||||
|
|
||||||
teams = await self.get_all_items(params=params)
|
teams = await self.get_all_items(params=params)
|
||||||
|
|
||||||
@ -92,19 +90,27 @@ class TeamService(BaseService[Team]):
|
|||||||
try:
|
try:
|
||||||
target_type = RosterType(roster_type)
|
target_type = RosterType(roster_type)
|
||||||
teams = [team for team in teams if team.roster_type() == target_type]
|
teams = [team for team in teams if team.roster_type() == target_type]
|
||||||
logger.debug(f"Filtered to {len(teams)} {roster_type} teams for owner {owner_id}")
|
logger.debug(
|
||||||
|
f"Filtered to {len(teams)} {roster_type} teams for owner {owner_id}"
|
||||||
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.warning(f"Invalid roster_type '{roster_type}' - returning all teams")
|
logger.warning(
|
||||||
|
f"Invalid roster_type '{roster_type}' - returning all teams"
|
||||||
|
)
|
||||||
|
|
||||||
if teams:
|
if teams:
|
||||||
logger.debug(f"Found {len(teams)} teams for owner {owner_id} in season {season}")
|
logger.debug(
|
||||||
|
f"Found {len(teams)} teams for owner {owner_id} in season {season}"
|
||||||
|
)
|
||||||
return teams
|
return teams
|
||||||
|
|
||||||
logger.debug(f"No teams found for owner {owner_id} in season {season}")
|
logger.debug(f"No teams found for owner {owner_id} in season {season}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@cached_single_item(ttl=1800) # 30-minute cache
|
@cached_single_item(ttl=1800) # 30-minute cache
|
||||||
async def get_team_by_owner(self, owner_id: int, season: Optional[int] = None) -> Optional[Team]:
|
async def get_team_by_owner(
|
||||||
|
self, owner_id: int, season: Optional[int] = None
|
||||||
|
) -> Optional[Team]:
|
||||||
"""
|
"""
|
||||||
Get the primary (Major League) team owned by a Discord user.
|
Get the primary (Major League) team owned by a Discord user.
|
||||||
|
|
||||||
@ -124,125 +130,129 @@ class TeamService(BaseService[Team]):
|
|||||||
Returns:
|
Returns:
|
||||||
Team instance or None if not found
|
Team instance or None if not found
|
||||||
"""
|
"""
|
||||||
teams = await self.get_teams_by_owner(owner_id, season, roster_type='ml')
|
teams = await self.get_teams_by_owner(owner_id, season, roster_type="ml")
|
||||||
return teams[0] if teams else None
|
return teams[0] if teams else None
|
||||||
|
|
||||||
async def get_team_by_abbrev(self, abbrev: str, season: Optional[int] = None) -> Optional[Team]:
|
async def get_team_by_abbrev(
|
||||||
|
self, abbrev: str, season: Optional[int] = None
|
||||||
|
) -> Optional[Team]:
|
||||||
"""
|
"""
|
||||||
Get team by abbreviation for a specific season.
|
Get team by abbreviation for a specific season.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
abbrev: Team abbreviation (e.g., 'NYY', 'BOS')
|
abbrev: Team abbreviation (e.g., 'NYY', 'BOS')
|
||||||
season: Season number (defaults to current season)
|
season: Season number (defaults to current season)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Team instance or None if not found
|
Team instance or None if not found
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
season = season or get_config().sba_season
|
season = season or get_config().sba_season
|
||||||
params = [
|
params = [("team_abbrev", abbrev.upper()), ("season", str(season))]
|
||||||
('team_abbrev', abbrev.upper()),
|
|
||||||
('season', str(season))
|
|
||||||
]
|
|
||||||
|
|
||||||
teams = await self.get_all_items(params=params)
|
teams = await self.get_all_items(params=params)
|
||||||
|
|
||||||
if teams:
|
if teams:
|
||||||
team = teams[0] # Should be unique per season
|
team = teams[0] # Should be unique per season
|
||||||
logger.debug(f"Found team {abbrev} for season {season}: {team.lname}")
|
logger.debug(f"Found team {abbrev} for season {season}: {team.lname}")
|
||||||
return team
|
return team
|
||||||
|
|
||||||
logger.debug(f"No team found for abbreviation '{abbrev}' in season {season}")
|
logger.debug(
|
||||||
|
f"No team found for abbreviation '{abbrev}' in season {season}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting team by abbreviation '{abbrev}': {e}")
|
logger.error(f"Error getting team by abbreviation '{abbrev}': {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_teams_by_season(self, season: int) -> List[Team]:
|
async def get_teams_by_season(self, season: int) -> List[Team]:
|
||||||
"""
|
"""
|
||||||
Get all teams for a specific season.
|
Get all teams for a specific season.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
season: Season number
|
season: Season number
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of teams in the season
|
List of teams in the season
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = [('season', str(season))]
|
params = [("season", str(season))]
|
||||||
|
|
||||||
teams = await self.get_all_items(params=params)
|
teams = await self.get_all_items(params=params)
|
||||||
logger.debug(f"Retrieved {len(teams)} teams for season {season}")
|
logger.debug(f"Retrieved {len(teams)} teams for season {season}")
|
||||||
return teams
|
return teams
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get teams for season {season}: {e}")
|
logger.error(f"Failed to get teams for season {season}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_teams_by_manager(self, manager_id: int, season: Optional[int] = None) -> List[Team]:
|
async def get_teams_by_manager(
|
||||||
|
self, manager_id: int, season: Optional[int] = None
|
||||||
|
) -> List[Team]:
|
||||||
"""
|
"""
|
||||||
Get teams managed by a specific manager.
|
Get teams managed by a specific manager.
|
||||||
|
|
||||||
Uses 'manager_id' query parameter which supports multiple manager matching.
|
Uses 'manager_id' query parameter which supports multiple manager matching.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
manager_id: Manager identifier
|
manager_id: Manager identifier
|
||||||
season: Season number (optional)
|
season: Season number (optional)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of teams managed by the manager
|
List of teams managed by the manager
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = [('manager_id', str(manager_id))]
|
params = [("manager_id", str(manager_id))]
|
||||||
|
|
||||||
if season:
|
if season:
|
||||||
params.append(('season', str(season)))
|
params.append(("season", str(season)))
|
||||||
|
|
||||||
teams = await self.get_all_items(params=params)
|
teams = await self.get_all_items(params=params)
|
||||||
logger.debug(f"Found {len(teams)} teams for manager {manager_id}")
|
logger.debug(f"Found {len(teams)} teams for manager {manager_id}")
|
||||||
return teams
|
return teams
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get teams for manager {manager_id}: {e}")
|
logger.error(f"Failed to get teams for manager {manager_id}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_teams_by_division(self, division_id: int, season: int) -> List[Team]:
|
async def get_teams_by_division(self, division_id: int, season: int) -> List[Team]:
|
||||||
"""
|
"""
|
||||||
Get teams in a specific division for a season.
|
Get teams in a specific division for a season.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
division_id: Division identifier
|
division_id: Division identifier
|
||||||
season: Season number
|
season: Season number
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of teams in the division
|
List of teams in the division
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
params = [
|
params = [("division_id", str(division_id)), ("season", str(season))]
|
||||||
('division_id', str(division_id)),
|
|
||||||
('season', str(season))
|
|
||||||
]
|
|
||||||
|
|
||||||
teams = await self.get_all_items(params=params)
|
teams = await self.get_all_items(params=params)
|
||||||
logger.debug(f"Retrieved {len(teams)} teams for division {division_id} in season {season}")
|
logger.debug(
|
||||||
|
f"Retrieved {len(teams)} teams for division {division_id} in season {season}"
|
||||||
|
)
|
||||||
return teams
|
return teams
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get teams for division {division_id}: {e}")
|
logger.error(f"Failed to get teams for division {division_id}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_team_roster(self, team_id: int, roster_type: str = 'current') -> Optional[Dict[str, Any]]:
|
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.
|
Get the roster for a team with position counts and player lists.
|
||||||
|
|
||||||
Returns roster data with active, shortil (minor league), and longil (injured list)
|
Returns roster data with active, shortil (injured list), and longil (minor league)
|
||||||
rosters. Each roster contains position counts and players sorted by descending WARa.
|
rosters. Each roster contains position counts and players sorted by descending WARa.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
team_id: Team identifier
|
team_id: Team identifier
|
||||||
roster_type: 'current' or 'next' roster
|
roster_type: 'current' or 'next' roster
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with roster structure:
|
Dictionary with roster structure:
|
||||||
{
|
{
|
||||||
@ -257,19 +267,19 @@ class TeamService(BaseService[Team]):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
data = await client.get(f'teams/{team_id}/roster/{roster_type}')
|
data = await client.get(f"teams/{team_id}/roster/{roster_type}")
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
logger.debug(f"Retrieved {roster_type} roster for team {team_id}")
|
logger.debug(f"Retrieved {roster_type} roster for team {team_id}")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
logger.debug(f"No roster data found for team {team_id}")
|
logger.debug(f"No roster data found for team {team_id}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get roster for team {team_id}: {e}")
|
logger.error(f"Failed to get roster for team {team_id}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def update_team(self, team_id: int, updates: dict) -> Optional[Team]:
|
async def update_team(self, team_id: int, updates: dict) -> Optional[Team]:
|
||||||
"""
|
"""
|
||||||
Update team information.
|
Update team information.
|
||||||
@ -287,52 +297,58 @@ class TeamService(BaseService[Team]):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to update team {team_id}: {e}")
|
logger.error(f"Failed to update team {team_id}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_team_standings_position(self, team_id: int, season: int) -> Optional[dict]:
|
async def get_team_standings_position(
|
||||||
|
self, team_id: int, season: int
|
||||||
|
) -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
Get team's standings information.
|
Get team's standings information.
|
||||||
|
|
||||||
Calls /standings/team/{team_id} endpoint which returns a Standings object.
|
Calls /standings/team/{team_id} endpoint which returns a Standings object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
team_id: Team identifier
|
team_id: Team identifier
|
||||||
season: Season number
|
season: Season number
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Standings object data for the team
|
Standings object data for the team
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
client = await self.get_client()
|
client = await self.get_client()
|
||||||
data = await client.get(f'standings/team/{team_id}', params=[('season', str(season))])
|
data = await client.get(
|
||||||
|
f"standings/team/{team_id}", params=[("season", str(season))]
|
||||||
|
)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
logger.debug(f"Retrieved standings for team {team_id}")
|
logger.debug(f"Retrieved standings for team {team_id}")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get standings for team {team_id}: {e}")
|
logger.error(f"Failed to get standings for team {team_id}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def is_valid_team_abbrev(self, abbrev: str, season: Optional[int] = None) -> bool:
|
async def is_valid_team_abbrev(
|
||||||
|
self, abbrev: str, season: Optional[int] = None
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if a team abbreviation is valid for a season.
|
Check if a team abbreviation is valid for a season.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
abbrev: Team abbreviation to validate
|
abbrev: Team abbreviation to validate
|
||||||
season: Season number (defaults to current)
|
season: Season number (defaults to current)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if the abbreviation is valid
|
True if the abbreviation is valid
|
||||||
"""
|
"""
|
||||||
team = await self.get_team_by_abbrev(abbrev, season)
|
team = await self.get_team_by_abbrev(abbrev, season)
|
||||||
return team is not None
|
return team is not None
|
||||||
|
|
||||||
async def get_current_season_teams(self) -> List[Team]:
|
async def get_current_season_teams(self) -> List[Team]:
|
||||||
"""
|
"""
|
||||||
Get all teams for the current season.
|
Get all teams for the current season.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of teams in current season
|
List of teams in current season
|
||||||
"""
|
"""
|
||||||
@ -340,4 +356,4 @@ class TeamService(BaseService[Team]):
|
|||||||
|
|
||||||
|
|
||||||
# Global service instance
|
# Global service instance
|
||||||
team_service = TeamService()
|
team_service = TeamService()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user