major-domo-v2/commands/draft/CLAUDE.md
Cal Corum 0294f89f4a Update CLAUDE.md files for recent draft system changes
commands/draft/CLAUDE.md:
- Mark all draft commands as implemented (not "pending")
- Document /draft-admin subcommands and auto-start behavior
- Add /draft-on-clock command documentation
- Update status to December 2025

tasks/CLAUDE.md:
- Document smart polling intervals (30s/15s/5s)
- Add auto-start behavior from /draft-admin commands
- Document on-clock announcement embed features
- Update to December 2025

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-10 23:18:25 -06:00

10 KiB

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

Skipped Pick Support

  • Purpose: Allow teams to make up picks they missed when not on the clock
  • Detection: Checks for picks with overall < current_overall and player_id = None
  • Behavior: If team is not on the clock but has skipped picks, allows drafting with earliest skipped pick
  • User Experience: Success message includes footer noting this is a "skipped pick makeup"
  • Draft Advancement: Does NOT advance the draft when using a skipped pick
# Skipped pick detection flow
if current_pick.owner.id != team.id:
    skipped_picks = await draft_pick_service.get_skipped_picks_for_team(
        season, team.id, current_overall
    )
    if skipped_picks:
        pick_to_use = skipped_picks[0]  # Earliest skipped pick
    else:
        # Return "Not Your Turn" error

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
# 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:

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():

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:

# 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():

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()

Implemented Commands

/draft-status

Display current draft state, timer, lock status

/draft-admin (Administrator Only)

Admin controls:

  • /draft-admin timer - Enable/disable timer (auto-starts monitor task)
  • /draft-admin set-pick - Set current pick (auto-starts monitor if timer active)
  • /draft-admin channels - Configure ping/result channels
  • /draft-admin wipe - Clear all picks for season
  • /draft-admin info - View detailed draft configuration

/draft-list

View auto-draft queue for your team

/draft-list-add

Add player to auto-draft queue

/draft-list-remove

Remove player from auto-draft queue

/draft-list-clear

Clear entire auto-draft queue

/draft-board

View draft board by round with pagination

/draft-on-clock

View detailed on-the-clock information including:

  • Current team on the clock
  • Deadline with relative timestamp
  • Team's current sWAR and cap space
  • Last 5 picks
  • Top 5 roster players by sWAR

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: December 2025 Status: All draft commands implemented and tested Recent: Added skipped pick support, draft monitor auto-start, on-clock announcements