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

fix(sports): honour every pending kickoff in the idle back-off, not just the first - #772

Merged
ChuckBuilds merged 1 commit into
mainfrom
fix/every-pending-kickoff
Oct 5, 2026
Merged

ChuckBuilds merged 1 commit into
mainfrom
fix/every-pending-kickoff

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

What

_note_scheduled_start_candidate() kept a single kickoff. While that kickoff was inside its 15-minute grace, every later kickoff was refused (candidate < current is false). Once the grace ended, the later kickoff had already passed, so it was refused again as "already past". The result: when two favourites kick off within 15 minutes of each other, the second one gets no grace of its own. If the first game isn't live by then (a rain delay, a postponement, or ESPN slow to flip it) and ESPN hasn't flipped the second game either, the back-off returns to its ceiling. The second game can then be noticed up to 15 minutes late.

Fix

  • Later kickoffs now wait in a short queue, _later_scheduled_starts, which keeps the earliest 8.
  • When the current kickoff's grace ends, a module function (_current_scheduled_start) promotes the earliest queued kickoff that is still inside its own grace. That can be one that has already passed. Queued kickoffs past their own grace are dropped.
  • _next_scheduled_start_ts keeps its meaning: it is the kickoff honoured now. Code that reads it or sets it by hand behaves as before. ufc's test_idle_league_backoff.py does this, and it still passes against this branch, as do all nine plugins' test_idle_league_backoff.py and football's test_kickoff_wakes_the_idle_poll.py.
  • The back-off stays bounded. A kickoff holds the live cadence for at most its own grace, so a postponed game costs the same quarter-hour as before. The queue is capped.
  • The mixin's method set is unchanged (no new method), so the plugins' test_stage3_mixin_copies.py MOVED table needs no entry. There is no new src/ module, so check_min_core_version.py needs no entry either.

Tests

TestLiveMixin in test/test_sports_shared.py gains these:

  • test_every_pending_kickoff_gets_its_grace, a table of 8 schedules. Each one replays the idle loop on a fake clock: every look offers each game not yet live, then sleeps for whatever _idle_live_interval() returns. Every game must be seen live within one 30 s poll of going live. The rows cover:
    • first postponed, or delayed 20 min, with the second 10 min later and flipped 14 min late
    • three kickoffs inside one grace
    • two kickoffs at the same time, then a third
    • 15 min apart
    • twenty kickoffs a minute apart, which overflows the queue
    • two baseline rows that passed before as well
  • Four focused tests:
    • a postponed kickoff costs one grace, then the back-off returns
    • a stale queued kickoff is skipped in favour of one still in its grace
    • the queue keeps the earliest kickoffs
    • duplicate offers are kept once

Mutation check:

Mutant New tests failing
original code 10
never queue 10
promote only future kickoffs (not passed-but-in-grace) 8
no swap when an earlier kickoff arrives 1
queue capped at 1 4
no pruning of stale queued kickoffs 1
no dedupe 1

Full suite on Windows, branch vs an origin/main worktree: the FAILED/ERROR id lists are identical (74 ids each, all pre-existing Windows failures). The branch has 12 more passes, which are the new tests. mypy 1.20.2: scripts/check_types.py reports only the pre-existing fetch_service.py unused-ignore, which also fails on main locally. sports_shared.py (not on the ratchet) has the same error count as main.

Related

Plugins PR (draft, stacked on #620) applies football 3.18.7's rule to the other eight scoreboards: a board offers only the kickoffs of games it would show. That PR and this one are independent; neither needs the other.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved live-game detection when multiple kickoffs are scheduled close together. Each eligible kickoff now receives its own grace period, helping games be detected promptly even when live-status updates are delayed or missing.
    • Postponed games no longer extend live polling beyond their own grace period, and outdated scheduled kickoffs are skipped.

…ust the first

_note_scheduled_start_candidate kept one kickoff. While it was inside its
15-minute grace every later kickoff was refused, and by the time the grace
ended the later one had passed and was refused again as already past. So of
two favourites kicking off within 15 minutes of each other, the second lost
its own grace: if the first game was not live by then (a rain delay, a
postponement, ESPN slow to flip it) and ESPN had not flipped the second
either, the back-off went straight back to its ceiling and the second game
was noticed up to that late.

Later kickoffs now wait in a short queue (_later_scheduled_starts, the
earliest 8). When the current kickoff's grace ends, the earliest queued one
still inside its own grace takes over -- including one that has already
passed. A kickoff still holds the live cadence for at most its own grace, so
a postponed game costs the same quarter of an hour as before, and
_next_scheduled_start_ts keeps its meaning for anything that reads or sets
it. The promotion is a module function, so the mixin's method set is
unchanged.

Table tests replay the idle loop on a fake clock over kickoff schedules;
mutation-checked (the old code fails 10 of the new tests).

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

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2af94ec3-9626-471c-ae26-fa0e3e8fafab
📥 Commits

Reviewing files that changed from the base of the PR and between b638b91 and 6caf69f.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/common/sports_shared.py
  • test/test_sports_shared.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The sports live manager now queues up to eight scheduled kickoffs. When a kickoff’s grace period expires, the manager can promote the earliest queued kickoff that remains within its grace period. Regression tests cover queue selection, polling cadence, and live-game detection.

Changes

Sports kickoff queue

Layer / File(s) Summary
Kickoff queue and polling cadence
src/common/sports_shared.py, CHANGELOG.md
The manager retains distinct kickoff candidates in a sorted, bounded queue and promotes eligible entries as grace periods expire. The polling clamp uses the active kickoff.
Kickoff queue regression coverage
test/test_sports_shared.py
Fake-clock tests cover overlapping and delayed kickoffs, stale and duplicate candidates, queue capacity, polling cadence, and game detection timing.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 6caf6

The kickoff queue has no identified merge-blocking issue; merge after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 6caf6

The change retains overlapping kickoffs without increasing polling speed or allowing unlimited queued state. No introduced security failure was established. Deployed plugin integration and concurrent access remain unverified, limiting confidence in the complete runtime assessment.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated effect is confined to each live-manager host's kickoff state and returned polling interval. Scoreboard plugins consuming the shared mixin may inherit the changed policy, but their deployed count, runtime ordering, and aggregate request exposure were not established.

Trust Boundaries and Controls

  • observed — The candidate consumer retains the existing checks for dictionary input, live or halftime status, timestamp availability, conversion errors, and already-past starts. Its changed path mutates scheduling state and returns intervals; it introduces no credential, authorization, or sensitive-data operation. Production timestamp provenance remains unverified.

Resilience and Maintainability Implications

  • observed — Cleanup is timestamp-based rather than game-identity-based: live observations do not remove queued kickoff timestamps in the shared methods. Remaining timestamps can sustain extra polling only while eligible for grace, and sequential promotion prunes stale entries. Concurrent mutation safety and plugin-specific reset behavior were not established.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 2 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: honoring each pending kickoff during the sports idle back-off.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 2 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Metric Results
Complexity 11

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 6fb2dc3 into main Oct 5, 2026
15 checks passed
@ChuckBuilds
ChuckBuilds deleted the fix/every-pending-kickoff branch October 5, 2026 13:52
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