Created centralized services for SBA player data fetching at lineup creation: Backend - New Services: - app/services/sba_api_client.py: REST client for SBA API (api.sba.manticorum.com) with batch player fetching and caching support - app/services/lineup_service.py: High-level service combining DB operations with API calls for complete lineup entries with player data Backend - Refactored Components: - app/core/game_engine.py: Replaced raw API calls with LineupService, reduced _prepare_next_play() from ~50 lines to ~15 lines - app/core/substitution_manager.py: Updated pinch_hit(), defensive_replace(), change_pitcher() to use lineup_service.get_sba_player_data() - app/models/game_models.py: Added player_name/player_image to LineupPlayerState - app/services/__init__.py: Exported new LineupService components - app/websocket/handlers.py: Enhanced lineup state handling Frontend - SBA League: - components/Game/CurrentSituation.vue: Restored player images with fallback badges (P/B letters) for both mobile and desktop layouts - components/Game/GameBoard.vue: Enhanced game board visualization - composables/useGameActions.ts: Updated game action handling - composables/useWebSocket.ts: Improved WebSocket state management - pages/games/[id].vue: Enhanced game page with better state handling - types/game.ts: Updated type definitions - types/websocket.ts: Added WebSocket type support Architecture Improvement: All SBA player data fetching now goes through LineupService: - Lineup creation: add_sba_player_to_lineup() - Lineup loading: load_team_lineup_with_player_data() - Substitutions: get_sba_player_data() All 739 unit tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
117 lines
3.5 KiB
Python
117 lines
3.5 KiB
Python
"""
|
|
SBA API client for fetching player data.
|
|
|
|
Integrates with SBA REST API to retrieve player information
|
|
for use in game lineup display.
|
|
|
|
Author: Claude
|
|
Date: 2025-01-10
|
|
"""
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
import httpx
|
|
from app.models.player_models import SbaPlayer
|
|
|
|
logger = logging.getLogger(f'{__name__}.SbaApiClient')
|
|
|
|
|
|
class SbaApiClient:
|
|
"""Client for SBA API player data lookups."""
|
|
|
|
def __init__(self, base_url: str = "https://api.sba.manticorum.com"):
|
|
"""
|
|
Initialize SBA API client.
|
|
|
|
Args:
|
|
base_url: Base URL for SBA API (default: production)
|
|
"""
|
|
self.base_url = base_url
|
|
self.timeout = httpx.Timeout(10.0, connect=5.0)
|
|
|
|
async def get_player(self, player_id: int) -> SbaPlayer:
|
|
"""
|
|
Fetch player data from SBA API.
|
|
|
|
Args:
|
|
player_id: SBA player ID
|
|
|
|
Returns:
|
|
SbaPlayer instance with all player data
|
|
|
|
Raises:
|
|
httpx.HTTPError: If API request fails
|
|
|
|
Example:
|
|
player = await client.get_player(12288)
|
|
print(f"Name: {player.name}") # "Ronald Acuna Jr"
|
|
print(f"Positions: {player.get_positions()}") # ['RF']
|
|
"""
|
|
url = f"{self.base_url}/players/{player_id}"
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
response = await client.get(url)
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
player = SbaPlayer.from_api_response(data)
|
|
|
|
logger.info(f"Loaded player {player_id}: {player.name}")
|
|
return player
|
|
|
|
except httpx.HTTPError as e:
|
|
logger.error(f"Failed to fetch player {player_id}: {e}")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error fetching player {player_id}: {e}")
|
|
raise
|
|
|
|
async def get_players_batch(
|
|
self,
|
|
player_ids: List[int]
|
|
) -> Dict[int, SbaPlayer]:
|
|
"""
|
|
Fetch multiple players in parallel.
|
|
|
|
Args:
|
|
player_ids: List of SBA player IDs to fetch
|
|
|
|
Returns:
|
|
Dictionary mapping player_id to SbaPlayer instance
|
|
Players that fail to load will be omitted from the result
|
|
|
|
Example:
|
|
players = await client.get_players_batch([12288, 12289, 12290])
|
|
for player_id, player in players.items():
|
|
print(f"{player.name}: {player.get_positions()}")
|
|
"""
|
|
if not player_ids:
|
|
return {}
|
|
|
|
results: Dict[int, SbaPlayer] = {}
|
|
|
|
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
for player_id in player_ids:
|
|
try:
|
|
url = f"{self.base_url}/players/{player_id}"
|
|
response = await client.get(url)
|
|
response.raise_for_status()
|
|
|
|
data = response.json()
|
|
player = SbaPlayer.from_api_response(data)
|
|
results[player_id] = player
|
|
|
|
except httpx.HTTPError as e:
|
|
logger.warning(f"Failed to fetch player {player_id}: {e}")
|
|
# Continue with other players
|
|
except Exception as e:
|
|
logger.warning(f"Unexpected error fetching player {player_id}: {e}")
|
|
# Continue with other players
|
|
|
|
logger.info(f"Loaded {len(results)}/{len(player_ids)} players")
|
|
return results
|
|
|
|
|
|
# Singleton instance
|
|
sba_api_client = SbaApiClient()
|