use correct AP Poll top 25 for NCAAFB

This commit is contained in:
Chuck
2025-09-12 11:50:51 -04:00
parent f8ab022da9
commit 548bc00e00
4 changed files with 670 additions and 0 deletions

View File

@@ -208,6 +208,10 @@ class LeaderboardManager:
logger.info(f"Using cached leaderboard data for {league_key}")
return cached_data.get('standings', [])
# Special handling for college football - use rankings endpoint
if league_key == 'college-football':
return self._fetch_ncaa_fb_rankings(league_config)
try:
logger.info(f"Fetching fresh leaderboard data for {league_key}")
@@ -281,6 +285,111 @@ class LeaderboardManager:
logger.error(f"Error fetching standings for {league_config['league']}: {e}")
return []
def _fetch_ncaa_fb_rankings(self, league_config: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Fetch NCAA Football rankings from ESPN API using the rankings endpoint."""
league_key = league_config['league']
cache_key = f"leaderboard_{league_key}_rankings"
# Try to get cached data first
cached_data = self.cache_manager.get_cached_data_with_strategy(cache_key, 'leaderboard')
if cached_data:
logger.info(f"Using cached rankings data for {league_key}")
return cached_data.get('standings', [])
try:
logger.info(f"Fetching fresh rankings data for {league_key}")
rankings_url = "https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings"
# Get rankings data
response = requests.get(rankings_url, timeout=self.request_timeout)
response.raise_for_status()
data = response.json()
# Increment API counter for sports data
increment_api_counter('sports', 1)
logger.info(f"Available rankings: {[rank['name'] for rank in data.get('availableRankings', [])]}")
logger.info(f"Latest season: {data.get('latestSeason', {})}")
logger.info(f"Latest week: {data.get('latestWeek', {})}")
rankings_data = data.get('rankings', [])
if not rankings_data:
logger.warning("No rankings data found")
return []
# Use the first ranking (usually AP Top 25)
first_ranking = rankings_data[0]
ranking_name = first_ranking.get('name', 'Unknown')
ranking_type = first_ranking.get('type', 'Unknown')
teams = first_ranking.get('ranks', [])
logger.info(f"Using ranking: {ranking_name} ({ranking_type})")
logger.info(f"Found {len(teams)} teams in ranking")
standings = []
# Process each team in the ranking
for team_data in teams:
team_info = team_data.get('team', {})
team_name = team_info.get('name', 'Unknown')
team_abbr = team_info.get('abbreviation', 'Unknown')
current_rank = team_data.get('current', 0)
record_summary = team_data.get('recordSummary', '0-0')
logger.debug(f" {current_rank}. {team_name} ({team_abbr}): {record_summary}")
# Parse the record string (e.g., "12-1", "8-4", "10-2-1")
wins = 0
losses = 0
ties = 0
win_percentage = 0
try:
parts = record_summary.split('-')
if len(parts) >= 2:
wins = int(parts[0])
losses = int(parts[1])
if len(parts) == 3:
ties = int(parts[2])
# Calculate win percentage
total_games = wins + losses + ties
win_percentage = wins / total_games if total_games > 0 else 0
except (ValueError, IndexError):
logger.warning(f"Could not parse record for {team_name}: {record_summary}")
continue
standings.append({
'name': team_name,
'abbreviation': team_abbr,
'rank': current_rank,
'wins': wins,
'losses': losses,
'ties': ties,
'win_percentage': win_percentage,
'record_summary': record_summary,
'ranking_name': ranking_name
})
# Limit to top teams (they're already ranked)
top_teams = standings[:league_config['top_teams']]
# Cache the results
cache_data = {
'standings': top_teams,
'timestamp': time.time(),
'league': league_key,
'ranking_name': ranking_name
}
self.cache_manager.save_cache(cache_key, cache_data)
logger.info(f"Fetched and cached {len(top_teams)} teams for {league_key} using {ranking_name}")
return top_teams
except Exception as e:
logger.error(f"Error fetching rankings for {league_key}: {e}")
return []
def _fetch_team_record(self, team_abbr: str, league_config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Fetch individual team record from ESPN API with caching."""
league = league_config['league']

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
Script to download all NCAA Football team logos from ESPN API
and save them with team abbreviations as filenames.
"""
import os
import requests
import json
from pathlib import Path
import time
def create_logo_directory():
"""Create the ncaaFBlogos directory if it doesn't exist."""
logo_dir = Path("test/ncaaFBlogos")
logo_dir.mkdir(parents=True, exist_ok=True)
return logo_dir
def fetch_teams_data():
"""Fetch team data from ESPN API."""
url = "https://site.api.espn.com/apis/site/v2/sports/football/college-football/teams"
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching teams data: {e}")
return None
def download_logo(url, filepath, team_name):
"""Download a logo from URL and save to filepath."""
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
with open(filepath, 'wb') as f:
f.write(response.content)
print(f"✓ Downloaded: {team_name} -> {filepath.name}")
return True
except requests.exceptions.RequestException as e:
print(f"✗ Failed to download {team_name}: {e}")
return False
def normalize_abbreviation(abbreviation):
"""Normalize team abbreviation to lowercase for filename."""
return abbreviation.lower()
def main():
"""Main function to download all NCAA FB team logos."""
print("Starting NCAA Football logo download...")
# Create directory
logo_dir = create_logo_directory()
print(f"Created/verified directory: {logo_dir}")
# Fetch teams data
print("Fetching teams data from ESPN API...")
data = fetch_teams_data()
if not data:
print("Failed to fetch teams data. Exiting.")
return
# Extract teams
teams = []
try:
sports = data.get('sports', [])
for sport in sports:
leagues = sport.get('leagues', [])
for league in leagues:
teams = league.get('teams', [])
break
except (KeyError, IndexError) as e:
print(f"Error parsing teams data: {e}")
return
print(f"Found {len(teams)} teams")
# Download logos
downloaded_count = 0
failed_count = 0
for team_data in teams:
team = team_data.get('team', {})
# Extract team information
abbreviation = team.get('abbreviation', '')
display_name = team.get('displayName', 'Unknown')
logos = team.get('logos', [])
if not abbreviation or not logos:
print(f"⚠ Skipping {display_name}: missing abbreviation or logos")
continue
# Get the default logo (first one is usually default)
logo_url = logos[0].get('href', '')
if not logo_url:
print(f"⚠ Skipping {display_name}: no logo URL")
continue
# Create filename
filename = f"{normalize_abbreviation(abbreviation)}.png"
filepath = logo_dir / filename
# Skip if already exists
if filepath.exists():
print(f"⏭ Skipping {display_name}: {filename} already exists")
continue
# Download logo
if download_logo(logo_url, filepath, display_name):
downloaded_count += 1
else:
failed_count += 1
# Small delay to be respectful to the API
time.sleep(0.1)
print(f"\nDownload complete!")
print(f"✓ Successfully downloaded: {downloaded_count} logos")
print(f"✗ Failed downloads: {failed_count}")
print(f"📁 Logos saved in: {logo_dir}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,288 @@
#!/usr/bin/env python3
"""
Test script to demonstrate NCAA Football leaderboard data gathering.
Shows the top 10 NCAA Football teams ranked by win percentage.
This script examines the actual ESPN API response structure to understand
how team records are provided in the teams endpoint.
"""
import sys
import os
import json
import time
import requests
from typing import Dict, Any, List, Optional
# Add the src directory to the path so we can import our modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from cache_manager import CacheManager
from config_manager import ConfigManager
class NCAAFBLeaderboardTester:
"""Test class to demonstrate NCAA Football leaderboard data gathering."""
def __init__(self):
self.cache_manager = CacheManager()
self.config_manager = ConfigManager()
self.request_timeout = 30
# NCAA Football configuration (matching the leaderboard manager)
self.ncaa_fb_config = {
'sport': 'football',
'league': 'college-football',
'teams_url': 'https://site.api.espn.com/apis/site/v2/sports/football/college-football/teams',
'top_teams': 10 # Show top 10 for this test
}
def examine_api_structure(self) -> None:
"""Examine the ESPN API response structure to understand available data."""
print("Examining ESPN API response structure...")
print("=" * 60)
try:
response = requests.get(self.ncaa_fb_config['teams_url'], timeout=self.request_timeout)
response.raise_for_status()
data = response.json()
print(f"API Response Status: {response.status_code}")
print(f"Response Keys: {list(data.keys())}")
sports = data.get('sports', [])
if sports:
print(f"Sports found: {len(sports)}")
sport = sports[0]
print(f"Sport keys: {list(sport.keys())}")
print(f"Sport name: {sport.get('name', 'Unknown')}")
leagues = sport.get('leagues', [])
if leagues:
print(f"Leagues found: {len(leagues)}")
league = leagues[0]
print(f"League keys: {list(league.keys())}")
print(f"League name: {league.get('name', 'Unknown')}")
teams = league.get('teams', [])
if teams:
print(f"Teams found: {len(teams)}")
# Examine first team structure
first_team = teams[0]
print(f"\nFirst team structure:")
print(f"Team keys: {list(first_team.keys())}")
team_info = first_team.get('team', {})
print(f"Team info keys: {list(team_info.keys())}")
print(f"Team name: {team_info.get('name', 'Unknown')}")
print(f"Team abbreviation: {team_info.get('abbreviation', 'Unknown')}")
# Check for record data
record = team_info.get('record', {})
print(f"Record keys: {list(record.keys())}")
if record:
items = record.get('items', [])
print(f"Record items: {len(items)}")
if items:
print(f"First record item: {items[0]}")
# Check for stats data
stats = team_info.get('stats', [])
print(f"Stats found: {len(stats)}")
if stats:
print("Available stats:")
for stat in stats[:5]: # Show first 5 stats
print(f" {stat.get('name', 'Unknown')}: {stat.get('value', 'Unknown')}")
# Check for standings data
standings = first_team.get('standings', {})
print(f"Standings keys: {list(standings.keys())}")
print(f"\nSample team data structure:")
print(json.dumps(first_team, indent=2)[:1000] + "...")
except Exception as e:
print(f"Error examining API structure: {e}")
def fetch_ncaa_fb_rankings_correct(self) -> List[Dict[str, Any]]:
"""Fetch NCAA Football rankings from ESPN API using the correct approach."""
cache_key = "leaderboard_college-football-rankings"
# Try to get cached data first
cached_data = self.cache_manager.get_cached_data_with_strategy(cache_key, 'leaderboard')
if cached_data:
print("Using cached rankings data for NCAA Football")
return cached_data.get('rankings', [])
try:
print("Fetching fresh rankings data for NCAA Football")
rankings_url = "https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings"
print(f"Rankings URL: {rankings_url}")
# Get rankings data
response = requests.get(rankings_url, timeout=self.request_timeout)
response.raise_for_status()
data = response.json()
print(f"Available rankings: {[rank['name'] for rank in data.get('availableRankings', [])]}")
print(f"Latest season: {data.get('latestSeason', {})}")
print(f"Latest week: {data.get('latestWeek', {})}")
rankings_data = data.get('rankings', [])
if not rankings_data:
print("No rankings data found")
return []
# Use the first ranking (usually AP Top 25)
first_ranking = rankings_data[0]
ranking_name = first_ranking.get('name', 'Unknown')
ranking_type = first_ranking.get('type', 'Unknown')
teams = first_ranking.get('ranks', [])
print(f"Using ranking: {ranking_name} ({ranking_type})")
print(f"Found {len(teams)} teams in ranking")
standings = []
# Process each team in the ranking
for i, team_data in enumerate(teams):
team_info = team_data.get('team', {})
team_name = team_info.get('name', 'Unknown')
team_abbr = team_info.get('abbreviation', 'Unknown')
current_rank = team_data.get('current', 0)
record_summary = team_data.get('recordSummary', '0-0')
print(f" {current_rank}. {team_name} ({team_abbr}): {record_summary}")
# Parse the record string (e.g., "12-1", "8-4", "10-2-1")
wins = 0
losses = 0
ties = 0
win_percentage = 0
try:
parts = record_summary.split('-')
if len(parts) >= 2:
wins = int(parts[0])
losses = int(parts[1])
if len(parts) == 3:
ties = int(parts[2])
# Calculate win percentage
total_games = wins + losses + ties
win_percentage = wins / total_games if total_games > 0 else 0
except (ValueError, IndexError):
print(f" Could not parse record: {record_summary}")
continue
standings.append({
'name': team_name,
'abbreviation': team_abbr,
'rank': current_rank,
'wins': wins,
'losses': losses,
'ties': ties,
'win_percentage': win_percentage,
'record_summary': record_summary,
'ranking_name': ranking_name
})
# Limit to top teams (they're already ranked)
top_teams = standings[:self.ncaa_fb_config['top_teams']]
# Cache the results
cache_data = {
'rankings': top_teams,
'timestamp': time.time(),
'league': 'college-football',
'ranking_name': ranking_name
}
self.cache_manager.save_cache(cache_key, cache_data)
print(f"Fetched and cached {len(top_teams)} teams for college-football")
return top_teams
except Exception as e:
print(f"Error fetching rankings for college-football: {e}")
return []
def display_standings(self, standings: List[Dict[str, Any]]) -> None:
"""Display the standings in a formatted way."""
if not standings:
print("No standings data available")
return
ranking_name = standings[0].get('ranking_name', 'Unknown Ranking') if standings else 'Unknown'
print("\n" + "="*80)
print(f"NCAA FOOTBALL LEADERBOARD - TOP 10 TEAMS ({ranking_name})")
print("="*80)
print(f"{'Rank':<4} {'Team':<25} {'Abbr':<6} {'Record':<12} {'Win %':<8}")
print("-"*80)
for team in standings:
record_str = f"{team['wins']}-{team['losses']}"
if team['ties'] > 0:
record_str += f"-{team['ties']}"
win_pct = team['win_percentage']
win_pct_str = f"{win_pct:.3f}" if win_pct > 0 else "0.000"
print(f"{team['rank']:<4} {team['name']:<25} {team['abbreviation']:<6} {record_str:<12} {win_pct_str:<8}")
print("="*80)
print(f"Total teams processed: {len(standings)}")
print(f"Data fetched at: {time.strftime('%Y-%m-%d %H:%M:%S')}")
def run_test(self) -> None:
"""Run the complete test."""
print("NCAA Football Leaderboard Data Gathering Test")
print("=" * 50)
print("This test demonstrates how the leaderboard manager should gather data:")
print("1. Fetches rankings from ESPN API rankings endpoint")
print("2. Uses poll-based rankings (AP, Coaches, etc.) not win percentage")
print("3. Gets team records from the ranking data")
print("4. Displays top 10 teams with their poll rankings")
print()
print("\n" + "="*60)
print("FETCHING RANKINGS DATA")
print("="*60)
# Fetch the rankings using the correct approach
standings = self.fetch_ncaa_fb_rankings_correct()
# Display the results
self.display_standings(standings)
# Show some additional info
if standings:
ranking_name = standings[0].get('ranking_name', 'Unknown')
print(f"\nAdditional Information:")
print(f"- API Endpoint: https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings")
print(f"- Single API call fetches poll-based rankings")
print(f"- Rankings are based on polls, not just win percentage")
print(f"- Data is cached to avoid excessive API calls")
print(f"- Using ranking: {ranking_name}")
# Show the best team
best_team = standings[0]
print(f"\nCurrent #1 Team: {best_team['name']} ({best_team['abbreviation']})")
print(f"Record: {best_team['wins']}-{best_team['losses']}{f'-{best_team['ties']}' if best_team['ties'] > 0 else ''}")
print(f"Win Percentage: {best_team['win_percentage']:.3f}")
print(f"Poll Ranking: #{best_team['rank']}")
def main():
"""Main function to run the test."""
try:
tester = NCAAFBLeaderboardTester()
tester.run_test()
except KeyboardInterrupt:
print("\nTest interrupted by user")
except Exception as e:
print(f"Error running test: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
"""
Test script to verify the updated leaderboard manager works correctly
with the new NCAA Football rankings endpoint.
"""
import sys
import os
import json
import time
from typing import Dict, Any
# Add the src directory to the path so we can import our modules
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from leaderboard_manager import LeaderboardManager
from cache_manager import CacheManager
from config_manager import ConfigManager
def test_updated_leaderboard_manager():
"""Test the updated leaderboard manager with NCAA Football rankings."""
print("Testing Updated Leaderboard Manager")
print("=" * 50)
# Create a mock display manager (we don't need the actual hardware for this test)
class MockDisplayManager:
def __init__(self):
self.matrix = type('Matrix', (), {'width': 64, 'height': 32})()
self.image = None
self.draw = None
def update_display(self):
pass
def set_scrolling_state(self, scrolling):
pass
def process_deferred_updates(self):
pass
# Create test configuration
test_config = {
'leaderboard': {
'enabled': True,
'enabled_sports': {
'ncaa_fb': {
'enabled': True,
'top_teams': 10
}
},
'update_interval': 3600,
'scroll_speed': 2,
'scroll_delay': 0.05,
'display_duration': 60,
'loop': True,
'request_timeout': 30,
'dynamic_duration': True,
'min_duration': 30,
'max_duration': 300,
'duration_buffer': 0.1,
'time_per_team': 2.0,
'time_per_league': 3.0
}
}
try:
# Initialize the leaderboard manager
print("Initializing LeaderboardManager...")
display_manager = MockDisplayManager()
leaderboard_manager = LeaderboardManager(test_config, display_manager)
print(f"Leaderboard enabled: {leaderboard_manager.is_enabled}")
print(f"Enabled sports: {[k for k, v in leaderboard_manager.enabled_sports.items() if v.get('enabled', False)]}")
# Test the NCAA Football rankings fetch
print("\nTesting NCAA Football rankings fetch...")
ncaa_fb_config = leaderboard_manager.league_configs['ncaa_fb']
print(f"NCAA FB config: {ncaa_fb_config}")
# Fetch standings using the new method
standings = leaderboard_manager._fetch_standings(ncaa_fb_config)
if standings:
print(f"\nSuccessfully fetched {len(standings)} teams")
print("\nTop 10 NCAA Football Teams (from rankings):")
print("-" * 60)
print(f"{'Rank':<4} {'Team':<25} {'Abbr':<6} {'Record':<12} {'Win %':<8}")
print("-" * 60)
for team in standings:
record_str = f"{team['wins']}-{team['losses']}"
if team['ties'] > 0:
record_str += f"-{team['ties']}"
win_pct = team['win_percentage']
win_pct_str = f"{win_pct:.3f}" if win_pct > 0 else "0.000"
print(f"{team.get('rank', 'N/A'):<4} {team['name']:<25} {team['abbreviation']:<6} {record_str:<12} {win_pct_str:<8}")
print("-" * 60)
# Show additional info
ranking_name = standings[0].get('ranking_name', 'Unknown') if standings else 'Unknown'
print(f"Ranking system used: {ranking_name}")
print(f"Data fetched at: {time.strftime('%Y-%m-%d %H:%M:%S')}")
# Test caching
print(f"\nTesting caching...")
cached_standings = leaderboard_manager._fetch_standings(ncaa_fb_config)
if cached_standings:
print("✓ Caching works correctly - data retrieved from cache")
else:
print("✗ Caching issue - no data retrieved from cache")
else:
print("✗ No standings data retrieved")
return False
print("\n✓ Leaderboard manager test completed successfully!")
return True
except Exception as e:
print(f"✗ Error testing leaderboard manager: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""Main function to run the test."""
try:
success = test_updated_leaderboard_manager()
if success:
print("\n🎉 All tests passed! The updated leaderboard manager is working correctly.")
else:
print("\n❌ Tests failed. Please check the errors above.")
except KeyboardInterrupt:
print("\nTest interrupted by user")
except Exception as e:
print(f"Error running test: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()