A good probabilistic forecast is not one that is never wrong; it is one whose surprises are rare in roughly the proportion it promised. Every week a few receivers land far out in the tail of what the model expected, and those weeks are worth looking at — both because they are the fun ones to remember and because they are the honest test of whether the uncertainty meant anything. Below are the 2025 NFL season’s biggest upside and downside surprises, ranked not by raw points but by how unlikely the model thought the result was.
Code
palette = ({accent:"#93c54b",accentDark:"#7aa83c",expert:"#b5651d",// warm brown for the expert-driven (Model A) signaldata:"#3a6ea5",// muted blue for the data-driven (Model B) signalmixture:"#3e3f3a",// ink for the blended predictivesand:"#f8f5f0",bust:"#c9b8a3",held:"#dfe7c8",strong:"#b9d68a",leagueWinner:"#93c54b"})// Probability formatting with the "no false precision" rule: anything that// would round to a flat tiny number is shown as "<1%"; high values mirror it.fmtPct =function (p) {if (p ==null||isNaN(p)) return"—";if (p <0.01) return"<1%";if (p >0.99) return">99%";return (100* p).toFixed(0) +"%";}// Fantasy-point formatting to one decimal.fmtFp =function (x) {if (x ==null||isNaN(x)) return"—";return x.toFixed(1);}// Four narrative-bin probabilities from the three exceedance probabilities.// Inputs are P(exceed floor), P(exceed target), P(exceed ceiling).narrativeProbs =function (pFloor, pTarget, pCeiling) {return {bust:Math.max(0,1- pFloor),held_up:Math.max(0, pFloor - pTarget),strong:Math.max(0, pTarget - pCeiling),league_winner:Math.max(0, pCeiling) };}// Map an ECR rank to its tier label (matches the export's ecr_tier bins).ecrTier =function (ecr) {if (ecr ==null||isNaN(ecr)) returnnull;if (ecr <=5) return"1-5";if (ecr <=12) return"6-12";if (ecr <=24) return"13-24";if (ecr <=48) return"25-48";if (ecr <=96) return"49-96";return"97+";}// Human-readable archetype labels for badges.archetypeLabel = ({fill_in_situation:"fill-in situation",emerging_player_elevation:"emerging player",late_season_expansion:"late-season expansion",recent_role_change:"recent role change",rookie_or_low_sample:"rookie / low sample",stable_veteran:"stable veteran",star_returning:"star returning"})
Code
db = DuckDBClient.of({surp:FileAttachment("data/surprises.parquet"),pred:FileAttachment("data/predictives.parquet")})surprises = db.query(`SELECT * FROM surp`)predictives = db.query(`SELECT * FROM pred`)cfg =FileAttachment("data/locked_config.json").json()slugify =function (s) {return (s ??"").toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");}linkFor = (id, wk) =>`index.html#week=${wk}&player=${slugify(nameById.get(String(id)) ??"")}`blendRows = predictives.filter(d => d.predictive==="cross_blend")nameById =newMap(blendRows.map(d => [String(d.player_id), d.player_display_name]))blendByKey =newMap(blendRows.map(d => [`${String(d.player_id)}|${Number(d.week)}`, d]))// Finish rank: position of realized among that week's active receivers.finishByKey = {const m =newMap();const byWeek = d3.group(blendRows.filter(d => d.realized_fp!=null), d =>Number(d.week));for (const [wk, rows] of byWeek) { rows.slice().sort((a, b) => d3.descending(a.realized_fp, b.realized_fp)).forEach((r, i) => m.set(`${String(r.player_id)}|${wk}`, i +1)); }return m;}records = surprises.map(s => {const key =`${String(s.player_id)}|${Number(s.week)}`;const b = blendByKey.get(key);if (!b || b.realized_fp==null) returnnull;return {id:String(s.player_id),week:Number(s.week),p_atleast: s.p_atleast,p_atmost: s.p_atmost,name: b.player_display_name,team: b.team,ecr: b.ecr_rank,mean: b.mean,p10: b.p10,p90: b.p90,realized: b.realized_fp,finish: finishByKey.get(key) };}).filter(r => r !=null)// Compact distribution strip: p10–p90 range, dashed mean tick, realized dot in the tail.strip =function (r) {const W =230, H =26, pad =7, y = H /2;const lo =Math.min(r.p10, r.realized,0), hi =Math.max(r.p90, r.realized);const sc = v => pad + (W -2* pad) * (v - lo) / ((hi - lo) ||1);returnhtml`<svg width="${W}" height="${H}" style="display:block;margin:0.25rem 0;"> <line x1="${pad}" y1="${y}" x2="${W - pad}" y2="${y}" stroke="var(--rc-sand-panel)" stroke-width="2"></line> <rect x="${sc(r.p10)}" y="${y -4}" width="${Math.max(1,sc(r.p90) -sc(r.p10))}" height="8" rx="2" fill="${palette.strong}" opacity="0.55"></rect> <line x1="${sc(r.mean)}" y1="${y -7}" x2="${sc(r.mean)}" y2="${y +7}" stroke="${palette.mixture}" stroke-width="1.5" stroke-dasharray="2,2"></line> <circle cx="${sc(r.realized)}" cy="${y}" r="4.5" fill="${palette.accent}" stroke="#fff" stroke-width="1"></circle> </svg>`;}card =function (r, kind) {const pct =Math.round(100* (kind ==="up"? r.p_atleast: r.p_atmost));const ecrTxt = r.ecr!=null?Math.round(r.ecr) :"—";const finTxt = r.finish??"—";const line = kind ==="up"?`The model gave ${r.name} about a ${pct}% chance of scoring ${fmtFp(r.realized)} or more in Week ${r.week}; they put up ${fmtFp(r.realized)} fp.`:`The model gave ${r.name} about a ${pct}% chance of scoring ${fmtFp(r.realized)} or fewer in Week ${r.week}; they managed ${fmtFp(r.realized)} fp.`;const ctx = kind ==="up"?`Ranked WR${ecrTxt}, finished as WR${finTxt} that week.`:`Ranked WR${ecrTxt}, finished as WR${finTxt}.`;returnhtml`<div class="disagreement-card" style="margin-bottom:0.6rem;"> <div style="display:flex;justify-content:space-between;align-items:baseline;gap:0.5rem;"> <a href="${linkFor(r.id, r.week)}" style="font-weight:700;">${r.name}</a> <span style="color:var(--rc-muted);font-size:0.85rem;">${r.team??"—"} · Week ${r.week}</span> </div>${strip(r)} <div style="font-size:0.9rem;margin-top:0.2rem;">${line}</div> <div style="font-size:0.8rem;color:var(--rc-muted);margin-top:0.15rem;">${ctx}</div> </div>`;}
Code
{const n = surprises.length;const above = surprises.filter(s => s.p_atleast<0.10).length;const below = surprises.filter(s => s.p_atmost<0.10).length;const pa =100* above / n, pb =100* below / n;const near = v =>Math.abs(v -10) <=2?"close to": (v >10?"a little above":"a little below");const qual = (near(pa) ===near(pb)) ?near(pa) :"in the neighborhood of";returnhtml`<p>Across the ${n.toLocaleString()} receiver-weeks of the 2025 NFL season, <strong>${pa.toFixed(1)}%</strong> finished above the model's 90th percentile and <strong>${pb.toFixed(1)}%</strong> below its 10th — ${qual} the one-in-ten (10%) a calibrated model expects at each tail.</p>`;}
Biggest upside surprises
Code
{const top = records.slice().sort((a, b) => d3.ascending(a.p_atleast, b.p_atleast)).slice(0,10);returnhtml`<div>${top.map(r =>card(r,"up"))}</div>`;}
Biggest downside surprises
Code
{const top = records.slice().sort((a, b) => d3.ascending(a.p_atmost, b.p_atmost)).slice(0,10);returnhtml`<div>${top.map(r =>card(r,"down"))}</div>`;}