major-domo-v2/commands/league/standings.py

265 lines
9.7 KiB
Python

"""
League Standings Commands
Implements slash commands for displaying league standings and playoff picture.
"""
from typing import Optional
import discord
from discord.ext import commands
from config import get_config
from models.team import Team
from services.standings_service import standings_service
from utils.logging import get_contextual_logger
from utils.decorators import logged_command
from views.embeds import EmbedColors, EmbedTemplate
class StandingsCommands(commands.Cog):
"""League standings command handlers."""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.logger = get_contextual_logger(f'{__name__}.StandingsCommands')
@discord.app_commands.command(
name="standings",
description="Display league standings"
)
@discord.app_commands.describe(
season="Season to show standings for (defaults to current season)",
division="Show specific division only (optional)"
)
@logged_command("/standings")
async def standings(
self,
interaction: discord.Interaction,
season: Optional[int] = None,
division: Optional[str] = None
):
"""Display league standings by division."""
await interaction.response.defer()
search_season = season or get_config().sba_current_season
if division:
# Show specific division
await self._show_division_standings(interaction, search_season, division)
else:
# Show all divisions
await self._show_all_standings(interaction, search_season)
@discord.app_commands.command(
name="playoff-picture",
description="Display current playoff picture"
)
@discord.app_commands.describe(
season="Season to show playoff picture for (defaults to current season)"
)
@logged_command("/playoff-picture")
async def playoff_picture(
self,
interaction: discord.Interaction,
season: Optional[int] = None
):
"""Display playoff picture with division leaders and wild card race."""
await interaction.response.defer()
search_season = season or get_config().sba_current_season
self.logger.debug("Fetching playoff picture", season=search_season)
playoff_data = await standings_service.get_playoff_picture(search_season)
if not playoff_data["division_leaders"] and not playoff_data["wild_card"]:
await interaction.followup.send(
f"❌ No playoff data available for season {search_season}.",
ephemeral=True
)
return
embed = await self._create_playoff_picture_embed(playoff_data, search_season)
await interaction.followup.send(embed=embed)
async def _show_all_standings(self, interaction: discord.Interaction, season: int):
"""Show standings for all divisions."""
self.logger.debug("Fetching all division standings", season=season)
divisions = await standings_service.get_standings_by_division(season)
if not divisions:
await interaction.followup.send(
f"❌ No standings available for season {season}.",
ephemeral=True
)
return
embeds = []
# Create embed for each division
for div_name, teams in divisions.items():
if teams: # Only create embed if division has teams
embed = await self._create_division_embed(div_name, teams, season)
embeds.append(embed)
await interaction.followup.send(embeds=embeds)
# # Send first embed, then follow up with others
# if embeds:
# await interaction.followup.send(embed=embeds[0])
# # Send additional embeds as follow-ups
# for embed in embeds[1:]:
# await interaction.followup.send(embed=embed)
async def _show_division_standings(self, interaction: discord.Interaction, season: int, division: str):
"""Show standings for a specific division."""
self.logger.debug("Fetching division standings", season=season, division=division)
divisions = await standings_service.get_standings_by_division(season)
# Find matching division (case insensitive)
target_division = None
division_lower = division.lower()
for div_name, teams in divisions.items():
if division_lower in div_name.lower():
target_division = (div_name, teams)
break
if not target_division:
available = ", ".join(divisions.keys())
await interaction.followup.send(
f"❌ Division '{division}' not found. Available divisions: {available}",
ephemeral=True
)
return
div_name, teams = target_division
if not teams:
await interaction.followup.send(
f"❌ No teams found in {div_name} division.",
ephemeral=True
)
return
embed = await self._create_division_embed(div_name, teams, season)
await interaction.followup.send(embed=embed)
async def _create_division_embed(self, division_name: str, teams, season: int) -> discord.Embed:
"""Create an embed for a division's standings."""
embed = EmbedTemplate.create_base_embed(
title=f"{division_name} Division",
description=f'Season {season}',
color=EmbedColors.PRIMARY
)
lead_team = None
# Create standings table
standing_text = f'```\n# Team W-L Win% RD GB St'
for i, team in enumerate(teams, 1):
if lead_team is None:
lead_team = team.team
if lead_team.color is not None:
embed.color = int(lead_team.color, 16)
embed.description = f'Leader: {lead_team.lname}'
# Format team line
standing_text += f'\n{i}.{team.team.abbrev: >4} {team.wins: >2}-{team.losses: <2} {team.winning_percentage:.3f} {team.run_diff: >4}'
# Add games behind if not first place
if team.div_gb is not None and team.div_gb > 0:
standing_text += f' {team.div_gb:4.1f}'
else:
standing_text += f' '
standing_text += f' {team.streak_wl.upper()}{team.streak_num}'
standing_text += '\n```'
embed.add_field(
name="Standings",
value=standing_text,
inline=False
)
# # Add additional stats for top teams
# if len(teams) >= 3:
# stats_lines = []
# for team in teams[:3]: # Top 3 teams
# stats_line = (
# f"**{team.team.abbrev}**: "
# f"Home {team.home_record} • "
# f"Last 8: {team.last8_record} • "
# f"Streak: {team.current_streak}"
# )
# stats_lines.append(stats_line)
# embed.add_field(
# name="Recent Form (Top 3)",
# value="\n".join(stats_lines),
# inline=False
# )
embed.set_footer(text=f"Season {season}")
return embed
async def _create_playoff_picture_embed(self, playoff_data, season: int) -> discord.Embed:
"""Create playoff picture embed."""
embed = EmbedTemplate.create_base_embed(
title=f"🏅 Playoff Picture - Season {season}",
color=EmbedColors.SUCCESS
)
# Division Leaders
if playoff_data["division_leaders"]:
leaders_lines = []
for i, team in enumerate(playoff_data["division_leaders"], 1):
division = team.team.division.division_name if hasattr(team.team, 'division') and team.team.division else "Unknown"
leaders_lines.append(
f"{i}. **{team.team.abbrev}** {team.wins}-{team.losses} "
f"({team.winning_percentage:.3f}) - *{division}*"
)
embed.add_field(
name="🥇 Division Leaders",
value="\n".join(leaders_lines),
inline=False
)
# Wild Card Race
if playoff_data["wild_card"]:
wc_lines = []
for i, team in enumerate(playoff_data["wild_card"][:8], 1): # Top 8 wild card
wc_gb = team.wild_card_gb_display
wc_line = (
f"{i}. **{team.team.abbrev}** {team.wins}-{team.losses} "
f"({team.winning_percentage:.3f})"
)
# Add games behind info
if wc_gb != "-":
wc_line += f" *{wc_gb} GB*"
elif i <= 4:
wc_line += " *In playoffs*"
wc_lines.append(wc_line)
# Add playoff cutoff line after 4th team
if i == 4:
wc_lines.append("─────────── *Playoff Cutoff* ───────────")
embed.add_field(
name="🎯 Wild Card Race (Top 4 make playoffs)",
value="\n".join(wc_lines),
inline=False
)
embed.set_footer(text=f"Season {season}")
return embed
async def setup(bot: commands.Bot):
"""Load the standings commands cog."""
await bot.add_cog(StandingsCommands(bot))