MLB Playoff Odds Tracker

Written each morning

Today's games

Slate and scores from ESPN. The right-hand number on each line is that team's current make-playoffs odds, so you can see what the game is worth.

Ask

Answers come from the numbers on this page and nothing else — no scores, rosters or history we do not log, and no scenarios: the odds we store are a single number per team, not a distribution.

Odds over time

Latest snapshot

About this project

A daily log of MLB playoff odds from two independent models, plus each morning’s games and a short written recap for every team. It exists because playoff odds are published as a snapshot — every source shows you today and forgets yesterday. Recording them daily turns a number into a trend, which is the part that actually tells you something.

cron 6amfreshness checkscrape 2 sourcesPostgresClaude: recap + previewstatic page

Deterministic spine, model at the edge

Fetching, parsing, storing and verifying are plain Python — cheaper, reproducible, and when they break you get a stack trace instead of a confident wrong answer. Claude is called at two points, both writing prose from numbers the scrapers already verified: a recap of yesterday, and a preview of tonight that may search the web for news no table of ours contains. Both are instructed never to invent a statistic, and the preview is told to trust our figures over anything it finds. If a model is deciding what the data is, the design is wrong. It should only decide how to say it.

What it cost

Thirty summaries a day is roughly 10k input and 5k output tokens — cents. The interesting constraint was never price. It was making sure the numbers handed to the model were right in the first place.

What was actually hard

  • Knowing what to keep. A table holding current state is not a log. One source served only current standings, so every run silently destroyed the previous day — unrecoverable. That decision has to be made before collection starts, not after.
  • Time zones. Timestamps arrive in UTC; the sport runs on Eastern. An 8pm game is already tomorrow in UTC, which quietly filed results under the wrong day and would have drawn the two models a day apart.
  • Two models disagreeing is the signal. They see identical W-L records, so a 25-point gap isn’t noise — it’s one method reading recent results and the other reading roster talent.
  • Nothing tells you when it breaks. Exit codes vanish under cron, so the job checks its own freshness and raises an alert rather than failing silently into a gap in the chart.
  • The host dictates the store. This ran on SQLite until a possible move to Heroku, whose dynos have an ephemeral filesystem — reset on every deploy and daily restart. A SQLite file would vanish within a day, so the whole thing moved to Postgres.

Reusing the pattern

The shape — schedule, collect, store durably, verify, then use a model for the one step that needs judgment — is not specific to baseball. Swap the scrapers and the prompt and it is a bot for prices, listings, deploys, or an inbox. The build notes and the traps worth knowing are in ARCHITECTURE.md, and every prompt and setting actually used is visible on the what we send to Claude page. The design tokens and components every page shares are catalogued on the styles page, and every page and endpoint this app answers is indexed on the routes page.