Skip to content

fix(soccer-scoreboard): correct TEAMS.md codes, explain why a league is empty - #233

Merged
ChuckBuilds merged 3 commits into
mainfrom
fix/soccer-team-codes
Jul 31, 2026
Merged

fix(soccer-scoreboard): correct TEAMS.md codes, explain why a league is empty#233
ChuckBuilds merged 3 commits into
mainfrom
fix/soccer-team-codes

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Reported problem

A user set their Premier League favourite to MUN for 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 MAN for Manchester United and MNC for Manchester City. The file said MUN and MCI. It wasn't isolated — diffing every documented code against ESPN's live endpoints:

League Wrong codes Also
Premier League MCIMNC, MUNMAN 4 relegated clubs still listed, 6 current ones missing
La Liga RMRMA 5 stale, 7 missing
Bundesliga AUGFCA, BAYMUN, BVBDOR, MNZM05 5 stale, 9 missing
Serie A ROMROMA, COMCOMO
Ligue 1 8 wrongOLLYON, OMOLM, ASMMON, TFCTOU, …

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: MUN is 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_teams is 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: MUN is 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:

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.

On the suggestions

String distance is useless at three characters: MUN scores identically against MAN and SUN, 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. MUN splits as M-anchester UN-ited, while Sunderland offers no word starting with M. Validated against all seven leagues' real data — MUNMAN, MCIMNC, MANUMAN, RMRMA, OMOLM, ASMMON — 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

  • 22 new tests in test_favorite_team_diagnostics.py, including the exact reported case.
  • Harness: all 24 screens PASS.
  • Existing soccer tests (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-scoreboard was 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

    • Added diagnostics for invalid favorite team codes, including suggested corrections.
    • Added messaging to clarify when valid teams have no fixtures because their season has not started.
    • Improved team-code support using current ESPN data across major leagues.
  • Documentation

    • Updated team abbreviation tables and guidance, including cross-league differences and troubleshooting information.
  • Bug Fixes

    • Corrected team abbreviations that could cause empty scoreboard displays.

…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
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 14 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: 67e4747d-e280-49df-b4c3-a10eac8328ed

📥 Commits

Reviewing files that changed from the base of the PR and between 4d96dc2 and 78630f0.

📒 Files selected for processing (4)
  • plugins.json
  • plugins/soccer-scoreboard/CHANGELOG.md
  • plugins/soccer-scoreboard/manager.py
  • plugins/soccer-scoreboard/manifest.json
📝 Walkthrough

Walkthrough

The 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.

Changes

Soccer scoreboard release

Layer / File(s) Summary
Favorite-team diagnostics and ESPN team codes
plugins/soccer-scoreboard/manager.py, plugins/soccer-scoreboard/TEAMS.md
Favorite-team codes are checked once per league against ESPN data, with correction suggestions and season-start diagnostics. League tables and troubleshooting guidance now use ESPN-aligned codes.
Diagnostic test coverage
plugins/soccer-scoreboard/test_favorite_team_diagnostics.py
Tests cover invalid, valid, mixed, case-sensitive, missing-fixture, fetch-failure, repeated-check, and suggestion scenarios.
Version and release documentation
plugins.json, plugins/soccer-scoreboard/manifest.json, plugins/soccer-scoreboard/CHANGELOG.md
Repository and plugin metadata advance to version 2.4.0, with release notes describing the team-code and logging changes.

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: correcting TEAMS.md codes and adding diagnostics for empty leagues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/soccer-team-codes

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

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 107 complexity

Metric Results
Complexity 107

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8d33894 and 4d96dc2.

📒 Files selected for processing (6)
  • plugins.json
  • plugins/soccer-scoreboard/CHANGELOG.md
  • plugins/soccer-scoreboard/TEAMS.md
  • plugins/soccer-scoreboard/manager.py
  • plugins/soccer-scoreboard/manifest.json
  • plugins/soccer-scoreboard/test_favorite_team_diagnostics.py

Comment thread plugins.json Outdated
Comment on lines +1413 to +1455
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment on lines +20 to +23
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 4 file(s) based on 4 unresolved review comments.

Files modified:

  • plugins.json
  • plugins/soccer-scoreboard/TEAMS.md
  • plugins/soccer-scoreboard/manager.py
  • plugins/soccer-scoreboard/manifest.json

Commit: f7736d5d5d8c29b850ea259a114b21d14de38b2a

The changes have been pushed to the fix/soccer-team-codes branch.

Time taken: 4m 57s

@ChuckBuilds
ChuckBuilds merged commit 49b0fe2 into main Jul 31, 2026
4 checks passed
ChuckBuilds pushed a commit that referenced this pull request Aug 1, 2026
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
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