implement some functionality from commit 8dccebf without a complete roll back

This commit is contained in:
ChuckBuilds
2025-07-21 19:48:20 -05:00
parent 243dc44eb9
commit 2516a06ad3

View File

@@ -84,140 +84,6 @@ class OddsTickerManager:
}
}
# Team name mappings for better display
self.team_names = {
'nfl': {
'TB': 'Bucs',
'DAL': 'Cowboys',
'NE': 'Patriots',
'NYG': 'Giants',
'NYJ': 'Jets',
'BUF': 'Bills',
'MIA': 'Dolphins',
'BAL': 'Ravens',
'CIN': 'Bengals',
'CLE': 'Browns',
'PIT': 'Steelers',
'HOU': 'Texans',
'IND': 'Colts',
'JAX': 'Jaguars',
'TEN': 'Titans',
'DEN': 'Broncos',
'KC': 'Chiefs',
'LV': 'Raiders',
'LAC': 'Chargers',
'ARI': 'Cardinals',
'ATL': 'Falcons',
'CAR': 'Panthers',
'CHI': 'Bears',
'DET': 'Lions',
'GB': 'Packers',
'MIN': 'Vikings',
'NO': 'Saints',
'PHI': 'Eagles',
'SEA': 'Seahawks',
'SF': '49ers',
'LAR': 'Rams',
'WAS': 'Commanders'
},
'ncaa_fb': {
'UGA': 'Georgia',
'AUB': 'Auburn',
'ALA': 'Alabama',
'LSU': 'LSU',
'FLA': 'Florida',
'TEN': 'Tennessee',
'TEX': 'Texas',
'OKL': 'Oklahoma',
'ORE': 'Oregon',
'WAS': 'Washington',
'USC': 'USC',
'UCLA': 'UCLA',
'MICH': 'Michigan',
'OSU': 'Ohio State',
'PSU': 'Penn State',
'ND': 'Notre Dame',
'CLEM': 'Clemson',
'FSU': 'Florida State',
'MIAMI': 'Miami',
'VT': 'Virginia Tech',
'UNC': 'North Carolina',
'DUKE': 'Duke',
'NCST': 'NC State',
'WAKE': 'Wake Forest',
'BC': 'Boston College',
'GT': 'Georgia Tech',
'LOU': 'Louisville',
'PITT': 'Pittsburgh',
'SYR': 'Syracuse',
'CUSE': 'Syracuse'
},
'mlb': {
'TB': 'Rays',
'TEX': 'Rangers',
'NYY': 'Yankees',
'BOS': 'Red Sox',
'BAL': 'Orioles',
'TOR': 'Blue Jays',
'CWS': 'White Sox',
'CLE': 'Guardians',
'DET': 'Tigers',
'KC': 'Royals',
'MIN': 'Twins',
'HOU': 'Astros',
'LAA': 'Angels',
'OAK': 'Athletics',
'SEA': 'Mariners',
'ATL': 'Braves',
'MIA': 'Marlins',
'NYM': 'Mets',
'PHI': 'Phillies',
'WSH': 'Nationals',
'CHC': 'Cubs',
'CIN': 'Reds',
'MIL': 'Brewers',
'PIT': 'Pirates',
'STL': 'Cardinals',
'ARI': 'Diamondbacks',
'COL': 'Rockies',
'LAD': 'Dodgers',
'SD': 'Padres',
'SF': 'Giants'
},
'nba': {
'DAL': 'Mavericks',
'BOS': 'Celtics',
'LAL': 'Lakers',
'LAC': 'Clippers',
'GSW': 'Warriors',
'PHX': 'Suns',
'SAC': 'Kings',
'POR': 'Trail Blazers',
'UTA': 'Jazz',
'DEN': 'Nuggets',
'OKC': 'Thunder',
'HOU': 'Rockets',
'SAS': 'Spurs',
'MEM': 'Grizzlies',
'NOP': 'Pelicans',
'MIN': 'Timberwolves',
'MIA': 'Heat',
'ORL': 'Magic',
'ATL': 'Hawks',
'CHA': 'Hornets',
'WAS': 'Wizards',
'DET': 'Pistons',
'CLE': 'Cavaliers',
'IND': 'Pacers',
'CHI': 'Bulls',
'MIL': 'Bucks',
'TOR': 'Raptors',
'PHI': '76ers',
'BKN': 'Nets',
'NYK': 'Knicks'
}
}
logger.info(f"OddsTickerManager initialized with enabled leagues: {self.enabled_leagues}")
logger.info(f"Show favorite teams only: {self.show_favorite_teams_only}")
@@ -237,13 +103,6 @@ class OddsTickerManager:
'large': ImageFont.load_default()
}
def _get_team_name(self, team_abbr: str, league: str) -> str:
"""Convert team abbreviation to full team name for display."""
league_key = league.lower()
if league_key in self.team_names and team_abbr in self.team_names[league_key]:
return self.team_names[league_key][team_abbr]
return team_abbr # Return original if no mapping found
def _fetch_team_record(self, team_abbr: str, league: str) -> str:
"""Fetch team record from ESPN API."""
# This is a simplified implementation; a more robust solution would cache team data
@@ -304,22 +163,10 @@ class OddsTickerManager:
league_config = self.league_configs[league_key]
logger.debug(f"Processing league {league_key}: enabled={league_config['enabled']}")
# Debug: Log what we're trying to fetch
logger.info(f"[OddsTicker] Fetching data for {league_key.upper()} (enabled: {league_config['enabled']})")
try:
# Fetch all upcoming games for this league
all_games = self._fetch_league_games(league_config, now)
logger.debug(f"Found {len(all_games)} games for {league_key}")
# Debug: Log what games were found
if all_games:
logger.info(f"[OddsTicker] Found {len(all_games)} games for {league_key.upper()}")
for game in all_games[:3]: # Show first 3 games
logger.info(f"[OddsTicker] {league_key.upper()} game: {game.get('away_team', 'N/A')} vs {game.get('home_team', 'N/A')}")
else:
logger.info(f"[OddsTicker] No games found for {league_key.upper()}")
league_games = []
if self.show_favorite_teams_only:
@@ -364,121 +211,102 @@ class OddsTickerManager:
return games_data
def _fetch_league_games(self, league_config: Dict[str, Any], now: datetime) -> List[Dict[str, Any]]:
"""
Fetches the full season schedule for a league from cache or API,
then filters for relevant upcoming games.
"""
sport = league_config['sport']
league = league_config['league']
current_year = now.year
cache_key = f"odds_ticker_schedule_{league}_{current_year}"
# 1. Fetch full schedule from cache or API
events = self.cache_manager.get(cache_key)
if not events:
logger.info(f"[OddsTicker] Fetching full {current_year} schedule for {league.upper()}...")
try:
# NCAAFB needs a specific seasontype for regular season
if league == 'college-football':
url = f"https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/scoreboard?dates={current_year}&seasontype=2"
else:
url = f"https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/scoreboard?dates={current_year}"
response = requests.get(url, timeout=15)
response.raise_for_status()
data = response.json()
events = data.get('events', [])
self.cache_manager.set(cache_key, events, expiration_seconds=86400) # Cache for 24 hours
logger.info(f"[OddsTicker] Cached {len(events)} events for {league.upper()} {current_year} season.")
except requests.exceptions.RequestException as e:
logger.error(f"[OddsTicker] API error fetching schedule for {league.upper()}: {e}")
return []
else:
logger.info(f"[OddsTicker] Using cached {len(events)}-event schedule for {league.upper()} {current_year}")
if not events:
return []
# 2. Filter the schedule for upcoming games within the display window
all_games = []
"""Fetch upcoming games for a specific league using day-by-day approach."""
games = []
yesterday = now - timedelta(days=1)
future_window = now + timedelta(days=self.future_fetch_days)
# Prepare for favorite team logic
num_days = (future_window - yesterday).days + 1
dates = [(yesterday + timedelta(days=i)).strftime("%Y%m%d") for i in range(num_days)]
# Optimization: If showing favorite teams only, track games found per team
favorite_teams = league_config.get('favorite_teams', []) if self.show_favorite_teams_only else []
team_games_found = {team: 0 for team in favorite_teams}
max_games_per_team = self.games_per_favorite_team
max_games = self.games_per_favorite_team if self.show_favorite_teams_only else None
all_games = []
# Prepare for general league game limit
games_found_total = 0
max_games_per_league = self.max_games_per_league
# Optimization: Track total games found when not showing favorite teams only
games_found = 0
max_games_per_league = self.max_games_per_league if not self.show_favorite_teams_only else None
# Sort all events by date to process them chronologically
events.sort(key=lambda x: x.get('date', ''))
for event in events:
# Apply global game limits to stop processing early
if self.show_favorite_teams_only:
if all(count >= max_games_per_team for count in team_games_found.values()):
logger.info(f"[OddsTicker] Found enough games for all favorite teams in {league.upper()}. Stopping search.")
for date in dates:
# Stop if we have enough games for favorite teams
if self.show_favorite_teams_only and all(team_games_found[t] >= max_games for t in favorite_teams):
break # All favorite teams have enough games, stop searching
# Stop if we have enough games for the league (when not showing favorite teams only)
if not self.show_favorite_teams_only and max_games_per_league and games_found >= max_games_per_league:
break # We have enough games for this league, stop searching
try:
sport = league_config['sport']
league = league_config['league']
url = f"https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/scoreboard?dates={date}"
logger.debug(f"Fetching {league} games from ESPN API for date: {date}")
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
for event in data.get('events', []):
# Stop if we have enough games for the league (when not showing favorite teams only)
if not self.show_favorite_teams_only and max_games_per_league and games_found >= max_games_per_league:
break
game_id = event['id']
status = event['status']['type']['name'].lower()
if status in ['scheduled', 'pre-game', 'status_scheduled']:
game_time = datetime.fromisoformat(event['date'].replace('Z', '+00:00'))
if now <= game_time <= future_window:
competitors = event['competitions'][0]['competitors']
home_team = next(c for c in competitors if c['homeAway'] == 'home')
away_team = next(c for c in competitors if c['homeAway'] == 'away')
home_abbr = home_team['team']['abbreviation']
away_abbr = away_team['team']['abbreviation']
# Only process favorite teams if enabled
if self.show_favorite_teams_only:
# Skip if both teams have already met their quota
for team in [home_abbr, away_abbr]:
if team in team_games_found and team_games_found[team] >= max_games:
continue
# Only add if at least one team still needs games
if not ((home_abbr in team_games_found and team_games_found[home_abbr] < max_games) or (away_abbr in team_games_found and team_games_found[away_abbr] < max_games)):
continue
# Build game dict (existing logic)
home_record = home_team.get('records', [{}])[0].get('summary', '') if home_team.get('records') else ''
away_record = away_team.get('records', [{}])[0].get('summary', '') if away_team.get('records') else ''
odds_data = self.odds_manager.get_odds(
sport=sport,
league=league,
event_id=game_id,
update_interval_seconds=7200
)
has_odds = False
if odds_data and not odds_data.get('no_odds'):
if odds_data.get('spread') is not None:
has_odds = True
if odds_data.get('home_team_odds', {}).get('spread_odds') is not None:
has_odds = True
if odds_data.get('away_team_odds', {}).get('spread_odds') is not None:
has_odds = True
if odds_data.get('over_under') is not None:
has_odds = True
game = {
'id': game_id,
'home_team': home_abbr,
'away_team': away_abbr,
'start_time': game_time,
'home_record': home_record,
'away_record': away_record,
'odds': odds_data if has_odds else None,
'logo_dir': league_config.get('logo_dir', f'assets/sports/{league.lower()}_logos')
}
all_games.append(game)
games_found += 1
# If favorite teams only, increment counters
if self.show_favorite_teams_only:
for team in [home_abbr, away_abbr]:
if team in team_games_found and team_games_found[team] < max_games:
team_games_found[team] += 1
# Stop if we have enough games for the league (when not showing favorite teams only)
if not self.show_favorite_teams_only and max_games_per_league and games_found >= max_games_per_league:
break
elif max_games_per_league and games_found_total >= max_games_per_league:
logger.info(f"[OddsTicker] Found max games ({max_games_per_league}) for {league.upper()}. Stopping search.")
break
status = event.get('status', {}).get('type', {}).get('name', 'unknown').lower()
if status not in ('status_scheduled', 'scheduled', 'pre-game'):
continue
game_time = datetime.fromisoformat(event['date'].replace('Z', '+00:00'))
if not (now <= game_time <= future_window):
continue
# This is a valid upcoming game, extract details
competitors = event.get('competitions', [{}])[0].get('competitors', [])
home_team = next((c for c in competitors if c.get('homeAway') == 'home'), None)
away_team = next((c for c in competitors if c.get('homeAway') == 'away'), None)
if not home_team or not away_team:
continue
home_abbr = home_team['team']['abbreviation']
away_abbr = away_team['team']['abbreviation']
# Apply favorite team filtering if necessary
if self.show_favorite_teams_only:
is_favorite_game = home_abbr in favorite_teams or away_abbr in favorite_teams
if not is_favorite_game:
continue
# Check if both teams in the game have already met their quota
if (home_abbr not in favorite_teams or team_games_found.get(home_abbr, 0) >= max_games_per_team) and \
(away_abbr not in favorite_teams or team_games_found.get(away_abbr, 0) >= max_games_per_team):
continue
# --- Game processing logic from old function ---
game_id = event['id']
home_record = home_team.get('records', [{}])[0].get('summary', '') if home_team.get('records') else ''
away_record = away_team.get('records', [{}])[0].get('summary', '') if away_team.get('records') else ''
odds_data = self.odds_manager.get_odds(sport=sport, league=league, event_id=game_id, update_interval_seconds=7200)
home_team_name = self._get_team_name(home_abbr, league)
away_team_name = self._get_team_name(away_abbr, league)
game = {
'id': game_id, 'home_team': home_team_name, 'away_team': away_team_name,
'home_abbr': home_abbr, 'away_abbr': away_abbr, 'start_time': game_time,
'home_record': home_record, 'away_record': away_record,
'odds': odds_data, 'logo_dir': league_config.get('logo_dir')
}
if not any(g['id'] == game['id'] for g in all_games):
all_games.append(game)
games_found_total += 1
if self.show_favorite_teams_only:
if home_abbr in team_games_found:
team_games_found[home_abbr] += 1
if away_abbr in team_games_found:
team_games_found[away_abbr] += 1
except Exception as e:
logger.error(f"Error fetching games for {league_config.get('league', 'unknown')} on {date}: {e}", exc_info=True)
return all_games
def _format_odds_text(self, game: Dict[str, Any]) -> str:
@@ -571,8 +399,8 @@ class OddsTickerManager:
datetime_font = self.fonts['medium'] # Use large font for date/time
# Get team logos
home_logo = self._get_team_logo(game['home_abbr'], game['logo_dir'])
away_logo = self._get_team_logo(game['away_abbr'], game['logo_dir'])
home_logo = self._get_team_logo(game['home_team'], game['logo_dir'])
away_logo = self._get_team_logo(game['away_team'], game['logo_dir'])
if home_logo:
home_logo = home_logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
@@ -788,11 +616,6 @@ class OddsTickerManager:
logger.info(f"Game {i+1}: {game['away_team']} @ {game['home_team']} - {game['start_time']}")
else:
logger.warning("No games found for odds ticker")
logger.info("This could be due to:")
logger.info("- No upcoming games in the next 7 days")
logger.info("- No favorite teams have upcoming games (if show_favorite_teams_only is True)")
logger.info("- API is not returning data")
logger.info("- Leagues are disabled in config")
except Exception as e:
logger.error(f"Error updating odds ticker: {e}", exc_info=True)