From de7280f4989e5f66120a65df752d725415d8a359 Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Mon, 30 Mar 2026 11:13:05 -0400 Subject: [PATCH 1/3] perf: parallelize baseball plugin updates and reduce startup time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Baseball update() was taking ~27s on first call due to 9 sequential sub-manager updates each making ESPN API calls. This change: - Parallelizes sub-manager updates using ThreadPoolExecutor (3 workers, one per league) — reduces wall time from ~27s to ~9s - Shares rankings cache per league via ClassVar so only 3 rankings fetches happen instead of 9 — saves ~6-12s - Reduces HTTP retry aggressiveness (3 retries @ 0.5s backoff instead of 5 retries @ 1s) — prevents 15s+ blocking on transient failures Modeled after the soccer plugin's existing parallel update pattern. Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/baseball-scoreboard/manager.py | 59 +++++++++++++------ plugins/baseball-scoreboard/manifest.json | 7 ++- plugins/baseball-scoreboard/milb_managers.py | 19 ++++++ plugins/baseball-scoreboard/mlb_managers.py | 19 ++++++ .../ncaa_baseball_managers.py | 19 ++++++ plugins/baseball-scoreboard/sports.py | 5 +- 6 files changed, 106 insertions(+), 22 deletions(-) diff --git a/plugins/baseball-scoreboard/manager.py b/plugins/baseball-scoreboard/manager.py index 6df9b04..5c6acf9 100644 --- a/plugins/baseball-scoreboard/manager.py +++ b/plugins/baseball-scoreboard/manager.py @@ -955,31 +955,54 @@ def _ensure_manager_updated(self, manager) -> None: self.logger.debug(f"Auto-refresh failed for manager {manager}: {exc}") def update(self) -> None: - """Update baseball game data.""" + """Update baseball game data using parallel manager updates.""" if not self.is_enabled: return - try: - # Update MLB managers if enabled - if self.mlb_enabled: - self.mlb_live.update() - self.mlb_recent.update() - self.mlb_upcoming.update() + from concurrent.futures import ThreadPoolExecutor, as_completed - # Update MiLB managers if enabled - if self.milb_enabled: - self.milb_live.update() - self.milb_recent.update() - self.milb_upcoming.update() + # Collect all enabled managers + managers_to_update = [] + if self.mlb_enabled: + managers_to_update.extend([ + ("MLB Live", self.mlb_live), + ("MLB Recent", self.mlb_recent), + ("MLB Upcoming", self.mlb_upcoming), + ]) + if self.milb_enabled: + managers_to_update.extend([ + ("MiLB Live", self.milb_live), + ("MiLB Recent", self.milb_recent), + ("MiLB Upcoming", self.milb_upcoming), + ]) + if self.ncaa_baseball_enabled: + managers_to_update.extend([ + ("NCAA Live", self.ncaa_baseball_live), + ("NCAA Recent", self.ncaa_baseball_recent), + ("NCAA Upcoming", self.ncaa_baseball_upcoming), + ]) - # Update NCAA Baseball managers if enabled - if self.ncaa_baseball_enabled: - self.ncaa_baseball_live.update() - self.ncaa_baseball_recent.update() - self.ncaa_baseball_upcoming.update() + if not managers_to_update: + return + def _safe_update(name_and_manager): + name, manager = name_and_manager + try: + manager.update() + except Exception as e: + self.logger.error(f"Error updating {name} manager: {e}") + + try: + # 3 workers = one per league, Pi-friendly concurrency + with ThreadPoolExecutor(max_workers=3, thread_name_prefix="baseball-update") as executor: + futures = { + executor.submit(_safe_update, item): item[0] + for item in managers_to_update + } + for future in as_completed(futures, timeout=25): + future.result() # propagate unexpected executor errors except Exception as e: - self.logger.error(f"Error updating managers: {e}") + self.logger.error(f"Error in parallel manager updates: {e}") def _get_managers_in_priority_order(self, mode_type: str) -> list: """ diff --git a/plugins/baseball-scoreboard/manifest.json b/plugins/baseball-scoreboard/manifest.json index 69fc693..5bb3956 100644 --- a/plugins/baseball-scoreboard/manifest.json +++ b/plugins/baseball-scoreboard/manifest.json @@ -1,7 +1,7 @@ { "id": "baseball-scoreboard", "name": "Baseball Scoreboard", - "version": "1.5.6", + "version": "1.6.0", "author": "ChuckBuilds", "description": "Live, recent, and upcoming baseball games across MLB, MiLB, and NCAA Baseball with real-time scores and schedules", "category": "sports", @@ -30,6 +30,11 @@ "branch": "main", "plugin_path": "plugins/baseball-scoreboard", "versions": [ + { + "released": "2026-03-30", + "version": "1.6.0", + "ledmatrix_min": "2.0.0" + }, { "released": "2026-03-29", "version": "1.5.6", diff --git a/plugins/baseball-scoreboard/milb_managers.py b/plugins/baseball-scoreboard/milb_managers.py index 44b2004..c7a4fbf 100644 --- a/plugins/baseball-scoreboard/milb_managers.py +++ b/plugins/baseball-scoreboard/milb_managers.py @@ -1,4 +1,5 @@ import logging +import time from datetime import datetime, timedelta from pathlib import Path from typing import Any, ClassVar, Dict, List, Optional @@ -30,6 +31,8 @@ class BaseMiLBManager(Baseball): _warning_cooldown: ClassVar[int] = 60 # Only log warnings once per minute _shared_data: ClassVar[Optional[Dict]] = None _last_shared_update: ClassVar[float] = 0 + _shared_rankings_cache: ClassVar[Dict] = {} + _shared_rankings_timestamp: ClassVar[float] = 0 def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.logger = logging.getLogger("MiLB") @@ -61,6 +64,22 @@ def __init__(self, config: Dict[str, Any], display_manager, cache_manager): ) self.league = "minor-league-baseball" + def _fetch_team_rankings(self) -> Dict[str, int]: + """Share rankings cache across all MiLB manager instances.""" + current_time = time.time() + if ( + BaseMiLBManager._shared_rankings_cache + and current_time - BaseMiLBManager._shared_rankings_timestamp + < self._rankings_cache_duration + ): + self._team_rankings_cache = BaseMiLBManager._shared_rankings_cache + return self._team_rankings_cache + + result = super()._fetch_team_rankings() + BaseMiLBManager._shared_rankings_cache = result + BaseMiLBManager._shared_rankings_timestamp = current_time + return result + @staticmethod def _convert_stats_game_to_espn_event(game: Dict) -> Dict: """Convert a single MLB Stats API game to ESPN event format. diff --git a/plugins/baseball-scoreboard/mlb_managers.py b/plugins/baseball-scoreboard/mlb_managers.py index a1d5814..2ee2856 100644 --- a/plugins/baseball-scoreboard/mlb_managers.py +++ b/plugins/baseball-scoreboard/mlb_managers.py @@ -1,4 +1,5 @@ import logging +import time from datetime import datetime from pathlib import Path from typing import Any, ClassVar, Dict, Optional @@ -24,6 +25,8 @@ class BaseMLBManager(Baseball): _warning_cooldown: ClassVar[int] = 60 # Only log warnings once per minute _shared_data: ClassVar[Optional[Dict]] = None _last_shared_update: ClassVar[float] = 0 + _shared_rankings_cache: ClassVar[Dict] = {} + _shared_rankings_timestamp: ClassVar[float] = 0 def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.logger = logging.getLogger("MLB") @@ -50,6 +53,22 @@ def __init__(self, config: Dict[str, Any], display_manager, cache_manager): ) self.league = "mlb" + def _fetch_team_rankings(self) -> Dict[str, int]: + """Share rankings cache across all MLB manager instances.""" + current_time = time.time() + if ( + BaseMLBManager._shared_rankings_cache + and current_time - BaseMLBManager._shared_rankings_timestamp + < self._rankings_cache_duration + ): + self._team_rankings_cache = BaseMLBManager._shared_rankings_cache + return self._team_rankings_cache + + result = super()._fetch_team_rankings() + BaseMLBManager._shared_rankings_cache = result + BaseMLBManager._shared_rankings_timestamp = current_time + return result + def _fetch_mlb_api_data(self, use_cache: bool = True) -> Optional[Dict]: """ Fetches the full season schedule for MLB using background threading. diff --git a/plugins/baseball-scoreboard/ncaa_baseball_managers.py b/plugins/baseball-scoreboard/ncaa_baseball_managers.py index bc6733a..6014ad7 100644 --- a/plugins/baseball-scoreboard/ncaa_baseball_managers.py +++ b/plugins/baseball-scoreboard/ncaa_baseball_managers.py @@ -1,4 +1,5 @@ import logging +import time from typing import ClassVar, Dict, Any, Optional from datetime import datetime import pytz @@ -22,6 +23,8 @@ class BaseNCAABaseballManager(Baseball): _last_shared_update: ClassVar[float] = 0 _processed_games_cache: ClassVar[Dict] = {} # Cache for processed game data _processed_games_timestamp: ClassVar[float] = 0 + _shared_rankings_cache: ClassVar[Dict] = {} + _shared_rankings_timestamp: ClassVar[float] = 0 def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.logger = logging.getLogger("NCAABaseball") @@ -51,6 +54,22 @@ def __init__(self, config: Dict[str, Any], display_manager, cache_manager): f"Display modes - Recent: {self.recent_enabled}, Upcoming: {self.upcoming_enabled}, Live: {self.live_enabled}" ) + def _fetch_team_rankings(self) -> Dict[str, int]: + """Share rankings cache across all NCAA Baseball manager instances.""" + current_time = time.time() + if ( + BaseNCAABaseballManager._shared_rankings_cache + and current_time - BaseNCAABaseballManager._shared_rankings_timestamp + < self._rankings_cache_duration + ): + self._team_rankings_cache = BaseNCAABaseballManager._shared_rankings_cache + return self._team_rankings_cache + + result = super()._fetch_team_rankings() + BaseNCAABaseballManager._shared_rankings_cache = result + BaseNCAABaseballManager._shared_rankings_timestamp = current_time + return result + def _fetch_ncaa_baseball_api_data(self, use_cache: bool = True) -> Optional[Dict]: """ Fetches the full season schedule for NCAA Baseball using date range approach to ensure diff --git a/plugins/baseball-scoreboard/sports.py b/plugins/baseball-scoreboard/sports.py index 606a302..ff91fc9 100644 --- a/plugins/baseball-scoreboard/sports.py +++ b/plugins/baseball-scoreboard/sports.py @@ -85,9 +85,8 @@ def __init__( self.session = requests.Session() retry_strategy = Retry( - total=5, # increased number of retries - backoff_factor=1, # increased backoff factor - # added 429 to retry list + total=3, + backoff_factor=0.5, # retries at 0s, 0.5s, 1s (1.5s total vs 15s before) status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET", "HEAD", "OPTIONS"], ) From bf7a33b8103df500257231126cade9eba2660016 Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Mon, 30 Mar 2026 11:18:11 -0400 Subject: [PATCH 2/3] perf: use full parallelism for all sub-manager updates With 3 workers, managers within the same league were still serialized. Since all managers are I/O-bound (ESPN API calls), running them all in parallel is safe and eliminates the remaining serialization bottleneck. Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/baseball-scoreboard/manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/baseball-scoreboard/manager.py b/plugins/baseball-scoreboard/manager.py index 5c6acf9..206e108 100644 --- a/plugins/baseball-scoreboard/manager.py +++ b/plugins/baseball-scoreboard/manager.py @@ -993,8 +993,9 @@ def _safe_update(name_and_manager): self.logger.error(f"Error updating {name} manager: {e}") try: - # 3 workers = one per league, Pi-friendly concurrency - with ThreadPoolExecutor(max_workers=3, thread_name_prefix="baseball-update") as executor: + # All managers run in parallel — they're I/O-bound (ESPN API calls) + # so more threads than cores is fine on Pi + with ThreadPoolExecutor(max_workers=len(managers_to_update), thread_name_prefix="baseball-update") as executor: futures = { executor.submit(_safe_update, item): item[0] for item in managers_to_update From 981bddd8d8334b7ea2d1c598ba3ab40978b04962 Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Mon, 30 Mar 2026 12:38:06 -0400 Subject: [PATCH 3/3] fix(baseball): address PR review findings - Update stale get_info() version from "1.3.0" to "1.6.0" - Use getattr guards in update() to prevent AttributeError if _initialize_managers partially failed - Fix as_completed/shutdown pattern: on timeout, log which managers are still running and call shutdown(wait=False, cancel_futures=True) instead of blocking via the context manager - Update manifest last_updated from "2026-03-29" to "2026-03-30" - Add threading.Lock with double-check pattern to rankings cache in all three league base managers (MLB, MiLB, NCAA) to prevent duplicate fetches under parallel execution Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/baseball-scoreboard/manager.py | 56 ++++++++++--------- plugins/baseball-scoreboard/manifest.json | 2 +- plugins/baseball-scoreboard/milb_managers.py | 23 ++++++-- plugins/baseball-scoreboard/mlb_managers.py | 23 ++++++-- .../ncaa_baseball_managers.py | 23 ++++++-- 5 files changed, 84 insertions(+), 43 deletions(-) diff --git a/plugins/baseball-scoreboard/manager.py b/plugins/baseball-scoreboard/manager.py index 206e108..9298b47 100644 --- a/plugins/baseball-scoreboard/manager.py +++ b/plugins/baseball-scoreboard/manager.py @@ -959,28 +959,25 @@ def update(self) -> None: if not self.is_enabled: return - from concurrent.futures import ThreadPoolExecutor, as_completed + from concurrent.futures import ThreadPoolExecutor, as_completed, TimeoutError - # Collect all enabled managers + # Collect all enabled managers (use getattr to guard against partial init) managers_to_update = [] if self.mlb_enabled: - managers_to_update.extend([ - ("MLB Live", self.mlb_live), - ("MLB Recent", self.mlb_recent), - ("MLB Upcoming", self.mlb_upcoming), - ]) + for name, attr in [("MLB Live", "mlb_live"), ("MLB Recent", "mlb_recent"), ("MLB Upcoming", "mlb_upcoming")]: + mgr = getattr(self, attr, None) + if mgr is not None: + managers_to_update.append((name, mgr)) if self.milb_enabled: - managers_to_update.extend([ - ("MiLB Live", self.milb_live), - ("MiLB Recent", self.milb_recent), - ("MiLB Upcoming", self.milb_upcoming), - ]) + for name, attr in [("MiLB Live", "milb_live"), ("MiLB Recent", "milb_recent"), ("MiLB Upcoming", "milb_upcoming")]: + mgr = getattr(self, attr, None) + if mgr is not None: + managers_to_update.append((name, mgr)) if self.ncaa_baseball_enabled: - managers_to_update.extend([ - ("NCAA Live", self.ncaa_baseball_live), - ("NCAA Recent", self.ncaa_baseball_recent), - ("NCAA Upcoming", self.ncaa_baseball_upcoming), - ]) + for name, attr in [("NCAA Live", "ncaa_baseball_live"), ("NCAA Recent", "ncaa_baseball_recent"), ("NCAA Upcoming", "ncaa_baseball_upcoming")]: + mgr = getattr(self, attr, None) + if mgr is not None: + managers_to_update.append((name, mgr)) if not managers_to_update: return @@ -992,18 +989,23 @@ def _safe_update(name_and_manager): except Exception as e: self.logger.error(f"Error updating {name} manager: {e}") + # All managers run in parallel — they're I/O-bound (ESPN API calls) + # so more threads than cores is fine on Pi + executor = ThreadPoolExecutor(max_workers=len(managers_to_update), thread_name_prefix="baseball-update") try: - # All managers run in parallel — they're I/O-bound (ESPN API calls) - # so more threads than cores is fine on Pi - with ThreadPoolExecutor(max_workers=len(managers_to_update), thread_name_prefix="baseball-update") as executor: - futures = { - executor.submit(_safe_update, item): item[0] - for item in managers_to_update - } - for future in as_completed(futures, timeout=25): - future.result() # propagate unexpected executor errors + futures = { + executor.submit(_safe_update, item): item[0] + for item in managers_to_update + } + for future in as_completed(futures, timeout=25): + future.result() # propagate unexpected executor errors + except TimeoutError: + still_running = [name for f, name in futures.items() if not f.done()] + self.logger.warning(f"Manager update timed out after 25s, still running: {still_running}") except Exception as e: self.logger.error(f"Error in parallel manager updates: {e}") + finally: + executor.shutdown(wait=False, cancel_futures=True) def _get_managers_in_priority_order(self, mode_type: str) -> list: """ @@ -2590,7 +2592,7 @@ def get_info(self) -> Dict[str, Any]: info = { "plugin_id": self.plugin_id, "name": "Baseball Scoreboard", - "version": "1.3.0", + "version": "1.6.0", "enabled": self.is_enabled, "display_size": f"{self.display_width}x{self.display_height}", "mlb_enabled": self.mlb_enabled, diff --git a/plugins/baseball-scoreboard/manifest.json b/plugins/baseball-scoreboard/manifest.json index 5bb3956..ac85b54 100644 --- a/plugins/baseball-scoreboard/manifest.json +++ b/plugins/baseball-scoreboard/manifest.json @@ -116,7 +116,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-03-29", + "last_updated": "2026-03-30", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/baseball-scoreboard/milb_managers.py b/plugins/baseball-scoreboard/milb_managers.py index c7a4fbf..16087a5 100644 --- a/plugins/baseball-scoreboard/milb_managers.py +++ b/plugins/baseball-scoreboard/milb_managers.py @@ -1,4 +1,5 @@ import logging +import threading import time from datetime import datetime, timedelta from pathlib import Path @@ -33,6 +34,7 @@ class BaseMiLBManager(Baseball): _last_shared_update: ClassVar[float] = 0 _shared_rankings_cache: ClassVar[Dict] = {} _shared_rankings_timestamp: ClassVar[float] = 0 + _shared_rankings_lock: ClassVar[threading.Lock] = threading.Lock() def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.logger = logging.getLogger("MiLB") @@ -65,7 +67,7 @@ def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.league = "minor-league-baseball" def _fetch_team_rankings(self) -> Dict[str, int]: - """Share rankings cache across all MiLB manager instances.""" + """Share rankings cache across all MiLB manager instances (thread-safe).""" current_time = time.time() if ( BaseMiLBManager._shared_rankings_cache @@ -75,10 +77,21 @@ def _fetch_team_rankings(self) -> Dict[str, int]: self._team_rankings_cache = BaseMiLBManager._shared_rankings_cache return self._team_rankings_cache - result = super()._fetch_team_rankings() - BaseMiLBManager._shared_rankings_cache = result - BaseMiLBManager._shared_rankings_timestamp = current_time - return result + with BaseMiLBManager._shared_rankings_lock: + # Double-check after acquiring lock + current_time = time.time() + if ( + BaseMiLBManager._shared_rankings_cache + and current_time - BaseMiLBManager._shared_rankings_timestamp + < self._rankings_cache_duration + ): + self._team_rankings_cache = BaseMiLBManager._shared_rankings_cache + return self._team_rankings_cache + + result = super()._fetch_team_rankings() + BaseMiLBManager._shared_rankings_cache = result + BaseMiLBManager._shared_rankings_timestamp = current_time + return result @staticmethod def _convert_stats_game_to_espn_event(game: Dict) -> Dict: diff --git a/plugins/baseball-scoreboard/mlb_managers.py b/plugins/baseball-scoreboard/mlb_managers.py index 2ee2856..dbb252b 100644 --- a/plugins/baseball-scoreboard/mlb_managers.py +++ b/plugins/baseball-scoreboard/mlb_managers.py @@ -1,4 +1,5 @@ import logging +import threading import time from datetime import datetime from pathlib import Path @@ -27,6 +28,7 @@ class BaseMLBManager(Baseball): _last_shared_update: ClassVar[float] = 0 _shared_rankings_cache: ClassVar[Dict] = {} _shared_rankings_timestamp: ClassVar[float] = 0 + _shared_rankings_lock: ClassVar[threading.Lock] = threading.Lock() def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.logger = logging.getLogger("MLB") @@ -54,7 +56,7 @@ def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.league = "mlb" def _fetch_team_rankings(self) -> Dict[str, int]: - """Share rankings cache across all MLB manager instances.""" + """Share rankings cache across all MLB manager instances (thread-safe).""" current_time = time.time() if ( BaseMLBManager._shared_rankings_cache @@ -64,10 +66,21 @@ def _fetch_team_rankings(self) -> Dict[str, int]: self._team_rankings_cache = BaseMLBManager._shared_rankings_cache return self._team_rankings_cache - result = super()._fetch_team_rankings() - BaseMLBManager._shared_rankings_cache = result - BaseMLBManager._shared_rankings_timestamp = current_time - return result + with BaseMLBManager._shared_rankings_lock: + # Double-check after acquiring lock + current_time = time.time() + if ( + BaseMLBManager._shared_rankings_cache + and current_time - BaseMLBManager._shared_rankings_timestamp + < self._rankings_cache_duration + ): + self._team_rankings_cache = BaseMLBManager._shared_rankings_cache + return self._team_rankings_cache + + result = super()._fetch_team_rankings() + BaseMLBManager._shared_rankings_cache = result + BaseMLBManager._shared_rankings_timestamp = current_time + return result def _fetch_mlb_api_data(self, use_cache: bool = True) -> Optional[Dict]: """ diff --git a/plugins/baseball-scoreboard/ncaa_baseball_managers.py b/plugins/baseball-scoreboard/ncaa_baseball_managers.py index 6014ad7..79c8298 100644 --- a/plugins/baseball-scoreboard/ncaa_baseball_managers.py +++ b/plugins/baseball-scoreboard/ncaa_baseball_managers.py @@ -1,4 +1,5 @@ import logging +import threading import time from typing import ClassVar, Dict, Any, Optional from datetime import datetime @@ -25,6 +26,7 @@ class BaseNCAABaseballManager(Baseball): _processed_games_timestamp: ClassVar[float] = 0 _shared_rankings_cache: ClassVar[Dict] = {} _shared_rankings_timestamp: ClassVar[float] = 0 + _shared_rankings_lock: ClassVar[threading.Lock] = threading.Lock() def __init__(self, config: Dict[str, Any], display_manager, cache_manager): self.logger = logging.getLogger("NCAABaseball") @@ -55,7 +57,7 @@ def __init__(self, config: Dict[str, Any], display_manager, cache_manager): ) def _fetch_team_rankings(self) -> Dict[str, int]: - """Share rankings cache across all NCAA Baseball manager instances.""" + """Share rankings cache across all NCAA Baseball manager instances (thread-safe).""" current_time = time.time() if ( BaseNCAABaseballManager._shared_rankings_cache @@ -65,10 +67,21 @@ def _fetch_team_rankings(self) -> Dict[str, int]: self._team_rankings_cache = BaseNCAABaseballManager._shared_rankings_cache return self._team_rankings_cache - result = super()._fetch_team_rankings() - BaseNCAABaseballManager._shared_rankings_cache = result - BaseNCAABaseballManager._shared_rankings_timestamp = current_time - return result + with BaseNCAABaseballManager._shared_rankings_lock: + # Double-check after acquiring lock + current_time = time.time() + if ( + BaseNCAABaseballManager._shared_rankings_cache + and current_time - BaseNCAABaseballManager._shared_rankings_timestamp + < self._rankings_cache_duration + ): + self._team_rankings_cache = BaseNCAABaseballManager._shared_rankings_cache + return self._team_rankings_cache + + result = super()._fetch_team_rankings() + BaseNCAABaseballManager._shared_rankings_cache = result + BaseNCAABaseballManager._shared_rankings_timestamp = current_time + return result def _fetch_ncaa_baseball_api_data(self, use_cache: bool = True) -> Optional[Dict]: """