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. Represents team draft board rankings and preferences.
""" """
from typing import Optional from typing import Optional, Dict, Any
from pydantic import Field from pydantic import Field
from models.base import SBABaseModel from models.base import SBABaseModel
@ -21,6 +21,32 @@ class DraftList(SBABaseModel):
team: Team = Field(..., description="Team object") team: Team = Field(..., description="Team object")
player: Player = Field(..., description="Player 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 @property
def team_id(self) -> int: def team_id(self) -> int:
"""Extract team ID from nested team object.""" """Extract team ID from nested team object."""

View File

@ -225,13 +225,24 @@ class DraftMonitorTask:
# Try each player in order # Try each player in order
for entry in draft_list: for entry in draft_list:
if not entry.player: if not entry.player:
self.logger.debug(f"Draft list entry has no player, skipping")
continue continue
player = entry.player 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 # Check if player is still available
if player.team_id != config.free_agent_team_id: 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 continue
# Attempt to draft this player # Attempt to draft this player
@ -299,7 +310,7 @@ class DraftMonitorTask:
return False return False
# Validate cap space # 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: if not is_valid:
self.logger.debug( self.logger.debug(

View File

@ -474,6 +474,68 @@ class TestDraftListModel:
assert top_pick.is_top_ranked is True assert top_pick.is_top_ranked is True
assert lower_pick.is_top_ranked is False 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: class TestModelCoverageExtras:
"""Additional model coverage tests.""" """Additional model coverage tests."""