From 9f00124fadec48e53ed347c89870101224bc691f Mon Sep 17 00:00:00 2001 From: ChuckBuilds <33324927+ChuckBuilds@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:05:36 -0500 Subject: [PATCH] Fix odds ticker dynamic duration calculation - Fixed double-counting of display width in total_scroll_width calculation - Added detailed debug logging for dynamic duration calculation - Added debug logging for scrolling behavior and loop resets - Created test script for debugging dynamic duration issues - The issue was that total_scroll_width included display width, causing incorrect duration calculations that resulted in early cutoff --- src/odds_ticker_manager.py | 33 +++++- test/test_odds_ticker_dynamic_duration.py | 122 ++++++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 test/test_odds_ticker_dynamic_duration.py diff --git a/src/odds_ticker_manager.py b/src/odds_ticker_manager.py index 4a31a90f..cdca88be 100644 --- a/src/odds_ticker_manager.py +++ b/src/odds_ticker_manager.py @@ -12,6 +12,14 @@ from src.cache_manager import CacheManager from src.config_manager import ConfigManager from src.odds_manager import OddsManager +# 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 logger = logging.getLogger(__name__) @@ -214,6 +222,9 @@ class OddsTickerManager: response.raise_for_status() data = response.json() + # Increment API counter for sports data + increment_api_counter('sports', 1) + # Different path for college sports records if league == 'college-football': record_items = data.get('team', {}).get('record', {}).get('items', []) @@ -371,6 +382,10 @@ class OddsTickerManager: response = requests.get(url, timeout=self.request_timeout) response.raise_for_status() data = response.json() + + # Increment API counter for sports data + increment_api_counter('sports', 1) + self.cache_manager.set(cache_key, data) logger.debug(f"Cached scoreboard for {league} on {date} with a TTL of {ttl} seconds.") else: @@ -1140,7 +1155,8 @@ class OddsTickerManager: gap_width = 24 # Reduced gap between games display_width = self.display_manager.matrix.width # Add display width of black space at start - total_width = display_width + sum(img.width for img in game_images) + gap_width * (len(game_images)) + content_width = sum(img.width for img in game_images) + gap_width * (len(game_images)) + total_width = display_width + content_width height = self.display_manager.matrix.height self.ticker_image = Image.new('RGB', (total_width, height), color=(0, 0, 0)) @@ -1156,8 +1172,14 @@ class OddsTickerManager: self.ticker_image.putpixel((bar_x, y), (255, 255, 255)) current_x += gap_width - # Calculate total scroll width for dynamic duration - self.total_scroll_width = total_width + # Calculate total scroll width for dynamic duration (only the content width, not including display width) + self.total_scroll_width = content_width + logger.debug(f"Odds ticker image creation:") + logger.debug(f" Display width: {display_width}px") + logger.debug(f" Content width: {content_width}px") + logger.debug(f" Total image width: {total_width}px") + logger.debug(f" Number of games: {len(game_images)}") + logger.debug(f" Gap width: {gap_width}px") self.calculate_dynamic_duration() def _draw_text_with_outline(self, draw: ImageDraw.Draw, text: str, position: tuple, font: ImageFont.FreeTypeFont, @@ -1278,6 +1300,9 @@ class OddsTickerManager: """Display the odds ticker.""" logger.debug("Entering display method") logger.debug(f"Odds ticker enabled: {self.is_enabled}") + logger.debug(f"Current scroll position: {self.scroll_position}") + logger.debug(f"Ticker image width: {self.ticker_image.width if self.ticker_image else 'None'}") + logger.debug(f"Dynamic duration: {self.dynamic_duration}s") if not self.is_enabled: logger.debug("Odds ticker is disabled, exiting display method.") @@ -1326,10 +1351,12 @@ class OddsTickerManager: if self.loop: # Reset position when we've scrolled past the end for a continuous loop if self.scroll_position >= self.ticker_image.width: + logger.debug(f"Odds ticker loop reset: scroll_position {self.scroll_position} >= image width {self.ticker_image.width}") self.scroll_position = 0 else: # Stop scrolling when we reach the end if self.scroll_position >= self.ticker_image.width - width: + logger.debug(f"Odds ticker reached end: scroll_position {self.scroll_position} >= {self.ticker_image.width - width}") self.scroll_position = self.ticker_image.width - width # Signal that scrolling has stopped self.display_manager.set_scrolling_state(False) diff --git a/test/test_odds_ticker_dynamic_duration.py b/test/test_odds_ticker_dynamic_duration.py new file mode 100644 index 00000000..76747c5d --- /dev/null +++ b/test/test_odds_ticker_dynamic_duration.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Test script for debugging OddsTickerManager dynamic duration calculation +""" + +import sys +import os +import time +import logging + +# Add the parent directory to the Python path so we can import from src +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from src.display_manager import DisplayManager +from src.config_manager import ConfigManager +from src.odds_ticker_manager import OddsTickerManager + +# Configure logging to show debug information +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s.%(msecs)03d - %(levelname)s:%(name)s:%(message)s', + datefmt='%H:%M:%S' +) + +def test_dynamic_duration(): + """Test the dynamic duration calculation for odds ticker.""" + print("Testing OddsTickerManager Dynamic Duration...") + + try: + # Load configuration + config_manager = ConfigManager() + config = config_manager.load_config() + + # Initialize display manager + display_manager = DisplayManager(config) + + # Initialize odds ticker + odds_ticker = OddsTickerManager(config, display_manager) + + print(f"Odds ticker enabled: {odds_ticker.is_enabled}") + print(f"Dynamic duration enabled: {odds_ticker.dynamic_duration_enabled}") + print(f"Min duration: {odds_ticker.min_duration}s") + print(f"Max duration: {odds_ticker.max_duration}s") + print(f"Duration buffer: {odds_ticker.duration_buffer}") + print(f"Scroll speed: {odds_ticker.scroll_speed}") + print(f"Scroll delay: {odds_ticker.scroll_delay}") + print(f"Display width: {display_manager.matrix.width}") + + if not odds_ticker.is_enabled: + print("Odds ticker is disabled in config. Enabling for test...") + odds_ticker.is_enabled = True + + # Temporarily disable favorite teams filter for testing + print("Temporarily disabling favorite teams filter to test display...") + original_show_favorite = odds_ticker.show_favorite_teams_only + odds_ticker.show_favorite_teams_only = False + + # Update odds ticker data + print("\nUpdating odds ticker data...") + odds_ticker.update() + + print(f"Found {len(odds_ticker.games_data)} games") + + if odds_ticker.games_data: + print("\nSample game data:") + for i, game in enumerate(odds_ticker.games_data[:3]): # Show first 3 games + print(f" Game {i+1}: {game['away_team']} @ {game['home_team']}") + print(f" Time: {game['start_time']}") + print(f" League: {game['league']}") + if game.get('odds'): + print(f" Has odds: Yes") + else: + print(f" Has odds: No") + print() + + # Check dynamic duration calculation + print("\nDynamic Duration Analysis:") + print(f"Total scroll width: {odds_ticker.total_scroll_width}px") + print(f"Calculated dynamic duration: {odds_ticker.dynamic_duration}s") + + # Calculate expected duration manually + display_width = display_manager.matrix.width + total_scroll_distance = display_width + odds_ticker.total_scroll_width + frames_needed = total_scroll_distance / odds_ticker.scroll_speed + total_time = frames_needed * odds_ticker.scroll_delay + buffer_time = total_time * odds_ticker.duration_buffer + calculated_duration = int(total_time + buffer_time) + + print(f"\nManual calculation:") + print(f" Display width: {display_width}px") + print(f" Content width: {odds_ticker.total_scroll_width}px") + print(f" Total scroll distance: {total_scroll_distance}px") + print(f" Frames needed: {frames_needed:.1f}") + print(f" Base time: {total_time:.2f}s") + print(f" Buffer time: {buffer_time:.2f}s ({odds_ticker.duration_buffer*100}%)") + print(f" Calculated duration: {calculated_duration}s") + + # Test display for a few iterations + print(f"\nTesting display for 10 iterations...") + for i in range(10): + print(f" Display iteration {i+1} starting...") + odds_ticker.display() + print(f" Display iteration {i+1} complete - scroll position: {odds_ticker.scroll_position}") + time.sleep(1) + + else: + print("No games found even with favorite teams filter disabled.") + + # Restore original setting + odds_ticker.show_favorite_teams_only = original_show_favorite + + # Cleanup + display_manager.cleanup() + print("\nTest completed successfully!") + + except Exception as e: + print(f"Error during test: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + test_dynamic_duration()