Skip to content

fix(masters): post-tournament phase and headshot bugs (v2.4.2)#103

Merged
ChuckBuilds merged 2 commits intoChuckBuilds:mainfrom
sarjent:feat/masters-post-tournament-improvements
Apr 16, 2026
Merged

fix(masters): post-tournament phase and headshot bugs (v2.4.2)#103
ChuckBuilds merged 2 commits intoChuckBuilds:mainfrom
sarjent:feat/masters-post-tournament-improvements

Conversation

@sarjent
Copy link
Copy Markdown
Contributor

@sarjent sarjent commented Apr 15, 2026

Summary

  • Fix post-tournament phase not transitioning to countdown: The phase detection was using an exact timedelta comparison against a padded end_date timestamp, causing it to linger in post-tournament longer than the configured post_tournament_display_days. Switched to calendar-day comparison in EDT so that "2 days after Sunday" reliably means Tuesday regardless of sub-day timestamp offsets.
  • Fix fallback end-date computation: _computed_fallback_meta() was building the tournament end window from the raw _masters_thursday() noon UTC timestamp, which shifted end_e.date() one day forward (to Monday instead of Sunday). Now normalizes the start to midnight EDT before computing the end.
  • Fix missing player headshots in Vegas scroll mode: get_player_headshot() returned None immediately when player_id was empty, discarding a valid url. The fix uses url as a cache/disk key fallback and proceeds to download if a URL is available.

Test plan

  • Verify tournament phase switches from post-tournament → countdown after the configured number of days (calendar days, not exact hours)
  • Verify headshots appear in Vegas scroll mode (masters_live_action mode)
  • Verify post_tournament_display_days = 0 skips post-tournament phase entirely (goes straight to countdown)
  • Verify post_tournament_display_days = 2 holds results for Monday + Tuesday after Sunday final

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Released version 2.4.2 with updated minimum compatibility requirements.
  • Bug Fixes

    • Fixed tournament window datetime calculations for accurate tournament period detection.
    • Improved post-tournament classification logic based on calendar day differences.
  • Improvements

    • Enhanced image caching and download support with fallback URL handling.
    • Upgraded error logging for image download issues.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

This PR updates the Masters Tournament plugin with four distinct changes: enhanced caching support for player headshots when URLs are available, a new version entry in the manifest, corrected fallback tournament end-time calculation for EDT midnight boundaries, and refined post-tournament classification logic using calendar-day differences instead of duration windows.

Changes

Cohort / File(s) Summary
Player Headshot Caching
plugins/masters-tournament/logo_loader.py
Added URL-derived stable_key to enable in-memory caching when player_id is absent but url is provided. Elevated download error logging from debug to warning level and switched logged identifier from player_id to stable_key. Disk caching now gated to only occur when player_id exists.
Plugin Version
plugins/masters-tournament/manifest.json
Extended versions list with new entry for version 2.4.2 (released 2026-04-15, ledmatrix_min_version 2.0.0).
Tournament Timing & Classification
plugins/masters-tournament/masters_data.py, plugins/masters-tournament/masters_helpers.py
Corrected _computed_fallback_meta() to normalize start time to midnight EDT before computing end datetime, accounting for _masters_thursday() returning noon UTC. Updated get_detailed_phase() post-tournament classification from duration-based window to calendar-day differences in EDT.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: bug fixes for post-tournament phase detection, headshot caching, and version bump (v2.4.2), which directly correspond to the four modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
plugins/masters-tournament/logo_loader.py (1)

167-198: Normalize URL before using it as cache/log identifier.

Using raw URLs directly can create high-cardinality cache keys and log query parameters. Prefer a normalized URL (or hash) when player_id is absent.

♻️ Suggested refinement
@@
 import logging
 import os
 from io import BytesIO
 from pathlib import Path
 from typing import Dict, Optional, Tuple
