This is the end of Phase II - the week table now has the attributes to handle state of the week #415
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!415
Loading…
Reference in a new issue
No description provided.
Delete branch "phase-2-week-state-machine-408"
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?
Review task #408 for detail of what was done
Resolves #408
This is the end of Phase II schema work; I've verified the migration chain (linear,
6f69813c34afis the current head), the status‑vocabulary cutover, and the new tests. Overall the change is clean and unusually well‑documented, but there's one silent‑correctness risk around thegame_statusvalue change worth confirming before merge.Review
The PR renames the
game_statusvocabulary (STATUS_FINAL→FINAL, etc.) via the newGameStatusStrEnum, adds nullable odds columns togame, addsgame_state/odds_statetoweek, and pivotsWeek.current()to return the row (callers now use.info). The test scaffolding (in‑memory SQLite fixture,make_game/make_week_of, op.env‑driven env seeding) is a genuinely good addition. Two things are worth a hard look and a few minor items follow.Blocking
game_statusvocabulary — existing rows silently mis‑classify.app/models/game.py:80-92andapp/tank01_api/tank01_api.py:31-37now emit/compare bare values ("FINAL","SCHEDULED"), butgame.game_statusis a plain string column and any row already stored with the oldSTATUS_*spelling is not rewritten byd183ea00f22f. New writes viaupdate_game.py:44/create_picks.py:52are fine, but a game that already reachedSTATUS_FINALand is no longer polled will readis_final == Falseforever — which flows into award recalculation and the_get_current_week_statefold (a staleSTATUS_FINALis neither final nor cancelled, so a truly‑done week never reachesALL_FINAL). Thec3f1a5d84b20cutover truncatedgame, so this only bites if any games have been created since (it is preseason now, andcreate_picksruns weekly). Please confirm thegametable is empty in prod, or add anUPDATE game SET game_status = ...step to the migration. It's a cheap fix for a failure that is invisible until standings look wrong.Worth fixing
assertused for control‑flow validation in_get_current_week_state(app/jobs/sync_week_state_from_game_status.py:26-31). Running underpython -Ostrips it, and it's logically always true given the preceding filter, so it buys nothing at runtime while reading as a real guard. Either drop it or turn it into an explicit raise if you actually want to catch an unexpected status leaking through.sync_week_state_from_game_status()(the DB‑writing wrapper) is untested and unwired. Tests cover the pure_get_current_week_statethoroughly, but the function that readsWeek.current, loads games, and commitsgame_statehas no coverage, and nothing schedules it yet (grepshows it only in its own file + tests). If wiring is deferred to a later phase that's fine — but the commit/persist path is exactly the part the pure‑function tests can't protect, and it's easy to add now with the newsessionfixture.Nits
Picks template now assumes a favorite that the model no longer guarantees.
favorite_team_idis nullable andunderdog_teamcorrectly returnsNone, butapp/templates/picks.j2:48still doesgame.favorite_team.long_name/game.spreadwith no guard. A line‑less game reaching the picks page renders a blank favorite /Nonespread. The design notes say theLOCKEDtransition will guarantee non‑null before the page is released — that gate isn't in this PR, so until it lands the template is a step ahead of its invariant.now()is shared as a single element instance across bothcreated_atandupdated_at(and asserver_default+onupdate) inapp/models/base.py:26-34. SQLAlchemy tolerates this for function elements, so no action needed — just noting it in case columns get copied around later.odds_state/OddsState.LOCKEDare modeled but never written anywhere yet — expected for Phase II, flagging only so it's a conscious deferral rather than a forgotten wire‑up.Nice touch:
test_a_week_of_nothing_but_postponements_reads_as_pendingdocumenting a knowingly‑unhandled case so it stays a decision rather than a rediscovered bug.pylint R0801 flagged the eleven-field Game construction duplicated across tests/test_week_state.py and tests/test_game_model.py. Fair catch: none of those fields below game_status is ever the subject of a test, so it was pure scaffolding copied twice. Moved to conftest as make_game()/make_week_of(), which is the arrangement pyproject.toml already sets up ("Repo root on sys.path so tests can import tests.conftest helpers") and how load_fixture is already shared. Call sites now write only the fields they assert on, and a new required column on Game breaks one place instead of two. Worth knowing: scripts/test_and_lint.sh lints $(git ls-files '*.py'), so it never saw either file while they were untracked and reported 10.00/10 throughout. R0801 only surfaced when pylint was pointed at them directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>