mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
espn api update for NCAAFB
This commit is contained in:
@@ -206,8 +206,8 @@ class BaseNCAAFBManager: # Renamed class
|
|||||||
|
|
||||||
def _fetch_ncaa_fb_api_data(self, use_cache: bool = True) -> Optional[Dict]:
|
def _fetch_ncaa_fb_api_data(self, use_cache: bool = True) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Fetches the full season schedule for NCAAFB, caches it, and then filters
|
Fetches the full season schedule for NCAAFB using week-by-week approach to ensure
|
||||||
for relevant games based on the current configuration.
|
we get all games, then caches the complete dataset.
|
||||||
"""
|
"""
|
||||||
now = datetime.now(pytz.utc)
|
now = datetime.now(pytz.utc)
|
||||||
current_year = now.year
|
current_year = now.year
|
||||||
@@ -226,19 +226,48 @@ class BaseNCAAFBManager: # Renamed class
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
self.logger.info(f"[NCAAFB] Fetching full {year} season schedule from ESPN API...")
|
self.logger.info(f"[NCAAFB] Fetching full {year} season schedule from ESPN API...")
|
||||||
|
year_events = []
|
||||||
|
|
||||||
|
# Fetch week by week to ensure we get complete season data
|
||||||
|
# College football typically has weeks 1-15 plus postseason
|
||||||
|
for week in range(1, 16):
|
||||||
|
try:
|
||||||
|
url = f"https://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard?seasontype=2&week={week}"
|
||||||
|
response = self.session.get(url, headers=self.headers, timeout=15)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
week_events = data.get('events', [])
|
||||||
|
year_events.extend(week_events)
|
||||||
|
|
||||||
|
# Log progress for first few weeks
|
||||||
|
if week <= 3:
|
||||||
|
self.logger.debug(f"[NCAAFB] Week {week}: fetched {len(week_events)} events")
|
||||||
|
|
||||||
|
# If no events found for this week, we might be past the season
|
||||||
|
if not week_events and week > 10:
|
||||||
|
self.logger.debug(f"[NCAAFB] No events found for week {week}, ending season fetch")
|
||||||
|
break
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.logger.warning(f"[NCAAFB] Error fetching week {week} for {year}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Also fetch postseason games (bowl games, playoffs)
|
||||||
try:
|
try:
|
||||||
url = f"https://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard?dates={year}&seasontype=2"
|
url = f"https://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard?seasontype=3"
|
||||||
response = self.session.get(url, headers=self.headers, timeout=15)
|
response = self.session.get(url, headers=self.headers, timeout=15)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
events = data.get('events', [])
|
postseason_events = data.get('events', [])
|
||||||
if use_cache:
|
year_events.extend(postseason_events)
|
||||||
self.cache_manager.set(cache_key, events)
|
self.logger.debug(f"[NCAAFB] Postseason: fetched {len(postseason_events)} events")
|
||||||
self.logger.info(f"[NCAAFB] Successfully fetched and cached {len(events)} events for {year} season.")
|
|
||||||
all_events.extend(events)
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
self.logger.error(f"[NCAAFB] API error fetching full schedule for {year}: {e}")
|
self.logger.warning(f"[NCAAFB] Error fetching postseason for {year}: {e}")
|
||||||
continue
|
|
||||||
|
if use_cache:
|
||||||
|
self.cache_manager.set(cache_key, year_events)
|
||||||
|
self.logger.info(f"[NCAAFB] Successfully fetched and cached {len(year_events)} events for {year} season.")
|
||||||
|
all_events.extend(year_events)
|
||||||
|
|
||||||
if not all_events:
|
if not all_events:
|
||||||
self.logger.warning("[NCAAFB] No events found in schedule data.")
|
self.logger.warning("[NCAAFB] No events found in schedule data.")
|
||||||
@@ -992,9 +1021,9 @@ 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
|
||||||
|
|
||||||
# Define date range for "recent" games (last 14 days)
|
# Define date range for "recent" games (last 21 days to capture games from 3 weeks ago)
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
recent_cutoff = now - timedelta(days=14)
|
recent_cutoff = now - timedelta(days=21)
|
||||||
|
|
||||||
# Process games and filter for final games, date range & favorite teams
|
# Process games and filter for final games, date range & favorite teams
|
||||||
processed_games = []
|
processed_games = []
|
||||||
|
|||||||
154
test_core_logic.py
Normal file
154
test_core_logic.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test the core logic of the web interface without Flask dependencies.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
class DictWrapper:
|
||||||
|
"""Wrapper to make dictionary accessible via dot notation for Jinja2 templates."""
|
||||||
|
def __init__(self, data=None):
|
||||||
|
# Store the original data
|
||||||
|
object.__setattr__(self, '_data', data if isinstance(data, dict) else {})
|
||||||
|
|
||||||
|
# Set attributes from the dictionary
|
||||||
|
if isinstance(data, dict):
|
||||||
|
for key, value in data.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
object.__setattr__(self, key, DictWrapper(value))
|
||||||
|
elif isinstance(value, list):
|
||||||
|
object.__setattr__(self, key, value)
|
||||||
|
else:
|
||||||
|
object.__setattr__(self, key, value)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
# Return a new empty DictWrapper for missing attributes
|
||||||
|
# This allows chaining like main_config.display.hardware.rows
|
||||||
|
return DictWrapper({})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# Return empty string for missing values to avoid template errors
|
||||||
|
data = object.__getattribute__(self, '_data')
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
return str(data)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
# Return 0 for missing numeric values
|
||||||
|
data = object.__getattribute__(self, '_data')
|
||||||
|
if not data:
|
||||||
|
return 0
|
||||||
|
try:
|
||||||
|
return int(data)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
# Return False for missing boolean values
|
||||||
|
data = object.__getattribute__(self, '_data')
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
return bool(data)
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
# Support .get() method like dictionaries
|
||||||
|
data = object.__getattribute__(self, '_data')
|
||||||
|
if data and key in data:
|
||||||
|
return data[key]
|
||||||
|
return default
|
||||||
|
|
||||||
|
def safe_get(obj, key_path, default=''):
|
||||||
|
"""Safely access nested dictionary values using dot notation."""
|
||||||
|
try:
|
||||||
|
keys = key_path.split('.')
|
||||||
|
current = obj
|
||||||
|
for key in keys:
|
||||||
|
if hasattr(current, key):
|
||||||
|
current = getattr(current, key)
|
||||||
|
elif isinstance(current, dict) and key in current:
|
||||||
|
current = current[key]
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
return current if current is not None else default
|
||||||
|
except (AttributeError, KeyError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
def safe_config_get(config, *keys, default=''):
|
||||||
|
"""Safely get nested config values with fallback."""
|
||||||
|
try:
|
||||||
|
current = config
|
||||||
|
for key in keys:
|
||||||
|
if hasattr(current, key):
|
||||||
|
current = getattr(current, key)
|
||||||
|
# Check if we got an empty DictWrapper
|
||||||
|
if isinstance(current, DictWrapper):
|
||||||
|
data = object.__getattribute__(current, '_data')
|
||||||
|
if not data: # Empty DictWrapper means missing config
|
||||||
|
return default
|
||||||
|
elif isinstance(current, dict) and key in current:
|
||||||
|
current = current[key]
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
|
||||||
|
# Final check for empty values
|
||||||
|
if current is None or (hasattr(current, '_data') and not object.__getattribute__(current, '_data')):
|
||||||
|
return default
|
||||||
|
return current
|
||||||
|
except (AttributeError, KeyError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
def simulate_template_rendering():
|
||||||
|
"""Simulate how the template would render configuration values."""
|
||||||
|
print("Simulating template rendering with actual config...")
|
||||||
|
|
||||||
|
# Load actual config
|
||||||
|
with open('config/config.json', 'r') as f:
|
||||||
|
config_data = json.load(f)
|
||||||
|
|
||||||
|
main_config = DictWrapper(config_data)
|
||||||
|
|
||||||
|
# Simulate template expressions that would be used
|
||||||
|
template_tests = [
|
||||||
|
# Input field values
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'rows', default=32)", 32),
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'cols', default=64)", 64),
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'brightness', default=95)", 95),
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'chain_length', default=2)", 2),
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'parallel', default=1)", 1),
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'hardware_mapping', default='adafruit-hat-pwm')", 'adafruit-hat-pwm'),
|
||||||
|
|
||||||
|
# Checkbox states
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'disable_hardware_pulsing', default=False)", False),
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'inverse_colors', default=False)", False),
|
||||||
|
("safe_config_get(main_config, 'display', 'hardware', 'show_refresh_rate', default=False)", False),
|
||||||
|
("safe_config_get(main_config, 'display', 'use_short_date_format', default=True)", True),
|
||||||
|
]
|
||||||
|
|
||||||
|
all_passed = True
|
||||||
|
for expression, expected in template_tests:
|
||||||
|
try:
|
||||||
|
result = eval(expression)
|
||||||
|
status = "✓" if result == expected else "✗"
|
||||||
|
print(f" {status} {expression.split('(')[0]}(...): {result} (expected: {expected})")
|
||||||
|
if result != expected:
|
||||||
|
all_passed = False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ {expression}: ERROR - {e}")
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
return all_passed
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 70)
|
||||||
|
print("Testing Core Web Interface Logic")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
success = simulate_template_rendering()
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
if success:
|
||||||
|
print("✓ ALL TEMPLATE SIMULATIONS PASSED!")
|
||||||
|
print("✓ The web interface should correctly display all config values!")
|
||||||
|
else:
|
||||||
|
print("✗ SOME TEMPLATE SIMULATIONS FAILED!")
|
||||||
|
print("✗ There may be issues with config display in the web interface!")
|
||||||
|
print("=" * 70)
|
||||||
Reference in New Issue
Block a user