improve odds ticker dynamic duration

This commit is contained in:
ChuckBuilds
2025-08-18 19:23:20 -05:00
parent 3c1706d4e8
commit 2b93eafcdf
8 changed files with 104 additions and 7 deletions

View File

@@ -6,7 +6,7 @@ import json
import os import os
from io import BytesIO from io import BytesIO
import requests import requests
from typing import Union from typing import Union, Dict, Any, Optional
from PIL import Image, ImageEnhance from PIL import Image, ImageEnhance
import queue # Added import import queue # Added import
@@ -15,6 +15,14 @@ from .spotify_client import SpotifyClient
from .ytm_client import YTMClient from .ytm_client import YTMClient
# Removed: import config # Removed: import config
# Import the API counter function from web interface
try:
from web_interface_v2 import increment_api_counter
except ImportError:
# Fallback if web interface is not available
def increment_api_counter(kind: str, count: int = 1):
pass
# Configure logging # Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -313,6 +321,10 @@ class MusicManager:
try: try:
response = requests.get(url, timeout=5) # 5-second timeout for image download response = requests.get(url, timeout=5) # 5-second timeout for image download
response.raise_for_status() # Raise an exception for bad status codes response.raise_for_status() # Raise an exception for bad status codes
# Increment API counter for music data
increment_api_counter('music', 1)
img_data = BytesIO(response.content) img_data = BytesIO(response.content)
img = Image.open(img_data) img = Image.open(img_data)

View File

@@ -13,6 +13,14 @@ from src.config_manager import ConfigManager
from src.odds_manager import OddsManager from src.odds_manager import OddsManager
import pytz import pytz
# Import the API counter function from web interface
try:
from web_interface_v2 import increment_api_counter
except ImportError:
# Fallback if web interface is not available
def increment_api_counter(kind: str, count: int = 1):
pass
# Constants # Constants
ESPN_NCAAMB_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard" ESPN_NCAAMB_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard"
@@ -328,6 +336,9 @@ class BaseNCAAMBasketballManager:
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
# Increment API counter for sports data
increment_api_counter('sports', 1)
if use_cache: if use_cache:
self.cache_manager.set(cache_key, data) self.cache_manager.set(cache_key, data)

View File

@@ -1,13 +1,22 @@
import requests import time
import logging import logging
import requests
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from src.cache_manager import CacheManager from src.cache_manager import CacheManager
from src.config_manager import ConfigManager import pytz
from typing import Optional, List, Dict, Any from typing import Dict, Any, Optional, List
# Import the API counter function from web interface
try:
from web_interface_v2 import increment_api_counter
except ImportError:
# Fallback if web interface is not available
def increment_api_counter(kind: str, count: int = 1):
pass
class OddsManager: class OddsManager:
def __init__(self, cache_manager: CacheManager, config_manager: ConfigManager): def __init__(self, cache_manager: CacheManager, config_manager=None):
self.cache_manager = cache_manager self.cache_manager = cache_manager
self.config_manager = config_manager self.config_manager = config_manager
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
@@ -31,6 +40,9 @@ class OddsManager:
response = requests.get(url, timeout=10) response = requests.get(url, timeout=10)
response.raise_for_status() response.raise_for_status()
raw_data = response.json() raw_data = response.json()
# Increment API counter for odds data
increment_api_counter('odds', 1)
self.logger.debug(f"Received raw odds data from ESPN: {json.dumps(raw_data, indent=2)}") self.logger.debug(f"Received raw odds data from ESPN: {json.dumps(raw_data, indent=2)}")
odds_data = self._extract_espn_data(raw_data) odds_data = self._extract_espn_data(raw_data)

View File

@@ -1194,6 +1194,8 @@ class OddsTickerManager:
def calculate_dynamic_duration(self): def calculate_dynamic_duration(self):
"""Calculate the exact time needed to display all odds ticker content""" """Calculate the exact time needed to display all odds ticker content"""
logger.debug(f"calculate_dynamic_duration called - dynamic_duration_enabled: {self.dynamic_duration_enabled}, total_scroll_width: {self.total_scroll_width}")
# If dynamic duration is disabled, use fixed duration from config # If dynamic duration is disabled, use fixed duration from config
if not self.dynamic_duration_enabled: if not self.dynamic_duration_enabled:
self.dynamic_duration = self.odds_ticker_config.get('display_duration', 60) self.dynamic_duration = self.odds_ticker_config.get('display_duration', 60)
@@ -1202,6 +1204,7 @@ class OddsTickerManager:
if not self.total_scroll_width: if not self.total_scroll_width:
self.dynamic_duration = self.min_duration # Use configured minimum self.dynamic_duration = self.min_duration # Use configured minimum
logger.debug(f"total_scroll_width is 0, using minimum duration: {self.min_duration}s")
return return
try: try:
@@ -1257,7 +1260,12 @@ class OddsTickerManager:
logger.debug("get_dynamic_duration called but total_scroll_width is 0, attempting update...") logger.debug("get_dynamic_duration called but total_scroll_width is 0, attempting update...")
try: try:
# Force an update to get the data and calculate proper duration # Force an update to get the data and calculate proper duration
self._perform_update() # Bypass the update interval check for duration calculation
self.games_data = self._fetch_upcoming_games()
self.scroll_position = 0
self.current_game_index = 0
self._create_ticker_image() # Create the composite image
logger.debug(f"Force update completed, total_scroll_width: {self.total_scroll_width}px")
except Exception as e: except Exception as e:
logger.error(f"Error updating odds ticker for dynamic duration: {e}") logger.error(f"Error updating odds ticker for dynamic duration: {e}")

