The quick / easy solution is to just make sure we're updating the player records on a schedule #495
No reviewers
Labels
No labels
Kestra
bug
enhancement
someday
subtask
☁️ api
🎛️ infrastructure
🐞 sentry
📆 2025 Season
📝 pages
allpicks
📝 pages
picks
📝 pages
standings
🚀 performance
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
johnsturgeon/tgfp-web!495
Loading…
Reference in a new issue
No description provided.
Delete branch "a-player-putting-their-picks-in-after-games-have-auto-started-do-not-re-calculate-their-standings-434"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
It's a DB only operation so running it hourly is just fine.
Resolves #434
This is a clean, low-risk change. I've reviewed the scheduler wiring, the new job, the dispatcher interaction, and the test-guard changes. Here's my review.
Review
This PR adds an hourly
update_player_recordsjob and, in passing, converts mostadd_jobcalls from dotted-string refs to direct callables plus addsnext_run_timeto the awards job for consistent startup behavior. The core change is sound and the test-guard rewrite correctly reflects the new "callables everywhere except the dispatcher" convention; I found no blocking issues.Worth fixing
schedule_update_all_awards(app/jobs/scheduler.py:182). The diff addsnext_run_time=datetime.now(timezone.utc)to the awards job, which is not mentioned in the PR description (it only claims player records + a typo). The effect is that a fullupdate_all_awardsrecalculation now fires on every process restart/deploy, in addition to the Tuesday cron. This appears intentional given the2cc4d51commit message ("running all scheduled jobs once on startup"), and it's consistent with the other interval jobs — but it's a heavier operation than a player-record rebuild, so it's worth confirming that's desired and calling it out in the PR description rather than shipping it silently under a "player records" title.Nits
Two job IDs can now run
update_player_recordsconcurrently. The dispatcher already launches it vialaunch_update_player_records()(idUPDATE_PLAYER_RECORDS,app/jobs/dispatcher.py:155), and this adds a second registration underSCHEDULED_UPDATE_PLAYER_RECORDS(app/jobs/scheduler.py:186). APScheduler'smax_instances=1is per-job-id, so a game finishing on the hour could run both at once.recalculate_recordis a full idempotent rebuild (app/models/player.py:110), so the worst case is duplicated work / transient row contention rather than corruption — safe, but worth a one-line comment noting the overlap is intentional and benign.Hourly scheduling amplifies a pre-existing N+1.
update_player_recordscallsrecalculate_recordper active player (app/jobs/update_player_records.py:9-10), and each call re-runsWeek.all_weeks_for_current_season(session)(app/models/player.py:114). That per-player week query isn't introduced here, but this PR puts it on an unconditional hourly cadence for all active players. Hoisting the week-id set out of the loop would be a cheap follow-up. Out of scope for this PR.No test asserts the new job is wired up.
test_integrity.pyonly guards string-ref resolution, so neither the newschedule_update_player_recordsregistration nor its inclusion inlifespan(app/main.py:127) is covered by any test. Consistent with the existingschedule_*functions (none are tested either), so this is a pre-existing gap, not a regression — just flagging it since the whole point of #422's guard was to catch scheduler drift.The string-ref → callable conversion is actually a net improvement for safety: callables are resolved at
scheduler.pyimport time, so a rename now fails in CI on import rather than at fire time in production, and the dispatcher (the one genuine import-cycle case) correctly stays a string and remains covered bytest_scheduled_jobs_resolve.