Per-favorite live-game duration for the 6 team scoreboards - #172
Conversation
Give live games without a favorite team a shorter on-screen turn than your favorites. When several games are live and you're showing all of them, favorites can linger (e.g. 30s) while non-favorite games flash by (e.g. 5s). Each affected league gains a per-league non_favorite_live_game_duration (Family B plugins expose it inside display_durations as non_favorite_live). It only takes effect when favorite teams are configured AND show_favorite_teams_only is off; it defaults to 0 (off), so existing behavior is byte-for-byte unchanged until opted in. Applied to the six team-based live-rotation scoreboards: football, baseball, basketball, soccer, hockey, and lacrosse. Per plugin: SportsLive reads the new key and computes an effective per-game dwell via _effective_live_duration(); manager.py forwards the key into mode_config; config_schema.json exposes it next to the live duration for the web UI; the README documents it with a when-it-applies truth table; the manifest version is bumped (minor); and a regression test is added. UFC (fight-based, no team favorites/rotation) and F1 (no per-game live rotation) are intentionally out of scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB
|
Warning Review limit reached
Next review available in: 34 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. 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 Run ID: 📒 Files selected for processing (16)
📝 WalkthroughWalkthroughThis PR adds a configurable ChangesNon-favorite live game duration feature (six sports plugins)
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 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 | 92 |
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.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugins/soccer-scoreboard/config_schema.json (1)
4028-4034: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winAdd
non_favorite_live_game_durationto custom league schema
config_schema.json'scustom_leagues.items.propertiesstill omitsnon_favorite_live_game_duration, so the UI/schema can’t set a value that_adapt_config_for_custom_league()already forwards. Add the sibling property next tolive_game_duration.🛠️ Proposed fix
"live_game_duration": { "type": "integer", "default": 20, "minimum": 10, "maximum": 120, "description": "Duration in seconds to display each live game" }, + "non_favorite_live_game_duration": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 120, + "description": "Duration in seconds for live games that do NOT involve a favorite team. Only applies when favorite teams are set AND 'show_favorite_teams_only' is off. 0 (default) = use live_game_duration for every live game." + },🤖 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/config_schema.json` around lines 4028 - 4034, The custom league schema is missing the non_favorite_live_game_duration property even though _adapt_config_for_custom_league() already passes it through. Add a sibling integer property next to live_game_duration inside custom_leagues.items.properties in config_schema.json, with matching validation/default-style metadata so the UI can set it consistently.
🧹 Nitpick comments (1)
plugins/lacrosse-scoreboard/test_non_favorite_live_duration.py (1)
50-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTest bypasses
__init__'s int-parsing/validation logic.
make_live()setsnon_favorite_live_game_durationdirectly on the instance instead of going throughSportsLive.__init__'stry/exceptint-conversion path (lines 1991-1996 insports.py), so that parsing/fallback logic (e.g., non-numeric orNoneconfig values) isn't covered by this test. This mirrors the same gap in sibling plugins' tests, so it's a pre-existing pattern rather than new here.🤖 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/test_non_favorite_live_duration.py` around lines 50 - 56, Update the live test setup so it exercises SportsLive.__init__ instead of bypassing it with object.__new__ in make_live. The current helper assigns non_favorite_live_game_duration directly and skips the int-parsing/validation path in SportsLive.__init__, so adjust the test to construct the _TestLive instance through the normal initializer and pass config values that cover the try/except conversion behavior, including non-numeric and None cases. Keep using make_live and _TestLive as the locator points, but ensure the test verifies the fallback logic rather than a direct attribute injection.
🤖 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/basketball-scoreboard/config_schema.json`:
- Around line 182-189: Update the four league schema descriptions for the
non_favorite_live_game_duration setting so they match the real runtime gating:
non-favorite live games can appear when favorite teams are configured and either
show_favorite_teams_only is off or show_all_live is on. Adjust the description
text in each league’s non_favorite_live_game_duration entry to remove the “only
when show_favorite_teams_only is off” limitation and reflect the broader
applicability consistently across the schema.
In `@plugins/basketball-scoreboard/README.md`:
- Around line 90-91: Update the README wording for the live game duration
settings so it matches the actual logic in the basketball scoreboard plugin:
both `live_game_duration` and `non_favorite_live_game_duration` should describe
non-favorite live games as being surfaced not only when
`show_favorite_teams_only` is off, but also when `show_all_live` is enabled.
Adjust the related explanation and the “Shorter dwell for non-favorite live
games” section to reference the runtime conditions used by the scoreboard’s
live-game selection behavior, not just `show_favorite_teams_only`.
In `@plugins/hockey-scoreboard/test_non_favorite_live_duration.py`:
- Around line 20-48: The test module stubs only the direct src imports, but
importing NHLLiveManager from nhl_managers still pulls in transitive
dependencies through hockey and sports. Extend _stub_core_src() to also register
safe stubs for those imported modules and their downstream dependencies (such as
PIL, requests, pytz, data_sources, dynamic_team_resolver, logo_downloader, and
base_odds_manager) before the NHLLiveManager import so collection-time imports
do not fail.
---
Outside diff comments:
In `@plugins/soccer-scoreboard/config_schema.json`:
- Around line 4028-4034: The custom league schema is missing the
non_favorite_live_game_duration property even though
_adapt_config_for_custom_league() already passes it through. Add a sibling
integer property next to live_game_duration inside
custom_leagues.items.properties in config_schema.json, with matching
validation/default-style metadata so the UI can set it consistently.
---
Nitpick comments:
In `@plugins/lacrosse-scoreboard/test_non_favorite_live_duration.py`:
- Around line 50-56: Update the live test setup so it exercises
SportsLive.__init__ instead of bypassing it with object.__new__ in make_live.
The current helper assigns non_favorite_live_game_duration directly and skips
the int-parsing/validation path in SportsLive.__init__, so adjust the test to
construct the _TestLive instance through the normal initializer and pass config
values that cover the try/except conversion behavior, including non-numeric and
None cases. Keep using make_live and _TestLive as the locator points, but ensure
the test verifies the fallback logic rather than a direct attribute injection.
🪄 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: d47fa892-a051-431e-9ae5-c19c6d3d12db
📒 Files selected for processing (37)
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_non_favorite_live_duration.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_non_favorite_live_duration.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_non_favorite_live_duration.pyplugins/hockey-scoreboard/README.mdplugins/hockey-scoreboard/config_schema.jsonplugins/hockey-scoreboard/manager.pyplugins/hockey-scoreboard/manifest.jsonplugins/hockey-scoreboard/sports.pyplugins/hockey-scoreboard/test_non_favorite_live_duration.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_non_favorite_live_duration.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_non_favorite_live_duration.py
| def _stub_core_src(): | ||
| def mod(name, **attrs): | ||
| m = types.ModuleType(name) | ||
| for k, v in attrs.items(): | ||
| setattr(m, k, v) | ||
| sys.modules.setdefault(name, m) | ||
| return m | ||
|
|
||
| mod("src") | ||
| mod("src.common") | ||
| mod("src.plugin_system") | ||
| mod("src.logo_downloader", LogoDownloader=object, download_missing_logo=lambda *a, **k: None) | ||
| mod("src.common.scroll_helper", ScrollHelper=object) | ||
| mod("src.plugin_system.base_plugin", BasePlugin=object, VegasDisplayMode=object) | ||
| mod("src.background_data_service", get_background_service=lambda *a, **k: None) | ||
|
|
||
|
|
||
| _stub_core_src() | ||
|
|
||
| from nhl_managers import NHLLiveManager # noqa: E402 | ||
|
|
||
|
|
||
| def make_live(favorite_teams=None, game_display_duration=30, | ||
| non_favorite_live_game_duration=0): | ||
| live = NHLLiveManager.__new__(NHLLiveManager) | ||
| live.favorite_teams = favorite_teams or [] | ||
| live.game_display_duration = game_display_duration | ||
| live.non_favorite_live_game_duration = non_favorite_live_game_duration | ||
| return live |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check nhl_managers.py's import surface for anything not stubbed in the test.
ast-grep run --pattern 'import $MOD' --lang python plugins/hockey-scoreboard/nhl_managers.py
ast-grep run --pattern 'from $MOD import $$$' --lang python plugins/hockey-scoreboard/nhl_managers.pyRepository: ChuckBuilds/ledmatrix-plugins
Length of output: 682
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for f in plugins/hockey-scoreboard/hockey.py plugins/hockey-scoreboard/sports.py; do
echo "== $f =="
wc -l "$f"
ast-grep run --pattern 'import $MOD' --lang python "$f"
ast-grep run --pattern 'from $MOD import $$$' --lang python "$f"
doneRepository: ChuckBuilds/ledmatrix-plugins
Length of output: 2156
Stub the transitive imports too. nhl_managers.py imports hockey and sports, and those modules pull in additional dependencies (PIL, requests, pytz, data_sources, dynamic_team_resolver, logo_downloader, base_odds_manager). The current stubs are not enough to keep collection-time imports safe.
🤖 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/hockey-scoreboard/test_non_favorite_live_duration.py` around lines 20
- 48, The test module stubs only the direct src imports, but importing
NHLLiveManager from nhl_managers still pulls in transitive dependencies through
hockey and sports. Extend _stub_core_src() to also register safe stubs for those
imported modules and their downstream dependencies (such as PIL, requests, pytz,
data_sources, dynamic_team_resolver, logo_downloader, and base_odds_manager)
before the NHLLiveManager import so collection-time imports do not fail.
- Merge origin/main (#171, baseball v1.17.1); resolve conflicts in baseball-scoreboard/manifest.json (re-apply the non-favorite-duration feature as a fresh minor bump 1.17.1 -> 1.18.0) and regenerate plugins.json. - Add non_favorite_live_game_duration to soccer custom_leagues schema (the custom-league manager branch already forwarded it; the schema omitted it). - Broaden the 'when it applies' wording in all six plugins' schema descriptions and READMEs: non-favorite live games are shown when show_favorite_teams_only is off OR show_all_live is on (docs-only; the runtime gate in _effective_live_duration is unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB
|
Pushed
Two nitpicks intentionally not changed:
Generated by Claude Code |
Summary
Adds a per-league
non_favorite_live_game_durationto the six team-based scoreboards so live games that don't involve a favorite team can get a shorter on-screen turn than your favorites (e.g. favorites 30s, everyone else 5s) when you're showing all live games. It only takes effect when favorite teams are configured andshow_favorite_teams_onlyis off, and defaults to0(off) so existing behavior is unchanged until opted in.Type of change
Plugin(s) affected
football-scoreboard,baseball-scoreboard,basketball-scoreboard,soccer-scoreboard,hockey-scoreboard,lacrosse-scoreboard.ufc-scoreboard(fight-based, no team favorites/rotation weighting) andf1-scoreboard(standings/results modes, no per-game live rotation) are intentionally out of scope — neither has a place for this knob.Related issues
N/A
Test plan
EMULATOR=true python3 run.py)scripts/dev_server.py)Details:
test_non_favorite_live_duration.pyto each of the 6 plugins exercising the realSportsLive._effective_live_duration(favorite → base duration; non-favorite → shorter; no favorites → base for all; knob0→ base for all;Nonecurrent game → base). All pass.test_favorite_live_boost.py/ soccer'stest_live_mode_targeting.py) — no regressions.config_schema.jsonas JSON Schema Draft-7 and confirmed the new option (withdefault/minimum/maximum/description) renders next to the live duration in each league block.python scripts/check_module_collisions.py→ clean;python update_registry.pysynced all sixlatest_versionbumps intoplugins.json.check_plugin.py) was not run here (needs a core checkout); the change is backward-compatible — with the default0every live game still useslive_game_duration, so rendered output is identical.Required for plugin changes
versioninplugins/<id>/manifest.json(football 2.6.0→2.7.0, baseball 1.8.0→1.9.0, basketball 1.6.0→1.7.0, soccer 2.2.0→2.3.0, hockey 1.3.0→1.4.0, lacrosse 1.3.0→1.4.0)class_nameinmanifest.jsonmatches the actual class inmanager.pyexactly (unchanged)entry_pointmatches the real file (unchanged)README.mdif config keys changed (each documents the new key with a "when it applies" truth table)config_schema.jsonis the source of truth for the web UI form — the new option is in the schema with adefault,description, and constraintsplugins.json) —plugins.jsonwas synced viaupdate_registry.pyChecklist
CONTRIBUTING.mdCONTRIBUTING.mdandCODE_OF_CONDUCT.mdNotes for reviewer
How it's wired. The two plugin families expose the option differently, matching each plugin's existing live-duration layout:
non_favorite_live_game_durationsits next tolive_game_duration;manager.pyforwards it intomode_config.display_durations(hockey, lacrosse): exposed asdisplay_durations.non_favorite_live, resolved intomode_configalongside the existingdisplay_durations.live → live_game_durationmapping.In
SportsLive, the single load-bearing change is the live rotation switch, which now compares against_effective_live_duration(self.current_game)instead of a flatgame_display_duration. basketball/hockey/lacrosse lacked a class-level_is_favorite_game, so a tiny one was factored out (no behavior change) to keep the helper uniform.Behavior (per live game while rotating):
live_game_duration(unchanged)live_game_durationlive_game_durationnon_favorite_live_game_duration(when > 0)Left as-is on purpose: the manager-side total mode-cycle math (
get_cycle_duration) still useslive_game_duration, which is the longer favorite value — a safe upper bound, so non-favorites just cycle faster within the same window and nothing gets cut off. Summing per-game durations there is a possible later refinement.🤖 Generated with Claude Code
https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB
Generated by Claude Code
Summary by CodeRabbit
New Features
Documentation
0means.Tests