Add dem_week parameter to player team updates

- Add optional dem_week parameter to PlayerService.update_player_team()
- Transaction freeze sets dem_week to current.week + 2
- /ilmove command sets dem_week to current.week
- Draft picks (manual and auto) set dem_week to current.week + 2
- Backwards compatible - admin commands don't set dem_week
- Add 4 unit tests for dem_week scenarios
- Enhanced logging shows dem_week values

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-02-01 21:12:06 -06:00
parent f4523b99f2
commit be3dbf1f8d
6 changed files with 227 additions and 20 deletions

View File

@ -304,10 +304,15 @@ class DraftPicksCog(commands.Cog):
await interaction.followup.send(embed=embed)
return
# Update player team
# Get current league state for dem_week calculation
from services.league_service import league_service
current = await league_service.get_current_state()
# Update player team with dem_week set to current.week + 2 for draft picks
updated_player = await player_service.update_player_team(
player_obj.id,
team.id
team.id,
dem_week=current.week + 2 if current else None
)
if not updated_player:

View File

@ -372,33 +372,65 @@ class PlayerService(BaseService[Player]):
return None
async def update_player_team(
self, player_id: int, new_team_id: int
self,
player_id: int,
new_team_id: int,
dem_week: Optional[int] = None
) -> Optional[Player]:
"""
Update a player's team assignment (for real-time IL moves).
This is used for immediate roster changes where the player needs to show
up on their new team right away, rather than waiting for transaction processing.
Update a player's team assignment.
Args:
player_id: Player ID to update
new_team_id: New team ID to assign
dem_week: Optional demotion/designation week to set.
- Transaction freeze: Current.week + 2
- Draft picks: Current.week + 2
- IL moves: Current.week
- Admin/Other: Not set (None)
Returns:
Updated player instance or None
Raises:
APIException: If player update fails
Examples:
# Transaction freeze (Monday morning)
await player_service.update_player_team(
player_id, new_team_id, dem_week=current.week + 2
)
# Draft pick (manual or auto-draft)
await player_service.update_player_team(
player_id, new_team_id, dem_week=current.week + 2
)
# IL move (/ilmove command)
await player_service.update_player_team(
player_id, new_team_id, dem_week=current.week
)
# Admin move (no dem_week)
await player_service.update_player_team(player_id, new_team_id)
"""
try:
logger.info(f"Updating player {player_id} team to {new_team_id}")
updated_player = await self.update_player(
player_id, {"team_id": new_team_id}
logger.info(
f"Updating player {player_id} team to {new_team_id}"
+ (f" with dem_week={dem_week}" if dem_week is not None else "")
)
# Build update dictionary
updates = {"team_id": new_team_id}
if dem_week is not None:
updates["dem_week"] = dem_week
updated_player = await self.update_player(player_id, updates)
if updated_player:
logger.info(
f"Successfully updated player {player_id} to team {new_team_id}"
+ (f" with dem_week={dem_week}" if dem_week is not None else "")
)
return updated_player
else:

View File

@ -340,11 +340,16 @@ class DraftMonitorTask:
self.logger.error(f"Failed to update pick {draft_pick.id}")
return False
# Update player team
# Get current league state for dem_week calculation
from services.player_service import player_service
from services.league_service import league_service
current = await league_service.get_current_state()
# Update player team with dem_week set to current.week + 2 for draft picks
updated_player = await player_service.update_player_team(
player.id,
draft_pick.owner.id
draft_pick.owner.id,
dem_week=current.week + 2 if current else None
)
if not updated_player:

View File

@ -465,11 +465,12 @@ class TransactionFreezeTask:
for transaction in transactions:
try:
# Update player's team via PATCH /players/{player_id}?team_id={new_team_id}
# Update player's team via PATCH /players/{player_id}?team_id={new_team_id}&dem_week={current.week+2}
await self._execute_player_update(
player_id=transaction.player.id,
new_team_id=transaction.newteam.id,
player_name=transaction.player.name
player_name=transaction.player.name,
dem_week=current.week + 2
)
success_count += 1
@ -683,7 +684,8 @@ class TransactionFreezeTask:
self,
player_id: int,
new_team_id: int,
player_name: str
player_name: str,
dem_week: Optional[int] = None
) -> bool:
"""
Execute a player roster update via API PATCH.
@ -692,6 +694,7 @@ class TransactionFreezeTask:
player_id: Player database ID
new_team_id: New team ID to assign
player_name: Player name for logging
dem_week: Optional designation week to set (typically Current.week + 2)
Returns:
True if update successful, False otherwise
@ -704,10 +707,15 @@ class TransactionFreezeTask:
"Updating player roster",
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id
new_team_id=new_team_id,
dem_week=dem_week
)
updated_player = await player_service.update_player_team(player_id, new_team_id)
updated_player = await player_service.update_player_team(
player_id,
new_team_id,
dem_week=dem_week
)
# Verify response (200 or 204 indicates success)
if updated_player is not None:
@ -715,7 +723,8 @@ class TransactionFreezeTask:
"Successfully updated player",
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id
new_team_id=new_team_id,
dem_week=dem_week
)
return True
else:
@ -723,7 +732,8 @@ class TransactionFreezeTask:
"Player update returned no response",
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id
new_team_id=new_team_id,
dem_week=dem_week
)
return False
@ -733,6 +743,7 @@ class TransactionFreezeTask:
player_id=player_id,
player_name=player_name,
new_team_id=new_team_id,
dem_week=dem_week,
error=str(e),
exc_info=True
)

View File

@ -392,3 +392,156 @@ class TestGlobalPlayerServiceInstance:
# But same configuration
assert service1.model_class == service2.model_class
assert service1.endpoint == service2.endpoint
class TestPlayerTeamUpdateWithDemWeek:
"""Test player team update functionality with dem_week parameter."""
@pytest.fixture
def mock_client(self):
"""Mock API client."""
client = AsyncMock()
return client
@pytest.fixture
def player_service_instance(self, mock_client):
"""Create PlayerService instance with mocked client."""
service = PlayerService()
service._client = mock_client
return service
def create_player_data(
self, player_id: int, name: str, team_id: int = 5, position: str = "C", **kwargs
):
"""Create complete player data for testing."""
base_data = {
"id": player_id,
"name": name,
"wara": 2.5,
"season": 12,
"team_id": team_id,
"image": f"https://example.com/player{player_id}.jpg",
"pos_1": position,
}
base_data.update(kwargs)
return base_data
@pytest.mark.asyncio
async def test_update_player_team_with_dem_week(
self, player_service_instance, mock_client
):
"""
Test player team update with dem_week parameter.
This test verifies that when dem_week is provided, it is correctly
included in the update dictionary passed to the API.
"""
player_id = 123
new_team_id = 5
dem_week = 15
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
result = await player_service_instance.update_player_team(
player_id, new_team_id, dem_week=dem_week
)
# Verify result
assert result is not None
assert result.team_id == new_team_id
# Verify API call included dem_week
mock_client.patch.assert_called_once_with(
"players",
{"team_id": new_team_id, "dem_week": dem_week},
player_id,
use_query_params=True
)
@pytest.mark.asyncio
async def test_update_player_team_without_dem_week(
self, player_service_instance, mock_client
):
"""
Test player team update without dem_week parameter maintains backwards compatibility.
This test verifies that when dem_week is not provided (None), it is NOT
included in the update dictionary, maintaining backwards compatibility with
admin commands that don't need to set dem_week.
"""
player_id = 123
new_team_id = 5
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
result = await player_service_instance.update_player_team(
player_id, new_team_id
)
# Verify result
assert result is not None
assert result.team_id == new_team_id
# Verify API call did NOT include dem_week
call_args = mock_client.patch.call_args[0][1]
assert "dem_week" not in call_args
assert call_args == {"team_id": new_team_id}
@pytest.mark.asyncio
async def test_update_player_team_dem_week_zero(
self, player_service_instance, mock_client
):
"""
Test that dem_week=0 IS included in the update.
This test verifies that dem_week=0 is treated as a valid value and included
in the update, since 0 is not None and may be a valid week number.
"""
player_id = 123
new_team_id = 5
dem_week = 0
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
await player_service_instance.update_player_team(
player_id, new_team_id, dem_week=dem_week
)
# Verify API call included dem_week=0
call_args = mock_client.patch.call_args[0][1]
assert call_args == {"team_id": new_team_id, "dem_week": 0}
@pytest.mark.asyncio
async def test_update_player_team_dem_week_none_explicit(
self, player_service_instance, mock_client
):
"""
Test that explicitly passing dem_week=None does NOT include it in updates.
This test verifies that when dem_week=None is explicitly passed, it behaves
the same as not passing the parameter at all.
"""
player_id = 123
new_team_id = 5
# Mock the API response
mock_client.patch.return_value = self.create_player_data(
player_id, "Test Player", team_id=new_team_id
)
await player_service_instance.update_player_team(
player_id, new_team_id, dem_week=None
)
# Verify API call did NOT include dem_week
call_args = mock_client.patch.call_args[0][1]
assert "dem_week" not in call_args

View File

@ -289,7 +289,8 @@ class SubmitConfirmationModal(discord.ui.Modal):
for txn in created_transactions:
updated_player = await player_service.update_player_team(
txn.player.id,
txn.newteam.id
txn.newteam.id,
dem_week=current_state.week
)
player_updates.append(updated_player)