Added 16 tests covering all aspects of injury modal validation including
regular season and playoff-specific game limits.
Test Coverage:
- BatterInjuryModal week validation (5 tests)
* Regular season weeks (1-18) acceptance
* Playoff weeks (19-21) acceptance
* Invalid weeks rejection (0, 22+)
- BatterInjuryModal game validation (6 tests)
* Regular season: games 1-4 valid, game 5+ rejected
* Playoff round 1 (week 19): games 1-5 valid, game 6+ rejected
* Playoff round 2 (week 20): games 1-7 valid
* Playoff round 3 (week 21): games 1-7 valid
- PitcherRestModal validation (4 tests)
* Same week validation as BatterInjuryModal
* Same game validation as BatterInjuryModal
- Config-driven validation (1 test)
* Verifies custom config values are respected
All tests use proper mocking patterns:
- PropertyMock for TextInput.value (read-only property)
- Correct patch paths for config and services
- Complete model data for Pydantic validation
Test Results: 16/16 passing ✅🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed bug where injury rolls during playoff weeks (19-21) were being rejected
with "weeks 1-18 only" error message.
Changes:
- Updated BatterInjuryModal and PitcherRestModal week validation
- Now uses config.weeks_per_season + config.playoff_weeks_per_season for max week
- Added dynamic game validation based on playoff round:
* Regular season (weeks 1-18): 4 games per week
* Playoff round 1 (week 19): 5 games
* Playoff round 2 (week 20): 7 games
* Playoff round 3 (week 21): 7 games
- Replaced hardcoded values with config-based calculations
Config values used:
- weeks_per_season (18)
- playoff_weeks_per_season (3)
- games_per_week (4)
- playoff_round_one_games (5)
- playoff_round_two_games (7)
- playoff_round_three_games (7)
Now injuries can be properly logged during all phases of the season including
playoffs with correct game validation for each round.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed critical bug where win probability progress bar displayed backwards:
- Away team with 75% win probability was showing as losing
- Home team with 25% win probability was showing as winning
Changes:
- Corrected comparison operators in create_team_progress_bar() function
- Enhanced UX by positioning percentage next to winning team:
* Home winning (>50%): Percentage on right (e.g., "POR ░▓▓▓▓▓▓▓▓▓► WV 95.0%")
* Away winning (<50%): Percentage on left (e.g., "75.0% POR ◄▓▓▓▓▓▓▓▓░░ WV")
* Even game (=50%): Percentage on both sides
- Added comprehensive test suite with 10 test cases covering all scenarios
- Updated docstring examples to reflect new format
All tests passing (10/10) ✅🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The API now has a DELETE /draftlist/team/{team_id} endpoint
that properly clears a team's draft list.
Updated clear_list() to use the new endpoint instead of trying
to POST an empty list, which was failing with "list index out of range"
error because the API expected at least one entry to determine team_id.
This resolves the "Clear Failed" error when using /draft-list-clear.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Multiple fixes for draft list functionality:
1. **Model Fix (draft_list.py):**
- API returns nested Team and Player objects, not just IDs
- Changed team_id/player_id from fields to @property methods
- Extract IDs from nested objects via properties
- Fixes Pydantic validation errors on GET operations
2. **Service Fix (draft_list_service.py):**
- Override _extract_items_and_count_from_response() for API quirk
- GET returns items under 'picks' key (not 'draftlist')
- Changed add_to_list() return type from single entry to full list
- Return verification list instead of trying to create new DraftList
- Fixes "Failed to add" error from validation issues
3. **Command Enhancement (list.py):**
- Display full draft list on successful add (not just confirmation)
- Show position where player was added
- Reuse existing create_draft_list_embed() for consistency
- Better UX - user sees complete context after adding player
API Response Format:
GET: {"count": N, "picks": [{team: {...}, player: {...}}]}
POST: {"count": N, "draft_list": [{team_id: X, player_id: Y}]}
This resolves:
- Empty list after adding player (Pydantic validation)
- "Add Failed" error despite successful operation
- Poor UX with minimal success feedback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Draft list API has NO individual CRUD endpoints.
Only endpoints available:
- GET (retrieve list)
- POST (bulk replacement - deletes all, inserts new list)
All modification operations (add, remove, clear, reorder, move) were
incorrectly using BaseService CRUD methods (create, delete, patch) which
don't exist for this endpoint.
Fixes:
- add_to_list(): Use bulk replacement with rank insertion logic
- remove_player_from_list(): Rebuild list without player
- clear_list(): POST empty list
- reorder_list(): POST list with new rank order
- move_entry_up(): Swap ranks and POST
- move_entry_down(): Swap ranks and POST
- remove_from_list(): Deprecated (no DELETE endpoint)
All operations now:
1. GET current list
2. Build updated list with modifications
3. POST entire updated list (bulk replacement)
This resolves all draft list modification failures including:
- "Add Failed" when adding players
- Remove operations failing silently
- Reorder/move operations failing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: API mismatch between bot service and database endpoint.
The draft list POST endpoint uses bulk replacement pattern:
- Expects: {"count": N, "draft_list": [...]}
- Deletes entire team's list
- Inserts all entries in bulk
The bot service was incorrectly using BaseService.create() which sends
a single entry object, causing the API to reject the request.
Fix:
- Rewrite add_to_list() to use bulk replacement pattern
- Get current list
- Add new entry with proper rank insertion
- Shift existing entries if inserting in middle
- POST entire updated list to API
- Return created entry as DraftList object
This resolves "Add Failed" error when adding players to draft queue.
Note: Direct client.post() call is appropriate here since the API
endpoint doesn't follow standard CRUD patterns (uses bulk replacement).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Two critical bugs in draft picks command:
1. Swapped arguments to get_team_by_owner():
- Was passing (season, owner_id)
- Should be (owner_id, season)
- This caused "Not a GM" error for all users
2. Using old field name ping_channel_id:
- Model was updated to use ping_channel
- Draft card posting still used old field name
Fixes:
- commands/draft/picks.py:136-139: Corrected argument order
- commands/draft/picks.py:258-261: Updated to use ping_channel
This resolves the "Not a GM" error when running /draft command.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Root cause: Field naming mismatch between bot model and database schema.
The database stores channel IDs in columns named 'result_channel' and
'ping_channel', but the bot's DraftData model incorrectly used
'result_channel_id' and 'ping_channel_id'.
Additionally, the draft data PATCH endpoint requires query parameters
instead of JSON body (like player, game, transaction, and injury endpoints).
Changes:
- models/draft_data.py: Renamed fields to match database schema
- result_channel_id → result_channel
- ping_channel_id → ping_channel
- services/draft_service.py: Added use_query_params=True to PATCH calls
- views/draft_views.py: Updated embed to use correct field names
- tasks/draft_monitor.py: Updated channel lookups to use correct field names
- tests/test_models.py: Updated test assertions to match new field names
This fixes:
- Channel configuration now saves correctly via /draft-admin channels
- Ping channel settings persist across bot restarts
- Result channel settings persist across bot restarts
- All draft data updates work properly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix Pydantic validation errors when channel IDs are not configured:
Issue:
- result_channel_id and ping_channel_id were required fields
- Database may not have these values configured yet
- /draft-admin info command failed with validation errors
Fixes:
1. models/draft_data.py:
- Make result_channel_id and ping_channel_id Optional[int]
- Update validator to handle None values
- Prevents validation errors on missing channel data
2. views/draft_views.py:
- Handle None channel IDs in admin info embed
- Display "Not configured" instead of invalid channel mentions
- Prevents formatting errors when channels not set
Testing:
- Validated model accepts None for channel IDs
- Validated model accepts int for channel IDs
- Validated model converts string channel IDs to int
- All validation tests pass
This allows draft system to work before channels are configured
via /draft-admin channels command.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement all remaining draft commands for comprehensive draft management:
New Commands:
- /draft-admin (Group) - Admin controls for draft management
* info - View current draft configuration
* timer - Enable/disable draft timer
* set-pick - Set current pick number
* channels - Configure Discord channels
* reset-deadline - Reset pick deadline
- /draft-status - View current draft state
- /draft-on-clock - Detailed "on the clock" information with recent/upcoming picks
- /draft-list - View team's auto-draft queue
- /draft-list-add - Add player to queue
- /draft-list-remove - Remove player from queue
- /draft-list-clear - Clear entire queue
- /draft-board - View draft picks by round
New Files:
- commands/draft/admin.py - Admin commands (app_commands.Group pattern)
- commands/draft/status.py - Status viewing commands
- commands/draft/list.py - Auto-draft queue management
- commands/draft/board.py - Draft board viewing
Features:
- Admin-only permissions for draft management
- FA player autocomplete for draft list
- Complete draft state visibility
- Round-by-round draft board viewing
- Lock status integration
- Timer and deadline management
Updated:
- commands/draft/__init__.py - Register all new cogs and group
All commands use @logged_command decorator for consistent logging and error handling.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add draft command package to bot startup sequence:
- Create setup_draft() function in commands/draft/__init__.py
- Follow standard package pattern with resilient loading
- Import and register in bot.py command packages list
Changes:
- commands/draft/__init__.py: Add setup function and cog exports
- bot.py: Import setup_draft and add to command_packages
The /draft command will now load automatically when the bot starts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update documentation across services, tasks, and commands:
Services Documentation (services/CLAUDE.md):
- Added Draft System Services section with all three services
- Documented why NO CACHING is used for draft services
- Explained architecture integration (global lock, background monitor)
- Documented hybrid linear+snake draft format
Tasks Documentation (tasks/CLAUDE.md):
- Added Draft Monitor task documentation
- Detailed self-terminating behavior and resource efficiency
- Explained global lock integration with commands
- Documented auto-draft process and channel requirements
Commands Documentation (commands/draft/CLAUDE.md):
- Complete reference for /draft command
- Global pick lock implementation details
- Pick validation flow (7-step process)
- FA player autocomplete pattern
- Cap space validation algorithm
- Race condition prevention strategy
- Troubleshooting guide and common issues
- Integration with background task
- Future commands roadmap
All documentation follows established patterns from existing
CLAUDE.md files with comprehensive examples and code snippets.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement /draft slash command with comprehensive pick validation:
Core Features:
- Global pick lock (asyncio.Lock) prevents concurrent picks
- 30-second stale lock auto-override for crash recovery
- FA player autocomplete with position and sWAR display
- Complete pick validation (GM status, turn order, cap space)
- Player team updates and draft pick recording
- Success/error embeds following EmbedTemplate patterns
Architecture:
- Uses @logged_command decorator (no manual error handling)
- Service layer integration (no direct API access)
- TeamService caching for GM validation (80% API reduction)
- Global lock in cog instance (not database - local only)
- Draft monitor task can acquire same lock for auto-draft
Validation Flow:
1. Check global lock (reject if active pick <30s)
2. Validate user is GM (cached lookup)
3. Get draft state and current pick
4. Validate user's turn or has skipped pick
5. Validate player is FA and cap space available
6. Execute pick with atomic updates
7. Post success and advance to next pick
Ready for /draft-status and /draft-admin commands next.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive embed and UI components for draft system:
- On the clock embed: Shows current pick with team info, deadline, recent/upcoming picks
- Draft status embed: Current state, timer status, lock status
- Player draft card: Player info when drafted
- Draft list embed: Team's auto-draft queue display
- Draft board embed: Round-by-round pick display
- Admin info embed: Detailed configuration for admins
- Error/success embeds: Pick validation feedback
All components follow EmbedTemplate patterns with consistent
styling and proper color usage. Ready for command integration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add self-terminating background task for draft timer monitoring:
- Runs every 15 seconds during active draft
- Checks timer status and self-terminates when disabled (resource efficient)
- Sends warnings at 60s and 30s remaining
- Triggers auto-draft from team's draft list when timer expires
- Respects global pick lock (acquires from DraftPicksCog)
- Safe startup with @before_loop pattern
- Comprehensive error handling with structured logging
Task integrates with draft services (no direct API access) and
follows established patterns from existing tasks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add three core services for draft system with no caching decorators
since draft data changes constantly during active drafts:
- DraftService: Core draft logic, timer management, pick advancement
- DraftPickService: Pick CRUD operations, queries by team/round/availability
- DraftListService: Auto-draft queue management with reordering
All services follow BaseService pattern with proper error handling
and structured logging. Ready for integration with commands and tasks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>