Fix odds and more (#81)

* fix(odds): Resolve incorrect sport and league parameters in _fetch_odds calls

- Fixed SportsCore._fetch_data() to call _fetch_odds(game) instead of _fetch_odds(game, sport_key, sport_key)
- Updated _fetch_odds method signature to accept only game parameter
- Added _fetch_odds_with_params helper method for sport-specific implementations
- Updated sport-specific managers to use correct sport and league parameters:
  - NFL: football/nfl
  - NCAA Football: football/college-football
  - NCAA Hockey: hockey/mens-college-hockey
- Ensures odds are fetched with correct ESPN API endpoints

Fixes #79

* Fix odds and other things

* update hockey

* fix rankings

* update imports

* Fix Logo Cache

* Add show_favorite_team_only attribute

---------

Co-authored-by: ChuckBuilds <33324927+ChuckBuilds@users.noreply.github.com>
Co-authored-by: Alex Resnick <adr8282@gmail.com>
This commit is contained in:
Alex Resnick
2025-09-28 15:34:21 -05:00
committed by GitHub
parent a98760f4d9
commit 1ee805901f
11 changed files with 127 additions and 502 deletions

View File

@@ -14,39 +14,13 @@ import requests
class Football(SportsCore):
"""Base class for football sports with common functionality."""
# Football sport configuration (moved from sport_configs.py)
SPORT_CONFIG = {
'update_cadence': 'weekly',
'season_length': 17, # NFL default
'games_per_week': 1,
'api_endpoints': ['scoreboard', 'standings'],
'sport_specific_fields': ['down', 'distance', 'possession', 'timeouts', 'is_redzone'],
'update_interval_seconds': 60,
'logo_dir': 'assets/sports/nfl_logos',
'show_records': True,
'show_ranking': True,
'show_odds': True,
'data_source_type': 'espn',
'api_base_url': 'https://site.api.espn.com/apis/site/v2/sports/football'
}
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
super().__init__(config, display_manager, cache_manager, logger, sport_key)
# Initialize football-specific architecture components
self.sport_config = self.get_sport_config()
self.api_extractor = ESPNFootballExtractor(logger)
self.data_source = ESPNDataSource(logger)
def get_sport_config(self) -> Dict[str, Any]:
"""Get football sport configuration."""
return self.SPORT_CONFIG.copy()
def _fetch_game_odds(self, _: Dict) -> None:
pass
def _fetch_odds(self, game: Dict, league: str) -> None:
super()._fetch_odds(game, "football", league)
self.sport = "football"
def _extract_game_details(self, game_event: Dict) -> Optional[Dict]:
"""Extract relevant game details from ESPN NCAA FB API response."""
@@ -122,13 +96,6 @@ class Football(SportsCore):
elif status["type"]["state"] == "pre":
period_text = details.get("game_time", "") # Show time for upcoming
# Timeouts (assuming max 3 per half, not carried over well in standard API)
# API often provides 'timeouts' directly under team, but reset logic is tricky
# We might need to simplify this or just use a fixed display if API is unreliable
# For upcoming games, we'll show based on number of games, not time window
# For recent games, we'll show based on number of games, not time window
is_within_window = True # Always include games, let the managers filter by count
details.update({
"period": period,
"period_text": period_text, # Formatted quarter/status
@@ -155,17 +122,6 @@ class Football(SportsCore):
logging.error(f"Error extracting game details: {e} from event: {game_event.get('id')}", exc_info=True)
return None
def _fetch_todays_games(self, league: str) -> Optional[Dict]:
"""Fetch only today's games for live updates (not entire season)."""
return super()._fetch_todays_games("football", league)
def _get_weeks_data(self, league: str) -> Optional[Dict]:
"""
Get partial data for immediate display while background fetch is in progress.
This fetches current/recent games only for quick response.
"""
return super()._get_weeks_data("football", league)
class FootballLive(Football):
def __init__(self, config: Dict[str, Any], display_manager: DisplayManager, cache_manager: CacheManager, logger: logging.Logger, sport_key: str):
super().__init__(config, display_manager, cache_manager, logger, sport_key)
@@ -250,21 +206,19 @@ class FootballLive(Football):
data = self._fetch_data()
new_live_games = []
if data and "events" in data:
for event in data["events"]:
details = self._extract_game_details(event)
for game in data["events"]:
details = self._extract_game_details(game)
if details and (details["is_live"] or details["is_halftime"]):
# If show_favorite_teams_only is true, only add if it's a favorite.
# Otherwise, add all games.
if self.mode_config.get("show_favorite_teams_only", False):
if self.show_favorite_teams_only:
if details["home_abbr"] in self.favorite_teams or details["away_abbr"] in self.favorite_teams:
if self.show_odds:
self._fetch_game_odds(details)
new_live_games.append(details)
else:
if self.show_odds:
self._fetch_game_odds(details)
new_live_games.append(details)
for game in new_live_games:
if self.show_odds:
self._fetch_odds(game)
# Log changes or periodically
current_time_for_log = time.time() # Use a consistent time for logging comparison
should_log = (
@@ -276,12 +230,12 @@ class FootballLive(Football):
if should_log:
if new_live_games:
filter_text = "favorite teams" if self.mode_config.get("show_favorite_teams_only", False) else "all teams"
filter_text = "favorite teams" if self.show_favorite_teams_only else "all teams"
self.logger.info(f"Found {len(new_live_games)} live/halftime games for {filter_text}.")
for game_info in new_live_games: # Renamed game to game_info
self.logger.info(f" - {game_info['away_abbr']}@{game_info['home_abbr']} ({game_info.get('status_text', 'N/A')})")
else:
filter_text = "favorite teams" if self.mode_config.get("show_favorite_teams_only", False) else "criteria"
filter_text = "favorite teams" if self.show_favorite_teams_only else "criteria"
self.logger.info(f"No live/halftime games found for {filter_text}.")
self.last_log_time = current_time_for_log
@@ -504,8 +458,7 @@ class FootballLive(Football):
if away_abbr:
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
rankings = self._fetch_team_rankings()
away_rank = rankings.get(away_abbr, 0)
away_rank = self._team_rankings_cache.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
else:
@@ -513,8 +466,7 @@ class FootballLive(Football):
away_text = ''
elif self.show_ranking:
# Show ranking only if available
rankings = self._fetch_team_rankings()
away_rank = rankings.get(away_abbr, 0)
away_rank = self._team_rankings_cache.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
else:
@@ -534,8 +486,7 @@ class FootballLive(Football):
if home_abbr:
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
rankings = self._fetch_team_rankings()
home_rank = rankings.get(home_abbr, 0)
home_rank = self._team_rankings_cache.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
else:
@@ -543,8 +494,7 @@ class FootballLive(Football):
home_text = ''
elif self.show_ranking:
# Show ranking only if available
rankings = self._fetch_team_rankings()
home_rank = rankings.get(home_abbr, 0)
home_rank = self._team_rankings_cache.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
else: