Sport scoreboards: favorite live boost + team exclusion - #170
Conversation
…l sport plugins Adds two new per-league config levers to baseball, basketball, football, hockey, soccer, and lacrosse scoreboards, addressing user feedback that disabling both "favorites only" and "show all live" gave live games no favorite-aware treatment at all: - favorite_live_boost (1-5, default 2): favorite's live game is always queued first when the live rotation refreshes and gets N turns for every 1 turn other live games get, via a Smooth Weighted Round-Robin schedule. Boost=1 reproduces today's exact behavior (no regression for users who don't touch it). - exclude_teams: hide specific teams from both live rotation and recent/final scores (spoiler protection for delayed viewing), taking precedence over every other filter. Each plugin bumped to its next minor version; plugins.json synced. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughThis PR adds ChangesExclude Teams and Favorite Live Boost Feature
Estimated code review effort: 4 (Complex) | ~75 minutes 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 | 214 |
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.
|
@coderabbitai review |
✅ Action performedReview finished.
|
Pre-existing manifest.json had "dependencies": {} but the plugin store's
manifest schema requires an array of strings. Unrelated to this PR's
feature work, but surfaced by CI's manifest-schema validation once this
plugin's manifest changed for the version bump.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
Flagged by Codacy static analysis (F401) on PR #170. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/football-scoreboard/sports.py (1)
1525-1549: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
exclude_teamsspoiler-protection is bypassed in the default (no-favorites) recent-games path.The new exclude filter is only applied inside
_select_recent_games_for_display(lines 1542-1549), but that method is only called whenself.show_favorite_teams_only and self.favorite_teams(line 1715). When that condition is false — the schema default, and the most common configuration —team_gamesis built directly fromprocessed_games(lines 1725-1734) with no exclude check anywhere inupdate(). Excluded teams will still appear in recent/final scores in this (default) case, contradicting the documented behavior ("hides selected teams from both live rotation and recent/final scores... takes precedence over all other filters").Compare with
plugins/lacrosse-scoreboard/sports.pylines 1611-1616, which applies the exclude check unconditionally while buildingprocessed_games, before any favorites branching — that is the correct pattern.🐛 Suggested fix: filter unconditionally when building processed_games
if is_eligible: game_time = game.get("start_time_utc") if ( game_time and game_time >= recent_cutoff and game.get("home_abbr") not in self.exclude_teams and game.get("away_abbr") not in self.exclude_teams ): processed_games.append(game)(applied around the existing eligibility check, mirroring the lacrosse plugin's implementation)
Also applies to: 1715-1734
🤖 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 1525 - 1549, The exclude_teams spoiler filter is only applied inside _select_recent_games_for_display, but update() can build team_games directly from processed_games when no favorites are enabled, so excluded teams still leak into recent/final scores. Move the exclusion check into the main processed_games construction path in update() (the same eligibility block that appends games), and keep _select_recent_games_for_display consistent with that behavior so exclude_teams takes precedence in all paths, similar to the lacrosse plugin’s unconditional filtering.
🧹 Nitpick comments (4)
plugins/lacrosse-scoreboard/manager.py (1)
719-724: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider clamping
favorite_live_boostto its documented 1–5 range.
resolve_valuereturns whatever raw value is in config (including flat/legacy keys) with no bounds check. A misconfigured value (0, negative, or a string) flows straight intomanager_config["favorite_live_boost"]and on into the SWRR scheduling logic, which likely assumes a positive integer weight.♻️ Proposed defensive clamp
exclude_teams = resolve_value(["teams", "exclude_teams"], ["exclude_teams"], []) - favorite_live_boost = resolve_value(["teams", "favorite_live_boost"], ["favorite_live_boost"], 2) + favorite_live_boost = resolve_value(["teams", "favorite_live_boost"], ["favorite_live_boost"], 2) + try: + favorite_live_boost = max(1, min(5, int(favorite_live_boost))) + except (TypeError, ValueError): + favorite_live_boost = 2🤖 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/lacrosse-scoreboard/manager.py` around lines 719 - 724, The `favorite_live_boost` value resolved in `manager.py` is passed through without validation, so clamp it to the documented 1–5 range before storing it in `manager_config["favorite_live_boost"]`. Update the config handling near `resolve_value` usage in the teams settings block to coerce invalid or non-integer inputs to a safe default and ensure the downstream SWRR weighting logic only receives a positive bounded integer.plugins/soccer-scoreboard/test_live_mode_targeting.py (1)
53-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSolid coverage of
_is_favorite_gameand_swrr_advance, with an acknowledged gap.Assertions correctly exercise the real
SportsLivemethods (not reimplementations) via a bare namespace object, and the boost=1/boost>1/single-game/empty-list cases all match hand-traced expected output. The in-file comment (lines 154-158) already flags that theexclude_teamsbranch insideSportsLive.update()itself isn't covered here since it needs the full ESPN fetch pipeline mocked.Want me to draft a test that mocks
_fetch_data/_extract_game_details(similar tolacrosse-scoreboard/test_favorite_live_boost.py's_make_live_managerpattern) to close that gap?Also applies to: 148-240
🤖 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/soccer-scoreboard/test_live_mode_targeting.py` at line 53, The test coverage is missing the exclude_teams branch inside SportsLive.update, so add a focused test that drives update() through the real method while mocking the ESPN pipeline. Use SportsLive as the target, and stub _fetch_data and _extract_game_details (similar to the _make_live_manager pattern in the lacrosse tests) so you can return controlled game data and verify the exclusion behavior without hitting live fetches. Ensure the new test exercises update()’s exclude_teams handling directly and asserts the filtered result.plugins/basketball-scoreboard/manifest.json (1)
21-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winChangelog field name inconsistent with football plugin's convention.
This entry uses
notesfor the changelog description, but football-scoreboard's manifest.json (same PR, same feature) useschangesfor its equivalent entry — andchangeswas already football's established convention prior to this PR. If any shared tooling renders per-plugin release notes from this array, inconsistent field names across plugins will silently drop this entry's description for basketball.♻️ Suggested fix for naming consistency
{ "released": "2026-07-02", "version": "1.6.0", - "notes": "Add exclude_teams (hide specific teams from live rotation and recent/final scores — spoiler protection) and filtering.favorite_live_boost (tune how much more often your favorite's live game appears in rotation vs other live games) per league.", + "changes": "Add exclude_teams (hide specific teams from live rotation and recent/final scores — spoiler protection) and filtering.favorite_live_boost (tune how much more often your favorite's live game appears in rotation vs other live games) per league.", "ledmatrix_min": "2.0.0" },🤖 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/basketball-scoreboard/manifest.json` around lines 21 - 26, The basketball-scoreboard manifest release entry uses an inconsistent changelog field name, so align it with the established convention used by football-scoreboard. Update the release object in manifest.json to use the same changelog property name as the other plugin’s manifest (the one consumed by shared release-note tooling), keeping the description text unchanged. This should be fixed in the manifest entry containing released, version, and ledmatrix_min so per-plugin release notes continue to render consistently.plugins/basketball-scoreboard/test_favorite_live_boost.py (1)
72-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winexclude_teams tests exercise a reimplementation, not the real filtering code path.
filter_decision()duplicates the inclusion logic fromSportsLive.update()rather than calling it. Cases 1-4 only validate this mirrored copy, so a future change to the real branch order/precedence inupdate()(lines 2621-2650 in sports.py) wouldn't be caught here. Baseball's and lacrosse's equivalent test files instead build aSportsLiveinstance, stub_fetch_data, and calllive.update()directly — giving true regression coverage of the production code path with comparable setup effort.🤖 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/basketball-scoreboard/test_favorite_live_boost.py` around lines 72 - 116, The exclude_teams tests are validating a copied filter_decision helper instead of the real SportsLive.update() logic, so they won’t catch regressions in the production branch order. Update this test file to instantiate a SportsLive via make_live_manager, stub _fetch_data as needed, and exercise live.update() directly like the baseball and lacrosse tests, then assert against the resulting live state for the same exclude_teams scenarios.
🤖 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/baseball-scoreboard/config_schema.json`:
- Around line 628-641: The `show_all_live` schema block in `config_schema.json`
is missing its closing brace, causing `favorite_live_boost` to be nested under a
boolean property instead of being a sibling under `filtering`. Fix the `milb`
and `ncaa_baseball` schema entries by closing `show_all_live` before defining
`favorite_live_boost`, matching the existing `mlb.filtering` structure. Make
sure the `favorite_live_boost` property remains directly under the same
`filtering` object so it is exposed at the path read by the runtime config
lookup.
---
Outside diff comments:
In `@plugins/football-scoreboard/sports.py`:
- Around line 1525-1549: The exclude_teams spoiler filter is only applied inside
_select_recent_games_for_display, but update() can build team_games directly
from processed_games when no favorites are enabled, so excluded teams still leak
into recent/final scores. Move the exclusion check into the main processed_games
construction path in update() (the same eligibility block that appends games),
and keep _select_recent_games_for_display consistent with that behavior so
exclude_teams takes precedence in all paths, similar to the lacrosse plugin’s
unconditional filtering.
---
Nitpick comments:
In `@plugins/basketball-scoreboard/manifest.json`:
- Around line 21-26: The basketball-scoreboard manifest release entry uses an
inconsistent changelog field name, so align it with the established convention
used by football-scoreboard. Update the release object in manifest.json to use
the same changelog property name as the other plugin’s manifest (the one
consumed by shared release-note tooling), keeping the description text
unchanged. This should be fixed in the manifest entry containing released,
version, and ledmatrix_min so per-plugin release notes continue to render
consistently.
In `@plugins/basketball-scoreboard/test_favorite_live_boost.py`:
- Around line 72-116: The exclude_teams tests are validating a copied
filter_decision helper instead of the real SportsLive.update() logic, so they
won’t catch regressions in the production branch order. Update this test file to
instantiate a SportsLive via make_live_manager, stub _fetch_data as needed, and
exercise live.update() directly like the baseball and lacrosse tests, then
assert against the resulting live state for the same exclude_teams scenarios.
In `@plugins/lacrosse-scoreboard/manager.py`:
- Around line 719-724: The `favorite_live_boost` value resolved in `manager.py`
is passed through without validation, so clamp it to the documented 1–5 range
before storing it in `manager_config["favorite_live_boost"]`. Update the config
handling near `resolve_value` usage in the teams settings block to coerce
invalid or non-integer inputs to a safe default and ensure the downstream SWRR
weighting logic only receives a positive bounded integer.
In `@plugins/soccer-scoreboard/test_live_mode_targeting.py`:
- Line 53: The test coverage is missing the exclude_teams branch inside
SportsLive.update, so add a focused test that drives update() through the real
method while mocking the ESPN pipeline. Use SportsLive as the target, and stub
_fetch_data and _extract_game_details (similar to the _make_live_manager pattern
in the lacrosse tests) so you can return controlled game data and verify the
exclusion behavior without hitting live fetches. Ensure the new test exercises
update()’s exclude_teams handling directly and asserts the filtered result.
🪄 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: 2d04654b-79a0-4084-abf2-178756ca29a2
📒 Files selected for processing (38)
plugins.jsonplugins/baseball-scoreboard/README.mdplugins/baseball-scoreboard/config_schema.jsonplugins/baseball-scoreboard/manager.pyplugins/baseball-scoreboard/manifest.jsonplugins/baseball-scoreboard/sports.pyplugins/baseball-scoreboard/test_favorite_live_boost.pyplugins/basketball-scoreboard/README.mdplugins/basketball-scoreboard/config_schema.jsonplugins/basketball-scoreboard/manager.pyplugins/basketball-scoreboard/manifest.jsonplugins/basketball-scoreboard/sports.pyplugins/basketball-scoreboard/test_favorite_live_boost.pyplugins/football-scoreboard/README.mdplugins/football-scoreboard/config_schema.jsonplugins/football-scoreboard/manager.pyplugins/football-scoreboard/manifest.jsonplugins/football-scoreboard/sports.pyplugins/football-scoreboard/test_favorite_live_boost.pyplugins/hockey-scoreboard/README.mdplugins/hockey-scoreboard/config_schema.jsonplugins/hockey-scoreboard/game_filter.pyplugins/hockey-scoreboard/manager.pyplugins/hockey-scoreboard/manifest.jsonplugins/hockey-scoreboard/sports.pyplugins/hockey-scoreboard/test_favorite_live_boost.pyplugins/lacrosse-scoreboard/README.mdplugins/lacrosse-scoreboard/config_schema.jsonplugins/lacrosse-scoreboard/manager.pyplugins/lacrosse-scoreboard/manifest.jsonplugins/lacrosse-scoreboard/sports.pyplugins/lacrosse-scoreboard/test_favorite_live_boost.pyplugins/soccer-scoreboard/README.mdplugins/soccer-scoreboard/config_schema.jsonplugins/soccer-scoreboard/manager.pyplugins/soccer-scoreboard/manifest.jsonplugins/soccer-scoreboard/sports.pyplugins/soccer-scoreboard/test_live_mode_targeting.py
Critical: baseball-scoreboard config_schema.json had a missing closing brace, nesting favorite_live_boost inside show_all_live's boolean schema for milb/ncaa_baseball - it was never actually exposed as a real config field for those two leagues (silently fell back to default 2 always). Major: football-scoreboard's exclude_teams spoiler-protection filter was only applied inside _select_recent_games_for_display, which is skipped entirely when show_favorite_teams_only is False (the schema default) - excluded teams' final scores still leaked through in that default path. Moved the filter to where processed_games is built so both branches inherit it; added a regression test that fails without the fix. Test-quality: extracted the inline live-game inclusion branch in basketball-scoreboard and soccer-scoreboard into a _classify_live_game() method (matching football's existing pattern) so their exclude_teams tests exercise the real production code path instead of a hand-copied reimplementation. Added direct test coverage for soccer's exclude branch that was previously only covered by code review. Investigated and consciously skipped two lower-priority nitpicks: - basketball's manifest.json "notes" vs football's "changes" changelog field name: verified repo-wide "notes" is the dominant convention (10 plugins) and "changes" is football's own pre-existing outlier, so no change needed. - lacrosse manager.py favorite_live_boost clamping: sports.py already clamps it defensively before use, so a second clamp in manager.py would be redundant. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
|
Addressed CodeRabbit's findings in ad90266: 🔴 Critical (fixed) — 🟠 Major (fixed) — Nitpicks:
All six plugins' test suites re-run and passing, manifest-schema validation passing for all six, module-collision check clean, and the plugin safety harness re-verified for every plugin touched in this round. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Summary
Addresses user feedback: with both "favorites only" and "show all live" disabled, live games got no favorite-aware treatment at all — it behaved identically to "show all live" with zero prioritization. Adds two new per-league config levers to baseball, basketball, football, hockey, soccer, and lacrosse scoreboards:
favorite_live_boost(integer, 1-5, default 2): your favorite's live game is always queued first whenever the live rotation refreshes, and getsNturns for every 1 turn other live games get, via a Smooth Weighted Round-Robin schedule (evenly spaced, not clumped).boost=1reproduces today's exact rotation order — zero behavior change for anyone who doesn't touch the setting. Never interrupts a game already on screen mid-turn.exclude_teams: hide specific teams from both the live rotation and recent/final scores (spoiler protection for delayed viewing) — takes precedence over every other filter, includingshow_all_live.Each plugin bumped to its next minor version with a changelog entry;
plugins.jsonsynced.Draft — open to iterate before merge.
Test plan
python3 -m py_compileon every touched.pyfile across all 6 pluginsconfig_schema.json/manifest.json/plugins.jsonscripts/check_module_collisions.py— no cross-plugin collisionsLEDMatrix/scripts/check_plugin.pysafety harness — PASS on all 7 matrix sizes × all modes for every one of the 6 plugins🤖 Generated with Claude Code
https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
Summary by CodeRabbit
exclude_teams,filtering.favorite_live_boost) across multiple scoreboard plugins.