mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
* Fix leaderboard scrolling performance after PR #39 merge - Restore leaderboard background updates that were accidentally removed - Fix duration method call from get_dynamic_duration() back to get_duration() - Restore proper fallback duration (600s instead of 60s) for leaderboard - Add back sports manager updates that feed data to leaderboard - Fix leaderboard defer_update priority to prevent scrolling lag These changes restore the leaderboard's dynamic duration calculation and ensure it gets proper background updates for smooth scrolling. * Apply PR #60 leaderboard performance optimizations - Change scroll_delay from 0.05s to 0.01s (100fps instead of 20fps) - Remove conditional scrolling logic - scroll every frame for smooth animation - Add FPS tracking and logging for performance monitoring - Restore high-framerate scrolling that was working before PR #39 merge These changes restore the smooth leaderboard scrolling performance that was achieved in PR #60 but was lost during the PR #39 merge. * Fix critical bugs identified in PR #39 review - Fix record filtering logic bug: change away_record == set to away_record in set - Fix incorrect sport specification: change 'nfl' to 'ncaa_fb' for NCAA Football data requests - These bugs were causing incorrect data display and wrong sport data fetching Addresses issues found by cursor bot in PR #39 review: - Record filtering was always evaluating to False - NCAA Football was fetching NFL data instead of college football data * Enhance cache clearing implementation from PR #39 - Add detailed logging to cache clearing process for better visibility - Log cache clearing statistics (memory entries and file count) - Improve startup logging to show cache clearing and data refetch process - Addresses legoguy1000's comment about preventing stale data issues This enhances the cache clearing implementation that was added in PR #39 to help prevent legacy cache issues and stale data problems. * continuing on base_classes - added baseball and api extractor since we don't use ESPN api for all sports * tests * fix missing duration * ensure milb, mlb, ncaa bb are all using new baseball base class properly * cursor rule to help with PR creation * fix image call * fix _scoreboard suffix on milb, MLB
This commit is contained in:
@@ -15,6 +15,10 @@ from src.background_data_service import get_background_service
|
||||
from src.logo_downloader import download_missing_logo, LogoDownloader
|
||||
from pathlib import Path
|
||||
|
||||
# Import new architecture components (individual classes will import what they need)
|
||||
from .api_extractors import ESPNFootballExtractor, ESPNBaseballExtractor, ESPNHockeyExtractor
|
||||
from .data_sources import ESPNDataSource, MLBAPIDataSource
|
||||
|
||||
class SportsCore:
|
||||
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
|
||||
self.logger = logger
|
||||
@@ -28,6 +32,11 @@ class SportsCore:
|
||||
self.display_height = self.display_manager.matrix.height
|
||||
|
||||
self.sport_key = sport_key
|
||||
|
||||
# Initialize new architecture components (will be overridden by sport-specific classes)
|
||||
self.sport_config = None
|
||||
self.api_extractor = None
|
||||
self.data_source = None
|
||||
self.mode_config = config.get(f"{sport_key}_scoreboard", {}) # Changed config key
|
||||
self.is_enabled = self.mode_config.get("enabled", False)
|
||||
self.show_odds = self.mode_config.get("show_odds", False)
|
||||
@@ -267,20 +276,143 @@ class SportsCore:
|
||||
return None
|
||||
|
||||
def _fetch_data(self) -> Optional[Dict]:
|
||||
"""Override this from the sports class"""
|
||||
pass
|
||||
"""Fetch data using the new architecture components."""
|
||||
try:
|
||||
# Use the data source to fetch live games
|
||||
live_games = self.data_source.fetch_live_games(self.sport_key, self.sport_key)
|
||||
|
||||
if not live_games:
|
||||
self.logger.debug(f"No live games found for {self.sport_key}")
|
||||
return None
|
||||
|
||||
# Use the API extractor to process each game
|
||||
processed_games = []
|
||||
for game_event in live_games:
|
||||
game_details = self.api_extractor.extract_game_details(game_event)
|
||||
if game_details:
|
||||
# Add sport-specific fields
|
||||
sport_fields = self.api_extractor.get_sport_specific_fields(game_event)
|
||||
game_details.update(sport_fields)
|
||||
|
||||
# Fetch odds if enabled
|
||||
if self.show_odds:
|
||||
self._fetch_odds(game_details, self.sport_key, self.sport_key)
|
||||
|
||||
processed_games.append(game_details)
|
||||
|
||||
if processed_games:
|
||||
self.logger.debug(f"Successfully processed {len(processed_games)} games for {self.sport_key}")
|
||||
return {
|
||||
'games': processed_games,
|
||||
'sport': self.sport_key,
|
||||
'timestamp': time.time()
|
||||
}
|
||||
else:
|
||||
self.logger.debug(f"No valid games processed for {self.sport_key}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching data for {self.sport_key}: {e}")
|
||||
return None
|
||||
|
||||
def _get_partial_schedule_data(self, year: int) -> List[Dict]:
|
||||
"""Override this from the sports class"""
|
||||
return []
|
||||
"""Get schedule data using the new architecture components."""
|
||||
try:
|
||||
# Calculate date range for the year
|
||||
start_date = datetime(year, 1, 1)
|
||||
end_date = datetime(year, 12, 31)
|
||||
|
||||
# Use the data source to fetch schedule
|
||||
schedule_games = self.data_source.fetch_schedule(
|
||||
self.sport_key,
|
||||
self.sport_key,
|
||||
(start_date, end_date)
|
||||
)
|
||||
|
||||
if not schedule_games:
|
||||
self.logger.debug(f"No schedule data found for {self.sport_key} in {year}")
|
||||
return []
|
||||
|
||||
# Use the API extractor to process each game
|
||||
processed_games = []
|
||||
for game_event in schedule_games:
|
||||
game_details = self.api_extractor.extract_game_details(game_event)
|
||||
if game_details:
|
||||
# Add sport-specific fields
|
||||
sport_fields = self.api_extractor.get_sport_specific_fields(game_event)
|
||||
game_details.update(sport_fields)
|
||||
processed_games.append(game_details)
|
||||
|
||||
self.logger.debug(f"Successfully processed {len(processed_games)} schedule games for {self.sport_key} in {year}")
|
||||
return processed_games
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching schedule data for {self.sport_key} in {year}: {e}")
|
||||
return []
|
||||
|
||||
def _fetch_immediate_games(self) -> List[Dict]:
|
||||
"""Override this from the sports class"""
|
||||
return []
|
||||
"""Fetch immediate games using the new architecture components."""
|
||||
try:
|
||||
# Use the data source to fetch live games
|
||||
live_games = self.data_source.fetch_live_games(self.sport_key, self.sport_key)
|
||||
|
||||
if not live_games:
|
||||
self.logger.debug(f"No immediate games found for {self.sport_key}")
|
||||
return []
|
||||
|
||||
# Use the API extractor to process each game
|
||||
processed_games = []
|
||||
for game_event in live_games:
|
||||
game_details = self.api_extractor.extract_game_details(game_event)
|
||||
if game_details:
|
||||
# Add sport-specific fields
|
||||
sport_fields = self.api_extractor.get_sport_specific_fields(game_event)
|
||||
game_details.update(sport_fields)
|
||||
processed_games.append(game_details)
|
||||
|
||||
self.logger.debug(f"Successfully processed {len(processed_games)} immediate games for {self.sport_key}")
|
||||
return processed_games
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching immediate games for {self.sport_key}: {e}")
|
||||
return []
|
||||
|
||||
def _fetch_game_odds(self, _: Dict) -> None:
|
||||
"""Override this from the sports class"""
|
||||
pass
|
||||
def _fetch_game_odds(self, game: Dict) -> None:
|
||||
"""Fetch odds for a specific game using the new architecture."""
|
||||
try:
|
||||
if not self.show_odds:
|
||||
return
|
||||
|
||||
# Check if we should only fetch for favorite teams
|
||||
is_favorites_only = self.mode_config.get("show_favorite_teams_only", False)
|
||||
if is_favorites_only:
|
||||
home_abbr = game.get('home_abbr')
|
||||
away_abbr = game.get('away_abbr')
|
||||
if not (home_abbr in self.favorite_teams or away_abbr in self.favorite_teams):
|
||||
self.logger.debug(f"Skipping odds fetch for non-favorite game in favorites-only mode: {away_abbr}@{home_abbr}")
|
||||
return
|
||||
|
||||
# Determine update interval based on game state
|
||||
is_live = game.get('is_live', False)
|
||||
update_interval = self.mode_config.get("live_odds_update_interval", 60) if is_live \
|
||||
else self.mode_config.get("odds_update_interval", 3600)
|
||||
|
||||
# Fetch odds using OddsManager
|
||||
odds_data = self.odds_manager.get_odds(
|
||||
sport=self.sport_key,
|
||||
league=self.sport_key,
|
||||
event_id=game['id'],
|
||||
update_interval_seconds=update_interval
|
||||
)
|
||||
|
||||
if odds_data:
|
||||
game['odds'] = odds_data
|
||||
self.logger.debug(f"Successfully fetched and attached odds for game {game['id']}")
|
||||
else:
|
||||
self.logger.debug(f"No odds data returned for game {game['id']}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching odds for game {game.get('id', 'N/A')}: {e}")
|
||||
|
||||
def _fetch_odds(self, game: Dict, sport: str, league: str) -> None:
|
||||
"""Fetch odds for a specific game if conditions are met."""
|
||||
@@ -339,7 +471,26 @@ class SportsCore:
|
||||
return False
|
||||
|
||||
def _fetch_team_rankings(self) -> Dict[str, int]:
|
||||
return {}
|
||||
"""Fetch team rankings using the new architecture components."""
|
||||
try:
|
||||
# Use the data source to fetch standings
|
||||
standings_data = self.data_source.fetch_standings(self.sport_key, self.sport_key)
|
||||
|
||||
if not standings_data:
|
||||
self.logger.debug(f"No standings data found for {self.sport_key}")
|
||||
return {}
|
||||
|
||||
# Extract rankings from standings data
|
||||
rankings = {}
|
||||
# This would need to be implemented based on the specific data structure
|
||||
# returned by each data source
|
||||
|
||||
self.logger.debug(f"Successfully fetched rankings for {self.sport_key}")
|
||||
return rankings
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error fetching team rankings for {self.sport_key}: {e}")
|
||||
return {}
|
||||
|
||||
def _extract_game_details_common(self, game_event: Dict) -> tuple[Dict | None, Dict | None, Dict | None, Dict | None, Dict | None]:
|
||||
if not game_event:
|
||||
@@ -393,7 +544,7 @@ class SportsCore:
|
||||
# Don't show "0-0" records - set to blank instead
|
||||
if home_record in {"0-0", "0-0-0"}:
|
||||
home_record = ''
|
||||
if away_record == {"0-0", "0-0-0"}:
|
||||
if away_record in {"0-0", "0-0-0"}:
|
||||
away_record = ''
|
||||
|
||||
details = {
|
||||
|
||||
Reference in New Issue
Block a user