huge cache refactor, dictonary error updates, and changed from time based game searching to game based for recent and upcoming

This commit is contained in:
Chuck
2025-07-21 21:37:22 -05:00
parent bf44d7b55b
commit ba232c58b7
5 changed files with 141 additions and 215 deletions

View File

@@ -119,7 +119,6 @@
"scroll_speed": 1, "scroll_speed": 1,
"scroll_delay": 0.01, "scroll_delay": 0.01,
"display_duration": 60, "display_duration": 60,
"future_fetch_days": 45,
"loop": true "loop": true
}, },
"calendar": { "calendar": {
@@ -138,7 +137,6 @@
"live_update_interval": 30, "live_update_interval": 30,
"recent_update_interval": 3600, "recent_update_interval": 3600,
"upcoming_update_interval": 3600, "upcoming_update_interval": 3600,
"recent_game_hours": 48,
"favorite_teams": ["TB"], "favorite_teams": ["TB"],
"logo_dir": "assets/sports/nhl_logos", "logo_dir": "assets/sports/nhl_logos",
"show_records": true, "show_records": true,
@@ -176,8 +174,8 @@
"live_update_interval": 30, "live_update_interval": 30,
"live_odds_update_interval": 3600, "live_odds_update_interval": 3600,
"odds_update_interval": 3600, "odds_update_interval": 3600,
"fetch_past_games": 1, "recent_games_to_show": 0,
"fetch_future_games": 2, "upcoming_games_to_show": 2,
"favorite_teams": ["TB", "DAL"], "favorite_teams": ["TB", "DAL"],
"logo_dir": "assets/sports/nfl_logos", "logo_dir": "assets/sports/nfl_logos",
"show_records": true, "show_records": true,
@@ -196,8 +194,8 @@
"live_odds_update_interval": 3600, "live_odds_update_interval": 3600,
"odds_update_interval": 3600, "odds_update_interval": 3600,
"season_cache_duration_seconds": 86400, "season_cache_duration_seconds": 86400,
"fetch_past_games": 1, "recent_games_to_show": 0,
"fetch_future_games": 2, "upcoming_games_to_show": 2,
"favorite_teams": ["UGA", "AUB"], "favorite_teams": ["UGA", "AUB"],
"logo_dir": "assets/sports/ncaa_fbs_logos", "logo_dir": "assets/sports/ncaa_fbs_logos",
"show_records": true, "show_records": true,
@@ -255,8 +253,8 @@
"odds_update_interval": 3600, "odds_update_interval": 3600,
"recent_update_interval": 3600, "recent_update_interval": 3600,
"upcoming_update_interval": 3600, "upcoming_update_interval": 3600,
"fetch_past_games": 1, "recent_games_to_show": 1,
"fetch_future_games": 5, "upcoming_games_to_show": 1,
"favorite_teams": ["TB", "TEX"], "favorite_teams": ["TB", "TEX"],
"logo_dir": "assets/sports/mlb_logos", "logo_dir": "assets/sports/mlb_logos",
"show_records": true, "show_records": true,
@@ -273,7 +271,8 @@
"live_update_interval": 30, "live_update_interval": 30,
"recent_update_interval": 3600, "recent_update_interval": 3600,
"upcoming_update_interval": 3600, "upcoming_update_interval": 3600,
"recent_game_hours": 48, "recent_games_to_show": 1,
"upcoming_games_to_show": 1,
"favorite_teams": ["TAM"], "favorite_teams": ["TAM"],
"logo_dir": "assets/sports/milb_logos", "logo_dir": "assets/sports/milb_logos",
"show_records": true, "show_records": true,
@@ -303,7 +302,6 @@
"recent_update_interval": 3600, "recent_update_interval": 3600,
"upcoming_update_interval": 3600, "upcoming_update_interval": 3600,
"recent_game_hours": 168, "recent_game_hours": 168,
"upcoming_fetch_days": 7,
"favorite_teams": ["LIV"], "favorite_teams": ["LIV"],
"leagues": ["eng.1", "esp.1", "ger.1", "ita.1", "fra.1", "uefa.champions", "usa.1"], "leagues": ["eng.1", "esp.1", "ger.1", "ita.1", "fra.1", "uefa.champions", "usa.1"],
"logo_dir": "assets/sports/soccer_logos", "logo_dir": "assets/sports/soccer_logos",

View File

@@ -942,7 +942,7 @@ class MiLBRecentManager(BaseMiLBManager):
self.current_game_index = 0 self.current_game_index = 0
self.last_update = 0 self.last_update = 0
self.update_interval = self.milb_config.get('recent_update_interval', 3600) # 1 hour self.update_interval = self.milb_config.get('recent_update_interval', 3600) # 1 hour
self.recent_hours = self.milb_config.get('recent_game_hours', 72) # Increased from 48 to 72 hours self.recent_games_to_show = self.milb_config.get('recent_games_to_show', 5) # Show last 5 games
self.last_game_switch = 0 # Track when we last switched games self.last_game_switch = 0 # Track when we last switched games
self.game_display_duration = 10 # Display each game for 10 seconds self.game_display_duration = 10 # Display each game for 10 seconds
self.last_warning_time = 0 self.last_warning_time = 0
@@ -966,10 +966,8 @@ class MiLBRecentManager(BaseMiLBManager):
# Process games # Process games
new_recent_games = [] new_recent_games = []
now = datetime.now(timezone.utc) # Make timezone-aware
recent_cutoff = now - timedelta(hours=self.recent_hours)
logger.info(f"[MiLB] Time window: {recent_cutoff} to {now}") logger.info(f"[MiLB] Processing {len(games)} games for recent games...")
for game_id, game in games.items(): for game_id, game in games.items():
# Convert game time to UTC datetime # Convert game time to UTC datetime
@@ -989,16 +987,19 @@ class MiLBRecentManager(BaseMiLBManager):
# Use status_state to determine if game is final # Use status_state to determine if game is final
is_final = game['status_state'] in ['post', 'final', 'completed'] is_final = game['status_state'] in ['post', 'final', 'completed']
is_within_time = recent_cutoff <= game_time <= now
self.logger.info(f"[MiLB] Game Time: {game_time.isoformat()}, Cutoff Time: {recent_cutoff.isoformat()}, Now: {now.isoformat()}") self.logger.info(f"[MiLB] Game Time: {game_time.isoformat()}")
self.logger.info(f"[MiLB] Is final: {is_final}, Is within time window: {is_within_time}") self.logger.info(f"[MiLB] Is final: {is_final}")
# Only add favorite team games that are final and within time window # Only add favorite team games that are final
if is_final and is_within_time: if is_final:
new_recent_games.append(game) new_recent_games.append(game)
logger.info(f"[MiLB] Added favorite team game to recent list: {game['away_team']} @ {game['home_team']}") logger.info(f"[MiLB] Added favorite team game to recent list: {game['away_team']} @ {game['home_team']}")
# Sort by game time (most recent first) and limit to recent_games_to_show
new_recent_games.sort(key=lambda x: x['start_time'], reverse=True)
new_recent_games = new_recent_games[:self.recent_games_to_show]
if new_recent_games: if new_recent_games:
logger.info(f"[MiLB] Found {len(new_recent_games)} recent games for favorite teams: {self.favorite_teams}") logger.info(f"[MiLB] Found {len(new_recent_games)} recent games for favorite teams: {self.favorite_teams}")
self.recent_games = new_recent_games self.recent_games = new_recent_games
@@ -1053,6 +1054,7 @@ class MiLBUpcomingManager(BaseMiLBManager):
self.current_game_index = 0 self.current_game_index = 0
self.last_update = 0 self.last_update = 0
self.update_interval = self.milb_config.get('upcoming_update_interval', 3600) # 1 hour self.update_interval = self.milb_config.get('upcoming_update_interval', 3600) # 1 hour
self.upcoming_games_to_show = self.milb_config.get('upcoming_games_to_show', 10) # Show next 10 games
self.last_warning_time = 0 self.last_warning_time = 0
self.warning_cooldown = 300 # Only show warning every 5 minutes self.warning_cooldown = 300 # Only show warning every 5 minutes
self.last_game_switch = 0 # Track when we last switched games self.last_game_switch = 0 # Track when we last switched games
@@ -1073,24 +1075,44 @@ class MiLBUpcomingManager(BaseMiLBManager):
if games: if games:
# Process games # Process games
new_upcoming_games = [] new_upcoming_games = []
now = datetime.now(timezone.utc) # Make timezone-aware
upcoming_cutoff = now + timedelta(hours=24)
logger.info(f"Looking for games between {now} and {upcoming_cutoff}") logger.info(f"[MiLB] Processing {len(games)} games for upcoming games...")
for game in games.get('events', []): for game_id, game in games.items():
game_data = self._extract_game_details(game) # Convert game time to UTC datetime
if game_data: game_time_str = game['start_time'].replace('Z', '+00:00')
new_upcoming_games.append(game_data) game_time = datetime.fromisoformat(game_time_str)
if game_time.tzinfo is None:
game_time = game_time.replace(tzinfo=timezone.utc)
# Check if this is a favorite team game
is_favorite_game = (game['home_team'] in self.favorite_teams or
game['away_team'] in self.favorite_teams)
if is_favorite_game:
logger.info(f"[MiLB] Checking favorite team game: {game['away_team']} @ {game['home_team']}")
logger.info(f"[MiLB] Game time (UTC): {game_time}")
logger.info(f"[MiLB] Game status: {game['status']}, State: {game['status_state']}")
# For upcoming games, we'll consider any game that:
# 1. Is not final (not 'post' or 'final' state)
# 2. Has a future start time
is_upcoming = (
game['status_state'] not in ['post', 'final', 'completed'] and
game_time > datetime.now(timezone.utc)
)
if is_upcoming:
new_upcoming_games.append(game)
logger.info(f"[MiLB] Added favorite team game to upcoming list: {game['away_team']} @ {game['home_team']}")
# Filter for favorite teams (though we already filtered above, this is a safety check) # Sort by game time (soonest first) and limit to upcoming_games_to_show
new_team_games = [game for game in new_upcoming_games new_upcoming_games.sort(key=lambda x: x['start_time'])
if game['home_team'] in self.favorite_teams or new_upcoming_games = new_upcoming_games[:self.upcoming_games_to_show]
game['away_team'] in self.favorite_teams]
if new_team_games: if new_upcoming_games:
logger.info(f"[MiLB] Found {len(new_team_games)} upcoming games for favorite teams") logger.info(f"[MiLB] Found {len(new_upcoming_games)} upcoming games for favorite teams")
self.upcoming_games = new_team_games self.upcoming_games = new_upcoming_games
if not self.current_game: if not self.current_game:
self.current_game = self.upcoming_games[0] self.current_game = self.upcoming_games[0]
else: else:

View File

@@ -414,6 +414,7 @@ class BaseMLBManager:
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
self.logger.info(f"Found {len(data.get('events', []))} total games for date {date}")
for event in data.get('events', []): for event in data.get('events', []):
game_id = event['id'] game_id = event['id']
@@ -436,6 +437,9 @@ class BaseMLBManager:
# Check if this is a favorite team game # Check if this is a favorite team game
is_favorite_game = (home_abbr in self.favorite_teams or away_abbr in self.favorite_teams) is_favorite_game = (home_abbr in self.favorite_teams or away_abbr in self.favorite_teams)
# Log all teams found for debugging
self.logger.debug(f"Found game: {away_abbr} @ {home_abbr} (Status: {status}, State: {status_state})")
# Only log detailed information for favorite teams # Only log detailed information for favorite teams
if is_favorite_game: if is_favorite_game:
self.logger.info(f"Found favorite team game: {away_abbr} @ {home_abbr} (Status: {status}, State: {status_state})") self.logger.info(f"Found favorite team game: {away_abbr} @ {home_abbr} (Status: {status}, State: {status_state})")
@@ -1070,7 +1074,7 @@ class MLBRecentManager(BaseMLBManager):
self.current_game_index = 0 self.current_game_index = 0
self.last_update = 0 self.last_update = 0
self.update_interval = self.mlb_config.get('recent_update_interval', 3600) self.update_interval = self.mlb_config.get('recent_update_interval', 3600)
self.recent_hours = self.mlb_config.get('recent_game_hours', 72) # Increased from 48 to 72 hours self.recent_games_to_show = self.mlb_config.get('recent_games_to_show', 5) # Show last 5 games
self.last_game_switch = 0 # Track when we last switched games self.last_game_switch = 0 # Track when we last switched games
self.game_display_duration = 10 # Display each game for 10 seconds self.game_display_duration = 10 # Display each game for 10 seconds
self.last_warning_time = 0 self.last_warning_time = 0
@@ -1094,13 +1098,11 @@ class MLBRecentManager(BaseMLBManager):
# Process games # Process games
new_recent_games = [] new_recent_games = []
now = datetime.now(timezone.utc) # Make timezone-aware
recent_cutoff = now - timedelta(hours=self.recent_hours)
self.logger.info(f"[MLB] Recent games time window: {recent_cutoff} to {now}") self.logger.info(f"[MLB] Processing {len(games)} games for recent games...")
for game_id, game in games.items(): for game_id, game in games.items():
self.logger.info(f"[MLB] Processing game {game_id} for recent games...") self.logger.debug(f"[MLB] Processing game {game_id} for recent games...")
# Convert game time to UTC datetime # Convert game time to UTC datetime
game_time_str = game['start_time'].replace('Z', '+00:00') game_time_str = game['start_time'].replace('Z', '+00:00')
game_time = datetime.fromisoformat(game_time_str) game_time = datetime.fromisoformat(game_time_str)
@@ -1112,7 +1114,7 @@ class MLBRecentManager(BaseMLBManager):
game['away_team'] in self.favorite_teams) game['away_team'] in self.favorite_teams)
if not is_favorite_game: if not is_favorite_game:
self.logger.info(f"[MLB] Skipping game {game_id} - not a favorite team.") self.logger.debug(f"[MLB] Skipping game {game_id} - not a favorite team.")
continue continue
self.logger.info(f"[MLB] Favorite team game found: {game['away_team']} @ {game['home_team']}") self.logger.info(f"[MLB] Favorite team game found: {game['away_team']} @ {game['home_team']}")
@@ -1121,18 +1123,21 @@ class MLBRecentManager(BaseMLBManager):
# Use status_state to determine if game is final # Use status_state to determine if game is final
is_final = game['status_state'] in ['post', 'final', 'completed'] is_final = game['status_state'] in ['post', 'final', 'completed']
is_within_time = recent_cutoff <= game_time <= now
self.logger.info(f"[MLB] Game Time: {game_time.isoformat()}, Cutoff Time: {recent_cutoff.isoformat()}, Now: {now.isoformat()}") self.logger.info(f"[MLB] Game Time: {game_time.isoformat()}")
self.logger.info(f"[MLB] Is final: {is_final}, Is within time window: {is_within_time}") self.logger.info(f"[MLB] Is final: {is_final}")
# Only add favorite team games that are final and within time window # Only add favorite team games that are final
if is_final and is_within_time: if is_final:
self.logger.info(f"[MLB] Adding game {game_id} to recent games list.") self.logger.info(f"[MLB] Adding game {game_id} to recent games list.")
self._fetch_odds(game) self._fetch_odds(game)
new_recent_games.append(game) new_recent_games.append(game)
else: else:
self.logger.info(f"[MLB] Skipping game {game_id} - does not meet criteria for recent games.") self.logger.info(f"[MLB] Skipping game {game_id} - not final.")
# Sort by game time (most recent first) and limit to recent_games_to_show
new_recent_games.sort(key=lambda x: x['start_time'], reverse=True)
new_recent_games = new_recent_games[:self.recent_games_to_show]
if new_recent_games: if new_recent_games:
logger.info(f"[MLB] Found {len(new_recent_games)} recent games for favorite teams: {self.favorite_teams}") logger.info(f"[MLB] Found {len(new_recent_games)} recent games for favorite teams: {self.favorite_teams}")
@@ -1188,6 +1193,7 @@ class MLBUpcomingManager(BaseMLBManager):
self.current_game_index = 0 self.current_game_index = 0
self.last_update = 0 self.last_update = 0
self.update_interval = self.mlb_config.get('upcoming_update_interval', 3600) self.update_interval = self.mlb_config.get('upcoming_update_interval', 3600)
self.upcoming_games_to_show = self.mlb_config.get('upcoming_games_to_show', 10) # Show next 10 games
self.last_warning_time = 0 self.last_warning_time = 0
self.warning_cooldown = 300 # Only show warning every 5 minutes self.warning_cooldown = 300 # Only show warning every 5 minutes
self.last_game_switch = 0 # Track when we last switched games self.last_game_switch = 0 # Track when we last switched games
@@ -1211,19 +1217,17 @@ class MLBUpcomingManager(BaseMLBManager):
# Process games # Process games
new_upcoming_games = [] new_upcoming_games = []
now = datetime.now(timezone.utc) # Make timezone-aware
upcoming_cutoff = now + timedelta(hours=24)
self.logger.info(f"[MLB] Upcoming games time window: {now} to {upcoming_cutoff}") self.logger.info(f"[MLB] Processing {len(games)} games for upcoming games...")
for game_id, game in games.items(): for game_id, game in games.items():
self.logger.info(f"[MLB] Processing game {game_id} for upcoming games...") self.logger.debug(f"[MLB] Processing game {game_id} for upcoming games...")
# Check if this is a favorite team game first # Check if this is a favorite team game first
is_favorite_game = (game['home_team'] in self.favorite_teams or is_favorite_game = (game['home_team'] in self.favorite_teams or
game['away_team'] in self.favorite_teams) game['away_team'] in self.favorite_teams)
if not is_favorite_game: if not is_favorite_game:
self.logger.info(f"[MLB] Skipping game {game_id} - not a favorite team.") self.logger.debug(f"[MLB] Skipping game {game_id} - not a favorite team.")
continue continue
game_time = datetime.fromisoformat(game['start_time'].replace('Z', '+00:00')) game_time = datetime.fromisoformat(game['start_time'].replace('Z', '+00:00'))
@@ -1234,20 +1238,14 @@ class MLBUpcomingManager(BaseMLBManager):
self.logger.info(f"[MLB] Favorite team game found: {game['away_team']} @ {game['home_team']} at {game_time}") self.logger.info(f"[MLB] Favorite team game found: {game['away_team']} @ {game['home_team']} at {game_time}")
self.logger.info(f"[MLB] Game status: {game['status']}, State: {game['status_state']}") self.logger.info(f"[MLB] Game status: {game['status']}, State: {game['status_state']}")
# Check if game is within our time window
is_within_time = now <= game_time <= upcoming_cutoff
# For upcoming games, we'll consider any game that: # For upcoming games, we'll consider any game that:
# 1. Is within our time window # 1. Is not final (not 'post' or 'final' state)
# 2. Is not final (not 'post' or 'final' state) # 2. Has a future start time
# 3. Has a future start time
is_upcoming = ( is_upcoming = (
is_within_time and
game['status_state'] not in ['post', 'final', 'completed'] and game['status_state'] not in ['post', 'final', 'completed'] and
game_time > now game_time > datetime.now(timezone.utc)
) )
self.logger.info(f"[MLB] Is within time window: {is_within_time}")
self.logger.info(f"[MLB] Is upcoming: {is_upcoming}") self.logger.info(f"[MLB] Is upcoming: {is_upcoming}")
if is_upcoming: if is_upcoming:
@@ -1255,16 +1253,15 @@ class MLBUpcomingManager(BaseMLBManager):
self._fetch_odds(game) self._fetch_odds(game)
new_upcoming_games.append(game) new_upcoming_games.append(game)
else: else:
self.logger.info(f"[MLB] Skipping game {game_id} - does not meet criteria for upcoming games.") self.logger.info(f"[MLB] Skipping game {game_id} - not upcoming.")
# Filter for favorite teams (though we already filtered above, this is a safety check) # Sort by game time (soonest first) and limit to upcoming_games_to_show
new_team_games = [game for game in new_upcoming_games new_upcoming_games.sort(key=lambda x: x['start_time'])
if game['home_team'] in self.favorite_teams or new_upcoming_games = new_upcoming_games[:self.upcoming_games_to_show]
game['away_team'] in self.favorite_teams]
if new_team_games: if new_upcoming_games:
logger.info(f"[MLB] Found {len(new_team_games)} upcoming games for favorite teams") logger.info(f"[MLB] Found {len(new_upcoming_games)} upcoming games for favorite teams")
self.upcoming_games = new_team_games self.upcoming_games = new_upcoming_games
if not self.current_game: if not self.current_game:
self.current_game = self.upcoming_games[0] self.current_game = self.upcoming_games[0]
else: else:

