Repository navigation
scoreboards: require core 3.5.0; drop bundled espn_dates and sports_helpers (sports consolidation stage 1a) - #563
Merged
Merged
Conversation
…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>
Contributor
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 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 | 77 |
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_datesandsrc.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
versions[0].ledmatrix_min_versionandcompatible_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 aversions[]entry and a CHANGELOG entry, andplugins.jsonis regenerated.<sport>_espn_dates.pyremoved: the 8 copies (385 lines each) are deleted, and the guarded import becomes a plainfrom src.common.espn_dates import .... The copies differed from core v3.5.0 only in type annotations (core Feat/chromaclock wide #661).sports_helperscopies removed: the parity script found all 104 method copies identical to core before deletion.SportsCorenow inheritsSportsHelpersMixin. The module-level helpers are imported under their old private names, so existingsports._clamp_windowcalls and patches keep working._get_weeks_dataoverrides removed: compared as ASTs, they match core v3.5.0 except for a leadingdate_str = "". Core doesn't need that line: onlydatetimearithmetic runs before the assignment, and the handler catches onlyRequestException._background_fetches_espn_rangesand_fetch_season_directlystay, because they serve the no-background-service path.test_espn_dates_copies.pygets aSUNSET_PLUGINSlist, likecheck_scroll_adoption. It fails if a copy or an import guard comes back.test_logo_placeholder_refresh.pyaccepts a plugin that uses core's helper.check_min_core_version.pydates its modules to 3.5.0. That table was missing thestore_*modules from core scoreboards: require core 3.8.7; use its game view model (sports family 10 adopt) #659, so they're added.Net: 50 files, +762 / −5,740.
Verification
All runs used core v3.5.0, comparing origin/main against this branch.
run_plugin_tests.pyfor the 8 ids gives the same result per test on both trees (299 passed, 2 failed). The 2 failures happen on main too: baseballtest_odds_placementand footballtest_football_plugin, which asks for input.scripts/test_*.pyand check script passes, 53 in all. That includescheck_version_bump.pynaming the 8 ids andupdate_registry.py --check.SportsHelpersMixinnow come fromsrc.common.*, and no bundled module is loaded.dir()of every manager class before and after loses no attributes._get_weeks_data, which differs only by thedate_str = ""line.SportsCoreimport resolves to core's mixins.Overlap
This conflicts with #553 on the baseball, football, hockey and lacrosse
manifest.json,CHANGELOG.mdandplugins.json. Whichever PR merges second has to move itsversions[0]entry back on top and bump its version again.🤖 Generated with Claude Code