This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
Head-to-head matchups for my ESPN fantasy football league: team scores, each starter's actual and projected points, and a win probability that ESPN's API doesn't give you — so I built one.
The NBA matchups page compares seven stat categories per team and counts who won more of them. NFL fantasy is a single number: total points scored by the active roster for the week. That collapses the team-level comparison to one bar, but it pushes all the interesting detail down to the player level — sixteen starters per side, each with an actual score and a projection, rather than seven category totals.
ESPN's fantasy football endpoint takes scoringPeriodId as the week number, and rosterForCurrentScoringPeriod only carries stats for whatever period you asked for. Omit the param entirely and ESPN defaults to the league's current week — which turned out to be the better design than guessing at it client-side.
My first pass estimated the live NFL week from the calendar date, then self-corrected once the real scoreboard loaded — a useEffect that called setWeek when the guess and the server disagreed. react-hooks/set-state-in-effect flagged it, and the fix was better than a workaround: the API already tells you the current week when you don't ask for a specific one, so there was never a reason to guess. The selected week is now either the visitor's explicit choice or null, and null means read the answer off the payload's status.currentMatchupPeriod.
There's no fantasy win-probability field anywhere in the payload. The closest thing already in the codebase is impliedProbability on the ZeroProof betting board, which turns an American price into a 0–1 probability. A fantasy matchup has no price, so I built the equivalent from what a matchup does have: each side's current score plus what its unfinished starters are still projected to add. The margin between those two projected finals feeds a normal-CDF approximation whose spread widens with how many points are still unplayed — a lead means less on Monday morning than it does once Sunday's games are done.
There's no dataset of "this matchup was actually a 62% favorite" to check the model against, so the tests pin invariants instead of exact numbers: equal projections split 50/50, a settled matchup with any lead reads near-certain, and swapping home and away complements the result. The test that actually verifies the design intent compares two matchups with the identical projected margin — one with most of the week's points still unplayed, one with almost none — and asserts the early one sits closer to a coin flip. That's the property the whole model exists to have.
A live ticker of the plays contributing to each score was out of scope for this PR. It needed a second, unrelated ESPN surface — the public game-summary API rather than the fantasy league endpoint — and it's only meaningful while games are live, which makes it harder to verify than everything else here. Shipping the scores and win probability first, and building the ticker as its own stacked PR, kept this one small enough to actually finish. It shipped shortly after — see the update below.
Update — Sept 22, 2026
Shipped as a stacked follow-up, and it landed simpler than the plan for it described.
The original plan for this ticker said each play would carry the fantasy points it contributed — "+6.4 pts" next to the play text. Once I actually looked at what ESPN's public game-summary API returns, that number doesn't exist anywhere upstream. Computing it would mean re-implementing my league's entire scoring-rule table (points per passing yard, per rushing touchdown, per reception, and so on) against each play's raw stat line — a second scoring engine, running client-side, that could silently drift from the number ESPN's own fantasy API already reports. That's a worse trade than just not having the number.
What shipped instead: the real play text ESPN already writes — "Dyami Brown 9 Yd pass from Trevor Lawrence (Cam Little Kick)" — tagged with whichever fantasy team rosters the player it mentions. Attribution is a plain substring match against each starter's exact name, not a player-id join, because the play-by-play text carries names, not ids. It's honest about what it is: the actual event, not a derived number I'd have to keep in sync with ESPN's.
Q1 10:51 TD Dyami Brown 9 Yd pass from Trevor Lawrence (Cam Little Kick)
Paul's Perfect TeamThe scoring-play feed lives on a completely different ESPN host (site.api.espn.com) from the fantasy league endpoint, and it identifies teams by abbreviation, not the fantasy payload's numeric proTeamId. I expected to need a lookup table hand-built from a reference site. Instead the public site API's own team-list endpoint uses the identical numbering — id 4 is CIN on both surfaces, confirmed against a real rostered Bengals player — so the map is just those 32 pairs, not a scraped or guessed table.