View File

@@ -14,6 +14,14 @@ from src.config_manager import ConfigManager
from src.odds_manager import OddsManager from src.odds_manager import OddsManager
import pytz import pytz
# Import the API counter function from web interface
try:
from web_interface_v2 import increment_api_counter
except ImportError:
# Fallback if web interface is not available
def increment_api_counter(kind: str, count: int = 1):
pass
# Constants # Constants
# ESPN_SOCCER_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/soccer/scoreboards" # Old URL # ESPN_SOCCER_SCOREBOARD_URL = "https://site.api.espn.com/apis/site/v2/sports/soccer/scoreboards" # Old URL
ESPN_SOCCER_LEAGUE_SCOREBOARD_URL_FORMAT = "http://site.api.espn.com/apis/site/v2/sports/soccer/{}/scoreboard" # New format string ESPN_SOCCER_LEAGUE_SCOREBOARD_URL_FORMAT = "http://site.api.espn.com/apis/site/v2/sports/soccer/{}/scoreboard" # New format string
@@ -192,6 +200,9 @@ class BaseSoccerManager:
response = requests.get(url, params=params, timeout=10) # Add timeout response = requests.get(url, params=params, timeout=10) # Add timeout
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
# Increment API counter for sports data
increment_api_counter('sports', 1)
cls.logger.debug(f"[Soccer Map Build] Fetched data for {league_slug}") cls.logger.debug(f"[Soccer Map Build] Fetched data for {league_slug}")
for event in data.get("events", []): for event in data.get("events", []):
@@ -264,6 +275,10 @@ class BaseSoccerManager:
response = requests.get(url, params=params) response = requests.get(url, params=params)
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
# Increment API counter for sports data
increment_api_counter('sports', 1)
self.logger.info(f"[Soccer] Fetched data from ESPN API for {league_slug} on {fetch_date}") self.logger.info(f"[Soccer] Fetched data from ESPN API for {league_slug} on {fetch_date}")
if use_cache: if use_cache:

View File

@@ -8,6 +8,14 @@ from rgbmatrix import RGBMatrix, RGBMatrixOptions
import os import os
from typing import Dict, Any from typing import Dict, Any
# Import the API counter function from web interface
try:
from web_interface_v2 import increment_api_counter
except ImportError:
# Fallback if web interface is not available
def increment_api_counter(kind: str, count: int = 1):
pass
# Get logger without configuring # Get logger without configuring
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -57,6 +65,10 @@ class YouTubeDisplay:
try: try:
response = requests.get(url) response = requests.get(url)
data = response.json() data = response.json()
# Increment API counter for YouTube data
increment_api_counter('youtube', 1)
if data['items']: if data['items']:
channel = data['items'][0] channel = data['items'][0]
return { return {

View File

@@ -1996,6 +1996,9 @@
<div><strong>Stocks:</strong> ${u.stocks || 0} used / ${f.stocks || 0} forecast</div> <div><strong>Stocks:</strong> ${u.stocks || 0} used / ${f.stocks || 0} forecast</div>
<div><strong>Sports:</strong> ${u.sports || 0} used / ${f.sports || 0} forecast</div> <div><strong>Sports:</strong> ${u.sports || 0} used / ${f.sports || 0} forecast</div>
<div><strong>News:</strong> ${u.news || 0} used / ${f.news || 0} forecast</div> <div><strong>News:</strong> ${u.news || 0} used / ${f.news || 0} forecast</div>
<div><strong>Odds:</strong> ${u.odds || 0} used / ${f.odds || 0} forecast</div>
<div><strong>Music:</strong> ${u.music || 0} used / ${f.music || 0} forecast</div>
<div><strong>YouTube:</strong> ${u.youtube || 0} used / ${f.youtube || 0} forecast</div>
`; `;
} catch (e) { } catch (e) {
// ignore // ignore

View File

@@ -815,6 +815,9 @@ api_counters = {
'stocks': {'used': 0}, 'stocks': {'used': 0},
'sports': {'used': 0}, 'sports': {'used': 0},
'news': {'used': 0}, 'news': {'used': 0},
'odds': {'used': 0},
'music': {'used': 0},
'youtube': {'used': 0},
} }
api_window_start = time.time() api_window_start = time.time()
api_window_seconds = 24 * 3600 api_window_seconds = 24 * 3600
@@ -875,6 +878,27 @@ def get_metrics():
except Exception: except Exception:
forecast['news'] = 0 forecast['news'] = 0
# Odds ticker
try:
o_int = int(config.get('odds_ticker', {}).get('update_interval', 3600))
forecast['odds'] = max(1, int(api_window_seconds / max(1, o_int)))
except Exception:
forecast['odds'] = 0
# Music manager (image downloads)
try:
m_int = int(config.get('music', {}).get('POLLING_INTERVAL_SECONDS', 5))
forecast['music'] = max(1, int(api_window_seconds / max(1, m_int)))
except Exception:
forecast['music'] = 0
# YouTube display
try:
y_int = int(config.get('youtube', {}).get('update_interval', 300))
forecast['youtube'] = max(1, int(api_window_seconds / max(1, y_int)))
except Exception:
forecast['youtube'] = 0
return jsonify({ return jsonify({
'status': 'success', 'status': 'success',
'window_seconds': api_window_seconds, 'window_seconds': api_window_seconds,