Skip to content

fix(masters-tournament): bug fixes, course tour redesign, dynamic dates, fun_facts config (v2.3.0)#98

Closed
sarjent wants to merge 4 commits intoChuckBuilds:mainfrom
sarjent:fix/masters-tournament-bugs
Closed

fix(masters-tournament): bug fixes, course tour redesign, dynamic dates, fun_facts config (v2.3.0)#98
sarjent wants to merge 4 commits intoChuckBuilds:mainfrom
sarjent:fix/masters-tournament-bugs

Conversation

@sarjent
Copy link
Copy Markdown
Contributor

@sarjent sarjent commented Apr 10, 2026

Summary

Bug fixes and enhancements for the masters-tournament plugin.

Bug fixes

  • Fun facts scroll reset: replaced 5-step counter with time-based dwell (fun_fact_duration config key, default 20 s) so the fact stays visible for its full duration
  • Hole-by-hole shared state: _hole_by_hole_index is now independent from _current_hole (used by course tour), preventing the two modes from stomping each other's cursor
  • Live action "Leader" label: now shows the actual score to par (e.g. -12) instead of the static string "Leader"
  • MC/WD/DQ score parsing: players with inactive status values (MC, WD, DQ, CUT, MDF, --) are now correctly identified via _INACTIVE_SCORE_VALUES; their score is set to None instead of silently returning 0
  • get_detailed_phase morning boundary: switched from time-based to calendar-day comparison for the practice/tournament boundary so that early-morning hours on tournament days (e.g. 6 am Thursday) are no longer misclassified as "practice"

Enhancements

Course tour hole card — two-column layout

  • Left column: hole number (larger font on large tier), par, yardage, zone badge
  • Right column: hole map image + hole name centered at bottom
  • Accounts for 64x32 small displays with a single-column fallback for tiny (<=32 px wide) displays

Dynamic Masters Thursday date

  • Added _masters_thursday(year) helper: finds the Thursday between April 6-12 (the correct rule, verified 2022-2028)
  • Replaced the old _second_thursday_of_april (which returned wrong dates for 2022 and 2023) in all callsites: get_detailed_phase, get_tournament_phase, countdown display, fallback meta computation
  • Old function kept as a backwards-compatible alias

Fun facts config filtering

  • _build_enabled_modes now handles bare-boolean mode config values ("fun_facts": false) in addition to the nested dict form ({"enabled": false}), fixing the case where the web UI saves the simpler form and fun facts ignored the setting

Testing

  • Added test_local.py: an offline test script for Windows/Mac/Linux that requires no Pi, no LED matrix, and no network connection
    • Section 1: _masters_thursday() date logic for 2022-2028 vs. known historical data
    • Section 2: get_detailed_phase() boundary cases across 2023/2024/2026 (practice, morning, live, evening, post, pre, off-season)
    • Section 3: _build_enabled_modes() fun_facts config filtering (5 scenarios)
    • Section 4: Visual hole card rendering - saves 4x-scaled PNGs to test_renders/ for 64x32, 128x64, 192x48 at holes 1/12/13/18

