major-domo-v2/commands/league/standings.py
Cal Corum 7b41520054 CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules
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>
2025-08-28 15:32:38 -05:00

273 lines
10 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 services.standings_service import standings_service
from utils.logging import get_contextual_logger
from utils.decorators import logged_command
from constants import SBA_CURRENT_SEASON
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()
try:
search_season = season or 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)
except Exception as e:
error_msg = f"❌ Error retrieving standings: {str(e)}"
if interaction.response.is_done():
await interaction.followup.send(error_msg, ephemeral=True)
else:
await interaction.response.send_message(error_msg, ephemeral=True)
raise
@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()
try:
search_season = season or 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)
except Exception as e:
error_msg = f"❌ Error retrieving playoff picture: {str(e)}"
if interaction.response.is_done():
await interaction.followup.send(error_msg, ephemeral=True)
else:
await interaction.response.send_message(error_msg, ephemeral=True)
raise
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)
# 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 - Season {season}",
color=EmbedColors.PRIMARY
)
# Create standings table
standings_lines = []
for i, team in enumerate(teams, 1):
# Format team line
team_line = (
f"{i}. **{team.team.abbrev}** {team.wins}-{team.losses} "
f"({team.winning_percentage:.3f})"
)
# Add games behind if not first place
if team.div_gb is not None and team.div_gb > 0:
team_line += f" *{team.div_gb:.1f} GB*"
standings_lines.append(team_line)
embed.add_field(
name="Standings",
value="\n".join(standings_lines),
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"Run differential shown as +/- • 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"Updated standings • Season {season}")
return embed
async def setup(bot: commands.Bot):
"""Load the standings commands cog."""
await bot.add_cog(StandingsCommands(bot))