镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

scoreboards: require core 3.5.0; drop bundled espn_dates and sports_helpers (sports consolidation stage 1a) - #563

Merged
ChuckBuilds merged 7 commits into
mainfrom
claude/stage1-sunset-3.5.0
Sep 28, 2026
Merged

ChuckBuilds merged 7 commits into
mainfrom
claude/stage1-sunset-3.5.0

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Stage 1 of the sports-code consolidation, part a. It covers the eight team scoreboards: afl, baseball, basketball, football, hockey, lacrosse, nrl and soccer. UFC is a separate PR.

Core v3.5.0 ships src.common.espn_dates and src.common.sports_helpers. These eight plugins carried private copies of both, so they now require 3.5.0 and use core's copies instead. Nothing a user sees changes.

Changes

  • Minimum core version: each of the eight now requires core 3.5.0 (versions[0].ledmatrix_min_version and compatible_versions). Each gets a minor version bump, following the earlier 3.2/3.3/3.4 minimum-version raises: afl 1.32.0, baseball 1.51.0, basketball 1.38.0, football 3.15.0, hockey 1.37.0, lacrosse 1.33.0, nrl 1.31.0, soccer 2.35.0. Each has a versions[] entry and a CHANGELOG entry, and plugins.json is regenerated.
  • <sport>_espn_dates.py removed: the 8 copies (385 lines each) are deleted, and the guarded import becomes a plain from src.common.espn_dates import .... The copies differed from core v3.5.0 only in type annotations (core Feat/chromaclock wide #661).
  • sports_helpers copies removed: the parity script found all 104 method copies identical to core before deletion. SportsCore now inherits SportsHelpersMixin. The module-level helpers are imported under their old private names, so existing sports._clamp_window calls and patches keep working.
  • _get_weeks_data overrides removed: compared as ASTs, they match core v3.5.0 except for a leading date_str = "". Core doesn't need that line: only datetime arithmetic runs before the assignment, and the handler catches only RequestException. _background_fetches_espn_ranges and _fetch_season_directly stay, because they serve the no-background-service path.
  • Guards:
    • test_espn_dates_copies.py gets a SUNSET_PLUGINS list, like check_scroll_adoption. It fails if a copy or an import guard comes back.
    • The helpers parity test now counts copies that were compared plus copies that are absent.
    • test_logo_placeholder_refresh.py accepts a plugin that uses core's helper.
    • check_min_core_version.py dates its modules to 3.5.0. That table was missing the store_* modules from core scoreboards: require core 3.8.7; use its game view model (sports family 10 adopt) #659, so they're added.
  • Docs: the 08 shared-sports-code guide and CLAUDE.md now say these modules come from core.

Net: 50 files, +762 / −5,740.

Verification

All runs used core v3.5.0, comparing origin/main against this branch.

  • Rendering: the harness renders all 8 plugins; all 176 PNGs are byte-identical.
  • Plugin tests: run_plugin_tests.py for the 8 ids gives the same result per test on both trees (299 passed, 2 failed). The 2 failures happen on main too: baseball test_odds_placement and football test_football_plugin, which asks for input.
  • Repo scripts: every scripts/test_*.py and check script passes, 53 in all. That includes check_version_bump.py naming the 8 ids and update_registry.py --check.
  • Import check: the espn_dates functions and SportsHelpersMixin now come from src.common.*, and no bundled module is loaded.
    • dir() of every manager class before and after loses no attributes.
    • Every method that moved has identical code, except _get_weeks_data, which differs only by the date_str = "" line.
  • On ledpi (Pi 4, core v3.5.0):
    • The branch's baseball, football, hockey and soccer are installed. Baseball and football, the enabled ones, loaded and cycled through live, recent and upcoming games with no new warnings or errors.
    • Hockey's and soccer's SportsCore import resolves to core's mixins.

Overlap

This conflicts with #553 on the baseball, football, hockey and lacrosse manifest.json, CHANGELOG.md and plugins.json. Whichever PR merges second has to move its versions[0] entry back on top and bump its version again.

🤖 Generated with Claude Code

ChuckBuilds and others added 7 commits September 28, 2026 16:16
…r table

Core v3.5.0 (5b30052b) is tagged. Every module the table held as None
("in no tagged release") ships in it -- verified against
`git show v3.5.0:src/...` -- so each entry now names 3.5.0. The tag test
also found three modules missing from the table, split out of the store
manager in core #659 (store_install, store_registry, store_update); they
first ship in v3.5.0 too.

The test tracks v3.5.0 as a tag. Its "an unreleased module is reported"
case used sports_helpers, which is released now; it plants a fake None
entry instead, and two new cases pin a 3.5.0 module against 3.4.0 and
3.5.0 floors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mps)

afl 1.32.0, baseball 1.51.0, basketball 1.38.0, football 3.15.0,
hockey 1.37.0, lacrosse 1.33.0, nrl 1.31.0, soccer 2.35.0.

versions[0].ledmatrix_min_version and compatible_versions rise from 3.4.0
to 3.5.0, the first core release that ships src.common.espn_dates and
src.common.sports_helpers. The following commits in this series delete the
bundled copies of both and the _get_weeks_data override; the versions[]
notes and CHANGELOG entries describe the whole series. A floor raise is a
minor bump, as for the 3.2.0 / 3.3.0 / 3.4.0 raises before it.
plugins.json regenerated with update_registry.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plainly

Each of the eight team scoreboards bundled a copy of core's
src/common/espn_dates.py behind a guarded import, for cores that predate
the module. They now floor on 3.5.0, which ships it, so the copy can never
run: deleted (8 x 385 lines), and the guard in sports.py and data_sources.py
collapses to a plain `from src.common.espn_dates import ESPN_MAX_LIMIT,
fetch_espn_scoreboard` -- a try/except with nothing behind it would only
hide which module was missing.

No behaviour change. On a 3.5.0 core the guard already took the core
branch, so the functions the plugins call are the ones they called before.
The deleted copies differed from core v3.5.0 only in typing (core #661's
mypy ratchet added `cast(...)` wrappers and one looser annotation); the
two ESPN_MAX_LIMIT / fetch_espn_scoreboard names each plugin imports exist
in core with identical bodies.

scripts/test_espn_dates_copies.py now asserts the sunset the way
check_scroll_adoption.py does for the scroll one: for each id in
SUNSET_PLUGINS the copy must be absent, no runtime module may import a
*_espn_dates bare name, every src.common.espn_dates import must be
unguarded, and at least one must exist. ufc still bundles its copy (a
separate PR); that copy is compared to core as an AST with annotations,
typing imports and cast() stripped, so core's typing-only edits no longer
fail it (on origin/main this test failed against core v3.5.0 for exactly
that reason). New self-checks cover the import finder.

football's and hockey's test_espn_date_ranges.py import core's espn_dates
instead of the deleted copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pies

Every team scoreboard's sports.py carried private copies of the helpers
core 3.5.0 ships in src.common.sports_helpers:

- module level: _MIN_WINDOW_DAYS, _MAX_WINDOW_DAYS, _clamp_window,
  _clamp_seconds, _logo_needs_refresh;
- on SportsCore: _upcoming_date_and_time_text, _mode_customization,
  _odds_color, _setting_int, _DWELL_REENTRY_GAP_SECONDS,
  _reset_dwell_on_reentry, _spread_weighted_order, _next_switch_index.

All deleted (~2,000 lines). SportsCore now lists SportsHelpersMixin after
SportsCoreSharedMixin, as the module's docstring prescribes. The
module-level names are imported under their old private spellings, so
sports.py's own callers and the tests that call sports._clamp_seconds /
sports._clamp_window are unchanged, and patching them on the module still
works.

No behaviour change:
- check_sports_helpers_parity.py against core v3.5.0 found all 104 copies
  in these eight plugins identical to core (normalised AST) before the
  deletion -- 13 names x 8 plugins, 0 drifted;
- none of the names is defined by SportsCoreSharedMixin or the Live/Recent
  shared mixins, and no plugin subclass overrides one, so every lookup
  that reached SportsCore's copy now reaches the mixin's, whose body is
  the same (checked per manager class, see the PR);
- the mixin adds two attributes the classes did not have: _favorite_key
  (nothing calls it) and a class default favorite_rotation_boost = 1,
  which SportsCore.__init__ always sets per instance and every reader
  already reads with getattr(..., 1).

