Add @cached_single_item decorator to get_team_by_owner() and get_team()
methods with 30-minute TTL. These methods are called on every command
for GM validation, reducing API calls by ~80% during active usage.
- Uses @cached_single_item (not @cached_api_call) since methods return Optional[Team]
- New convenience method get_team_by_owner() for single-team GM validation
- Cache keys: team:owner:{season}:{owner_id} and team🆔{team_id}
- get_teams_by_owner() remains uncached as it returns List[Team]
- Updated CLAUDE.md with caching strategy and future invalidation patterns
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Voice cleanup service used manual while loop instead of @tasks.loop
- Did not wait for bot readiness before starting
- Startup verification could miss stale entries
- Manual channel deletions did not unpublish associated scorecards
Changes:
- Refactored VoiceChannelCleanupService to use @tasks.loop(minutes=1)
- Added @before_loop decorator with await bot.wait_until_ready()
- Updated bot.py to use setup_voice_cleanup() pattern
- Fixed scorecard unpublishing for manually deleted channels
- Fixed scorecard unpublishing for wrong channel type scenarios
- Updated cleanup interval from 60 seconds to 1 minute (same behavior)
- Changed cleanup reason message from "15+ minutes" to "5+ minutes" (matches actual threshold)
Benefits:
- Safe startup: cleanup waits for bot to be fully ready
- Reliable stale cleanup: startup verification guaranteed to run
- Complete cleanup: scorecards unpublished in all scenarios
- Consistent pattern: follows same pattern as other background tasks
- Better error handling: integrated with discord.py task lifecycle
Testing:
- All 19 voice command tests passing
- Updated test fixtures to handle new task pattern
- Fixed test assertions for new cleanup reason message
Documentation:
- Updated commands/voice/CLAUDE.md with new architecture
- Documented all four cleanup scenarios for scorecards
- Added task lifecycle information
- Updated configuration section
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed all diagnostic logging statements added for transaction bug tracking
from INFO level to DEBUG level. This reduces log noise during normal operations
while preserving detailed diagnostics when needed.
Changes:
- All 🔍 DIAGNOSTIC messages changed to DEBUG level
- All ✅ player found messages changed to DEBUG level
- All roster validation tracking changed to DEBUG level
- WARNING (⚠️) and ERROR (❌) messages preserved at their levels
Files modified:
- commands/transactions/dropadd.py - Player roster detection diagnostics
- commands/transactions/ilmove.py - Player roster detection diagnostics
- services/transaction_builder.py - Transaction validation tracking
- views/transaction_embed.py - Submission handler mode tracking
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented automatic posting of transaction notifications to #transaction-log
channel when transactions are submitted via /dropadd or /ilmove commands.
**New Utility:**
- `utils/transaction_logging.py` - Centralized transaction logging function
- `post_transaction_to_log()` - Posts transaction embed to #transaction-log
- Uses team's ML affiliate for consistent branding
- Displays team thumbnail, colors, and player moves
- Shows season from transaction data (not hardcoded)
- Format matches legacy transaction log embeds
**Integration Points:**
- `/dropadd` (scheduled transactions) - Posts when submitted
- `/ilmove` (immediate transactions) - Posts when executed
- Both use shared `post_transaction_to_log()` function
**Embed Format:**
```
Week 18 Transaction
[Team Name]
Player Moves:
- **PlayerName** (sWAR) from OLDTEAM to NEWTEAM
- **PlayerName** (sWAR) from OLDTEAM to NEWTEAM
[Team Thumbnail]
Footer: "SBa Season 12" with SBA logo
```
**Features:**
- Automatic ML affiliate lookup for consistent team display
- Team colors and thumbnails in embeds
- Season number from transaction data
- Graceful error handling (logs warnings, doesn't block submission)
- Matches legacy embed format exactly
**Files Changed:**
- NEW: `utils/transaction_logging.py` - Transaction logging utility
- MODIFIED: `views/transaction_embed.py` - Added logging calls on submission
**Testing:**
- Transaction builder tests pass (31/31)
- No new test failures introduced
- Logging is non-blocking (submission succeeds even if logging fails)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed critical bug where player drops/releases were assigned to team id 503 (DENIL)
instead of the correct Free Agency team (id 498). The transaction_builder.py had
a hardcoded team ID instead of using the config value.
**Root Cause:**
- Line 446 in services/transaction_builder.py hardcoded `id=503` for Free Agency
- Correct Free Agency team ID is 498 (from config.free_agent_team_id)
- Team 503 is DENIL, causing dropped players to appear on wrong roster
**Fix:**
- Changed to use `config.free_agent_team_id` (498) dynamically
- All drop/release transactions now correctly target Free Agency
**Affected Players in Production:**
- Luis Torrens, Jake Meyers, Lance Lynn, Seranthony Dominguez
- These players need manual correction in production database
**Testing:**
- All 31 transaction_builder tests pass
- Validates correct team assignment for drop operations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added new admin-only command to manually clear #live-sba-scores
- Clears all messages from the channel (up to 100)
- Sets @everyone view permission to False (hides channel)
- Bot retains access via role permissions
- Provides detailed feedback on operation success
- Updated /admin-help to include new command
Use cases:
- Manual cleanup of stale scorebug displays
- Testing channel visibility functionality
- Emergency channel management
- Forcing channel to hidden state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Created utils/scorebug_helpers.py with shared scorebug functions
- create_scorebug_embed(): Unified embed creation for command and background task
- create_team_progress_bar(): Win probability visualization
- Fixed win probability bar to show dark blocks weighted toward winning team
- Arrow extends from the side with advantage
- Home winning: "POR ░▓▓▓▓▓▓▓▓▓► WV 95.0%"
- Away winning: "POR ◄▓▓▓▓▓▓▓░░░ WV 30.0%"
- Changed embed color from score-based to win probability-based
- Embed shows color of team favored to win, not necessarily winning
- Creates fun psychological element showing momentum/advantage
- Added dynamic channel visibility for #live-sba-scores
- Channel visible to @everyone when active games exist
- Channel hidden when no games are active
- Bot retains access via role permissions
- Added set_channel_visibility() to utils/discord_helpers.py
- Eliminated ~220 lines of duplicate code across files
- Removed duplicate embed creation from commands/gameplay/scorebug.py
- Removed duplicate embed creation from tasks/live_scorebug_tracker.py
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhancement: Allow releasing players to Free Agency via /ilmove
Changes:
- Added "Free Agency" as destination choice in command options
- Updated destination_map to include "fa" -> RosterType.FREE_AGENCY
- Added logic to fetch FA team from config (free_agent_team_id: 498)
- Set to_team correctly: FA team when releasing, user team otherwise
This allows users to immediately release players to Free Agency for current week,
complementing the existing ML/MiL/IL destination options.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Bug: /dropadd was incorrectly rejecting players from Minor League affiliates
Root Cause: Ownership check used team.id comparison instead of organizational check
Fix: Use team.is_same_organization() to properly handle ML/MiL/IL affiliates
Before: player.team.id != builder.team.id (fails for WVMiL when user owns WV)
After: not builder.team.is_same_organization(player.team) (correctly identifies same org)
This brings /dropadd in line with /ilmove implementation which already used
the correct organizational check pattern.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
App Version: 2.0.4
Created reusable text_utils module with split_text_for_fields() function
to handle rare play results that exceed Discord's 1024 character field limit.
Changes:
- New utils/text_utils.py with intelligent text splitting on delimiters
- Updated commands/dice/rolls.py to split long rare play results into multiple fields
- Automatic part indicators for multi-chunk results (e.g., "Part 1/2")
- Handles outfield rare plays (~1135 chars) by splitting into 2 fields
- Maintains single field for shorter infield/pitcher rare plays
Benefits:
- Fixes Discord field limit issue for outfield rare plays
- Reusable utility for any future long-text scenarios
- Clean breaks on semantic boundaries (between result types)
- No multiple embeds needed - everything in single embed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added data consistency check to automatically clear stale injury records
where is_active=True but il_return=None. This prevents old injury records
from blocking new injury creation when players should not be injured.
- Detects mismatch between injury table and player il_return field
- Auto-clears stale injury records with warning log
- Allows legitimate injuries with matching il_return to block command
- Logs both warning (stale data found) and info (cleared) for debugging
Fixes issue where Ronald Acuna Jr had active injury record but no il_return.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed bug where responder_team variable was only assigned conditionally
but always referenced, causing UnboundLocalError when clearing injuries
for major league players.
- Initialize responder_team = None before conditional check
- Ensures variable is defined for both ML and non-ML teams
- Conditional expression on line 674 now works correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This enhancement automatically unpublishes scorecards when their associated
voice channels are deleted by the cleanup service, ensuring data synchronization
and reducing unnecessary API calls to Google Sheets for inactive games.
Implementation:
- Added gameplay commands package with scorebug/scorecard functionality
- Created ScorebugService for reading live game data from Google Sheets
- VoiceChannelTracker now stores text_channel_id for voice-to-text association
- VoiceChannelCleanupService integrates ScorecardTracker for automatic cleanup
- LiveScorebugTracker monitors published scorecards and updates displays
- Bot initialization includes gameplay commands and live scorebug tracker
Key Features:
- Voice channels track associated text channel IDs
- cleanup_channel() unpublishes scorecards during normal cleanup
- verify_tracked_channels() unpublishes scorecards for stale entries on startup
- get_voice_channel_for_text_channel() enables reverse lookup
- LiveScorebugTracker logging improved (debug level for missing channels)
Testing:
- Added comprehensive test coverage (2 new tests, 19 total pass)
- Tests verify scorecard unpublishing in cleanup and verification scenarios
Documentation:
- Updated commands/voice/CLAUDE.md with scorecard cleanup integration
- Updated commands/gameplay/CLAUDE.md with background task integration
- Updated tasks/CLAUDE.md with automatic cleanup details
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Image Version: 2.0.3
Added ability to add charts with multiple images and manage them incrementally.
Service Layer Changes (chart_service.py):
- Added add_image_to_chart() method to append URLs to existing charts
- Added remove_image_from_chart() method to remove specific URLs
- Validation to prevent duplicate URLs in charts
- Protection against removing the last image from a chart
Command Layer Changes (charts.py):
- Modified /chart-manage add to accept comma-separated URLs
- Parse and strip URLs from comma-delimited string
- Shows image count in success message
- Displays first image in response embed
- Added /chart-manage add-image command for incremental additions
- Added /chart-manage remove-image command to remove specific URLs
- All commands use chart autocomplete for easy selection
- Admin/Help Editor permission checks on all management commands
Usage Examples:
# Add chart with multiple images in one command
/chart-manage add defense "Defense Chart" gameplay "https://example.com/def1.png, https://example.com/def2.png"
# Add additional images later
/chart-manage add-image defense https://example.com/def3.png
# Remove specific image
/chart-manage remove-image defense https://example.com/def2.png🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added new command to resolve caching issue where manual edits to
charts.json were not reflected in the bot without a restart.
Changes:
- Added /chart-manage reload command to ChartManageGroup
- Calls chart_service.reload_charts() to refresh in-memory cache
- Shows success message with chart and category counts
- Requires admin or Help Editor role permissions
- Uses @logged_command decorator for automatic logging
Fixes: Chart updates not appearing until bot restart
Resolves: Permission denied issue with charts.json (ownership fixed separately)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adding 17 CLAUDE.md files across the project to provide detailed context
and implementation guidelines for AI development agents:
Root Documentation:
- CLAUDE.md - Main project guide with Git workflow requirements
Component Documentation:
- commands/CLAUDE.md - Command architecture and patterns
- models/CLAUDE.md - Pydantic models and validation
- services/CLAUDE.md - Service layer and API interactions
- tasks/CLAUDE.md - Background tasks and automation
- tests/CLAUDE.md - Testing strategies and patterns
- utils/CLAUDE.md - Utility functions and decorators
- views/CLAUDE.md - Discord UI components and embeds
Command Package Documentation:
- commands/help/CLAUDE.md - Help system implementation
- commands/injuries/CLAUDE.md - Injury management commands
- commands/league/CLAUDE.md - League-wide commands
- commands/players/CLAUDE.md - Player information commands
- commands/profile/CLAUDE.md - User profile commands
- commands/teams/CLAUDE.md - Team information commands
- commands/transactions/CLAUDE.md - Transaction management
- commands/utilities/CLAUDE.md - Utility commands
- commands/voice/CLAUDE.md - Voice channel management
Key Updates:
- Updated .gitignore to track CLAUDE.md files in version control
- Added Git Workflow section requiring branch-based development
- Documented all architectural patterns and best practices
- Included comprehensive command/service implementation guides
These files provide essential context for AI agents working on the codebase,
ensuring consistent patterns, proper error handling, and maintainable code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The command was incorrectly using get_team_schedule(weeks=1) which fetches
the first week of the season rather than the current week's games. Changed
to use get_week_schedule(current_week) with proper team filtering.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented comprehensive /ilmove command system for immediate roster changes (IL moves,
activations, etc.) that execute instantly for the current week, complementing the existing
/dropadd system which schedules moves for next week.
New Features:
- /ilmove command: Interactive transaction builder for THIS week (immediate execution)
- /clearilmove command: Clear current IL move transaction builder
- Dual-mode transaction system: Scheduled (/dropadd) vs Immediate (/ilmove)
Key Fixes:
- Organizational team matching: Minor League players (WVMiL) now correctly recognized as
valid targets for their Major League organization (WV)
- Transaction POST format: Fixed to use correct batch API format with count/moves structure
- RosterType to team affiliate mapping: Moves to IL now correctly assign players to WVIL
instead of WV, and moves from MiL correctly reference WVMiL as source team
- Player team updates: Added update_player_team() method for immediate team assignments
Technical Changes:
- commands/transactions/ilmove.py: New command with organizational validation
- commands/transactions/__init__.py: Register ILMoveCommands cog
- services/transaction_service.py: create_transaction_batch() with correct batch format
- services/player_service.py: update_player_team() for immediate updates
- services/transaction_builder.py: RosterType affiliate resolution with async team lookups
- views/transaction_embed.py: Dual-mode support with context-aware instructions
Code Reuse:
- 95% code sharing between /dropadd and /ilmove via shared TransactionBuilder
- Same validation, UI, and move tracking - only submission differs
- Context-aware command_name parameter for dynamic UI instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added /jump command for baserunner stealing mechanics with pickoff/balk detection.
Enhanced dice rolling commands with team color support in embeds.
Improved /admin-sync with local/global options and prefix command fallback.
Fixed type safety issues in admin commands and injury management.
Updated config for expanded draft rounds and testing mode.
Key changes:
- commands/dice/rolls.py: New /jump and !j commands with special cases for pickoff (d20=1) and balk (d20=2)
- commands/dice/rolls.py: Added team/channel color support to /ab and dice embeds
- commands/dice/rolls.py: Added pitcher position to /fielding command with proper range/error charts
- commands/admin/management.py: Enhanced /admin-sync with local/clear options and !admin-sync prefix fallback
- commands/admin/management.py: Fixed Member type checking and channel type validation
- commands/injuries/management.py: Fixed responder team detection for injury clearing
- models/custom_command.py: Made creator_id optional for execute endpoint compatibility
- config.py: Updated draft_rounds to 32 and enabled testing mode
- services/transaction_builder.py: Adjusted ML roster limit to 26
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements comprehensive automated system for weekly transaction freeze periods
with priority-based contested player resolution.
New Features:
- Weekly freeze/thaw task (Monday 00:00 freeze, Saturday 00:00 thaw)
- Priority resolution for contested transactions (worst teams get first priority)
- Admin league management commands (/freeze-begin, /freeze-end, /advance-week)
- Enhanced API client to handle string-based transaction IDs (moveids)
- Service layer methods for transaction cancellation, unfreezing, and bulk operations
- Offseason mode configuration flag to disable freeze operations
Technical Changes:
- api/client.py: URL-encode object_id parameter to handle colons in moveids
- bot.py: Initialize and shutdown transaction freeze task
- config.py: Add offseason_flag to BotConfig
- services/league_service.py: Add update_current_state() for week/freeze updates
- services/transaction_service.py: Add cancel/unfreeze methods with bulk support
- tasks/transaction_freeze.py: Main freeze/thaw automation with error recovery
- commands/admin/league_management.py: Manual admin controls for freeze system
Infrastructure:
- .gitlab-ci.yml and .gitlab/: GitLab CI/CD pipeline configuration
- .mcp.json: MCP server configuration
- Dockerfile.versioned: Versioned Docker build support
- .dockerignore: Added .gitlab/ to ignore list
Testing:
- tests/test_tasks_transaction_freeze.py: Comprehensive freeze task tests
The system uses team standings to fairly resolve contested players (multiple teams
trying to acquire the same player), with worst-record teams getting priority.
Includes comprehensive error handling, GM notifications, and admin reporting.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add interactive PlayerStatsView with toggle buttons to show/hide batting and
pitching statistics independently in the /player command. Stats are hidden by
default with clean, user-friendly buttons (💥 batting, ⚾ pitching) that update
the embed in-place. Only the command caller can toggle stats, and buttons
timeout after 5 minutes.
Player Stats Toggle Feature:
- Add views/players.py with PlayerStatsView class
- Update /player command to use interactive view
- Stats hidden by default, shown on button click
- Independent batting/pitching toggles
- User-restricted interactions with timeout handling
Injury System Enhancements:
- Add BatterInjuryModal and PitcherRestModal for injury logging
- Add player_id extraction validator to Injury model
- Fix injury creation to merge API request/response data
- Add responders parameter to BaseView for multi-user interactions
API Client Improvements:
- Handle None values correctly in PATCH query parameters
- Convert None to empty string for nullable fields in database
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added confirmation embed before clearing injury
- Shows player info, team, expected return, games missed
- User prompted: "Is {Player} cleared to return?"
- Buttons: "Clear Injury" / "Cancel"
- 3-minute timeout for response
- Team GMs can also confirm/cancel
- Added player autocomplete to /injury clear command
- Uses same autocomplete as /injury roll
- Prioritizes user's team players
- Updated commands/injuries/CLAUDE.md documentation
- Documented new confirmation flow
- Added autocomplete parameter details
- Clarified user flow and responders
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit addresses critical bugs in the injury command system and
establishes best practices for Discord command groups.
## Critical Fixes
### 1. GroupCog → app_commands.Group Migration
- **Problem**: `commands.GroupCog` has a duplicate interaction processing bug
causing "404 Unknown interaction" errors when deferring responses
- **Root Cause**: GroupCog triggers command handler twice, consuming the
interaction token before the second execution can respond
- **Solution**: Migrated InjuryCog to InjuryGroup using `app_commands.Group`
pattern (same as ChartManageGroup and ChartCategoryGroup)
- **Result**: Reliable command execution, no more 404 errors
### 2. GiphyService GIF URL Fix
- **Problem**: Giphy service returned web page URLs (https://giphy.com/gifs/...)
instead of direct image URLs, preventing Discord embed display
- **Root Cause**: Code accessed `data.url` instead of `data.images.original.url`
- **Solution**: Updated both `get_disappointment_gif()` and `get_gif()` methods
to use correct API response path for embeddable GIF URLs
- **Result**: GIFs now display correctly in Discord embeds
## Documentation
### Command Groups Best Practices (commands/README.md)
Added comprehensive section documenting:
- **Critical Warning**: Never use `commands.GroupCog` - use `app_commands.Group`
- **Technical Explanation**: Why GroupCog fails (duplicate execution bug)
- **Migration Guide**: Step-by-step conversion from GroupCog to Group
- **Comparison Table**: Key differences between the two approaches
- **Working Examples**: References to ChartManageGroup, InjuryGroup patterns
## Architecture Changes
### Injury Commands (`commands/injuries/`)
- Converted from `commands.GroupCog` to `app_commands.Group`
- Registration via `bot.tree.add_command()` instead of `bot.add_cog()`
- Removed workarounds for GroupCog duplicate interaction issues
- Clean defer/response pattern with `@logged_command` decorator
### GiphyService (`services/giphy_service.py`)
- Centralized from `commands/soak/giphy_service.py`
- Now returns direct GIF image URLs for Discord embeds
- Maintains Trump GIF filtering (legacy behavior)
- Added gif_url to log output for debugging
### Configuration (`config.py`)
- Added `giphy_api_key` and `giphy_translate_url` settings
- Environment variable support via `GIPHY_API_KEY`
- Default values provided for out-of-box functionality
## Files Changed
- commands/injuries/: New InjuryGroup with app_commands.Group pattern
- services/giphy_service.py: Centralized service with GIF URL fix
- commands/soak/giphy_service.py: Backwards compatibility wrapper
- commands/README.md: Command groups best practices documentation
- config.py: Giphy configuration settings
- services/__init__.py: GiphyService exports
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>