Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 84 additions & 3 deletions plugins/masters-tournament/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
from masters_renderer_enhanced import MastersRendererEnhanced
from logo_loader import MastersLogoLoader
from masters_helpers import (
AUGUSTA_HOLES,
calculate_tournament_countdown,
filter_favorite_players,
get_detailed_phase,
get_score_description,
get_tournament_phase,
sort_leaderboard,
)
Expand Down Expand Up @@ -133,6 +135,15 @@ def __init__(self, plugin_id, config, display_manager, cache_manager, plugin_man
self._last_player_card_advance = 0.0
self._player_card_interval = config.get("player_card_duration", 8)

# Live alert detection — track score changes between updates
self._previous_scores: Dict[str, int] = {} # player_name -> score
self._alert_queue: List[Dict] = [] # pending birdie/eagle alerts
self._alert_index = 0
self._last_alert_advance = 0.0
self._alert_dwell = config.get("display_modes", {}).get(
"live_action", {}
).get("duration", 10)

# Vegas scroll mode: fixed card block width. Cards render at
# (scroll_card_width × display_height) regardless of the panel width
# so long chained displays (e.g. 5×64 = 320 wide) scroll smoothly
Expand Down Expand Up @@ -331,6 +342,9 @@ def _update_leaderboard(self):

sorted_board = sort_leaderboard(raw_leaderboard)

# Detect score changes for live alerts before filtering
self._detect_score_changes(sorted_board)

favorites = self.config.get("favorite_players", [])
top_n = self.config.get("display_modes", {}).get("leaderboard", {}).get("top_n", 10)
always_show = self.config.get("display_modes", {}).get("leaderboard", {}).get(
Expand All @@ -342,6 +356,50 @@ def _update_leaderboard(self):
)
self.logger.debug(f"Updated leaderboard with {len(self._leaderboard_data)} players")

def _detect_score_changes(self, leaderboard: List[Dict]) -> None:
"""Compare current scores against previous update to detect birdies/eagles.

Only generates alerts for score improvements (birdie or better).
Uses the player's current_hole and Augusta's hole pars to determine
whether the score change was a birdie, eagle, or albatross.
"""
new_scores: Dict[str, int] = {}
new_alerts: List[Dict] = []

for player in leaderboard:
name = player.get("player", "")
score = player.get("score", 0)
hole = player.get("current_hole") or 0
new_scores[name] = score

if not self._previous_scores or name not in self._previous_scores:
continue

prev_score = self._previous_scores[name]
change = score - prev_score # negative = improvement
if change >= 0:
continue

# Use hole par to get an accurate description
hole_par = AUGUSTA_HOLES.get(hole, {}).get("par", 4)
desc = get_score_description(change, hole_par)

# Only alert on birdie or better
if desc in ("Birdie", "Eagle", "Albatross"):
new_alerts.append({
"player": name,
"hole": hole,
"score_desc": desc,
})
self.logger.info(f"Live alert: {name} {desc} on hole {hole}")

self._previous_scores = new_scores

if new_alerts:
self._alert_queue = new_alerts
self._alert_index = 0
self._last_alert_advance = time.time()

def _update_schedule(self):
"""Update schedule data from API."""
self._schedule_data = self.data_source.fetch_schedule()
Expand Down Expand Up @@ -483,9 +541,32 @@ def _display_schedule(self, force_clear: bool) -> bool:
)

def _display_live_action(self, force_clear: bool) -> bool:
"""Show live alert if enhanced renderer available, else leaderboard."""
if hasattr(self.renderer, "render_live_alert") and self._leaderboard_data:
# Show the leader's current status as a live alert
"""Show live birdie/eagle alerts, falling back to the leader."""
if not hasattr(self.renderer, "render_live_alert"):
return self._display_leaderboard(force_clear)

# Rotate through queued alerts on a dwell timer
if self._alert_queue:
now = time.time()
if now - self._last_alert_advance >= self._alert_dwell:
self._alert_index += 1
self._last_alert_advance = now
# Expire the queue once we've shown all alerts
if self._alert_index >= len(self._alert_queue):
self._alert_queue = []
self._alert_index = 0
else:
alert = self._alert_queue[self._alert_index]
return self._show_image(
self.renderer.render_live_alert(
alert["player"],
alert["hole"],
alert["score_desc"],
)
)

# No pending alerts — show the leader's current status
if self._leaderboard_data:
leader = self._leaderboard_data[0]
return self._show_image(
self.renderer.render_live_alert(
Expand Down
2 changes: 1 addition & 1 deletion plugins/masters-tournament/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"ledmatrix_min_version": "2.0.0"
}
],
"last_updated": "2026-04-09",
"last_updated": "2026-04-10",
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

Manifest version bump is missing for this code change.

last_updated was synced, but the plugin code changed in plugins/masters-tournament/masters_renderer.py, so the manifest should also bump patch version (e.g., 2.3.02.3.1) and prepend the new entry in versions.

Suggested manifest update
-  "version": "2.3.0",
+  "version": "2.3.1",
   "versions": [
+    {
+      "version": "2.3.1",
+      "released": "2026-04-10",
+      "ledmatrix_min_version": "2.0.0"
+    },
     {
       "version": "2.3.0",
       "released": "2026-04-10",
       "ledmatrix_min_version": "2.0.0"
     },

As per coding guidelines: “Plugin version MUST be bumped in plugins/<plugin-id>/manifest.json whenever any plugin code changes” and “Add the new version FIRST … and keep the version field in sync with the top entry in versions.”

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

In `@plugins/masters-tournament/manifest.json` at line 107, Update the plugin
manifest to reflect the code change in masters_renderer.py by bumping the patch
version (e.g., 2.3.0 → 2.3.1), set the top-level "version" field to that new
version, prepend a new entry for the new version at the start of the "versions"
array (including updated "last_updated" and any relevant notes), and ensure
"last_updated" remains correct; this keeps the manifest (version, versions[],
last_updated) in sync with the code change.

"compatible_versions": [
">=2.0.0"
]
Expand Down
3 changes: 3 additions & 0 deletions plugins/masters-tournament/masters_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,9 @@ def _wrap_text(self, text: str, max_w: int,
for word in words:
# Break oversized words into chunks that fit.
if self._text_width(draw, word, font) > max_w:
if current_line:
lines.append(current_line)
current_line = ""
for ch in word:
test = current_line + ch
if self._text_width(draw, test, font) <= max_w:
Expand Down