fix(soccer-scoreboard): correct TEAMS.md codes, explain why a league is empty - #233
Conversation
…is empty A user set their Premier League favourite to "MUN" for Manchester United, straight out of TEAMS.md, and saw nothing. Two separate problems. TEAMS.md was wrong. ESPN uses MAN for Manchester United and MNC for Manchester City; the file said MUN and MCI. It was not isolated — Real Madrid was RM rather than RMA, Ligue 1 had eight wrong codes including Lyon (LYON not OL) and Marseille (OLM not OM), Bundesliga four, and several rosters were a season out of date. Every table is now generated from ESPN's live team endpoints and verified against them, cross-checked with the standings endpoint. Worse than a typo: MUN is a real code — it is Bayern Munich. Favourites match by abbreviation across every enabled league, so anyone following the old docs with the Bundesliga also enabled would have quietly followed the wrong club. TEAMS.md now lists the codes that mean different clubs in different leagues. The second problem is that this failed silently. Matching is an exact string comparison against ESPN's abbreviation with no aliasing, so a plausible code matches nothing and the display just stays empty. Between seasons a *correct* code produces exactly the same empty screen, and the logs could not tell them apart. Once per league the plugin now reports which it is: favorite team 'MUN' is not a Premier League team code. Closest match is 'MAN' (Manchester United). See TEAMS.md for the full list. Premier League favorite teams MAN look correct, but the league has no fixtures published yet — its season starts 21 August 2026. An empty display until then is expected, not a configuration problem. Suggestions match how people abbreviate rather than by string distance, which is useless at three characters: 'MUN' scores identically against 'MAN' and 'SUN', so Manchester United and Sunderland tie and the hint is a coin flip. Requiring each part of the code to prefix a word of the club name separates them — 'MUN' splits as M-anchester UN-ited, while Sunderland offers no word starting with M. Verified against all seven leagues' real data: MUN->MAN, MCI->MNC, MANU->MAN, RM->RMA, OM->OLM, ASM->MON, and a wrong-case code is told so explicitly. The check is diagnostic only: it runs once per league, is never retried on failure, and any error is swallowed so it cannot disturb updates. Harness passes at all 24 screens; existing soccer tests still pass. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
|
Warning Review limit reached
Next review available in: 14 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 Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe soccer scoreboard plugin now validates favorite-team abbreviations against ESPN data, reports invalid or not-yet-scheduled teams, documents refreshed league mappings, adds diagnostic tests, and releases version 2.4.0. ChangesSoccer scoreboard release
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ScoreboardPlugin
participant LeagueManagers
participant ESPN
participant Logger
ScoreboardPlugin->>LeagueManagers: read favorite_teams
ScoreboardPlugin->>ESPN: fetch team codes and season data
ESPN-->>ScoreboardPlugin: return mappings and fixture state
ScoreboardPlugin->>Logger: log warnings or informational diagnostics
🚥 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 | 107 |
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.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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.json`:
- Line 3: Update the manifest’s release metadata for version 2.4.0 so the
top-level last_updated value is 2026-07-29, ensuring update_registry.py
propagates the correct date instead of the soccer entry’s stale 2026-07-17
value.
In `@plugins/soccer-scoreboard/manager.py`:
- Around line 1413-1455: Update _fetch_league_teams and _fetch_season_start to
read and refresh their ESPN responses through self.cache_manager using
plugin-namespaced cache keys, rather than making uncached synchronous requests.
Schedule cache refreshes from update so diagnostics are populated asynchronously
and cannot delay scoreboard update threads, while preserving the existing
parsing and diagnostic behavior.
In `@plugins/soccer-scoreboard/TEAMS.md`:
- Line 18: Update the heading in TEAMS.md from H3 to H2 so it follows the
document title and preserves heading hierarchy and generated navigation.
- Around line 20-23: Update the favorites-matching explanation in TEAMS.md to
state that each manager’s favorite_teams comes from its own league
configuration. Clarify that abbreviation matching is scoped to configured
favorites per league, so enabling multiple leagues alone does not make an eng.1
favorite select a Bundesliga team; duplicate codes match across leagues only
when configured in each league.
🪄 Autofix (Beta)
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ed4dbcb0-9152-4724-a879-8fe49f88817f
📒 Files selected for processing (6)
plugins.jsonplugins/soccer-scoreboard/CHANGELOG.mdplugins/soccer-scoreboard/TEAMS.mdplugins/soccer-scoreboard/manager.pyplugins/soccer-scoreboard/manifest.jsonplugins/soccer-scoreboard/test_favorite_team_diagnostics.py
| def _fetch_league_teams(self, league_key: str) -> Dict[str, str]: | ||
| """ESPN's {abbreviation: display name} for a league.""" | ||
| import requests | ||
|
|
||
| url = ("https://site.api.espn.com/apis/site/v2/sports/soccer/" | ||
| "{}/teams".format(league_key)) | ||
| payload = requests.get(url, timeout=15).json() | ||
| entries = payload['sports'][0]['leagues'][0]['teams'] | ||
| return { | ||
| t['team']['abbreviation']: t['team']['displayName'] | ||
| for t in entries if t.get('team', {}).get('abbreviation') | ||
| } | ||
|
|
||
| def _fetch_season_start(self, league_key: str) -> Optional[str]: | ||
| """ | ||
| First fixture date when a league has none scheduled yet, else None. | ||
|
|
||
| ESPN keeps publishing a league's calendar between seasons while its | ||
| scoreboard is empty, which is exactly the state that looks like a broken | ||
| config. | ||
| """ | ||
| import requests | ||
| from datetime import datetime, timezone | ||
|
|
||
| url = ("https://site.api.espn.com/apis/site/v2/sports/soccer/" | ||
| "{}/scoreboard".format(league_key)) | ||
| payload = requests.get(url, timeout=15).json() | ||
|
|
||
| if payload.get('events'): | ||
| return None # fixtures exist; an empty screen is not about the season | ||
|
|
||
| calendar = (payload.get('leagues') or [{}])[0].get('calendar') or [] | ||
| for entry in calendar: | ||
| raw = entry if isinstance(entry, str) else entry.get('startDate') | ||
| if not raw: | ||
| continue | ||
| try: | ||
| when = datetime.fromisoformat(raw.replace('Z', '+00:00')) | ||
| except ValueError: | ||
| continue | ||
| if when > datetime.now(timezone.utc): | ||
| return when.strftime('%d %B %Y') | ||
| return None |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Do not block updates on uncached diagnostics.
These direct requests run synchronously before manager update threads start, so an ESPN stall can delay each enabled league by up to 30 seconds. Cache the responses through self.cache_manager with plugin-namespaced keys, and schedule refreshes so diagnostics cannot hold up scoreboard updates.
As per coding guidelines: “Fetch or refresh network data in update, never in display, and use the shared self.cache_manager for network-fetched data” and “Namespace cache keys with the plugin ID.”
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 1418-1418: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.get(url, timeout=15)
Note: [CWE-918] Server-Side Request Forgery (SSRF).
(ssrf-requests)
[warning] 1438-1438: Request-controlled URL passed to requests; validate against an allowlist to prevent SSRF.
Context: requests.get(url, timeout=15)
Note: [CWE-918] Server-Side Request Forgery (SSRF).
(ssrf-requests)
🤖 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/manager.py` around lines 1413 - 1455, Update
_fetch_league_teams and _fetch_season_start to read and refresh their ESPN
responses through self.cache_manager using plugin-namespaced cache keys, rather
than making uncached synchronous requests. Schedule cache refreshes from update
so diagnostics are populated asynchronously and cannot delay scoreboard update
threads, while preserving the existing parsing and diagnostic behavior.
Source: Coding guidelines
| Manchester City (not `MCI`), `RMA` for Real Madrid (not `RM`), and `LYON` / | ||
| `OLM` for Lyon and Marseille (not `OL` / `OM`). | ||
|
|
||
| ### Codes that mean different clubs in different leagues |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use an H2 heading here.
### skips directly from the document title to H3, breaking heading hierarchy and generated navigation.
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)
[warning] 18-18: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
🤖 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/TEAMS.md` at line 18, Update the heading in
TEAMS.md from H3 to H2 so it follows the document title and preserves heading
hierarchy and generated navigation.
Source: Linters/SAST tools
| Favourites are matched by abbreviation across every league you have enabled, so | ||
| if two of your enabled leagues share a code you will match both. The one to | ||
| watch is `MUN`, which is **Bayern Munich** in the Bundesliga — not Manchester | ||
| United. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Clarify that favorites are configured per league.
Each manager receives favorite_teams from its own league config. A code only matches multiple leagues when it is configured under each of those leagues; merely enabling both leagues does not make an eng.1 favorite select a Bundesliga team.
🤖 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/TEAMS.md` around lines 20 - 23, Update the
favorites-matching explanation in TEAMS.md to state that each manager’s
favorite_teams comes from its own league configuration. Clarify that
abbreviation matching is scoped to configured favorites per league, so enabling
multiple leagues alone does not make an eng.1 favorite select a Bundesliga team;
duplicate codes match across leagues only when configured in each league.
…mes release Both branches bumped soccer-scoreboard to 2.4.0 — main's is the UTC start-time fix (#228). Rebased this work onto 2.5.0 so main's release notes are preserved beneath it rather than replaced. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Fixes Applied SuccessfullyFixed 4 file(s) based on 4 unresolved review comments. Files modified:
Commit: The changes have been pushed to the Time taken: |
Review follow-ups on #239. f1-scoreboard's manifest disagreed with itself: last_updated said 2026-07-28 while its newest versions[] entry for the same 1.7.1 said 2026-08-01. update_registry.py mirrors last_updated into the catalog, so the stale date propagated there too. Set it from the release entry and regenerated plugins.json. The 08-shared-sports-code doc still showed `except ImportError` for the base_odds_manager guard, but the code was narrowed to ModuleNotFoundError with a name check in an earlier round -- so the doc was teaching the pattern its own examples no longer use. A bare ImportError also swallows failures raised *inside* a core module that is present, silently loading the bundled copy and hiding a broken install. clock-simple's font loader caught bare Exception; narrowed to OSError, which is what FreeType raises for a missing, unreadable or malformed face. All three committed goldens pass unchanged. Left alone: - The measurement fallback in clock-simple stays broad on purpose. It runs on the render path and deliberately degrades to draw_text's own centring; letting a measurement hiccup propagate would blank a clock. - soccer-scoreboard's _check_favorite_teams blocking update() is real, but it arrived in #233 (49b0fe2) from main and is not this PR's code. - Five other manifests carry the same date drift from earlier PRs; out of scope here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgbA8SMutQQpXkMG8LMmC4
Reported problem
A user set their Premier League favourite to
MUNfor Manchester United — straight out of TEAMS.md — and saw nothing on the display. Two separate problems, and the docs were the first one.1. TEAMS.md had wrong codes
ESPN uses
MANfor Manchester United andMNCfor Manchester City. The file saidMUNandMCI. It wasn't isolated — diffing every documented code against ESPN's live endpoints:MCI→MNC,MUN→MANRM→RMAAUG→FCA,BAY→MUN,BVB→DOR,MNZ→M05ROM→ROMA,COM→COMOOL→LYON,OM→OLM,ASM→MON,TFC→TOU, …Every table is now generated from ESPN's live team endpoints and verified against them. I cross-checked the roster against the standings endpoint too, since the pre-season team list looked surprising — both agree exactly.
Worse than a typo:
MUNis a real code — it's Bayern Munich. Favourites match by abbreviation across every enabled league, so anyone following the old docs with the Bundesliga also on would have quietly followed the wrong club. TEAMS.md now documents the codes that mean different clubs in different leagues (MUN,BRE,MON,PAR,SCP,FCA,TOR).2. It failed silently, and two different causes looked identical
Matching is an exact string comparison against ESPN's abbreviation with no aliasing (
DynamicTeamResolver.resolve_teamsis a pass-through for soccer), so a plausible code matches nothing and the display just stays empty.The trap is that between seasons a correct code produces exactly the same empty screen — and the logs couldn't tell them apart. In this user's case both were true:
MUNis wrong, and the 2026-27 Premier League doesn't start until 21 August, so fixing the code alone wouldn't have made anything appear.Once per league, the plugin now says which it is:
On the suggestions
String distance is useless at three characters:
MUNscores identically againstMANandSUN, so Manchester United and Sunderland tie and the hint becomes a coin flip — my first attempt duly suggested Sunderland.So suggestions match how people actually abbreviate: each part of the code must prefix a word of the club name, in order.
MUNsplits as M-anchester UN-ited, while Sunderland offers no word starting with M. Validated against all seven leagues' real data —MUN→MAN,MCI→MNC,MANU→MAN,RM→RMA,OM→OLM,ASM→MON— and a wrong-case code is told so explicitly, since matching is case-sensitive.Safety
The check is purely diagnostic: once per league per process, never retried on failure, every error swallowed so it cannot disturb updates. It re-runs on config change so a corrected code gets confirmed rather than staying silent.
Testing
test_favorite_team_diagnostics.py, including the exact reported case.test_live_mode_targeting,test_non_favorite_live_duration,test_world_cup_flags) still pass.check_module_collisions.py: OK across 41 plugins.Note for reviewers
This matching pattern is shared with the other scoreboard plugins, so the same silent-failure mode exists there.
nrl-scoreboardwas already moved to matching on ESPN team ID rather than abbreviation (#189) for related reasons — worth considering more widely, since ESPN can change abbreviations on promotion and relegation. Happy to port the diagnostic to the other sports if useful.🤖 Generated with Claude Code
https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
Summary by CodeRabbit
New Features
Documentation
Bug Fixes