test_check_sports_helpers_parity.py counted comparisons >= 100, which
adoption drops to ufc's 11; it now counts compared + absent and asserts
the eight scoreboards import the core module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each team scoreboard overrode SportsCoreSharedMixin._get_weeks_data
because the v3.4.0 mixin sent ESPN the lookback/lookahead window as a raw
date range, which ESPN has answered with 400 since 2026-09-15. Core
v3.5.0's mixin (src/common/sports_shared.py) routes it through
fetch_espn_scoreboard from src.common.espn_dates, the same helper the
overrides called, so the eight overrides (8 x 35 lines) are deleted and
the lookup now reaches the mixin.

Compared as ASTs (docstrings stripped), each override is identical to the
v3.5.0 mixin's body except for one leading statement, `date_str = ""`
(the analysis' F8). That difference is unreachable: the mixin's `except`
catches only requests.exceptions.RequestException, and the only
statements before its `date_str = ...` assignment are datetime.now(),
two timedelta subtractions and strftime -- none performs a request or can
raise RequestException. So wherever the handler reads date_str it is
already bound in both versions, and any other exception propagates from
both unchanged.

The names the body resolves are the same objects: the mixin's module
imports fetch_espn_scoreboard and ESPN_MAX_LIMIT from
src.common.espn_dates, as sports.py now does, and self.session /
self.headers / self.sport / self.league / schedule_look*_days come from
the instance. No test patches sports.datetime or sports.fetch_espn_scoreboard,
and football's test_espn_date_ranges.py exercises the lookback-window
recovery through the mixin now (it passes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reboards

08-shared-sports-code.md described <sport>_espn_dates.py as bundled in all
nine scoreboards and the sports helpers as guarded copies. After stage 1
only ufc bundles them; the eight team scoreboards floor on 3.5.0, import
src.common.espn_dates plainly and inherit SportsHelpersMixin. Records the
dropped _get_weeks_data override and the two methods kept for the
no-background-service path, and adds stage 1 to the sunset history.
CLAUDE.md non-negotiable #7 says the same in one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_logo_placeholder_refresh.py extracted _logo_needs_refresh from
afl-scoreboard/sports.py and required every logo loader to carry a copy.
The eight team scoreboards now import core's helper as
`logo_needs_refresh as _logo_needs_refresh` from src.common.sports_helpers,
so the guard failed (1 failure, 6 errors) though nothing had regressed.

A loader must now either define the helper or import core's (new test);
the byte-identity check covers the copies that remain (ufc's). The six
behaviour tests run, as subtests, against the first remaining plugin copy
and -- when a core checkout is found -- against core's function, which is
what the eight scoreboards now execute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 6319cb60-27c2-45ef-9169-f7b3bf8a096e


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 77 complexity

Metric Results
Complexity 77

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
ChuckBuilds merged commit 94b2c50 into main Sep 28, 2026
5 checks passed
@ChuckBuilds
ChuckBuilds deleted the claude/stage1-sunset-3.5.0 branch September 28, 2026 20:59
ChuckBuilds added a commit that referenced this pull request Sep 29, 2026
…orts consolidation stage 1b) (#564)

* test(ufc-scoreboard): harness fixture and goldens, the baseline before the mixin adoption

ufc-scoreboard had no test/harness.json, so CI's safety harness rendered it
from schema defaults against live ESPN, and no golden could catch a visual
change. This adds a deterministic fixture and 16 goldens (8 sizes x
ufc_recent / ufc_upcoming), generated from origin/main's plugin on core
v3.5.0, as the before-image for adopting core's shared sports mixins.

The fixture seeds the season cache key BaseUFCManager reads first
(ufc_scoreboard_schedule_2026) with cards either side of a frozen
2026-03-10 18:00 UTC: two finals and a postponed bout for Recent (the
postponed one must stay off it), two scheduled bouts for Upcoming, and one
card outside each window. Every bout has a seeded odds entry, so show_odds
stays on without an odds request and the upcoming card draws a line.

- display.use_short_date_format is on: the long date goes through the
  harness display manager's format_date_with_ordinal, whose %-d raises on
  Windows, and every bout then fails to parse.
- Fighters carry no ESPN id, so no headshot is fetched; cards render
  without headshots, as they do for a fighter ESPN has none for.
- Live is not rendered. The live manager is built and updated whatever
  show_live says, and _fetch_todays_games() is a direct ESPN call;
  test_mode plus a huge live_update_interval stops that fetch. The one
  request left is the headshot prefetch for test_mode's built-in fight
  (ESPN answers 404), which nothing rendered depends on: a run with DNS
  blocked produced byte-identical PNGs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(scripts): v3.5.0 is released; date its modules in the core-floor table

Core v3.5.0 (5b30052b) is tagged. Every module the table held as None
("in no tagged release") ships in it -- verified against
`git show v3.5.0:src/...` -- so each entry now names 3.5.0. The tag test
also found three modules missing from the table, split out of the store
manager in core #659 (store_install, store_registry, store_update); they
first ship in v3.5.0 too.

The test tracks v3.5.0 as a tag. Its "an unreleased module is reported"
case used sports_helpers, which is released now; it plants a fake None
entry instead, and two new cases pin a 3.5.0 module against 3.4.0 and
3.5.0 floors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6c19db1)

* ufc-scoreboard: floor on core 3.5.0 (minor bump to 1.16.0)

versions[0].ledmatrix_min_version and compatible_versions rise from 3.4.0
to 3.5.0, the first core release that ships src.common.espn_dates and
src.common.sports_helpers. The following commits delete the bundled
ufc_espn_dates.py and adopt core's shared sports mixins; the versions[]
notes and the CHANGELOG entry describe the whole series, including its one
behaviour change (the #599 idle wake-up). A floor raise is a minor bump, as
for the eight team scoreboards in #563. plugins.json regenerated with
update_registry.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ufc-scoreboard: delete the bundled ufc_espn_dates.py; import core's plainly

ufc bundled a copy of core's src/common/espn_dates.py behind a guarded
import, for cores that predate the module. It now floors on 3.5.0, which
ships it, so the copy can never run: deleted (385 lines), and the guard in
sports.py and data_sources.py collapses to a plain
`from src.common.espn_dates import ESPN_MAX_LIMIT, fetch_espn_scoreboard`.

No behaviour change. On a 3.5.0 core the guard already took the core
branch, so the functions the plugin calls are the ones it called before.
The deleted copy matches core v3.5.0 once type annotations, typing imports
and cast() wrappers are set aside (core #661's typing-only edits), the
comparison #563's test_espn_dates_copies.py makes.

scripts/test_espn_dates_copies.py required all nine copies. It gains a
SUNSET_PLUGINS set -- the name #563 uses, so the two merge as a union --
holding ufc-scoreboard: its copy must stay deleted and no module of it may
import a bundled *_espn_dates; its fetches are still scanned for a direct
`dates` request. Checked by planting a copy and a bare import (both fail).
The test's remaining failure against core v3.5.0 (the eight remaining
copies differ from core in typing) is on origin/main too; #563 fixes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ufc-scoreboard: inherit core's SportsHelpersMixin; delete the private copies

ufc's sports.py carried private copies of the helpers core 3.5.0 ships in
src.common.sports_helpers:

- module level: _MIN_WINDOW_DAYS, _MAX_WINDOW_DAYS, _clamp_window,
  _clamp_seconds, _logo_needs_refresh;
- on SportsCore: _mode_customization, _setting_int,
  _DWELL_REENTRY_GAP_SECONDS, _reset_dwell_on_reentry,
  _spread_weighted_order, _next_switch_index.

All deleted (the same treatment #563 gives the eight team scoreboards).
SportsCore now lists SportsHelpersMixin before ABC. The module-level names
are imported under their old private spellings, so sports.py's callers and
the tests that call sports._clamp_seconds / sports._clamp_window are
unchanged.

No behaviour change:
- check_sports_helpers_parity.py against core v3.5.0 found all 11 of ufc's
  copies identical to core's (normalised AST) before the deletion; it now
  reports the 13 names absent and the core module imported;
- no UFC subclass (MMA*, UFC*Manager) defines any of these names, so every
  lookup that reached SportsCore's copy now reaches the mixin's;
- the mixin adds _favorite_key, _odds_color and
  _upcoming_date_and_time_text, which nothing in ufc calls, and a class
  default favorite_rotation_boost = 1, which SportsCore.__init__ always
  overrides per instance and the one reader reads with getattr(..., 1);
- all 16 harness renders are byte-identical and the 23 plugin tests pass.

test_logo_placeholder_refresh.py required a local _logo_needs_refresh in
every logo loader. A loader may now import core's under the private name
instead (ADOPTED_RE, the pattern #563 uses); the byte-identity check skips
those and still pins the copies that remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ufc-scoreboard: inherit core's shared sports mixins; delete 18 private copies

ufc was the one scoreboard whose sports.py did not use core's
src.common.sports_shared (3.3.0). SportsCore, SportsLive and SportsRecent
now inherit SportsCoreSharedMixin, SportsLiveSharedMixin and
SportsRecentSharedMixin, in the order the eight team scoreboards use, and
their private copies are deleted (-442 lines):

- identical to core's as a docstring-stripped AST (15): SportsCore
  _should_log, _custom_scorebug_layout, cleanup, _game_divisions,
  _league_has_rankings, _normalise_divisions, _normalise_quality,
  _check_ranking_coverage, _favorites_first, _compose_selection;
  SportsLive _detect_stale_games, _note_live_fetch; SportsRecent __init__,
  _get_zero_clock_duration, _clear_zero_clock_tracking;
- same behaviour, different text (2): _get_weeks_data differs only by a
  leading `date_str = ""`, unreachable for the reason #563's c8224ec gives
  (nothing before the mixin's own assignment can raise the
  RequestException its handler catches); _round_robin_favorites names its
  matcher `matcher` instead of `team_in` and tests `is None` instead of
  `callable()` on the same getattr -- the same two closures chosen by the
  same condition;
- _idle_live_interval (1): the one behaviour change, below.

Kept, because core's behave differently: SportsCore._draw_scorebug_layout,
_get_layout_offset (core's routes through src.element_style) and
_draw_text_with_outline (core's takes element= and resolves colour from
it). They are defined on SportsCore itself, which precedes the mixin in
every MRO. SportsLiveSharedMixin and SportsRecentSharedMixin sit before
SportsCore in SportsLive / SportsRecent, and SportsCore defines none of
their names except __init__, where the Recent mixin's __init__ is the
deleted SportsRecent.__init__ verbatim and calls on to SportsCore's.

Behaviour change (the only one): the #599 idle wake-up. Before, ufc's
_idle_live_interval was the pre-#599 body, and update()'s
getattr(self, "_note_scheduled_start_candidate", None) found nothing, so
an idle league sat at live_idle_max_interval_seconds (900s default) and
could sleep through the first bout of a card. Now each live fetch offers
every non-live event's start_time_utc (MMA._extract_game_details sets it
from the card's date) to _note_scheduled_start_candidate, which keeps the
earliest future start as _next_scheduled_start_ts; _idle_live_interval
computes the same back-off as before and then clamps it: before a known
start, to max(update_interval, seconds until it); within 15 minutes after
it, to update_interval (live_update_interval, 30s by default); otherwise
unchanged. No request is added. Driven end to end through the real
UFCLiveManager.update with a scheduled card two hours out and 40 empty
looks: before 900/900/900/900s at T-2h/T-2m/T+5m/T+1h, after
900/120/30/900s. test_idle_league_backoff.py gains those cases; they fail
on the previous commit.

Checked otherwise unchanged:
- every attribute of all 12 UFC classes (SportsCore .. UFC*Manager)
  before and after: none lost; the rest resolve to core only where the
  body is the same (or is one of the three cases above); the mixins add
  attributes nothing in ufc reads (grep of every attribute access and
  getattr/hasattr string), apart from _note_scheduled_start_candidate;
- the three managers built through the plugin from the harness config
  hold the same instance attributes with the same values;
- all 16 harness renders byte-identical; 23 plugin tests pass.

check_sports_drift.py reports SportsCore._league_has_rankings back in
sync (ufc's was a divergent copy); baseline regenerated, dropping that
one entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: ufc is on core's shared sports code and espn_dates

08-shared-sports-code.md counted nine bundled <sport>_espn_dates.py copies
and said every scoreboard imports src.common.sports_shared, which was not
true of ufc until now. The table row drops to the eight team scoreboards and
names ufc's sunset; the sports_helpers section records that ufc (1.16.0,
floor 3.5.0) inherits core's four mixins, what its sports.py keeps, and that
the adoption brought it the #599 kickoff clamp. "Floors at 3.4.0" becomes
"3.4.0 or above", the wording #563 uses too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant