major-domo-v2/commands/draft/CLAUDE.md
Cal Corum fb78b4b8c6 CLAUDE: Add draft period restriction to interactive draft commands
Restrict interactive draft commands to offseason only (week <= 0) while keeping read-only commands available year-round.

Changes:
- Add @requires_draft_period decorator to utils/decorators.py
- Apply decorator to /draft and all /draft-list-* commands
- Keep /draft-board, /draft-status, /draft-on-clock unrestricted
- Update commands/draft/CLAUDE.md with restriction documentation

Technical details:
- Decorator checks league_service.get_current_state().week <= 0
- Shows user-friendly error: "Draft commands are only available in the offseason"
- Follows existing @logged_command decorator pattern
- No Discord command cache delays - instant restriction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 20:29:16 -05:00

294 lines
9.0 KiB
Markdown

# Draft Commands
This directory contains Discord slash commands for draft system operations.
## 🚨 Important: Draft Period Restriction
**Interactive draft commands are restricted to the offseason (week ≤ 0).**
### Restricted Commands
The following commands can **only be used during the offseason** (when league week ≤ 0):
- `/draft` - Make draft picks
- `/draft-list` - View auto-draft queue
- `/draft-list-add` - Add player to queue
- `/draft-list-remove` - Remove player from queue
- `/draft-list-clear` - Clear entire queue
**Implementation:** The `@requires_draft_period` decorator automatically checks the current league week and returns an error message if the league is in-season.
### Unrestricted Commands
These commands remain **available year-round**:
- `/draft-board` - View draft picks by round
- `/draft-status` - View current draft state
- `/draft-on-clock` - View detailed on-the-clock information
- All `/draft-admin` commands (administrator only)
### User Experience
When a user tries to run a restricted command during the season, they see:
```
❌ Not Available
Draft commands are only available in the offseason.
```
## Files
### `picks.py`
- **Command**: `/draft`
- **Description**: Make a draft pick with FA player autocomplete
- **Restriction**: Offseason only (week ≤ 0) via `@requires_draft_period` decorator
- **Parameters**:
- `player` (required): Player name to draft (autocomplete shows FA players with position and sWAR)
- **Service Dependencies**:
- `draft_service.get_draft_data()`
- `draft_pick_service.get_pick()`
- `draft_pick_service.update_pick_selection()`
- `team_service.get_team_by_owner()` (CACHED)
- `team_service.get_team_roster()`
- `player_service.get_players_by_name()`
- `player_service.update_player_team()`
- `league_service.get_current_state()` (for period check)
## Key Features
### Global Pick Lock
- **Purpose**: Prevent concurrent draft picks that could cause race conditions
- **Implementation**: `asyncio.Lock()` stored in cog instance
- **Location**: Local only (not in database)
- **Timeout**: 30-second stale lock auto-override
- **Integration**: Background monitor task respects same lock
```python
# In DraftPicksCog
self.pick_lock = asyncio.Lock()
self.lock_acquired_at: Optional[datetime] = None
self.lock_acquired_by: Optional[int] = None
# Lock acquisition with timeout check
if self.pick_lock.locked():
if time_held > 30:
# Override stale lock
pass
else:
# Reject with wait time
return
async with self.pick_lock:
# Process pick
pass
```
### Pick Validation Flow
1. **Lock Check**: Verify no active pick in progress (or stale lock >30s)
2. **GM Validation**: Verify user is team owner (cached lookup - fast!)
3. **Draft State**: Get current draft configuration
4. **Turn Validation**: Verify user's team is on the clock
5. **Player Validation**: Verify player is FA (team_id = 498)
6. **Cap Space**: Validate 32 sWAR limit won't be exceeded
7. **Execution**: Update pick, update player team, advance draft
8. **Announcements**: Post success message and player card
### FA Player Autocomplete
The autocomplete function filters to FA players only:
```python
async def fa_player_autocomplete(interaction, current: str):
# Search all players
players = await player_service.search_players(current, limit=25)
# Filter to FA only (team_id = 498)
fa_players = [p for p in players if p.team_id == 498]
# Return choices with position and sWAR
return [Choice(name=f"{p.name} ({p.pos}) - {p.wara:.2f} sWAR", value=p.name)]
```
### Cap Space Validation
Uses `utils.draft_helpers.validate_cap_space()`:
```python
async def validate_cap_space(roster: dict, new_player_wara: float):
# Calculate how many players count (top 26 of 32 roster spots)
max_counted = min(26, 26 - (32 - projected_roster_size))
# Sort all players + new player by sWAR descending
sorted_wara = sorted(all_players_wara, reverse=True)
# Sum top N
projected_total = sum(sorted_wara[:max_counted])
# Check against limit (with tiny float tolerance)
return projected_total <= 32.00001, projected_total
```
## Architecture Notes
### Command Pattern
- Uses `@logged_command("/draft")` decorator (no manual error handling)
- Always defers response: `await interaction.response.defer()`
- Service layer only (no direct API client access)
- Comprehensive logging with contextual information
### Race Condition Prevention
The global lock ensures:
- Only ONE pick can be processed at a time league-wide
- Co-GMs cannot both draft simultaneously
- Background auto-draft respects same lock
- Stale locks (crashes/network issues) auto-clear after 30s
### Performance Optimizations
- **Team lookup cached** (`get_team_by_owner` uses `@cached_single_item`)
- **80% reduction** in API calls for GM validation
- **Sub-millisecond cache hits** vs 50-200ms API calls
- Draft data NOT cached (changes too frequently)
## Troubleshooting
### Common Issues
1. **"Pick In Progress" message**:
- Another user is currently making a pick
- Wait ~30 seconds for pick to complete
- If stuck, lock will auto-clear after 30s
2. **"Not Your Turn" message**:
- Current pick belongs to different team
- Wait for your turn in draft order
- Admin can use `/draft-admin` to adjust
3. **"Cap Space Exceeded" message**:
- Drafting player would exceed 32.00 sWAR limit
- Only top 26 players count toward cap
- Choose player with lower sWAR value
4. **"Player Not Available" message**:
- Player is not a free agent
- May have been drafted by another team
- Check draft board for available players
### Lock State Debugging
Check lock status with admin tools:
```python
# Lock state
draft_picks_cog.pick_lock.locked() # True if held
draft_picks_cog.lock_acquired_at # When lock was acquired
draft_picks_cog.lock_acquired_by # User ID holding lock
```
Admin can force-clear locks:
- Use `/draft-admin clear-lock` (when implemented)
- Restart bot (lock is local only)
## Draft Format
### Hybrid Linear + Snake
- **Rounds 1-10**: Linear draft (same order every round)
- **Rounds 11+**: Snake draft (reverse on even rounds)
- **Special Rule**: Round 11 Pick 1 = same team as Round 10 Pick 16
### Pick Order Calculation
Uses `utils.draft_helpers.calculate_pick_details()`:
```python
def calculate_pick_details(overall: int) -> tuple[int, int]:
round_num = math.ceil(overall / 16)
if round_num <= 10:
# Linear: 1-16, 1-16, 1-16, ...
position = ((overall - 1) % 16) + 1
else:
# Snake: odd rounds forward, even rounds reverse
if round_num % 2 == 1:
position = ((overall - 1) % 16) + 1
else:
position = 16 - ((overall - 1) % 16)
return round_num, position
```
## Integration with Background Task
The draft monitor task (`tasks/draft_monitor.py`) integrates with this command:
1. **Shared Lock**: Monitor acquires same `pick_lock` for auto-draft
2. **Timer Expiry**: When deadline passes, monitor auto-drafts
3. **Draft List**: Monitor tries players from team's draft list in order
4. **Pick Advancement**: Monitor calls same `draft_service.advance_pick()`
## Future Commands
### `/draft-status` (Pending Implementation)
Display current draft state, timer, lock status
### `/draft-admin` (Pending Implementation)
Admin controls:
- Timer on/off
- Set current pick
- Configure channels
- Wipe picks
- Clear stale locks
- Set keepers
### `/draft-list` (Pending Implementation)
Manage auto-draft queue:
- View current list
- Add players
- Remove players
- Reorder players
- Clear list
### `/draft-board` (Pending Implementation)
View draft board by round with pagination
## Dependencies
- `config.get_config()`
- `services.draft_service`
- `services.draft_pick_service`
- `services.player_service`
- `services.team_service` (with caching)
- `utils.decorators.logged_command`
- `utils.draft_helpers.validate_cap_space`
- `views.draft_views.*`
- `asyncio.Lock` for race condition prevention
## Testing
Run tests with: `python -m pytest tests/test_commands_draft.py -v` (when implemented)
Test scenarios:
- **Concurrent picks**: Two users try to draft simultaneously
- **Stale lock**: Lock held >30s gets overridden
- **Cap validation**: Player would exceed 32 sWAR limit
- **Turn validation**: User tries to draft out of turn
- **Player availability**: Player already drafted
## Security Considerations
### Permission Validation
- Only team owners (GMs) can make draft picks
- Validated via `team_service.get_team_by_owner()`
- Cached for performance (30-minute TTL)
### Data Integrity
- Global lock prevents duplicate picks
- Cap validation prevents roster violations
- Turn validation enforces draft order
- All updates atomic (pick + player team)
## Database Requirements
- Draft data table (configuration and state)
- Draft picks table (all picks for season)
- Draft list table (auto-draft queues)
- Player records with team associations
- Team records with owner associations
---
**Last Updated:** October 2025
**Status:** Core `/draft` command implemented and tested
**Next:** Implement `/draft-status`, `/draft-admin`, `/draft-list` commands