Skip to content

Explain an empty scoreboard instead of leaving the user guessing - #235

Merged
ChuckBuilds merged 4 commits into
mainfrom
feat/favorite-team-diagnostics
Aug 3, 2026
Merged

Explain an empty scoreboard instead of leaving the user guessing#235
ChuckBuilds merged 4 commits into
mainfrom
feat/favorite-team-diagnostics

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Stacked on #234 — targets fix/team-code-corrections because it touches the same manifests. Merge #234 first and this retargets to main cleanly.

Why

Favorite teams are matched by exact ESPN abbreviation. A code that isn't real matches no game, so the plugin shows nothing — with no hint that the code is the problem. And out of season, a perfectly correct code produces the identical empty screen.

The two were indistinguishable from the logs. That's how a user ends up asking whether their config is broken when it's only July. This is the generalised version of the soccer fix in #233, applied to the other seven plugins that match teams by abbreviation.

What the user now sees

WARNING  NFL favorite team 'GBP' is not a NFL team code. Closest match is
         'GB' (Green Bay Packers). Every code this league accepts is listed
         at https://site.api.espn.com/.../nfl/teams?limit=1000.

INFO     NFL favorite teams TB look correct, but the league has nothing on
         until 06 August 2026. An empty display until then is expected, not
         a configuration problem.

INFO     NCAA Baseball favorite teams UGA look correct, but the season has
         finished and the next one's fixtures are not published yet.

Getting the suggestion right

String similarity is useless at three characters — MUN scores identically against MAN and SUN, so Manchester United and Sunderland tie and the suggestion is a coin flip. Instead, candidates that read as word-initial abbreviations rank first, then fragments, then similarity.

Input Suggests Why it's hard
GBP GB (Green Bay Packers)
BAMA ALA (Alabama Crimson Tide) a fragment inside a word, abbreviates nothing in it
SCAR SC (South Carolina) also fits Rutgers Scarlet; first-word match wins
UTA UTAH (Utah Mammoth) retired code
bos BOS — "codes are case-sensitive" told, not guessed
ZZZZZZ (nothing) no suggestion beats a wrong one

Reading the schedule was the subtle part

Both of these are real ESPN behaviour, confirmed against live endpoints:

  1. An out-of-season league does not return an empty scoreboard. ESPN rolls forward to the next day that has fixtures, so in July the NHL endpoint returns seven September games. Emptiness can't be the signal — the date is, and it's more useful anyway.
  2. A finished season rolls nowhere and returns its last game instead, months in the past. So dates must be filtered before the soonest one means anything.

Filtering on "later than now" then introduces a third bug: it drops games that started earlier today and reports a live slate as a dead season. The window is therefore the last 24 hours, which is also timezone-proof.

Verified against every league these plugins cover:

Quiet (games on) Reports a start date Reports season finished
MLB, AFL, NRL, WNBA NFL, NCAA FB, NHL, NBA, NCAAM basketball NCAA baseball, NCAAW hockey

Safety

This runs inside update(), so it was built not to be able to hurt anything:

  • Daemon thread — never delays a frame.
  • Once per league per process, re-armed only on a config change.
  • Every failure swallowed to a debug line.
  • A league whose ESPN endpoint returns no teams at all (college lacrosse) draws no conclusion rather than calling a valid code wrong.
  • Dynamic groups (AP_TOP_25) are excluded, so they're never reported as bad codes.

Structure

Each plugin ships its own copy of the module — the loader gives plugins no shared library to import from — under a plugin-unique name per the module-collision rule in CLAUDE.md. A test asserts the copies stay byte-identical while they live in one checkout, since silent drift is the obvious failure mode of copies.

Testing

  • 30 unit tests, fully offline (ESPN faked via sys.modules, since the method imports requests in its own body).
  • Safety harness: 24/24 PASS on every plugin — 168 renders, zero failures, zero tracebacks.
  • Module-collision check clean across 42 plugins.
  • Validated end-to-end on real hardware (512×64 Pi): all four message paths appeared as intended, no errors, no load failures.

claude added 2 commits July 29, 2026 21:12
Several plugins documented — or in one case offered in a picker — team
abbreviations that ESPN does not use, so copying them matched no team and
the plugin silently showed nothing.

odds-ticker's NHL picker was the only one where the user could not work
around it: it listed UTA (a retired code, labelled with the club's former
name "Utah Hockey Club" rather than "Utah Mammoth") and omitted the
Seattle Kraken entirely, so that team could not be selected at all. The
enum and labels are now generated from ESPN's team endpoint and match it
exactly at 32 teams.

The rest are description-only corrections to the favorite_teams examples:

  basketball  NBA    GSW   -> GS     (Golden State Warriors)
  basketball  WNBA   NYL   -> NY     (New York Liberty)
  basketball  WNBA   LAS   -> LA     (Los Angeles Sparks)
  basketball  NCAAW  UCONN -> CONN   (UConn Huskies)
  basketball  NCAAW  SCAR  -> SC     (South Carolina Gamecocks)
  football    NCAAFB BAMA  -> ALA    (Alabama Crimson Tide)
  hockey      NCAAWH WISC  -> WIS    (Wisconsin Badgers)

