fix(scoreboards): import datetime, timezone and ZoneInfo in game_renderer - #339
Merged
Conversation
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: Pro Plus 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
|
…erer
`scroll_card.date_format` offers "weekday" ('weekday "Fri Sep 19"') in
every scoreboard's config_schema.json, but seven of the eight
game_renderer.py copies used datetime, timezone and ZoneInfo without
importing any of them. Selecting that option raised NameError, and
neither helper catches it:
_weekday_for except (ValueError, TypeError)
_card_tzinfo except (KeyError, ValueError, TypeError, OSError)
so it propagated out of the render instead of falling back to the plain
date. Reproduced directly:
>>> GameRenderer(128, 64, {"scroll_card": {"date_format": "weekday"}})
... ._format_game_date("10/12", game)
NameError: name 'datetime' is not defined
hockey-scoreboard already had the imports and rendered the option
correctly; the other seven now match it. baseball needed only timezone
and ZoneInfo -- it already imported datetime.
Affected: afl, baseball, basketball, football, lacrosse, nrl, soccer.
game_renderer.py is a shared-shape module, so all lineage members are
fixed here per docs/plugin-development/08-shared-sports-code.md.
The except clauses are left as they are: with the names resolving, an
unusable timezone falls back to UTC rather than raising, which the new
guard asserts.
Why nothing caught this: the render harness and every golden image use
the default "abbrev" format, so the weekday branch never executed. The
new scripts/test_weekday_date_format.py exercises the option itself
across all eight copies -- each in its own subprocess, since they all
import as the bare name `game_renderer` and would shadow one another.
It checks the option does not raise, renders an actual weekday, applies
the configured timezone (01:00 UTC Tuesday is still Monday in New York,
so a "Tue" result would mean the conversion was skipped), and that a
bogus zone name falls back instead of blowing up. Removing the imports
again fails it.
Verified: harness green for all seven (24/24, lacrosse 16/16); each
plugin's own suite matches its origin/main baseline exactly, pass for
pass and fail for fail; module-collision check clean.
Rebuilt on main after #336, which bumped all seven versions and touched
the same files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ChuckBuilds
force-pushed
the
fix/scoreboard-weekday-nameerror
branch
from
August 30, 2026 03:55
e3c49c5 to
e5aac62
Compare
ChuckBuilds
added a commit
that referenced
this pull request
Aug 30, 2026
…dage The possession football was placed a fixed 3px from the down & distance text and guarded only by `ball_x_center > 0` -- a check that a number had not come out negative, not that the ball fit anywhere in particular. On a 64px-wide panel it landed on the timeout bars; the guard could not see the right-hand edge at all, so home possession pushed it off a narrow panel; and on the adaptive 256x128 card it was drawn at x 262-274 of a 256px card, entirely off the edge. The ball's size is unchanged -- a fixed 7x5 on the classic paths, px() radii on the adaptive card -- and so is the wording beside it. The ball is placed around the text now, never the other way round. Where a row genuinely cannot hold both (a 64x32 leaves ~30px between the timeout bars and the text alone wants 29) the down & distance wins and the ball is omitted rather than the wording shortened. Fixed on all three paths that draw it: football.py's classic scorebug, GameRenderer's classic card (scroll mode), and its adaptive card. Separately, the adaptive card drew the down & distance in PressStart2P, ~4x wider per character than the 4x6 face: on a 192x48 panel that rendered "3rd & 8 at KC 42" 128px wide against the classic layout's 65px, and on a 256x128 it reached the full 256px, clipping the line at both ends. That line alone now uses a dedicated ADAPTIVE_LADDER_DETAIL on the compact 4x6 face, both rungs verified crisp (measure_font_crispness == 0.0). The clock, status band, records and dates keep ADAPTIVE_LADDER_TEXT and the face they had -- every recent and upcoming golden is pixel-identical to main. With the narrower face the yardage also fits where it previously did not. The long form was gated on `display_width > 128`, a proxy for "is there room" that got a 128x64 wrong: the string is 65px in the 4x6 face and the free band there is wider. It is now preferred whenever it fits the space the timeout bars and records leave free, the same trade the classic layout already makes via _fit_text. ADAPTIVE_LADDER_TEXT and the possession helpers exist only in this plugin's game_renderer.py, so no lineage port is needed. test_possession_ball_has_room.py pins the two properties that matter: the ball is exactly the size it always was, and the down & distance drawn is identical whether possession is home, away or unknown -- so the ball can never be paid for out of the wording. 671 checks across three render paths x eight harness sizes. Harness 24/24. Plugin suite 26 passed with the same two pre-existing environment failures as main. Rebased onto main after #336, which bumped the version and touched the same renderer; football goes to 2.27.2 since #339 takes 2.27.1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…bprocess Codacy gates newly-introduced issues, and spawning a subprocess per plugin introduced two Bandit findings (B404 importing subprocess, B603 call with untrusted input) that the repo's existing users of it -- run_plugin_tests.py and test_odds_centre_collision.py -- are grandfathered against. Subprocesses were only ever a way to keep eight modules that are all called `game_renderer` from shadowing one another. importlib.util does that directly: each copy is loaded under a name of its own, with the plugin directory on sys.path for the duration because one copy (baseball) imports a plugin-local helper. Fewer moving parts and faster, and the file is now clean under pyflakes, pycodestyle and Bandit alike. Same 40 checks, same coverage: stripping the imports from a plugin still fails it with the original NameError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 tasks
ChuckBuilds
added a commit
that referenced
this pull request
Aug 30, 2026
…dage (#337) The possession football was placed a fixed 3px from the down & distance text and guarded only by `ball_x_center > 0` -- a check that a number had not come out negative, not that the ball fit anywhere in particular. On a 64px-wide panel it landed on the timeout bars; the guard could not see the right-hand edge at all, so home possession pushed it off a narrow panel; and on the adaptive 256x128 card it was drawn at x 262-274 of a 256px card, entirely off the edge. The ball's size is unchanged -- a fixed 7x5 on the classic paths, px() radii on the adaptive card -- and so is the wording beside it. The ball is placed around the text now, never the other way round. Where a row genuinely cannot hold both (a 64x32 leaves ~30px between the timeout bars and the text alone wants 29) the down & distance wins and the ball is omitted rather than the wording shortened. Fixed on all three paths that draw it: football.py's classic scorebug, GameRenderer's classic card (scroll mode), and its adaptive card. Separately, the adaptive card drew the down & distance in PressStart2P, ~4x wider per character than the 4x6 face: on a 192x48 panel that rendered "3rd & 8 at KC 42" 128px wide against the classic layout's 65px, and on a 256x128 it reached the full 256px, clipping the line at both ends. That line alone now uses a dedicated ADAPTIVE_LADDER_DETAIL on the compact 4x6 face, both rungs verified crisp (measure_font_crispness == 0.0). The clock, status band, records and dates keep ADAPTIVE_LADDER_TEXT and the face they had -- every recent and upcoming golden is pixel-identical to main. With the narrower face the yardage also fits where it previously did not. The long form was gated on `display_width > 128`, a proxy for "is there room" that got a 128x64 wrong: the string is 65px in the 4x6 face and the free band there is wider. It is now preferred whenever it fits the space the timeout bars and records leave free, the same trade the classic layout already makes via _fit_text. ADAPTIVE_LADDER_TEXT and the possession helpers exist only in this plugin's game_renderer.py, so no lineage port is needed. test_possession_ball_has_room.py pins the two properties that matter: the ball is exactly the size it always was, and the down & distance drawn is identical whether possession is home, away or unknown -- so the ball can never be paid for out of the wording. 671 checks across three render paths x eight harness sizes. Harness 24/24. Plugin suite 26 passed with the same two pre-existing environment failures as main. Rebased onto main after #336, which bumped the version and touched the same renderer; football goes to 2.27.2 since #339 takes 2.27.1. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Chuck <chuck@example.com>
Football's manifest resolved by renumbering this branch's release entry to 2.27.3, dated today, on top of main's 2.27.1 and 2.27.2 -- 2.27.1 was claimed here before #335 took it. plugins.json regenerated by the hook. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YF7Q48EYCCkU1Vs932uDY1
ChuckBuilds
pushed a commit
that referenced
this pull request
Aug 30, 2026
Seven manifests resolved by keeping each plugin's colour release on top of main's arrays; plugins.json regenerated by the hook. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YF7Q48EYCCkU1Vs932uDY1
ChuckBuilds
added a commit
that referenced
this pull request
Aug 30, 2026
main landed four commits that touch the same files as this branch: 0855372 fix(scoreboards): import datetime, timezone and ZoneInfo in game_renderer (#339) 6e8b863 fix(football-scoreboard): give the possession ball room (#337) 85a5555 feat(football): favourites first, ranked others, and a rotation (#335) 93bd554 feat(sports): apply the matchup separator settings to every mode (#336) 17 conflicts: eight sports.py, eight manifest.json, and the generated plugins.json. sports.py -- every conflict was the same shape. #336 moved the upcoming card's centre (matchup separator, or the date and time stacked, or nothing) out of the inline scorebug code and into _draw_upcoming_center_switch, which is exactly where this branch had scaled the stacked date/time offsets. Those edits are dead weight now: SportsUpcoming sets _DRAWS_SCORE = False, so _time_font_size() returns the un-grown 8 and max(7, 8-1) / max(9, 8+1) are the original 7 and 9. Resolved to main's side throughout, then re-asserted _DRAWS_SCORE on the SportsUpcoming that main's restructure left behind (football's #335 moved that class's body, so the flag came away with the hunk). Audited rather than assumed: diffing each resolved file against origin/main leaves 200-300 added lines -- the helper block and its comments -- and 3 to 6 removed, each one a line this branch deliberately replaced (return fonts, the 1.5x max_width, score_y's -14 and -3, date_y's -7, football's two "00-00" probes and its centre-gap return). No upcoming-card line is removed, so #336's relocation is intact. manifest.json -- main released the very version numbers this branch had claimed and then some, so the branch entries could not be kept. Took main's manifest whole, including its full versions[] history, and re-stacked this branch's entry on top a minor above where main now sits: afl 1.16.0, baseball 1.34.0, basketball 1.23.0, football 2.28.0, hockey 1.19.0, lacrosse 1.18.0, nrl 1.15.0, soccer 2.18.0. CHANGELOG headings follow. plugins.json regenerated. Verified against the merged origin/main across ten sizes (64x32, 128x32, 256x32, 64x64, 96x48, 192x48, 128x64, 256x64, 256x128, 384x96): * 240 harness renders, all PASS, no overflow or fill warnings. * All 80 upcoming renders byte-identical to main -- the screen draws no score and this branch leaves it alone. * Live and recent byte-identical at 64x32, 128x32 and 256x32 for every plugin, and football also at 64x64. Taller panels take the larger score. * Adaptive layout: only 192x48 live and recent move; the "VS" separator and every other size are byte-identical. * Plugin self-test failures identical to origin/main (11 pre-existing on this machine), including main's new test_switch_upcoming_center, which covers the region resolved to its side. * check_module_collisions clean across 43 plugins. Co-Authored-By: Claude Opus 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.
Summary
scroll_card.date_formatoffers"weekday"(documented asweekday "Fri Sep 19") in every scoreboard'sconfig_schema.json, but seven of the eightgame_renderer.pycopies useddatetime,timezoneandZoneInfowithout importing any of them. Choosing that option raisedNameError, and neither helper'sexceptcatches it —_weekday_forcatches(ValueError, TypeError),_card_tzinfocatches(KeyError, ValueError, TypeError, OSError)— so it propagated out of the render instead of falling back to the plain date.Reproduced directly:
hockey-scoreboardalready had the imports and rendered the option correctly. The other seven now match it.Type of change
Plugin(s) affected
afl,baseball,basketball,football,lacrosse,nrl,soccer(all-scoreboard).game_renderer.pyis a shared-shape module, so all lineage members are fixed in this PR perdocs/plugin-development/08-shared-sports-code.md.baseballneeded onlytimezoneandZoneInfo— it already importeddatetime. Hockey is untouched.Related issues
N/A — found by pyflakes (
undefined name) while reviewing a static-analysis report on #337, then traced to a reachable config path.Why nothing caught it
The render harness and every golden image use the default
abbrevformat, so the weekday branch never executed. Two lines of imports were missing for as long as the feature has existed and no test touched it.Test plan
origin/mainbaseline exactly, pass for pass and fail for fail — baseline captured by stashing this change, so the pre-existing Windows-environment failures are accounted for rather than assumed.check_module_collisions.pyclean across 43 plugins.New guard:
scripts/test_weekday_date_format.pyExercises the option itself across all eight copies. Every copy is called
game_renderer, so each is loaded under its own module name viaimportlib.utilrather than imported — otherwise whichever landed insys.modulesfirst would be silently tested eight times. Per plugin it asserts:date_format: "weekday"does not raiseTueresult would mean the conversion was skipped or silently fell back to UTC40 checks, all passing. Stripping the imports from any one plugin fails it with the original
NameError. The timezone assertion self-skips where the platform has no tz database (Windows withouttzdata), since the renderer correctly falls back to UTC there — an environment limitation, not a defect.Required for plugin changes
versionin all seven manifests, new entry at the top ofversionsclass_name/entry_pointunchangedplugins.jsonregenerated withupdate_registry.py— only the sevenlatest_versionfields changedChecklist
CONTRIBUTING.mdNotes for reviewer
Rebuilt on
mainafter #336 landed, which bumped all seven versions and touched the same files. Versions here are the post-#336 patch bumps (e.g. football2.27.0 → 2.27.1).Version coordination with #337: that PR takes
football-scoreboardto 2.27.2, this one to 2.27.1, so they merge in either order without a re-bump. They are otherwise independent and touch different parts ofgame_renderer.py— imports here, the down & distance ladder there.The
exceptclauses are deliberately left alone. Once the names resolve, an unusable timezone falls back to UTC rather than raising, which the new guard asserts — soNameErrorwas not masking a needed fallback, it was preventing one.pyflakesreports one remaining finding in these files,'freetype' imported but unused, which is pre-existing and looks intentional (an availability probe). Left as is.