View File

@@ -54,6 +54,9 @@ class BaseNCAAFBManager: # Renamed class
self.update_interval = self.ncaa_fb_config.get("update_interval_seconds", 60) self.update_interval = self.ncaa_fb_config.get("update_interval_seconds", 60)
self.show_records = self.ncaa_fb_config.get('show_records', False) self.show_records = self.ncaa_fb_config.get('show_records', False)
self.season_cache_duration = self.ncaa_fb_config.get("season_cache_duration_seconds", 86400) # 24 hours default self.season_cache_duration = self.ncaa_fb_config.get("season_cache_duration_seconds", 86400) # 24 hours default
# Number of games to show (instead of time-based windows)
self.recent_games_to_show = self.ncaa_fb_config.get("recent_games_to_show", 5) # Show last 5 games
self.upcoming_games_to_show = self.ncaa_fb_config.get("upcoming_games_to_show", 10) # Show next 10 games
# Set up session with retry logic # Set up session with retry logic
self.session = requests.Session() self.session = requests.Session()
@@ -79,8 +82,6 @@ class BaseNCAAFBManager: # Renamed class
self.current_game = None self.current_game = None
self.fonts = self._load_fonts() self.fonts = self._load_fonts()
self.favorite_teams = self.ncaa_fb_config.get("favorite_teams", []) self.favorite_teams = self.ncaa_fb_config.get("favorite_teams", [])
self.fetch_past_games = self.ncaa_fb_config.get("fetch_past_games", 1)
self.fetch_future_games = self.ncaa_fb_config.get("fetch_future_games", 1)
# Check display modes to determine what data to fetch # Check display modes to determine what data to fetch
display_modes = self.ncaa_fb_config.get("display_modes", {}) display_modes = self.ncaa_fb_config.get("display_modes", {})
@@ -219,46 +220,9 @@ class BaseNCAAFBManager: # Renamed class
upcoming_events.sort(key=lambda x: x['date']) upcoming_events.sort(key=lambda x: x['date'])
past_events.sort(key=lambda x: x['date'], reverse=True) past_events.sort(key=lambda x: x['date'], reverse=True)
# Select the correct number of games for favorite teams # Include all games in shared data - let individual managers filter by count
selected_upcoming = [] selected_upcoming = upcoming_events
if self.upcoming_enabled and self.favorite_teams: selected_past = past_events
games_found = {team: 0 for team in self.favorite_teams}
for game in upcoming_events:
competitors = game.get('competitions', [{}])[0].get('competitors', [])
home_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'home'), '')
away_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'away'), '')
team_in_game = None
if home_team in self.favorite_teams and games_found[home_team] < self.fetch_future_games:
team_in_game = home_team
elif away_team in self.favorite_teams and games_found[away_team] < self.fetch_future_games:
team_in_game = away_team
if team_in_game:
selected_upcoming.append(game)
games_found[team_in_game] += 1
if all(count >= self.fetch_future_games for count in games_found.values()):
break
selected_past = []
if self.recent_enabled and self.favorite_teams:
games_found = {team: 0 for team in self.favorite_teams}
for game in past_events:
competitors = game.get('competitions', [{}])[0].get('competitors', [])
home_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'home'), '')
away_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'away'), '')
team_in_game = None
if home_team in self.favorite_teams and games_found[home_team] < self.fetch_past_games:
team_in_game = home_team
elif away_team in self.favorite_teams and games_found[away_team] < self.fetch_past_games:
team_in_game = away_team
if team_in_game:
selected_past.append(game)
games_found[team_in_game] += 1
if all(count >= self.fetch_past_games for count in games_found.values()):
break
# Combine all relevant events into a single list # Combine all relevant events into a single list
BaseNCAAFBManager.all_events = live_events + selected_upcoming + selected_past BaseNCAAFBManager.all_events = live_events + selected_upcoming + selected_past
@@ -559,6 +523,9 @@ class BaseNCAAFBManager: # Renamed class
home_timeouts = home_team.get("timeouts", 3) # Default to 3 if not specified home_timeouts = home_team.get("timeouts", 3) # Default to 3 if not specified
away_timeouts = away_team.get("timeouts", 3) # Default to 3 if not specified away_timeouts = away_team.get("timeouts", 3) # Default to 3 if not specified
# 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 = { details = {
"id": game_event.get("id"), "id": game_event.get("id"),
@@ -586,6 +553,7 @@ class BaseNCAAFBManager: # Renamed class
"down_distance_text": down_distance_text, # Added Down/Distance "down_distance_text": down_distance_text, # Added Down/Distance
"possession": situation.get("possession") if situation else None, # ID of team with possession "possession": situation.get("possession") if situation else None, # ID of team with possession
"possession_indicator": possession_indicator, # Added for easy home/away check "possession_indicator": possession_indicator, # Added for easy home/away check
"is_within_window": is_within_window, # Whether game is within display window
} }
# Basic validation (can be expanded) # Basic validation (can be expanded)
@@ -999,12 +967,12 @@ class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class
events = data['events'] events = data['events']
# self.logger.info(f"[NCAAFB Recent] Processing {len(events)} events from shared data.") # Changed log prefix # self.logger.info(f"[NCAAFB Recent] Processing {len(events)} events from shared data.") # Changed log prefix
# Process games and filter for final & within window & favorite teams # Process games and filter for final games & favorite teams
processed_games = [] processed_games = []
for event in events: for event in events:
game = self._extract_game_details(event) game = self._extract_game_details(event)
# Filter criteria: must be final, within time window # Filter criteria: must be final
if game and game['is_final'] and game.get('is_within_window', True): # Assume within window if key missing, check logic if game and game['is_final']:
processed_games.append(game) processed_games.append(game)
# Filter for favorite teams # Filter for favorite teams
@@ -1017,6 +985,9 @@ class NCAAFBRecentManager(BaseNCAAFBManager): # Renamed class
# Sort by game time, most recent first # Sort by game time, most recent first
team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.min.replace(tzinfo=timezone.utc), reverse=True) team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.min.replace(tzinfo=timezone.utc), reverse=True)
# Limit to the specified number of recent games
team_games = team_games[:self.recent_games_to_show]
# Cache the processed games # Cache the processed games
self._cache_processed_games('recent', team_games) self._cache_processed_games('recent', team_games)
@@ -1212,8 +1183,8 @@ class NCAAFBUpcomingManager(BaseNCAAFBManager): # Renamed class
processed_games = [] processed_games = []
for event in events: for event in events:
game = self._extract_game_details(event) game = self._extract_game_details(event)
# Filter criteria: must be upcoming ('pre' state) and within time window # Filter criteria: must be upcoming ('pre' state)
if game and game['is_upcoming'] and game.get('is_within_window', True): # Assume within window if key missing, check logic if game and game['is_upcoming']:
processed_games.append(game) processed_games.append(game)
# Debug logging to see what games we have # Debug logging to see what games we have
@@ -1246,6 +1217,9 @@ class NCAAFBUpcomingManager(BaseNCAAFBManager): # Renamed class
# Sort by game time, earliest first # Sort by game time, earliest first
team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.max.replace(tzinfo=timezone.utc)) team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.max.replace(tzinfo=timezone.utc))
# Limit to the specified number of upcoming games
team_games = team_games[:self.upcoming_games_to_show]
# Cache the processed games # Cache the processed games
self._cache_processed_games('upcoming', team_games) self._cache_processed_games('upcoming', team_games)