Each description now also says these are ESPN's codes and are not always
the ones you would guess, since that is the underlying trap.

Every code here was verified against
site.api.espn.com/apis/site/v2/sports/{sport}/{league}/teams?limit=1000.
The limit matters: without it the default page size truncates the NCAA
responses (362 of 755 teams) and makes valid codes look wrong.

Left alone deliberately: lacrosse-scoreboard's WISC/MINN/OSU and
BU/BC/MICH examples, and baseball-scoreboard's MiLB DUR/SWB/NOR, because
ESPN's lacrosse team endpoints return zero teams and the MiLB one 404s.
Unverifiable, so not guessed at.

No rendering code changed; the safety harness passes for all four plugins
at every size.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
Favorite teams are matched by exact ESPN abbreviation, so a code that is
not real matches no game and the plugin shows nothing — with no hint that
the code is the problem. Out of season, a perfectly correct code produces
the identical empty screen. The two were indistinguishable from the logs,
which is how a user ends up asking whether their config is broken when it
is only July.

Each of these plugins now says which case it is:

  WARNING  NFL favorite team 'GBP' is not a NFL team code. Closest match
           is 'GB' (Green Bay Packers). Every code this league accepts is
           listed at https://site.api.espn.com/.../nfl/teams?limit=1000.

  INFO     NFL favorite teams TB look correct, but the league has nothing
           on until 06 August 2026. An empty display until then is
           expected, not a configuration problem.

  INFO     NCAA Baseball favorite teams UGA look correct, but the season
           has finished and the next one's fixtures are not published yet.

Suggestions rank word-initial matches first, because string similarity is
useless at three characters: 'MUN' scores identically against 'MAN' and
'SUN', so Manchester United and Sunderland tie and the answer is a coin
flip. Fragments are handled too ('BAMA' is inside 'Alabama' but
abbreviates nothing in it), and a code that only differs in case is told
so rather than guessed at.

Reading the schedule turned out to be the subtle part, and both traps are
real ESPN behaviour confirmed against live endpoints:

- An out-of-season league does not return an empty scoreboard. ESPN rolls
  forward to the next day with fixtures, so in July the NHL endpoint
  returns seven September games. Emptiness cannot be the signal.
- A *finished* season rolls nowhere and returns its last game instead,
  months in the past — so dates must be filtered before the soonest one
  means anything. Filtering on "later than now" then wrongly drops games
  that started earlier today and reports a live slate as a dead season,
  so the window is the last 24 hours.

Verified against every league these plugins cover: MLB, AFL, NRL and WNBA
correctly stay quiet; NFL, NCAA football, NHL, NBA and NCAA men's
basketball report their start dates; NCAA baseball and NCAA women's
hockey report finished seasons.

Safety, since this runs inside update():
- It runs on a daemon thread, so it never delays a frame.
- Once per league per process, re-armed only when the config changes.
- Every failure path is swallowed to a debug line. A plugin whose ESPN
  endpoint returns no teams at all (college lacrosse) draws no conclusion
  rather than calling a valid code wrong.

Each plugin ships its own copy of the module, since the loader gives
plugins no shared library to import from, under a plugin-unique name per
the module-collision rule. A test asserts the copies stay byte-identical
while they live in one checkout.

Tested: 30 unit tests; safety harness 24/24 PASS per plugin (168 renders,
zero failures); module-collision check clean. Validated end-to-end on real
hardware, where all four message paths appeared as intended with no errors.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@ChuckBuilds, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bcb6f918-22d2-4571-b967-d2c2b878cdef

📥 Commits

Reviewing files that changed from the base of the PR and between 5a5553a and 3412851.

📒 Files selected for processing (26)
  • plugins.json
  • plugins/afl-scoreboard/afl_favorite_check.py
  • plugins/afl-scoreboard/manager.py
  • plugins/afl-scoreboard/manifest.json
  • plugins/baseball-scoreboard/CHANGELOG.md
  • plugins/baseball-scoreboard/baseball_favorite_check.py
  • plugins/baseball-scoreboard/manager.py
  • plugins/baseball-scoreboard/manifest.json
  • plugins/basketball-scoreboard/basketball_favorite_check.py
  • plugins/basketball-scoreboard/manager.py
  • plugins/basketball-scoreboard/manifest.json
  • plugins/football-scoreboard/CHANGELOG.md
  • plugins/football-scoreboard/football_favorite_check.py
  • plugins/football-scoreboard/manager.py
  • plugins/football-scoreboard/manifest.json
  • plugins/hockey-scoreboard/hockey_favorite_check.py
  • plugins/hockey-scoreboard/manager.py
  • plugins/hockey-scoreboard/manifest.json
  • plugins/hockey-scoreboard/test_favorite_check.py
  • plugins/lacrosse-scoreboard/CHANGELOG.md
  • plugins/lacrosse-scoreboard/lacrosse_favorite_check.py
  • plugins/lacrosse-scoreboard/manager.py
  • plugins/lacrosse-scoreboard/manifest.json
  • plugins/nrl-scoreboard/manager.py
  • plugins/nrl-scoreboard/manifest.json
  • plugins/nrl-scoreboard/nrl_favorite_check.py

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.

