Tank01 provider DTO and week-pinned client #431

Merged
johnsturgeon merged 7 commits from create-the-pydantic-models-in-the-shape-of-the-tank-game-428 into main 2026-08-31 17:02:32 +02:00
Owner

First slice of the provider-boundary rewrite (#427). Draft on purpose --
further slices land on this branch as they are done.

What is here

  • app/tank01_api/schemas.py -- Pydantic mirror of /getNFLGamesForWeek.
    Tank01's field names verbatim, every field a str. Seven of the seventeen
    fields on the wire: the ones we actually consume.
  • app/tank01_api/client.py -- Tank01Client, pinned to one week, fetches
    and memoizes, returns DTOs and nothing pool-shaped.
  • tests/test_tank01_client.py -- 13 tests.

Nothing is wired into the running app. tank01_api.py is untouched, so this
changes no behavior.

Two decisions worth reviewing

gameStatusCode is deliberately not modelled. This endpoint reports it
stale -- the schedule says "0" for games /getNFLScoresOnly knows are live
or final. Leaving it off the DTO makes "status comes from the scores payload"
structural rather than a comment someone can miss.

gameDate is carried verbatim and must never be derived. It is the
provider's join key to /getNFLBettingOdds?gameDate=, in US Eastern.
Deriving it from gameTime_epoch looks safe and is wrong for every game
kicking off after 8pm ET -- the UTC date has already rolled over, so it would
silently request odds for the wrong day on every Thu/Sun/Mon night game. See
#430.

Verification

162 passed, pylint 10.00/10, flake8 clean.

Version bump held until this leaves draft.

🤖 Generated with Claude Code

First slice of the provider-boundary rewrite (#427). Draft on purpose -- further slices land on this branch as they are done. ## What is here - `app/tank01_api/schemas.py` -- Pydantic mirror of `/getNFLGamesForWeek`. Tank01's field names verbatim, every field a `str`. Seven of the seventeen fields on the wire: the ones we actually consume. - `app/tank01_api/client.py` -- `Tank01Client`, pinned to one week, fetches and memoizes, returns DTOs and nothing pool-shaped. - `tests/test_tank01_client.py` -- 13 tests. Nothing is wired into the running app. `tank01_api.py` is untouched, so this changes no behavior. ## Two decisions worth reviewing **`gameStatusCode` is deliberately not modelled.** This endpoint reports it stale -- the schedule says `"0"` for games `/getNFLScoresOnly` knows are live or final. Leaving it off the DTO makes "status comes from the scores payload" structural rather than a comment someone can miss. **`gameDate` is carried verbatim and must never be derived.** It is the provider's join key to `/getNFLBettingOdds?gameDate=`, in US Eastern. Deriving it from `gameTime_epoch` looks safe and is wrong for every game kicking off after 8pm ET -- the UTC date has already rolled over, so it would silently request odds for the wrong day on every Thu/Sun/Mon night game. See #430. ## Verification 162 passed, pylint 10.00/10, flake8 clean. Version bump held until this leaves draft. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add the Tank01 provider DTO and a week-pinned client
All checks were successful
Claude Code Review / claude-review (pull_request) Has been skipped
Python FastAPI Jinja Linting / build (pull_request) Successful in 23s
Tests / pytest (pull_request) Successful in 11s
314030d54e
First slice of the provider-boundary rewrite: a Pydantic mirror of
/getNFLGamesForWeek and a client that fetches and memoizes it.

The DTO speaks Tank01's vocabulary verbatim -- camelCase names, every field
a `str` -- because coercing here would move translation into the DTO, and
translation belongs in the mapper. It models the seven fields we consume of
the seventeen on the wire; `gameStatusCode` is deliberately absent, since
this endpoint reports it stale and status must come from /getNFLScoresOnly.

The client takes `season_type` as the int the `week` table stores and encodes
it to "reg" at the wire, so no caller has to learn Tank01's spelling. Nothing
is wired into the running app yet -- tank01_api.py is untouched.

resolves #428

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Name the DTO and client property after the endpoint
All checks were successful
Claude Code Review / claude-review (pull_request) Has been skipped
Python FastAPI Jinja Linting / build (pull_request) Successful in 23s
Tests / pytest (pull_request) Successful in 10s
74d96fd51b
Tank01ScheduleGame put three names on one concept -- /getNFLGamesForWeek,
Tank01ScheduleGame, client.schedule -- at the layer whose whole job is to
mirror the provider faithfully. The `Tank01` prefix promises the provider's
vocabulary, so everything after it should be the provider's too.

Below the mapper, names are Tank01's. Pool vocabulary starts at the mapper's
output and not before: translate once, not at every hop.

The `get` prefix is dropped because every Tank01 endpoint carries it, and
`get_` reads wrong on a cached_property. The literal "/getNFLGamesForWeek"
still sits in the params call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Finished up the client and the facts
All checks were successful
Claude Code Review / claude-review (pull_request) Has been skipped
Python FastAPI Jinja Linting / build (pull_request) Successful in 34s
Tests / pytest (pull_request) Successful in 15s
e793847c48
Replace the scheduler with a control loop
Some checks failed
Claude Code Review / claude-review (pull_request) Has been skipped
Python FastAPI Jinja Linting / build (pull_request) Failing after 30s
Tests / pytest (pull_request) Successful in 14s
4d856e5b58
The old model scheduled per-week jobs from a weekly planner, so a restart
between Wednesdays came up with no nags and no score polling. `tick` now runs
every minute, reads the week's state, and dispatches at most one job -- rules
are level-triggered, so anything missed is simply retried on the next tick.

`app/ingest` replaces the NflApi wrapper: provider DTO -> mapper -> fact ->
ingester, with each fact type owning a disjoint set of columns so one write
cannot clobber another's. NflApi and its 56 tests are gone; the client and
mappers cover the same ground offline.

Dispatcher decides, scheduler launches. Job ids are constants on the scheduler,
which also owns `jobstate` and hands the loop UTC-aware last-success times --
the naive/aware split had already produced one TypeError in a live rule.

resolves #428

Mostly authored by me with a huge assist by Claude Opus 5 <noreply@anthropic.com>
Fixed up some straggling issues
All checks were successful
Claude Code Review / claude-review (pull_request) Has been skipped
Python FastAPI Jinja Linting / build (pull_request) Successful in 23s
Tests / pytest (pull_request) Successful in 11s
8c010294cd
Close out the provider boundary and control loop issues
All checks were successful
Python FastAPI Jinja Linting / build (pull_request) Successful in 25s
Tests / pytest (pull_request) Successful in 11s
Claude Code Review / claude-review (pull_request) Has been skipped
4c3f9cc08c
resolves #428
resolves #427
resolves #429
resolves #430
resolves #425
resolves #432
resolves #433
resolves #404
resolves #414

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
johnsturgeon changed title from WIP: Tank01 provider DTO and week-pinned client to Tank01 provider DTO and week-pinned client 2026-08-31 16:34:01 +02:00
johnsturgeon deleted branch create-the-pydantic-models-in-the-shape-of-the-tank-game-428 2026-08-31 17:02:32 +02:00
Sign in to join this conversation.
No description provided.