View File

@@ -23,56 +23,7 @@ logging.basicConfig(
datefmt='%Y-%m-%d %H:%M:%S' datefmt='%Y-%m-%d %H:%M:%S'
) )
# Re-add CacheManager definition temporarily until it's confirmed where it lives
class CacheManager:
"""Manages caching of ESPN API responses."""
_instance = None
_cache = {}
_cache_timestamps = {}
def __new__(cls):
if cls._instance is None:
cls._instance = super(CacheManager, cls).__new__(cls)
return cls._instance
@classmethod
def get(cls, key: str, max_age: int = 60) -> Optional[Dict]:
"""
Get data from cache if it exists and is not stale.
Args:
key: Cache key (usually the date string)
max_age: Maximum age of cached data in seconds
Returns:
Cached data if valid, None if missing or stale
"""
if key not in cls._cache:
return None
timestamp = cls._cache_timestamps.get(key, 0)
if time.time() - timestamp > max_age:
# Data is stale, remove it
del cls._cache[key]
del cls._cache_timestamps[key]
return None
return cls._cache[key]
@classmethod
def set(cls, key: str, data: Dict) -> None:
"""
Store data in cache with current timestamp.
Args:
key: Cache key (usually the date string)
data: Data to cache
"""
cls._cache[key] = data
cls._cache_timestamps[key] = time.time()
@classmethod
def clear(cls) -> None:
"""Clear all cached data."""
cls._cache.clear()
cls._cache_timestamps.clear()
class BaseNFLManager: # Renamed class class BaseNFLManager: # Renamed class
@@ -102,8 +53,6 @@ class BaseNFLManager: # Renamed class
self.current_game = None self.current_game = None
self.fonts = self._load_fonts() self.fonts = self._load_fonts()
self.favorite_teams = self.nfl_config.get("favorite_teams", []) self.favorite_teams = self.nfl_config.get("favorite_teams", [])
self.fetch_past_games = self.nfl_config.get("fetch_past_games", 1)
self.fetch_future_games = self.nfl_config.get("fetch_future_games", 1)
# Check display modes to determine what data to fetch # Check display modes to determine what data to fetch
display_modes = self.nfl_config.get("display_modes", {}) display_modes = self.nfl_config.get("display_modes", {})
@@ -122,6 +71,16 @@ class BaseNFLManager: # Renamed class
self._logo_cache = {} self._logo_cache = {}
# Set up session with retry logic
self.session = requests.Session()
self.session.mount('http://', requests.adapters.HTTPAdapter(max_retries=3))
self.session.mount('https://', requests.adapters.HTTPAdapter(max_retries=3))
# Set up headers for ESPN API
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
self.logger.info(f"Initialized NFL manager with display dimensions: {self.display_width}x{self.display_height}") self.logger.info(f"Initialized NFL manager with display dimensions: {self.display_width}x{self.display_height}")
self.logger.info(f"Logo directory: {self.logo_dir}") self.logger.info(f"Logo directory: {self.logo_dir}")
self.logger.info(f"Display modes - Recent: {self.recent_enabled}, Upcoming: {self.upcoming_enabled}, Live: {self.live_enabled}") self.logger.info(f"Display modes - Recent: {self.recent_enabled}, Upcoming: {self.upcoming_enabled}, Live: {self.live_enabled}")
@@ -172,7 +131,7 @@ class BaseNFLManager: # Renamed class
except Exception as e: except Exception as e:
self.logger.error(f"Error fetching odds for game {game.get('id', 'N/A')}: {e}") self.logger.error(f"Error fetching odds for game {game.get('id', 'N/A')}: {e}")
def _fetch_shared_data(self) -> None: def _fetch_shared_data(self) -> Optional[Dict]:
""" """
Fetches the full season schedule for NFL, caches it, and then filters Fetches the full season schedule for NFL, caches it, and then filters
for relevant games based on the current configuration. for relevant games based on the current configuration.
@@ -199,11 +158,11 @@ class BaseNFLManager: # Renamed class
self.logger.info(f"[NFL] Successfully fetched and cached {len(events)} events for the {current_year} season.") self.logger.info(f"[NFL] Successfully fetched and cached {len(events)} events for the {current_year} season.")
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
self.logger.error(f"[NFL] API error fetching full schedule: {e}") self.logger.error(f"[NFL] API error fetching full schedule: {e}")
return return None
if not events: if not events:
self.logger.warning("[NFL] No events found in the schedule data.") self.logger.warning("[NFL] No events found in the schedule data.")
return return None
# Filter the events for live, upcoming, and recent games # Filter the events for live, upcoming, and recent games
live_events = [] live_events = []
@@ -227,52 +186,16 @@ class BaseNFLManager: # Renamed class
upcoming_events.sort(key=lambda x: x['date']) upcoming_events.sort(key=lambda x: x['date'])
past_events.sort(key=lambda x: x['date'], reverse=True) past_events.sort(key=lambda x: x['date'], reverse=True)
# Select the correct number of games for favorite teams # Include all games in shared data - let individual managers filter by count
selected_upcoming = [] selected_upcoming = upcoming_events
if self.upcoming_enabled and self.favorite_teams: selected_past = past_events
games_found = {team: 0 for team in self.favorite_teams}
for game in upcoming_events:
competitors = game.get('competitions', [{}])[0].get('competitors', [])
home_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'home'), '')
away_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'away'), '')
# Check if this game involves any favorite teams that still need games
team_in_game = None
if home_team in self.favorite_teams and games_found[home_team] < self.fetch_future_games:
team_in_game = home_team
elif away_team in self.favorite_teams and games_found[away_team] < self.fetch_future_games:
team_in_game = away_team
if team_in_game:
selected_upcoming.append(game)
games_found[team_in_game] += 1
# Stop if we have found enough games for all teams
if all(count >= self.fetch_future_games for count in games_found.values()):
break
selected_past = []
if self.recent_enabled and self.favorite_teams:
games_found = {team: 0 for team in self.favorite_teams}
for game in past_events:
competitors = game.get('competitions', [{}])[0].get('competitors', [])
home_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'home'), '')
away_team = next((c['team']['abbreviation'] for c in competitors if c.get('homeAway') == 'away'), '')
team_in_game = None
if home_team in self.favorite_teams and games_found[home_team] < self.fetch_past_games:
team_in_game = home_team
elif away_team in self.favorite_teams and games_found[away_team] < self.fetch_past_games:
team_in_game = away_team
if team_in_game:
selected_past.append(game)
games_found[team_in_game] += 1
if all(count >= self.fetch_past_games for count in games_found.values()):
break
# Combine all relevant events into a single list # Combine all relevant events into a single list
BaseNFLManager.all_events = live_events + selected_upcoming + selected_past BaseNFLManager.all_events = live_events + selected_upcoming + selected_past
self.logger.info(f"[NFL] Processed schedule: {len(live_events)} live, {len(selected_upcoming)} upcoming, {len(selected_past)} recent games.") self.logger.info(f"[NFL] Processed schedule: {len(live_events)} live, {len(selected_upcoming)} upcoming, {len(selected_past)} recent games.")
# Return the data in the expected format
return {'events': BaseNFLManager.all_events}
def _fetch_data(self, date_str: str = None) -> Optional[Dict]: def _fetch_data(self, date_str: str = None) -> Optional[Dict]:
"""Fetch data using shared data mechanism or direct fetch for live.""" """Fetch data using shared data mechanism or direct fetch for live."""
@@ -294,7 +217,11 @@ class BaseNFLManager: # Renamed class
return None return None
else: else:
# For non-live games, use the shared cache # For non-live games, use the shared cache
return self._fetch_shared_data() shared_data = self._fetch_shared_data()
if shared_data is None:
self.logger.warning("[NFL] No shared data available")
return None
return shared_data
def _load_fonts(self): def _load_fonts(self):
"""Load fonts used by the scoreboard.""" """Load fonts used by the scoreboard."""
@@ -949,8 +876,8 @@ class NFLRecentManager(BaseNFLManager): # Renamed class
processed_games = [] processed_games = []
for event in events: for event in events:
game = self._extract_game_details(event) game = self._extract_game_details(event)
# Filter criteria: must be final, within time window # Filter criteria: must be final
if game and game['is_final'] and game.get('is_within_window', True): # Assume within window if key missing if game and game['is_final']:
# Fetch odds if enabled # Fetch odds if enabled
if self.show_odds: if self.show_odds:
self._fetch_odds(game) self._fetch_odds(game)
@@ -966,6 +893,10 @@ class NFLRecentManager(BaseNFLManager): # Renamed class
# Sort by game time, most recent first # Sort by game time, most recent first
team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.min.replace(tzinfo=self._get_timezone()), reverse=True) team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.min.replace(tzinfo=self._get_timezone()), reverse=True)
# Limit to the specified number of recent games (default 5)
recent_games_to_show = self.nfl_config.get("recent_games_to_show", 5)
team_games = team_games[:recent_games_to_show]
# Check if the list of games to display has changed # Check if the list of games to display has changed
new_game_ids = {g['id'] for g in team_games} new_game_ids = {g['id'] for g in team_games}
@@ -1152,8 +1083,8 @@ class NFLUpcomingManager(BaseNFLManager): # Renamed class
processed_games = [] processed_games = []
for event in events: for event in events:
game = self._extract_game_details(event) game = self._extract_game_details(event)
# Filter criteria: must be upcoming ('pre' state) and within time window # Filter criteria: must be upcoming ('pre' state)
if game and game['is_upcoming'] and game.get('is_within_window', True): # Assume within window if key missing if game and game['is_upcoming']:
# Fetch odds if enabled # Fetch odds if enabled
if self.show_odds: if self.show_odds:
self._fetch_odds(game) self._fetch_odds(game)
@@ -1177,6 +1108,10 @@ class NFLUpcomingManager(BaseNFLManager): # Renamed class
# Sort by game time, earliest first # Sort by game time, earliest first
team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.max.replace(tzinfo=self._get_timezone())) team_games.sort(key=lambda g: g.get('start_time_utc') or datetime.max.replace(tzinfo=self._get_timezone()))
# Limit to the specified number of upcoming games (default 10)
upcoming_games_to_show = self.nfl_config.get("upcoming_games_to_show", 10)
team_games = team_games[:upcoming_games_to_show]
# Log changes or periodically # Log changes or periodically
should_log = ( should_log = (