From 1ffe3e7c16f6c35ca4667bda5b53df3a5ee1cc44 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Sat, 9 Aug 2025 15:54:15 -0500 Subject: [PATCH] add favorite team filtering to soccer manager and ensure timezones are respected --- src/soccer_managers.py | 64 ++++++++-- test/test_soccer_favorite_teams.py | 194 +++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 test/test_soccer_favorite_teams.py diff --git a/src/soccer_managers.py b/src/soccer_managers.py index 6b625fe6..125b45e4 100644 --- a/src/soccer_managers.py +++ b/src/soccer_managers.py @@ -94,13 +94,26 @@ class BaseSoccerManager: self.logger.info(f"Upcoming fetch days: {self.upcoming_fetch_days}") # Log new setting self.logger.info(f"Team map file: {self.team_map_file}") self.logger.info(f"Team map update interval: {self.team_map_update_days} days") + + # Log favorite teams configuration + show_favorites_only = self.soccer_config.get("show_favorite_teams_only", False) + if show_favorites_only: + self.logger.info(f"Favorite teams filtering enabled. Favorite teams: {self.favorite_teams}") + else: + self.logger.info("Favorite teams filtering disabled. Showing all teams.") - self.config_manager = ConfigManager(config) + self.config_manager = ConfigManager() def _get_timezone(self): try: - return pytz.timezone(self.config_manager.get_timezone()) + timezone_str = self.config_manager.get_timezone() + self.logger.debug(f"[Soccer] Config timezone: {timezone_str}") + return pytz.timezone(timezone_str) except pytz.UnknownTimeZoneError: + self.logger.warning(f"[Soccer] Unknown timezone: {timezone_str}, falling back to UTC") + return pytz.utc + except Exception as e: + self.logger.error(f"[Soccer] Error getting timezone: {e}, falling back to UTC") return pytz.utc def _fetch_odds(self, game: Dict) -> None: @@ -327,7 +340,7 @@ class BaseSoccerManager: if team_abbrev in self._logo_cache: return self._logo_cache[team_abbrev] - # Try to find the logo file with case-insensitive matching + # Try to find the logo file with case-insensitive matching and common variations logo_path = None expected_path = os.path.join(self.logo_dir, f"{team_abbrev}.png") @@ -335,13 +348,38 @@ class BaseSoccerManager: if os.path.exists(expected_path): logo_path = expected_path else: - # Try case-insensitive matching + # Try case-insensitive matching and common variations try: for filename in os.listdir(self.logo_dir): - if filename.lower() == f"{team_abbrev.lower()}.png": + filename_lower = filename.lower() + team_abbrev_lower = team_abbrev.lower() + + # Exact case-insensitive match + if filename_lower == f"{team_abbrev_lower}.png": logo_path = os.path.join(self.logo_dir, filename) self.logger.debug(f"Found case-insensitive match: {filename} for {team_abbrev}") break + + # Handle common team abbreviation variations + if team_abbrev == "MTL": + # Montreal variations + if filename_lower in ["cf_montral.png", "mon.png", "montreal.png"]: + logo_path = os.path.join(self.logo_dir, filename) + self.logger.debug(f"Found Montreal variation: {filename} for {team_abbrev}") + break + elif team_abbrev == "LAFC": + # LAFC variations + if filename_lower in ["lafc.png", "la_fc.png"]: + logo_path = os.path.join(self.logo_dir, filename) + self.logger.debug(f"Found LAFC variation: {filename} for {team_abbrev}") + break + elif team_abbrev == "NY": + # New York variations + if filename_lower in ["ny.png", "nycfc.png", "nyrb.png"]: + logo_path = os.path.join(self.logo_dir, filename) + self.logger.debug(f"Found NY variation: {filename} for {team_abbrev}") + break + except (OSError, PermissionError) as e: self.logger.warning(f"Error listing directory {self.logo_dir}: {e}") @@ -470,6 +508,7 @@ class BaseSoccerManager: if start_time_utc: local_time = start_time_utc.astimezone(self._get_timezone()) game_time = local_time.strftime("%I:%M%p").lower().lstrip('0') # e.g., 2:30pm + self.logger.debug(f"[Soccer] Timezone conversion - UTC: {start_time_utc}, Local: {local_time}, Formatted: {game_time}") # Check date format from config use_short_date_format = self.config.get('display', {}).get('use_short_date_format', False) @@ -759,10 +798,14 @@ class SoccerLiveManager(BaseSoccerManager): if data and "events" in data: for event in data["events"]: details = self._extract_game_details(event) - # Ensure it's live and involves a favorite team (if specified) + # Ensure it's live if details and details["is_live"]: self._fetch_odds(details) new_live_games.append(details) + + # Filter for favorite teams only if the config is set + if self.soccer_config.get("show_favorite_teams_only", False) and self.favorite_teams: + new_live_games = [game for game in new_live_games if game['home_abbr'] in self.favorite_teams or game['away_abbr'] in self.favorite_teams] # Logging should_log = (current_time - self.last_log_time >= self.log_interval or @@ -770,11 +813,13 @@ class SoccerLiveManager(BaseSoccerManager): not self.live_games) if should_log: if new_live_games: - self.logger.info(f"[Soccer] Found {len(new_live_games)} live games involving favorite teams / all teams.") + filter_text = "favorite teams" if self.soccer_config.get("show_favorite_teams_only", False) and self.favorite_teams else "all teams" + self.logger.info(f"[Soccer] Found {len(new_live_games)} live games involving {filter_text}.") for game in new_live_games: self.logger.info(f"[Soccer] Live game: {game['away_abbr']} vs {game['home_abbr']} ({game['game_clock_display']}) - {game['league']}") else: - self.logger.info("[Soccer] No live games found matching criteria.") + filter_text = "favorite teams" if self.soccer_config.get("show_favorite_teams_only", False) and self.favorite_teams else "criteria" + self.logger.info(f"[Soccer] No live games found matching {filter_text}.") self.last_log_time = current_time # Update game list and current game @@ -825,7 +870,8 @@ class SoccerLiveManager(BaseSoccerManager): else: # No live games found if self.live_games: # Log only if previously had games - self.logger.info("[Soccer] All live games have ended or no longer match criteria.") + filter_text = "favorite teams" if self.soccer_config.get("show_favorite_teams_only", False) and self.favorite_teams else "criteria" + self.logger.info(f"[Soccer] All live games have ended or no longer match {filter_text}.") self.live_games = [] self.current_game = None diff --git a/test/test_soccer_favorite_teams.py b/test/test_soccer_favorite_teams.py new file mode 100644 index 00000000..f11dee1d --- /dev/null +++ b/test/test_soccer_favorite_teams.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +""" +Test script to verify soccer manager favorite teams filtering functionality. +This test checks that when show_favorite_teams_only is enabled, only games +involving favorite teams are processed. +""" + +import sys +import os +import json +from datetime import datetime, timedelta +import pytz + +# Add the src directory to the path so we can import the soccer managers +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) + +from soccer_managers import BaseSoccerManager +from display_manager import DisplayManager +from cache_manager import CacheManager + +def create_test_config(show_favorite_teams_only=True, favorite_teams=None): + """Create a test configuration for soccer manager.""" + if favorite_teams is None: + favorite_teams = ["DAL", "TB"] + + config = { + "soccer_scoreboard": { + "enabled": True, + "show_favorite_teams_only": show_favorite_teams_only, + "favorite_teams": favorite_teams, + "leagues": ["usa.1"], + "logo_dir": "assets/sports/soccer_logos", + "recent_game_hours": 168, + "update_interval_seconds": 3600 + }, + "display": { + "hardware": { + "rows": 32, + "cols": 64, + "chain_length": 1 + } + }, + "timezone": "America/Chicago" + } + return config + +def create_test_game_data(): + """Create test game data with various teams.""" + now = datetime.now(pytz.utc) + + games = [ + { + "id": "1", + "date": now.isoformat(), + "competitions": [{ + "status": { + "type": {"name": "STATUS_IN_PROGRESS", "shortDetail": "45'"} + }, + "competitors": [ + { + "homeAway": "home", + "team": {"abbreviation": "DAL"}, + "score": "2" + }, + { + "homeAway": "away", + "team": {"abbreviation": "LAFC"}, + "score": "1" + } + ] + }], + "league": {"slug": "usa.1", "name": "MLS"} + }, + { + "id": "2", + "date": now.isoformat(), + "competitions": [{ + "status": { + "type": {"name": "STATUS_IN_PROGRESS", "shortDetail": "30'"} + }, + "competitors": [ + { + "homeAway": "home", + "team": {"abbreviation": "TB"}, + "score": "0" + }, + { + "homeAway": "away", + "team": {"abbreviation": "NY"}, + "score": "0" + } + ] + }], + "league": {"slug": "usa.1", "name": "MLS"} + }, + { + "id": "3", + "date": now.isoformat(), + "competitions": [{ + "status": { + "type": {"name": "STATUS_IN_PROGRESS", "shortDetail": "15'"} + }, + "competitors": [ + { + "homeAway": "home", + "team": {"abbreviation": "LAFC"}, + "score": "1" + }, + { + "homeAway": "away", + "team": {"abbreviation": "NY"}, + "score": "1" + } + ] + }], + "league": {"slug": "usa.1", "name": "MLS"} + } + ] + return games + +def test_favorite_teams_filtering(): + """Test that favorite teams filtering works correctly.""" + print("Testing soccer manager favorite teams filtering...") + + # Test 1: With favorite teams filtering enabled + print("\n1. Testing with show_favorite_teams_only=True") + config = create_test_config(show_favorite_teams_only=True, favorite_teams=["DAL", "TB"]) + + # Create mock display and cache managers + display_manager = DisplayManager(config) + cache_manager = CacheManager() + + # Create soccer manager + soccer_manager = BaseSoccerManager(config, display_manager, cache_manager) + + # Create test game data + test_games = create_test_game_data() + + # Process games and check filtering + filtered_games = [] + for game_event in test_games: + details = soccer_manager._extract_game_details(game_event) + if details and details["is_live"]: + filtered_games.append(details) + + # Apply favorite teams filtering + if soccer_manager.soccer_config.get("show_favorite_teams_only", False) and soccer_manager.favorite_teams: + filtered_games = [game for game in filtered_games if game['home_abbr'] in soccer_manager.favorite_teams or game['away_abbr'] in soccer_manager.favorite_teams] + + print(f" Total games: {len(test_games)}") + print(f" Live games: {len([g for g in test_games if g['competitions'][0]['status']['type']['name'] == 'STATUS_IN_PROGRESS'])}") + print(f" Games after favorite teams filtering: {len(filtered_games)}") + + # Verify only games with DAL or TB are included + expected_teams = {"DAL", "TB"} + for game in filtered_games: + home_team = game['home_abbr'] + away_team = game['away_abbr'] + assert home_team in expected_teams or away_team in expected_teams, f"Game {home_team} vs {away_team} should not be included" + print(f" āœ“ Included: {away_team} vs {home_team}") + + # Test 2: With favorite teams filtering disabled + print("\n2. Testing with show_favorite_teams_only=False") + config = create_test_config(show_favorite_teams_only=False, favorite_teams=["DAL", "TB"]) + soccer_manager = BaseSoccerManager(config, display_manager, cache_manager) + + filtered_games = [] + for game_event in test_games: + details = soccer_manager._extract_game_details(game_event) + if details and details["is_live"]: + filtered_games.append(details) + + # Apply favorite teams filtering (should not filter when disabled) + if soccer_manager.soccer_config.get("show_favorite_teams_only", False) and soccer_manager.favorite_teams: + filtered_games = [game for game in filtered_games if game['home_abbr'] in soccer_manager.favorite_teams or game['away_abbr'] in soccer_manager.favorite_teams] + + print(f" Total games: {len(test_games)}") + print(f" Live games: {len([g for g in test_games if g['competitions'][0]['status']['type']['name'] == 'STATUS_IN_PROGRESS'])}") + print(f" Games after filtering (should be all live games): {len(filtered_games)}") + + # Verify all live games are included when filtering is disabled + assert len(filtered_games) == 3, f"Expected 3 games, got {len(filtered_games)}" + print(" āœ“ All live games included when filtering is disabled") + + print("\nāœ… All tests passed! Favorite teams filtering is working correctly.") + +if __name__ == "__main__": + try: + test_favorite_teams_filtering() + except Exception as e: + print(f"āŒ Test failed: {e}") + import traceback + traceback.print_exc() + sys.exit(1)