Fix auto-draft player availability check with nested API parsing

DraftList.from_api_data() now properly calls Player.from_api_data()
for nested player objects, ensuring player.team_id is correctly
extracted from the nested team object. Also fixed validate_cap_space()
unpacking to accept all 3 return values.

Bug: Auto-draft was marking all players as "not available" because
player.team_id was None (None != 547 always True).

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-12-11 12:53:07 -06:00
parent 9093055bb5
commit 602c87590e
3 changed files with 102 additions and 3 deletions

View File

@ -3,7 +3,7 @@ Draft preference list model
Represents team draft board rankings and preferences.
"""
from typing import Optional
from typing import Optional, Dict, Any
from pydantic import Field
from models.base import SBABaseModel
@ -21,6 +21,32 @@ class DraftList(SBABaseModel):
team: Team = Field(..., description="Team object")
player: Player = Field(..., description="Player object")
@classmethod
def from_api_data(cls, data: Dict[str, Any]) -> 'DraftList':
"""
Create DraftList instance from API data, ensuring nested objects are properly handled.
The API returns nested team and player objects. We need to ensure Player.from_api_data()
is called so that player.team_id is properly extracted from the nested team object.
Without this, Pydantic's default construction doesn't call from_api_data() on nested
objects, leaving player.team_id as None.
"""
if not data:
raise ValueError("Cannot create DraftList from empty data")
# Make a copy to avoid modifying original
draft_list_data = data.copy()
# Handle nested team object
if 'team' in draft_list_data and isinstance(draft_list_data['team'], dict):
draft_list_data['team'] = Team.from_api_data(draft_list_data['team'])
# Handle nested player object - CRITICAL for team_id extraction
if 'player' in draft_list_data and isinstance(draft_list_data['player'], dict):
draft_list_data['player'] = Player.from_api_data(draft_list_data['player'])
return cls(**draft_list_data)
@property
def team_id(self) -> int:
"""Extract team ID from nested team object."""

View File

@ -225,13 +225,24 @@ class DraftMonitorTask:
# Try each player in order
for entry in draft_list:
if not entry.player:
self.logger.debug(f"Draft list entry has no player, skipping")
continue
player = entry.player
# Debug: Log player team_id for troubleshooting
self.logger.debug(
f"Checking player {player.name}: team_id={player.team_id}, "
f"FA team_id={config.free_agent_team_id}, "
f"team.id={player.team.id if player.team else 'None'}"
)
# Check if player is still available
if player.team_id != config.free_agent_team_id:
self.logger.debug(f"Player {player.name} no longer available, skipping")
self.logger.debug(
f"Player {player.name} no longer available "
f"(team_id={player.team_id} != FA={config.free_agent_team_id}), skipping"
)
continue
# Attempt to draft this player
@ -299,7 +310,7 @@ class DraftMonitorTask:
return False
# Validate cap space
is_valid, projected_total = await validate_cap_space(roster, player.wara)
is_valid, projected_total, cap_limit = await validate_cap_space(roster, player.wara)
if not is_valid:
self.logger.debug(

View File

@ -474,6 +474,68 @@ class TestDraftListModel:
assert top_pick.is_top_ranked is True
assert lower_pick.is_top_ranked is False
def test_draft_list_from_api_data_extracts_player_team_id(self):
"""
Test that DraftList.from_api_data() properly extracts player.team_id from nested team object.
This is critical for auto-draft functionality. The API returns player data with a nested
team object (not a flat team_id). Without the custom from_api_data(), Pydantic's default
construction doesn't call Player.from_api_data(), leaving player.team_id as None.
Bug fixed: Auto-draft was failing because player.team_id was None, causing all players
to be incorrectly marked as "not available" (None != 547 always True).
"""
# Simulate API response format - nested objects, NOT flat IDs
api_response = {
'id': 303,
'season': 13,
'rank': 1,
'team': {
'id': 548,
'abbrev': 'WV',
'sname': 'Black Bears',
'lname': 'West Virginia Black Bears',
'season': 13
},
'player': {
'id': 12843,
'name': 'George Springer',
'wara': 0.31,
'image': 'https://example.com/springer.png',
'season': 13,
'pos_1': 'CF',
# Note: NO flat team_id here - it's nested in 'team' below
'team': {
'id': 547, # Free Agent team
'abbrev': 'FA',
'sname': 'Free Agents',
'lname': 'Free Agents',
'season': 13
}
}
}
# Create DraftList using from_api_data (what BaseService calls)
draft_entry = DraftList.from_api_data(api_response)
# Verify nested objects are created
assert draft_entry.team is not None
assert draft_entry.player is not None
# CRITICAL: player.team_id must be extracted from nested team object
assert draft_entry.player.team_id == 547, \
f"player.team_id should be 547 (FA), got {draft_entry.player.team_id}"
# Verify the nested team object is also populated
assert draft_entry.player.team is not None
assert draft_entry.player.team.id == 547
assert draft_entry.player.team.abbrev == 'FA'
# Verify DraftList's own team data
assert draft_entry.team.id == 548
assert draft_entry.team.abbrev == 'WV'
assert draft_entry.team_id == 548 # Property from nested team
class TestModelCoverageExtras:
"""Additional model coverage tests."""