How the data moves
The whole system is one automated pass: from a game I just played, through a warehouse and five models, to the pages on this site. Here is every stage it runs, in order.
Riot Match-V5 API
Every game I play is the source, whatever the queue. Each run makes one cheap match-ID call and pulls full detail for anything new.
Ingest
New IDs land in a discovery log first; full match detail (about 90 stats per player) is then fetched and written, one match and its ten players committed together.
Snowflake warehouse
matches and participants hold the raw history, and all pipeline state lives here too, so a run is correct on a throwaway CI machine with no local disk to depend on.
New-game gate
The one rule everything hangs on: predictions only regenerate when a genuinely new ranked solo game has been played. Stats refresh on any new game, but a re-run on the same data changes nothing.
Walk-forward models
Five models each predict the next champion, trained only on ranked games that came before. Every prediction is written to one wide row beside the champion actually played, so correctness is inherent, not a later claim.
Publish
The live pick, the scoreboard, stats, and the playstyle analysis are written as JSON rows under one generic output table, so the site reads them all with a single query shape.
This website
Flask reads the published tables straight from Snowflake and renders everything you are looking at. It never writes back, so the pipeline stays the single source of truth.
Two choices carry it. The gate makes a prediction a pure function of the games behind it, so it never quietly re-rolls. And because every prediction is written beside the game actually played, the numbers on the Models page are earned, not claimed.