@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 7 medium

Alerts:
⚠ 7 issues (≤ 0 issues of at least minor severity)

Results:
7 new issues

Category Results
Security 7 medium

View in Codacy

🟢 Metrics 719 complexity

Metric Results
Complexity 719

View in Codacy

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.

@ChuckBuilds

Copy link
Copy Markdown
Owner Author

Merge order: land #229 and #234 first.

I merge-simulated all ten open PRs onto main. With #229 and #234 in first, the only conflict here is baseball's manifest.json and CHANGELOG.md — both PRs insert a new entry at the head of the versions array, which git cannot reconcile even though the entries are independent. manager.py merges clean, so there is no code conflict.

Resolution is simply to keep both entries, giving 1.21.0 above 1.20.1. I verified that resolution and then checked the fully-combined tree:

  • registry consistent with every manifest, no mismatches
  • module-collision check clean across 42 plugins
  • check_team_pickers.py green (it needs Fix wrong ESPN team codes in pickers and help text #234's picker fix)
  • safety harness: 280 renders across the 14 affected plugins, 0 failures, 0 golden drift
  • plugin unit tests identical to main — the remaining failures (a No module named 'src' collection error, an afl timezone test, two order-dependent baseball tests) are all pre-existing and reproduce on origin/main unchanged

Base automatically changed from fix/team-code-corrections to main July 31, 2026 19:32
claude added 2 commits August 2, 2026 13:32
#237/#239

Feature files (*_favorite_check.py, managers) applied cleanly. Resolved the
version-mechanics conflicts against current main; the config_schema/odds-ticker
changes from #234 that #235 still carried are now no-ops (already in main).
Versions corrected in the follow-up commit.
Minor bumps (new user-facing feature) for the seven plugins #235 actually
changes, each above main's post-#237 version:

  afl 1.2.0, baseball 1.21.0, basketball 1.9.0, football 2.10.0,
  hockey 1.6.0, lacrosse 1.6.0, nrl 1.2.0

odds-ticker is intentionally NOT bumped: its only change in #235 was the NHL
picker correction (UTA->UTAH, +Seattle) that came from #234 and is already in
main, so it has no net change here. The original bump list also targeted
numbers main has since passed via #234/#236/#237/#239; corrected. plugins.json
regenerated; every changed plugin is strictly above main.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
@ChuckBuilds
ChuckBuilds merged commit 6b7a9b3 into main Aug 3, 2026
3 of 4 checks passed
@ChuckBuilds
ChuckBuilds deleted the feat/favorite-team-diagnostics branch August 3, 2026 13:15
ChuckBuilds pushed a commit that referenced this pull request Aug 3, 2026
…s passed

#235 landed while this PR was open and bumped both plugins past it --
baseball to 1.21.0 and football to 2.10.0 -- which stranded this branch's
1.20.4 / 2.9.4 below main. CI rejects a bump that is not above main's version,
and users already on the higher version would never receive the change, so the
fix is re-released on top rather than merged as-is:

  baseball-scoreboard  1.20.4 -> 1.21.1
  football-scoreboard   2.9.4 -> 2.10.1

Conflicts and how each was resolved:

- Both manifests: git collapsed the two competing head insertions into one
  entry, which silently drops the entry main shipped. Kept BOTH -- this
  branch's, renumbered and on top, then #235's 1.21.0 / 2.10.0 beneath it.
- Both CHANGELOGs: same shape, same resolution. Neither release note is lost.
- plugins.json: regenerated from main rather than merged textually
  (`git show origin/main:plugins.json > plugins.json && python3
  update_registry.py`). The diff against main is exactly the two version
  bumps plus the regenerated timestamp.
- Both manager.py files auto-merged: #235 added a favorite-check module and
  called it from a different region than the has_live_content() logging this
  branch rewrote.

Verified on the merged tree:
- the throttle changes are intact and the old unthrottled per-league
  logger.info lines are gone from both plugins
- #235's favorite_check modules are present and still wired into both
  managers
- safety harness 24/24 (baseball) and 16/16 (football), no golden drift
- module-collision check clean across 42 plugins
- manifest version, versions[0] and plugins.json latest_version agree
- plugin test suites: same 4 baseball / 3 football failures as origin/main,
  each confirmed pre-existing against a worktree of the new main

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
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.

2 participants