feat(scoreboards): color a recent game's score by how the favorite team did - #259
Conversation
…am did In scroll and Vegas modes a series against the same opponent scrolls past as several near-identical cards -- same two logos, only the digits change -- and working out which of them your team won means reading the score and remembering which side you are. That is the whole problem this fixes. Adds customization.favorite_result_colors to the eight team scoreboards that show a numeric final score (AFL, baseball, basketball, football, hockey, lacrosse, NRL, soccer): with it enabled, a finished game's score is drawn green when the favorite team won and red when it lost, with all three colors configurable. Off by default, so nothing changes until a user opts in. The colour is resolved in both render paths, which are independent copies: SportsCore for the one-game-at-a-time switch view and GameRenderer for the scroll/Vegas cards (which is also what get_vegas_content renders through). Only finished games are tinted; live and upcoming cards are untouched. A game needs exactly one favorite team -- neither side, or both, keeps the normal color, since a favorite-vs-favorite game has no losing side worth flagging in red. Games now carry their league manager's resolved favorites so the scroll renderer, which otherwise only sees the game dict and the raw config, can match dynamic groups such as AP_TOP_25; the raw config is read as well so an edit applies before the next data refresh. Matching accepts the ESPN id as well as the abbreviation, which is how NRL keys its favorites. NRL and Soccer declared their customization block at the schema root rather than inside properties, so the web UI never rendered it and setting it tripped the root's additionalProperties. Moved it where it belongs, which is also what makes the new setting reachable there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RRLLpvsvXgy1pskvaNVJWx
|
Warning Review limit reached
Next review available in: 46 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (17)
📝 WalkthroughWalkthroughEight scoreboard plugins add optional favorite-team result colors for finished games. The change includes validated configuration, result classification, renderer integration, tests, documentation, release notes, and catalog version updates. ChangesFavorite result colors
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant SportsCore
participant GameDetails
participant GameRenderer
SportsCore->>GameDetails: attach resolved favorite teams
GameDetails->>GameRenderer: provide teams, scores, and game state
GameRenderer->>GameRenderer: classify result and resolve RGB color
GameRenderer->>GameRenderer: render recent score
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 460 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
The scroll/Vegas recent card was the only place in any scoreboard that drew a final score gold. This plugin's own switch-mode scorebug draws it white, and so does every other sport, so the same game changed colour depending on which display mode you were in. Folded into the 1.23.0 entry rather than a separate bump: that version is not released yet, and this ships with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RRLLpvsvXgy1pskvaNVJWx
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
plugins/afl-scoreboard/sports.py (1)
490-494: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAnnotate
FAVORITE_RESULT_COLOR_DEFAULTSwithClassVarin all four files. Each file declares the same mutable dict as a class attribute without aClassVarannotation, and Ruff reports RUF012 at every site. The repository already follows this pattern withTOURNAMENT_ROUND_ORDER: ClassVar[Dict[str, int]]inplugins/basketball-scoreboard/sports.py.
plugins/afl-scoreboard/sports.py#L490-L494: add: ClassVar[Dict[str, tuple]]and importClassVarfromtyping.plugins/basketball-scoreboard/sports.py#L327-L331: add: ClassVar[Dict[str, tuple]];ClassVaris already imported.plugins/basketball-scoreboard/game_renderer.py#L294-L298: add: ClassVar[Dict[str, tuple]]and importClassVarfromtyping.plugins/soccer-scoreboard/game_renderer.py#L291-L295: add: ClassVar[Dict[str, tuple]]and importClassVarfromtyping.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/afl-scoreboard/sports.py` around lines 490 - 494, Annotate FAVORITE_RESULT_COLOR_DEFAULTS as ClassVar[Dict[str, tuple]] in all four sites: plugins/afl-scoreboard/sports.py:490-494, plugins/basketball-scoreboard/sports.py:327-331, plugins/basketball-scoreboard/game_renderer.py:294-298, and plugins/soccer-scoreboard/game_renderer.py:291-295. Add the ClassVar import from typing in the AFL sports, basketball game_renderer, and soccer game_renderer files; basketball sports already imports it.Source: Linters/SAST tools
plugins/baseball-scoreboard/test_favorite_result_colors.py (1)
158-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd scroll/Vegas tie-color coverage.
GameRenderer._favorite_result()has a separate tie branch, but this path only tests wins and losses. Add assertions for the default tie color and a configuredtie_color.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/baseball-scoreboard/test_favorite_result_colors.py` around lines 158 - 183, Extend test_scroll_card_colors around GameRenderer._recent_score_color with finished-game tie assertions, covering the default tie color and a separately configured tie_color. Use a tied game and verify the configured renderer returns the expected custom color, while the default renderer preserves the established default tie color.plugins/football-scoreboard/sports.py (2)
446-450: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueScore parsing is stricter here than in the renderer.
SportsCore._favorite_resultusesint(str(...).strip()). The matchingGameRenderer._side_scoreusesint(float(...)). A score value such as"5.0"resolves in scroll mode but falls back to the default color in switch mode. ESPN returns integer strings today, so this is a consistency concern rather than a current defect. Align both paths on one parse rule.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/football-scoreboard/sports.py` around lines 446 - 450, Align score parsing in SportsCore._favorite_result with GameRenderer._side_score by applying the same int(float(...)) conversion to both home_score and away_score, while preserving the existing invalid-value fallback behavior.
400-404: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMutable class attribute
FAVORITE_RESULT_COLOR_DEFAULTSrepeated across six modules. Ruff reports RUF012 at each site because the same dict literal is copied into every plugin as a class attribute. Annotate it asClassVar(or wrap it intypes.MappingProxyType) so the value cannot be mutated per instance. Apply the same pattern at every site.
plugins/football-scoreboard/sports.py#L400-L404: annotate the dict asClassVar[Dict[str, Tuple[int, int, int]]]and addClassVar/Tupleto thetypingimport.plugins/hockey-scoreboard/sports.py#L351-L355: apply the same annotation and import update.plugins/football-scoreboard/game_renderer.py#L385-L389: apply the same annotation and import update.plugins/hockey-scoreboard/game_renderer.py#L329-L333: apply the same annotation and import update.plugins/nrl-scoreboard/game_renderer.py#L291-L295: apply the same annotation and import update.plugins/lacrosse-scoreboard/game_renderer.py#L338-L342: apply the same annotation and import update.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/football-scoreboard/sports.py` around lines 400 - 404, Make FAVORITE_RESULT_COLOR_DEFAULTS a ClassVar[Dict[str, Tuple[int, int, int]]] and add ClassVar and Tuple to the typing imports in plugins/football-scoreboard/sports.py#L400-L404, plugins/hockey-scoreboard/sports.py#L351-L355, plugins/football-scoreboard/game_renderer.py#L385-L389, plugins/hockey-scoreboard/game_renderer.py#L329-L333, plugins/nrl-scoreboard/game_renderer.py#L291-L295, and plugins/lacrosse-scoreboard/game_renderer.py#L338-L342.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/lacrosse-scoreboard/sports.py`:
- Around line 359-365: Update _coerce_rgb in
plugins/lacrosse-scoreboard/sports.py (lines 359-365) and
plugins/nrl-scoreboard/sports.py (lines 516-522) to first require value to be a
list or tuple containing exactly three items; otherwise return fallback.
Preserve the existing per-channel integer conversion and clamping for valid RGB
sequences.
---
Nitpick comments:
In `@plugins/afl-scoreboard/sports.py`:
- Around line 490-494: Annotate FAVORITE_RESULT_COLOR_DEFAULTS as
ClassVar[Dict[str, tuple]] in all four sites:
plugins/afl-scoreboard/sports.py:490-494,
plugins/basketball-scoreboard/sports.py:327-331,
plugins/basketball-scoreboard/game_renderer.py:294-298, and
plugins/soccer-scoreboard/game_renderer.py:291-295. Add the ClassVar import from
typing in the AFL sports, basketball game_renderer, and soccer game_renderer
files; basketball sports already imports it.
In `@plugins/baseball-scoreboard/test_favorite_result_colors.py`:
- Around line 158-183: Extend test_scroll_card_colors around
GameRenderer._recent_score_color with finished-game tie assertions, covering the
default tie color and a separately configured tie_color. Use a tied game and
verify the configured renderer returns the expected custom color, while the
default renderer preserves the established default tie color.
In `@plugins/football-scoreboard/sports.py`:
- Around line 446-450: Align score parsing in SportsCore._favorite_result with
GameRenderer._side_score by applying the same int(float(...)) conversion to both
home_score and away_score, while preserving the existing invalid-value fallback
behavior.
- Around line 400-404: Make FAVORITE_RESULT_COLOR_DEFAULTS a ClassVar[Dict[str,
Tuple[int, int, int]]] and add ClassVar and Tuple to the typing imports in
plugins/football-scoreboard/sports.py#L400-L404,
plugins/hockey-scoreboard/sports.py#L351-L355,
plugins/football-scoreboard/game_renderer.py#L385-L389,
plugins/hockey-scoreboard/game_renderer.py#L329-L333,
plugins/nrl-scoreboard/game_renderer.py#L291-L295, and
plugins/lacrosse-scoreboard/game_renderer.py#L338-L342.
🪄 Autofix
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 Plus
Run ID: 11f02605-ee6d-493f-8d86-ddbf0b6c9a5e
📒 Files selected for processing (43)
plugins.jsonplugins/afl-scoreboard/README.mdplugins/afl-scoreboard/config_schema.jsonplugins/afl-scoreboard/game_renderer.pyplugins/afl-scoreboard/manifest.jsonplugins/afl-scoreboard/sports.pyplugins/baseball-scoreboard/README.mdplugins/baseball-scoreboard/config_schema.jsonplugins/baseball-scoreboard/game_renderer.pyplugins/baseball-scoreboard/manifest.jsonplugins/baseball-scoreboard/sports.pyplugins/baseball-scoreboard/test_favorite_result_colors.pyplugins/basketball-scoreboard/README.mdplugins/basketball-scoreboard/config_schema.jsonplugins/basketball-scoreboard/game_renderer.pyplugins/basketball-scoreboard/manifest.jsonplugins/basketball-scoreboard/sports.pyplugins/football-scoreboard/README.mdplugins/football-scoreboard/config_schema.jsonplugins/football-scoreboard/game_renderer.pyplugins/football-scoreboard/manifest.jsonplugins/football-scoreboard/sports.pyplugins/hockey-scoreboard/README.mdplugins/hockey-scoreboard/config_schema.jsonplugins/hockey-scoreboard/game_renderer.pyplugins/hockey-scoreboard/manifest.jsonplugins/hockey-scoreboard/sports.pyplugins/lacrosse-scoreboard/README.mdplugins/lacrosse-scoreboard/config_schema.jsonplugins/lacrosse-scoreboard/game_renderer.pyplugins/lacrosse-scoreboard/manifest.jsonplugins/lacrosse-scoreboard/sports.pyplugins/nrl-scoreboard/README.mdplugins/nrl-scoreboard/config_schema.jsonplugins/nrl-scoreboard/game_renderer.pyplugins/nrl-scoreboard/manifest.jsonplugins/nrl-scoreboard/sports.pyplugins/nrl-scoreboard/test_nrl_plugin.pyplugins/soccer-scoreboard/README.mdplugins/soccer-scoreboard/config_schema.jsonplugins/soccer-scoreboard/game_renderer.pyplugins/soccer-scoreboard/manifest.jsonplugins/soccer-scoreboard/sports.py
…th paths Addresses the CodeRabbit review on #259. Each helper is duplicated per plugin, so every one of the 16 copies is fixed, not just the sites the review named. _coerce_rgb accepted any 3-element iterable, so a configured colour of "123" unpacked into (1, 2, 3) -- a near-black -- instead of falling back. Only a 3-character string hit this (a short list or a longer one already failed the unpack), but it silently produced a colour where the intent was a fallback. Now requires a list or tuple of exactly three items. SportsCore._favorite_result parsed scores with int(str(...)) while GameRenderer._side_score used int(float(...)), so "5.0" coloured in scroll mode but not in switch mode. ESPN returns integer strings today so nothing was visibly broken, but the two paths must agree on what a usable score is; both now use int(float(...)). A new test asserts they agree rather than checking each in isolation. FAVORITE_RESULT_COLOR_DEFAULTS is annotated ClassVar (RUF012), matching the existing TOURNAMENT_ROUND_ORDER annotation in basketball's sports.py. Tests: the malformed-colour case is now a table including "123", and the scroll/Vegas path covers its own tie branch, default and configured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RRLLpvsvXgy1pskvaNVJWx
Pull Request
Summary
Adds an optional
customization.favorite_result_colorssetting to the eight team scoreboards: with it on, a finished game's score is drawn green when your favorite team won and red when it lost. The motivating case is scroll/Vegas mode, where a series against the same opponent goes past as several near-identical cards — same two logos, only the digits change — and there is no quick way to tell a win from a loss. Off by default, so nothing changes until a user opts in.Type of change
Also carries one incidental schema fix — see Notes for reviewer.
Plugin(s) affected
afl-scoreboard,baseball-scoreboard,basketball-scoreboard,football-scoreboard,hockey-scoreboard,lacrosse-scoreboard,nrl-scoreboard,soccer-scoreboardNot touched:
cricket-scoreboard(already greens the winner and uses a different renderer),ufc-scoreboard(fights have no numeric score),f1-scoreboard(no favorite-team win/loss).Related issues
N/A
Test plan
EMULATOR=true python3 run.py)scripts/dev_server.py)What was run in this environment:
plugins/baseball-scoreboard/test_favorite_result_colors.py(8 groups, all passing) covers win/loss/tie/no-favorite/both-favorites, the feature being off by default, unusable scores, custom + clamped + malformed colors, all three sources of favorites, and the nested hockey/lacrosse payload shape.GameRenderer.render_game_card(game, "recent")and confirmed the score pixels come out green on a win and red on a loss, and that the card is pixel-identical to before with the feature off.srcon the path, nopytest, interactive prompts, missing fonts).nrl-scoreboard/test_nrl_plugin.pyneeded a one-line update because it asserted the old (broken) location of the customization block; it passes.python scripts/check_module_collisions.py— OK, no new modules were added.The core safety harness could not be run here (it lives in the LEDMatrix core repo). Worth an eyeball on hardware, though the golden fixtures all use
favorite_teams: [], so the feature is inert for them.Required for plugin changes
versioninplugins/<id>/manifest.json— AFL 1.4.0, baseball 1.23.0, basketball 1.11.0, football 2.12.0, hockey 1.8.0, lacrosse 1.8.0, NRL 1.4.0, soccer 2.7.0, each with aversionschangelog entryclass_nameinmanifest.jsonmatches the actual class inmanager.pyexactly — unchangedentry_pointmatches the real file — unchangedREADME.mdif config keys changed — new Favorite Team Result Colors section plus a feature bullet in all 8config_schema.jsonis the source of truth for the web UI form —enabled(default: false) pluswin_color/loss_color/tie_colorcolour-picker arrays markedx-advanced, all with defaults, descriptions and constraintsplugins.json)Checklist
CONTRIBUTING.mdCONTRIBUTING.mdandCODE_OF_CONDUCT.mdNotes for reviewer
Two render paths, deliberately. The switch view (
sports.py) and the scroll/Vegas cards (game_renderer.py) are independent copies, so the colour is resolved in both.get_vegas_content()renders throughgame_renderer, so the ticker case is covered by that half. Only finished games are tinted —_score_color_for(game, game_type)returns the normal colour for live and upcoming cards.Why "exactly one favorite team". If neither side is a favorite the score is left alone; if both are, it is also left alone, since a favorite-vs-favorite game has no losing side worth flagging in red. Non-numeric or missing scores (postponements) fall back to the normal colour too.
Where favorites come from in the scroll renderer.
GameRendereronly ever sees the game dict and the raw config, so games now carry their league manager's resolved favorites ("favorite_teams"in_extract_game_details_common) — that is the only place dynamic groups such asAP_TOP_25appear expanded. The raw config is read as well and the two are unioned, so a config edit applies before the next data refresh and hand-built game dicts still work. Matching accepts the ESPN id as well as the abbreviation, which is how NRL keys its favorites.Incidental fix worth a look: NRL and Soccer declared their
customizationblock at the schema root, a sibling ofpropertiesrather than one of them. The web UI rendersproperties, and the root setsadditionalProperties: false, so that block was inert and a config settingcustomizationwould not validate. This PR moves it intoproperties(and adds it to the rootx-propertyOrder), which is what makes the new setting reachable there — but it also means the existing font/layout customization UI appears for those two plugins for the first time. That is the desired behaviour as far as I can tell, since theirsports.pyalready reads those keys, but it is the one change here that is not purely additive. It accounts for most of the line count in those two schema diffs (the block is re-indented one level).test_nrl_plugin.pyasserted the old location and is updated accordingly.🤖 Generated with Claude Code
https://claude.ai/code/session_01RRLLpvsvXgy1pskvaNVJWx
Generated by Claude Code
Summary by CodeRabbit