major-domo-v2/services/draft_list_service.py
Cal Corum 23cf16d596 CLAUDE: Add draft system services (no caching)
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>
2025-10-24 12:04:26 -05:00

369 lines
11 KiB
Python

"""
Draft list service for Discord Bot v2.0
Handles team draft list (auto-draft queue) operations. NO CACHING - lists change frequently.
"""
import logging
from typing import Optional, List
from services.base_service import BaseService
from models.draft_list import DraftList
from exceptions import APIException
logger = logging.getLogger(f'{__name__}.DraftListService')
class DraftListService(BaseService[DraftList]):
"""
Service for draft list operations.
IMPORTANT: This service does NOT use caching decorators because draft lists
change as users add/remove players from their auto-draft queues.
Features:
- Get team's draft list (ranked by priority)
- Add player to draft list
- Remove player from draft list
- Reorder draft list
- Clear entire draft list
"""
def __init__(self):
"""Initialize draft list service."""
super().__init__(DraftList, 'draftlist')
logger.debug("DraftListService initialized")
async def get_team_list(
self,
season: int,
team_id: int
) -> List[DraftList]:
"""
Get team's draft list ordered by rank.
NOT cached - teams update their lists frequently during draft.
Args:
season: Draft season
team_id: Team ID
Returns:
List of DraftList entries ordered by rank (1 = highest priority)
"""
try:
params = [
('season', str(season)),
('team_id', str(team_id)),
('sort', 'rank-asc') # Order by priority
]
entries = await self.get_all_items(params=params)
logger.debug(f"Found {len(entries)} draft list entries for team {team_id}")
return entries
except Exception as e:
logger.error(f"Error getting draft list for team {team_id}: {e}")
return []
async def add_to_list(
self,
season: int,
team_id: int,
player_id: int,
rank: Optional[int] = None
) -> Optional[DraftList]:
"""
Add player to team's draft list.
If rank is not provided, adds to end of list.
Args:
season: Draft season
team_id: Team ID
player_id: Player ID to add
rank: Priority rank (1 = highest), None = add to end
Returns:
Created DraftList entry or None if creation failed
"""
try:
# If rank not provided, get current list and add to end
if rank is None:
current_list = await self.get_team_list(season, team_id)
rank = len(current_list) + 1
entry_data = {
'season': season,
'team_id': team_id,
'player_id': player_id,
'rank': rank
}
created_entry = await self.create(entry_data)
if created_entry:
logger.info(f"Added player {player_id} to team {team_id} draft list at rank {rank}")
else:
logger.error(f"Failed to add player {player_id} to draft list")
return created_entry
except Exception as e:
logger.error(f"Error adding player {player_id} to draft list: {e}")
return None
async def remove_from_list(
self,
entry_id: int
) -> bool:
"""
Remove entry from draft list by ID.
Args:
entry_id: Draft list entry database ID
Returns:
True if deletion succeeded
"""
try:
result = await self.delete(entry_id)
if result:
logger.info(f"Removed draft list entry {entry_id}")
else:
logger.error(f"Failed to remove draft list entry {entry_id}")
return result
except Exception as e:
logger.error(f"Error removing draft list entry {entry_id}: {e}")
return False
async def remove_player_from_list(
self,
season: int,
team_id: int,
player_id: int
) -> bool:
"""
Remove specific player from team's draft list.
Args:
season: Draft season
team_id: Team ID
player_id: Player ID to remove
Returns:
True if player was found and removed
"""
try:
# Get team's list
entries = await self.get_team_list(season, team_id)
# Find entry with this player
for entry in entries:
if entry.player_id == player_id:
return await self.remove_from_list(entry.id)
logger.warning(f"Player {player_id} not found in team {team_id} draft list")
return False
except Exception as e:
logger.error(f"Error removing player {player_id} from draft list: {e}")
return False
async def clear_list(
self,
season: int,
team_id: int
) -> bool:
"""
Clear entire draft list for team.
Args:
season: Draft season
team_id: Team ID
Returns:
True if all entries were deleted successfully
"""
try:
entries = await self.get_team_list(season, team_id)
if not entries:
logger.debug(f"No draft list entries to clear for team {team_id}")
return True
success = True
for entry in entries:
if not await self.remove_from_list(entry.id):
success = False
if success:
logger.info(f"Cleared {len(entries)} draft list entries for team {team_id}")
else:
logger.warning(f"Failed to clear some draft list entries for team {team_id}")
return success
except Exception as e:
logger.error(f"Error clearing draft list for team {team_id}: {e}")
return False
async def reorder_list(
self,
season: int,
team_id: int,
new_order: List[int]
) -> bool:
"""
Reorder team's draft list.
Args:
season: Draft season
team_id: Team ID
new_order: List of player IDs in desired order
Returns:
True if reordering succeeded
"""
try:
# Get current list
entries = await self.get_team_list(season, team_id)
# Build mapping of player_id -> entry
entry_map = {e.player_id: e for e in entries}
# Update each entry with new rank
success = True
for new_rank, player_id in enumerate(new_order, start=1):
if player_id not in entry_map:
logger.warning(f"Player {player_id} not in draft list, skipping")
continue
entry = entry_map[player_id]
if entry.rank != new_rank:
updated = await self.patch(entry.id, {'rank': new_rank})
if not updated:
logger.error(f"Failed to update rank for entry {entry.id}")
success = False
if success:
logger.info(f"Reordered draft list for team {team_id}")
else:
logger.warning(f"Some errors occurred reordering draft list for team {team_id}")
return success
except Exception as e:
logger.error(f"Error reordering draft list for team {team_id}: {e}")
return False
async def move_entry_up(
self,
season: int,
team_id: int,
player_id: int
) -> bool:
"""
Move player up one position in draft list (higher priority).
Args:
season: Draft season
team_id: Team ID
player_id: Player ID to move up
Returns:
True if move succeeded
"""
try:
entries = await self.get_team_list(season, team_id)
# Find player's current position
current_entry = None
for entry in entries:
if entry.player_id == player_id:
current_entry = entry
break
if not current_entry:
logger.warning(f"Player {player_id} not found in draft list")
return False
if current_entry.rank == 1:
logger.debug(f"Player {player_id} already at top of draft list")
return False
# Swap with entry above (rank - 1)
above_entry = next((e for e in entries if e.rank == current_entry.rank - 1), None)
if not above_entry:
logger.error(f"Could not find entry above rank {current_entry.rank}")
return False
# Swap ranks
await self.patch(current_entry.id, {'rank': current_entry.rank - 1})
await self.patch(above_entry.id, {'rank': above_entry.rank + 1})
logger.info(f"Moved player {player_id} up to rank {current_entry.rank - 1}")
return True
except Exception as e:
logger.error(f"Error moving player {player_id} up in draft list: {e}")
return False
async def move_entry_down(
self,
season: int,
team_id: int,
player_id: int
) -> bool:
"""
Move player down one position in draft list (lower priority).
Args:
season: Draft season
team_id: Team ID
player_id: Player ID to move down
Returns:
True if move succeeded
"""
try:
entries = await self.get_team_list(season, team_id)
# Find player's current position
current_entry = None
for entry in entries:
if entry.player_id == player_id:
current_entry = entry
break
if not current_entry:
logger.warning(f"Player {player_id} not found in draft list")
return False
if current_entry.rank == len(entries):
logger.debug(f"Player {player_id} already at bottom of draft list")
return False
# Swap with entry below (rank + 1)
below_entry = next((e for e in entries if e.rank == current_entry.rank + 1), None)
if not below_entry:
logger.error(f"Could not find entry below rank {current_entry.rank}")
return False
# Swap ranks
await self.patch(current_entry.id, {'rank': current_entry.rank + 1})
await self.patch(below_entry.id, {'rank': below_entry.rank - 1})
logger.info(f"Moved player {player_id} down to rank {current_entry.rank + 1}")
return True
except Exception as e:
logger.error(f"Error moving player {player_id} down in draft list: {e}")
return False
# Global service instance
draft_list_service = DraftListService()