Test plan

  • Run python test_local.py from plugins/masters-tournament/ - all 25 checks should pass
  • Open test_renders/*.png to visually inspect two-column hole card layout at each display size
  • Confirm fun_facts obeys "display_modes": {"fun_facts": false} in plugin config
  • Confirm countdown shows correct 2027 Masters date (April 8) when run in post-tournament mode

Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Redesigned hole card with a clearer two-column layout.
  • Bug Fixes
    • More accurate player activity and score handling (missing scores shown as “--” and grayed).
    • Improved tournament phase detection and timezone-aware countdowns.
    • Refined fun-facts timing and independent hole-by-hole progression.
  • Tests
    • Added a local test script to exercise date logic, phases, config parsing, and rendering.
  • Chores
    • Plugin metadata updated to v2.3.1.

sarjent and others added 3 commits April 10, 2026 07:59
- fun_facts: replace 5-step scroll reset with time-based dwell (20s
  default, configurable via fun_fact_duration) so long facts have time
  to scroll fully before advancing to the next one
- hole_by_hole: give masters_hole_by_hole its own hole cursor and timer
  (hole_by_hole_index / last_hole_advance["hole_by_hole"]) independent
  of masters_course_tour, preventing double-advancement when both modes
  are active in the same phase rotation
- countdown: replace hardcoded April 10 fallback date with
  MastersDataSource._second_thursday_of_april() so the countdown is
  correct in future years; also use timezone-aware datetime.now(utc)
- live_action: pass leader's actual score-to-par string instead of the
  literal "Leader" label to render_live_alert, so viewers see the score
- score parsing: detect MC/WD/DQ/CUT/MDF ESPN values via
  _INACTIVE_SCORE_VALUES; add is_active field to player dicts so
  renderers can distinguish "even par" from "not competing"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ts config (2.2.9)

course tour (render_hole_card):
- Replaced header+image+footer layout with a true two-column design
  across all display tiers (64x32 included):
  · Left column: hole number in font_header (larger than detail text),
    par and yardage in font_detail, zone badge at bottom when space allows
  · Right column: hole map image centred vertically with hole name
    centred below it
- Tiny (≤32 wide) falls back to a compact single-column layout
- Updated both MastersRenderer (base) and MastersRendererEnhanced
  (_render_hole_card_with_image) to use the new layout

countdown / phase dates:
- Added _masters_thursday(year) to masters_helpers.py implementing the
  correct "Thursday between April 6-12" rule, verified against 2022-2026
  historical data (fixes _second_thursday_of_april which returned wrong
  dates, e.g. April 13 for 2023 when Masters started April 6)
- get_tournament_phase and get_detailed_phase fallback paths now compute
  practice/tournament windows dynamically from _masters_thursday instead
  of relying on hardcoded April 10-13 day numbers
- masters_data._computed_fallback_meta now delegates to _masters_thursday
- manager._display_countdown hard-fallback uses _masters_thursday directly

fun_facts config:
- _build_enabled_modes now handles bare-boolean mode_config values
  ("fun_facts": false) in addition to nested {"enabled": false} dicts,
  matching what the LEDMatrix web UI may serialise

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- get_detailed_phase: switch from time-based to calendar-day comparisons
  for the practice/tournament boundary so that early-morning hours on a
  tournament day (e.g. 6 am Thursday) are not misclassified as "practice"
- Fix _masters_thursday docstring: 2028 -> April 6 (not April 12)
- Add test_local.py: offline test suite covering Thursday-date logic,
  get_detailed_phase phase boundaries for 2022-2028, fun_facts config
  filtering, and visual hole-card rendering at 64x32 / 128x64 / 192x48

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 91f6e2a5-fa9b-4e59-8e65-4185fb46aa2d

📥 Commits

Reviewing files that changed from the base of the PR and between 88cffeb and 08132a4.

📒 Files selected for processing (8)
  • plugins.json
  • plugins/masters-tournament/manager.py
  • plugins/masters-tournament/manifest.json
  • plugins/masters-tournament/masters_data.py
  • plugins/masters-tournament/masters_helpers.py
  • plugins/masters-tournament/masters_renderer.py
  • plugins/masters-tournament/masters_renderer_enhanced.py
  • plugins/masters-tournament/test_local.py

📝 Walkthrough

Walkthrough

Bumps the Masters Tournament plugin to v2.3.1 and plugins.json last_updated; replaces hardcoded April rules with a computed Masters Thursday window (timezone-aware), refactors mode/config parsing and rotation/timer logic, improves score/is_active handling, redesigns hole-card rendering, and adds a local test script.

Changes

Cohort / File(s) Summary
Metadata & Versioning
plugins.json, plugins/masters-tournament/manifest.json
Updated last_updated in plugins.json and plugin manifest (2026-04-092026-04-10) and bumped plugin version from 2.2.72.3.1; added new versions entries (2.3.1, 2.3.0, 2.2.9, 2.2.8).
Helpers & Tournament Window
plugins/masters-tournament/masters_helpers.py
Added _masters_thursday(year) (timezone-aware UTC at noon) and changed format_score_to_par to accept Optional[int]. Replaced hardcoded April-day logic with computed Masters window for get_tournament_phase and get_detailed_phase.
Data & Score Handling
plugins/masters-tournament/masters_data.py
Use _masters_thursday for off-season fallback; add _INACTIVE_SCORE_VALUES and _is_active_competitor; leaderboard player objects gain is_active; _calculate_score_to_par() now returns Optional[int] (None for unknown/inactive).
Display Manager & Timers
plugins/masters-tournament/manager.py
Use timezone-aware now (datetime.now(timezone.utc)); extended _build_enabled_modes() to accept missing, boolean, or dict configs and skip unknown keys; introduced independent hole-by-hole cursor/timer state; fun-facts advancement uses _fact_dwell and _last_fact_change timing.
Rendering (Hole Cards & Players)
plugins/masters-tournament/masters_renderer.py, plugins/masters-tournament/masters_renderer_enhanced.py
Treat missing scores as None and map to light-gray color; redesigned hole-card to a two-column layout (left: number/par/yard + optional chip; right: centered hole image with single-line, width-clamped name); added tiny single-column fallback; adjusted sizing, truncation, and palette.
Testing
plugins/masters-tournament/test_local.py
New standalone local test script validating _masters_thursday() dates, get_detailed_phase() classifications, _build_enabled_modes() behavior for fun_facts, and optional Pillow-based hole-card render outputs saved to test_renders/.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main changes: bug fixes, course tour redesign, dynamic dates, config improvements, and version bump to v2.3.0, all clearly reflected in the changeset.

✏️ 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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/masters-tournament/masters_data.py (1)

627-644: ⚠️ Potential issue | 🟠 Major

Inactive players should not be normalized to E.

Returning 0 for MC/WD/DQ/CUT/MDF/-- makes downstream renderers show E and count those players in even-par totals, so the new is_active flag never actually fixes the user-visible bug. This needs a non-score sentinel such as None, with the sort/render paths handling it explicitly.

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

In `@plugins/masters-tournament/masters_data.py` around lines 627 - 644, The
_calculate_score_to_par function currently maps inactive score display values
(MC/WD/DQ/CUT/MDF/-- and "E" sentinel cases) to 0 which causes downstream
renderers to treat them as even par; change the logic in _calculate_score_to_par
to return None for any inactive or non-numeric displayValue (use the existing
self._INACTIVE_SCORE_VALUES and the checks for empty/"-"/"E" to detect these
cases) and only return int for valid numeric scores (handle "+" and "-" prefixes
as before); update any callers (sort/render code) to explicitly handle None as
“not competing” instead of treating it like 0.
🤖 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/manifest.json`:
- Around line 46-59: Update the new release entries in manifest.json to use the
schema key ledmatrix_min instead of ledmatrix_min_version: in the versions array
(newest entries at the top) replace each "ledmatrix_min_version" property with
"ledmatrix_min" and keep the same values for the existing version, released and
version fields so the manifest conforms to the repo schema.

In `@plugins/masters-tournament/masters_helpers.py`:
- Around line 492-493: The fallback comparison uses timedelta(0) < thu_e - date
which can raise TypeError when callers pass naive datetimes because _to_eastern
leaves them untouched; change the check to compare date objects instead (e.g.,
ensure you compute date_date from the incoming date and use thu_e.date() or
otherwise cast thu_e to a date) so both operands are date objects; update the
logic around thu_e, date and date_date in the relevant function(s) (referencing
_to_eastern, thu_e and date_date) to perform date-to-date subtraction rather
than mixing datetime and date types.

In `@plugins/masters-tournament/test_local.py`:
- Around line 203-206: The test SIZES list is using MastersRenderer for the
"64x32_small" case but manager.py selects MastersRendererEnhanced for width >=
64, so update the SIZES entry for "64x32_small" to use MastersRendererEnhanced
(replace MastersRenderer with MastersRendererEnhanced in that tuple) so the test
exercises the production renderer choice; ensure the tuple remains
("64x32_small", 64, 32, MastersRendererEnhanced).
- Around line 132-134: The current loop registers top-level module stubs
(sys.modules.setdefault(mod_name, MagicMock())) which persist and cause later
imports to return MagicMocks; instead, scope those stubs to the manager import
namespace by first importing the manager module (or using its __name__) and then
registering stubs under f"{manager.__name__}.{mod_name}" (use
sys.modules.setdefault(f"{manager.__name__}.{mod_name}", MagicMock())). Update
the code that currently references sys.modules.setdefault, mod_name and
MagicMock to use the manager-prefixed module names so only imports performed by
the manager get mocked.

---

Outside diff comments:
In `@plugins/masters-tournament/masters_data.py`:
- Around line 627-644: The _calculate_score_to_par function currently maps
inactive score display values (MC/WD/DQ/CUT/MDF/-- and "E" sentinel cases) to 0
which causes downstream renderers to treat them as even par; change the logic in
_calculate_score_to_par to return None for any inactive or non-numeric
displayValue (use the existing self._INACTIVE_SCORE_VALUES and the checks for
empty/"-"/"E" to detect these cases) and only return int for valid numeric
scores (handle "+" and "-" prefixes as before); update any callers (sort/render
code) to explicitly handle None as “not competing” instead of treating it like
0.
🪄 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: dbe9c27f-b399-482b-ac8f-a57bed401ffd

📥 Commits

Reviewing files that changed from the base of the PR and between eda1201 and 88cffeb.

📒 Files selected for processing (8)
  • plugins.json
  • plugins/masters-tournament/manager.py
  • plugins/masters-tournament/manifest.json
  • plugins/masters-tournament/masters_data.py
  • plugins/masters-tournament/masters_helpers.py
  • plugins/masters-tournament/masters_renderer.py
  • plugins/masters-tournament/masters_renderer_enhanced.py
  • plugins/masters-tournament/test_local.py

Comment on lines +46 to +59
{
"version": "2.3.0",
"released": "2026-04-10",
"ledmatrix_min_version": "2.0.0"
},
{
"version": "2.2.9",
"released": "2026-04-10",
"ledmatrix_min_version": "2.0.0"
},
{
"version": "2.2.8",
"released": "2026-04-10",
"ledmatrix_min_version": "2.0.0"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use ledmatrix_min in the new release entries.

The newly added versions still use ledmatrix_min_version, but the repo schema for manifest.json version entries is ledmatrix_min. Keeping the old key here can leave compatibility/store tooling reading incomplete metadata for the newest releases.

As per coding guidelines, "Add the new version FIRST (most recent at top) to the versions array in manifest.json, with fields: released (date), version (semver), and ledmatrix_min (minimum LEDMatrix version)".

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

In `@plugins/masters-tournament/manifest.json` around lines 46 - 59, Update the
new release entries in manifest.json to use the schema key ledmatrix_min instead
of ledmatrix_min_version: in the versions array (newest entries at the top)
replace each "ledmatrix_min_version" property with "ledmatrix_min" and keep the
same values for the existing version, released and version fields so the
manifest conforms to the repo schema.

- get_detailed_phase: replace `thu_e - date` with `thu_date - date_date`
  (date-to-date subtraction) to avoid TypeError when caller passes a naive
  datetime that _to_eastern leaves untouched

- _calculate_score_to_par: return Optional[int]; None for inactive/unknown
  (MC/WD/DQ/CUT/MDF/--/empty) instead of 0, so callers can distinguish
  "not competing" from "even par"

- format_score_to_par: accept Optional[int]; return "--" for None

- _score_color: return light_gray for None score instead of raising TypeError

- sort_leaderboard: sort None-score players to the end (treat as 999)

- renderer score reads: drop default=0 so None propagates correctly to
  format_score_to_par/_score_color; guard field-overview sums with `or 0`

- test_local.py SIZES: use MastersRendererEnhanced for 64x32 (matches
  manager.py which selects Enhanced for width >= 64)

- test_local.py stubs: pop masters_renderer/masters_renderer_enhanced from
  sys.modules before section 4 so real renderer classes are imported

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sarjent
Copy link
Copy Markdown
Contributor Author

sarjent commented Apr 10, 2026

Closing — will maintain this as a standalone plugin repo instead.

@sarjent sarjent closed this Apr 10, 2026
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.

1 participant