+from urllib.parse import urlsplit, urlunsplit
@@
-        cache_key = f"player_{player_id or url}_{max_size}"
+        normalized_url = None
+        if url:
+            parts = urlsplit(url)
+            normalized_url = urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))
+        id_for_cache = player_id or normalized_url
+        cache_key = f"player_{id_for_cache}_{max_size}"
@@
-                logger.warning(f"Failed to download headshot for {player_id or url}: {e}")
+                logger.warning(f"Failed to download headshot for {id_for_cache}: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/masters-tournament/logo_loader.py` around lines 167 - 198, The
cache/logging currently uses the raw URL when player_id is absent which can
create high-cardinality keys and leak query params; update the logic around
cache_key creation (used in cache_key, player_id, url) to normalize the URL or
replace it with a stable hashed identifier (e.g., compute an sha256/hex of url)
before building cache_key and before logging in the download error path, and
ensure the disk filename logic (players_dir / f"{player_id}.png") remains
unchanged when player_id exists; apply this normalization/hash in both the cache
lookup and when emitting logger.warning in the download/cached-load exception
handlers so no raw URL/query string is stored or logged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/masters-tournament/masters_helpers.py`:
- Line 458: Replace the non-ASCII multiplication sign in the inline comment
"means N full calendar days, not N×24 hours from the padded end" with an ASCII
character to satisfy Ruff RUF003; update that comment (the one containing "means
N full calendar days, not N×24 hours from the padded end") to use "x" or "times"
(e.g., "not N x 24 hours" or "not N times 24 hours") so only ASCII characters
remain.

---

Nitpick comments:
In `@plugins/masters-tournament/logo_loader.py`:
- Around line 167-198: The cache/logging currently uses the raw URL when
player_id is absent which can create high-cardinality keys and leak query
params; update the logic around cache_key creation (used in cache_key,
player_id, url) to normalize the URL or replace it with a stable hashed
identifier (e.g., compute an sha256/hex of url) before building cache_key and
before logging in the download error path, and ensure the disk filename logic
(players_dir / f"{player_id}.png") remains unchanged when player_id exists;
apply this normalization/hash in both the cache lookup and when emitting
logger.warning in the download/cached-load exception handlers so no raw
URL/query string is stored or logged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0cd5d819-21d9-47b0-86d5-ff3345082196

📥 Commits

Reviewing files that changed from the base of the PR and between 12109c5 and 7cbadbb.

📒 Files selected for processing (5)
  • plugins.json
  • plugins/masters-tournament/logo_loader.py
  • plugins/masters-tournament/manifest.json
  • plugins/masters-tournament/masters_data.py
  • plugins/masters-tournament/masters_helpers.py

Comment thread plugins/masters-tournament/masters_helpers.py Outdated
@sarjent
Copy link
Copy Markdown
Contributor Author

sarjent commented Apr 16, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
plugins/masters-tournament/logo_loader.py (1)

204-205: Narrow exception handling in download path.

At line 204, except Exception is too broad and may mask non-I/O bugs. Catch request/image-related exceptions explicitly:

-            except Exception as e:
+            except (requests.RequestException, OSError) as e:
                 logger.warning(f"Failed to download headshot for {stable_key}: {e}")

This keeps failures scoped to expected exceptions from HTTP requests, image processing, and file I/O operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/masters-tournament/logo_loader.py` around lines 204 - 205, Replace
the broad "except Exception as e" that logs "Failed to download headshot for
{stable_key}" with narrow exception handling for expected I/O/image/HTTP errors:
catch requests.exceptions.RequestException, PIL.UnidentifiedImageError (or
PIL.Image.DecompressionBombError if relevant), and OSError (file I/O) and log
the same message with the caught exception; ensure you add the necessary imports
(requests.exceptions and PIL exception classes) and keep the logger.warning call
and stable_key reference intact so only request/image/file errors are masked,
not unrelated bugs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@plugins/masters-tournament/logo_loader.py`:
- Around line 204-205: Replace the broad "except Exception as e" that logs
"Failed to download headshot for {stable_key}" with narrow exception handling
for expected I/O/image/HTTP errors: catch requests.exceptions.RequestException,
PIL.UnidentifiedImageError (or PIL.Image.DecompressionBombError if relevant),
and OSError (file I/O) and log the same message with the caught exception;
ensure you add the necessary imports (requests.exceptions and PIL exception
classes) and keep the logger.warning call and stable_key reference intact so
only request/image/file errors are masked, not unrelated bugs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 654a506f-2495-4165-91f6-89703f214d15

📥 Commits

Reviewing files that changed from the base of the PR and between 7cbadbb and 678e54b.

📒 Files selected for processing (2)
  • plugins/masters-tournament/logo_loader.py
  • plugins/masters-tournament/masters_helpers.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/masters-tournament/masters_helpers.py

sarjent and others added 2 commits April 16, 2026 10:48
Phase transition:
- _computed_fallback_meta used _masters_thursday() (noon UTC / 08:00 EDT)
  as its start time; adding +3 days +23:59:59 landed on Monday morning
  UTC rather than Sunday 23:59 EDT, making end_e.date() one day too late
  and extending post-tournament by ~8 extra hours on the fallback path.
  Fix: normalise start to midnight EDT (04:00 UTC) before computing end.
- get_detailed_phase post-tournament check used exact timedelta from the
  padded end timestamp rather than calendar days in EDT, making N-day
  semantics unintuitive. Fix: use (date.date() - end_e.date()).days <= N.

Headshots in Vegas scroll:
- get_player_headshot returned None immediately when player_id was empty,
  discarding a valid headshot_url. Post-tournament ESPN sometimes omits
  the athlete id field. Fix: proceed to URL download when url is provided,
  using the url itself as the in-memory cache key; only skip disk caching
  when there is no stable player_id filename.
- Download failures were logged at DEBUG (invisible). Raised to WARNING.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace non-ASCII multiplication sign in masters_helpers.py comment
  to satisfy Ruff RUF003
- Normalize URL-based headshot cache keys using sha256 prefix instead
  of raw URL to avoid high-cardinality keys and query param leakage in
  logs; disk filename logic for player_id unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ChuckBuilds ChuckBuilds force-pushed the feat/masters-post-tournament-improvements branch from 678e54b to a349969 Compare April 16, 2026 14:49
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
plugins/masters-tournament/logo_loader.py (1)

178-180: Persist URL-only headshots to disk using stable_key as filename.

Right now, URL-only calls (empty player_id) skip disk cache entirely. In plugins/masters-tournament/masters_renderer.py (Lines 630-645), this call pattern is expected, so you lose persistent cache benefits and become more network-dependent across restarts.

💾 Suggested adjustment
-        # Check disk cache (only when we have a stable player_id filename)
-        player_path = self.players_dir / f"{player_id}.png" if player_id else None
-        if player_path and player_path.exists():
+        # Check disk cache for both player_id and URL-only paths.
+        # stable_key is player_id when present, otherwise a URL hash.
+        player_path = self.players_dir / f"{stable_key}.png"
+        if player_path.exists():
             try:
                 img = Image.open(player_path).convert("RGBA")
                 img = self._crop_to_fill(img, max_size)
                 self._cache[cache_key] = img
                 return img
             except Exception as e:
                 logger.warning(f"Failed to load cached headshot {player_id}: {e}")
...
-                if player_path:
-                    img.save(player_path, "PNG")
+                img.save(player_path, "PNG")

Also applies to: 198-199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/masters-tournament/logo_loader.py` around lines 178 - 180, The code
skips disk caching when player_id is empty; change the logic that builds
player_path in the logo loader so it uses stable_key as the filename when
player_id is missing (i.e., set player_path = self.players_dir / f"{player_id or
stable_key}.png" when stable_key is available), then continue to check
player_path.exists() and read/write the cache as normal; update any related
branches (the similar block around the other occurrence) to mirror this behavior
and ensure stable_key is validated before using it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@plugins/masters-tournament/logo_loader.py`:
- Around line 178-180: The code skips disk caching when player_id is empty;
change the logic that builds player_path in the logo loader so it uses
stable_key as the filename when player_id is missing (i.e., set player_path =
self.players_dir / f"{player_id or stable_key}.png" when stable_key is
available), then continue to check player_path.exists() and read/write the cache
as normal; update any related branches (the similar block around the other
occurrence) to mirror this behavior and ensure stable_key is validated before
using it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc58f4f5-8089-4adc-9c70-a1bccb73e712

📥 Commits

Reviewing files that changed from the base of the PR and between 678e54b and a349969.

📒 Files selected for processing (4)
  • plugins/masters-tournament/logo_loader.py
  • plugins/masters-tournament/manifest.json
  • plugins/masters-tournament/masters_data.py
  • plugins/masters-tournament/masters_helpers.py
✅ Files skipped from review due to trivial changes (1)
  • plugins/masters-tournament/manifest.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugins/masters-tournament/masters_helpers.py
  • plugins/masters-tournament/masters_data.py

@ChuckBuilds ChuckBuilds merged commit 5988d7e into ChuckBuilds:main Apr 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants