From 602c87590e9fcfc957441086f58b4a9c089d61e5 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Thu, 11 Dec 2025 12:53:07 -0600 Subject: [PATCH] Fix auto-draft player availability check with nested API parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- models/draft_list.py | 28 ++++++++++++++++++- tasks/draft_monitor.py | 15 ++++++++-- tests/test_models.py | 62 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/models/draft_list.py b/models/draft_list.py index 920570f..709675e 100644 --- a/models/draft_list.py +++ b/models/draft_list.py @@ -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.""" diff --git a/tasks/draft_monitor.py b/tasks/draft_monitor.py index a09259f..cb282c7 100644 --- a/tasks/draft_monitor.py +++ b/tasks/draft_monitor.py @@ -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( diff --git a/tests/test_models.py b/tests/test_models.py index cee5161..34f734a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -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."""