---
title: Fit per-stat outcome error grids and price kit widths from them
type: task
status: completed
description: >-
  Fit realized-over-projected ratio grids per NFL stat on the pre-kickoff vendor consensus in the
  league database, and let the pricing kit draw market widths from them instead of hand-filed bands,
  adopted only if the replay shows it pays.
base_uri: user:task/league/fit-stat-outcome-error-grids.md
created_at: '2026-10-02T23:50:06.012Z'
entity_id: c5f03ccb-a8aa-4bcb-bc27-6b5058da2acc
finished_at: '2026-10-06T01:47:43.235Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
public_read: true
relations:
  - relates [[█████████████-██████████████-██████-███████-████-█████-████-████]]
tags:
  - user:tag/league-xo-football.md
  - user:tag/nfl-betting.md
  - user:tag/software-task.md
updated_at: '2026-10-06T01:47:43.235Z'
visibility_analyzed_at: '2026-11-06T00:17:53.154Z'
---

# Implementation Plan: Fit per-stat outcome error grids and price kit widths from them

## Overview

- Fit, per NFL stat, the realized-over-projected ratio distribution of the pre-kickoff vendor consensus, bucketed by position and projection level, and store it as quantile grids in the league database.
- Let the NFL pricing kit draw each priced market as the profile's central times a ratio from that stat's grid, so pricing width comes from measured error rather than from hand-filed bands.
- Impact: profile bands have been filed too narrow at every grain measured (team plays 62 of 65 under the calibrated floor; target and route share bands covering 0.59-0.69 against an 80 percent target), and the kit turns that overconfidence into large edges, which lose (edge 0.15 and up: 45 markets hit 0.311 against 0.479). Grid widths replace the per-quantity floors one rule at a time would otherwise need.
- Ordering: follows the operator's 2026-10-02 design ruling on [[█████████████-██████████████-██████-███████-████-█████-████-████]] (§ Design direction). Grid mode reaches live pricing only after that task's replay harness shows it pays; the replay lands after the last week-4 kickoff (2026-10-06 00:15Z), together with per-(pid, family) draw seeding. Nothing here changes a week-4 ruling.

## Background

- The league repo already fits fantasy-point ratio grids: `scripts/calculate-projection-outcome-ratio-quantiles.mjs` (equal-count buckets of at least 80 rows), `libs-shared/simulation/fit-player-distribution.mjs` (`build_outcome_ratio_grid`: 199 quantiles at 0.005-0.995, shifted to mean 1; `sample_ratio_grid`: linear interpolation, clamped tails; `build_player_marginal`: brackets a projection between bucket medians), stored in `projection_outcome_ratio_quantiles` (a private ad hoc DDL file), job type 163, run by hand once a season, mocha specs under `test/`.
- That fantasy loader reads the current-state consensus, which for most played player-weeks was last updated after kickoff (2025: differs from the pre-kickoff reconstruction by more than 0.5 receiving yards on 3,899 of 10,741 player-weeks). `libs-server/simulation/load-projections-as-of-kickoff.mjs` rebuilds each source's latest run at or before the player's own kickoff and returns a per-stat `stats` map; it has a spec.
- Vendor per-stat history: `projections_history` 2020-2026, 9-13 sources a season, about 600 players. Realized: `player_gamelogs` under the same column names.
- Fit volume with pre-kickoff consensus above 0 and an active gamelog, 2022-2025: about 5,000-6,600 player-weeks a season per receiving and rushing stat; passing about 650-1,100, of which about 550 starters.
- 2025 sanity: receptions projected 4-6 run p10/p50/p90 0.39/0.86/1.55 (mean 0.915); passing yards 220-260 run 0.63/0.99/1.32.
- The kit (`cli/nfl/pricing-kit.py`, Python, reads the league database through `base db query league`) builds samplers in `DefaultModel.one` / `draw` (two-piece normal for a median count, Poisson for a mean count, shifted lognormal for yardage, targets as share times attempts, composites as sums) with scale arms 0.93/1.0/1.07, an off-profile check against the filed central, and a tail guard at the filed p10-p90.

### Assumptions

- The as-of-kickoff loader can serve a season-wide fit by calling it per week with a default scoring format, since only its `projections` map needs `league_settings` — UNCHECKED, verified by the loader step's spec.
- Low projections degenerate as ratios (receptions projected 0.3-1: 62 percent zeros) — checked by the research query, 2025.
- `projections_history.generated_at` is unique per source, pid, generated_at, week and season, so a kickoff cutoff is well defined — checked by the research read of the table's unique key.
- Job type 170 is taken (`IMPORT_ESPN_COLLEGE_STATISTICS`), so this build takes 171 — checked: `grep -n ': 1[67][0-9]' libs-shared/job-constants.mjs`.
- Profile centrals carry error of the same order as the vendor consensus, so a consensus-fitted width is the right width around a profile central — UNCHECKED; the 2026 profile-row coverage step tests it before the replay.
- The precedent transfers because the fantasy grid's correctness rests on the ratio being fitted on the same projection kind it is later applied to; here the grid is fitted on consensus and applied to a profile central, which is exactly the assumption above, so it is tested rather than assumed.

## Design

- **League loader.** `libs-server/simulation/load-stat-outcome-ratio-rows.mjs` returns, per regular-season player-week, the pre-kickoff consensus per stat (via `load_projections_as_of_kickoff`) and the realized value, keeping only players who took the field (the fantasy loader's snaps-or-non-placeholder rule).
- **Stats** (closed set, the kit's family vocabulary): `receptions`, `targets`, `receiving_yards`, `rushing_attempts`, `rushing_yards`, `passing_attempts`, `passing_completions`, `passing_yards`, and two composites fitted directly so the kit never models within-player correlation: `rushing_receiving_yards`, `passing_rushing_yards`. Touchdowns stay on Poisson.
- **Floors** per stat in `libs-shared/simulation/stat-outcome-ratio-constants.mjs`: receptions and targets 1, rushing attempts 3, passing attempts 15, passing completions 10, and yardage floors set from the build's zero-share report. Below its floor a projection gets no grid.
- **Fit.** `scripts/calculate-stat-outcome-ratio-quantiles.mjs --year N` fits season N's grids on seasons N-3 to N-1, keyed by stat, position and equal-count projection bucket (at least 80 rows, at most 10 buckets, 5 for passing), reusing `build_outcome_ratio_grid` and the bucketing in `fit_position_buckets` unchanged. It replaces the season in one transaction and reports job type 171 `CALCULATE_STAT_OUTCOME_RATIO_QUANTILES`.
- **Table** `stat_outcome_ratio_quantiles`: `season_year`, `stat`, `position`, `projection_bucket_index`, `projection_median`, `sample_size`, `outcome_ratio_quantiles` (199 values), `fitted_season_years`, `built_at`; primary key season, stat, position, bucket. Added by an adhoc DDL file, `yarn db:exec`, `yarn export:schema`, in one commit with its code.
- **Holdout check.** `scripts/measure-stat-outcome-ratio-coverage.mjs` fits on 2022-2024 and reports 2025 80 percent coverage and pinball loss per stat and bucket.
- **Kit.** A Python twin of `sample_ratio_grid` and the bucket-median bracketing in `pricing-kit.py`, tested against fixtures the JS sampler writes. Grids load once per run. For a market whose central is above its floor: draw = central times ratio divided by the grid median for a median-filed central, or central times ratio for a mean-filed one, so the draw's centre equals the filed central and the off-profile check holds. Below the floor or with no grid, the current model runs. The tail guard reads the priced distribution's p10-p90. `rules_version` also stamps the grid table's `built_at`.
- **Width modes for the replay only.** `price --width filed|grid` plus an arms switch, so the replay compares filed bands, grid with 0.93/1.07 arms, and grid alone. The losing modes and the option are deleted in this plan once the replay rules.
- No new dependencies. The kit gains one league query.
- **Knowledge Base Impact:** league `docs/simulation-system.md` (table, loader, script; the fantasy-grid leak as a `[finding]`); the kit docstring and `cli/nfl/ABOUT.md`; the distribution-sampler row in `text/nfl/nfl-betting/reference/betting-system-design.md`; `guideline/nfl/betting/file-game-assessments.md` once grid mode wins (filed bands stay graded but stop setting pricing width); `████████████████████████████████████████` gains the as-of-kickoff note. No new tags.
- **Working tier:** holdout and replay output go under the existing `scratch/nfl-betting/harden-loop/` slug, owned by the hardening task, in `stat-error-grids/`; it retires with that slug.
- **Shape:** bounded to this one task; no orchestrator.

## Notes

- Fit on the as-of-kickoff consensus only; the current-state consensus understates spread through post-kickoff updates.
- The consensus averages a source mix that drifts by season (about 4 to 8 sources a player-week); the three-season window smooths it, and the holdout check shows whether it matters.
- Passing stats are thin; at most 5 buckets.
- The fantasy-grid leak is out of scope and routed to the league lane as an observation.

## Plan

### Core Implementation

- [x] **Stat constants and floors**
  - File: `repository/active/league/libs-shared/simulation/stat-outcome-ratio-constants.mjs`
  - Purpose: one closed stat vocabulary with each stat's gamelog column(s), projection column(s) and floor, shared by the build and the kit fixtures.
  - Verify: `yarn test:isolated test/simulation.stat-outcome-ratio-constants.spec.mjs` asserts every stat maps to existing `player_gamelogs` and `projections_history` columns.
- [x] **Leak-free loader**
  - File: `repository/active/league/libs-server/simulation/load-stat-outcome-ratio-rows.mjs`
  - Purpose: pre-kickoff consensus and realized value per player-week and stat, active players only.
  - Verify: its spec asserts a post-kickoff source run is excluded, a placeholder gamelog is dropped, and a composite sums its parts; run with `yarn test:isolated`.
- [x] **Table**
  - Files: `repository/active/league/private/db/adhoc/2026-10-<dd>-stat-outcome-ratio-quantiles.sql`, `db/schema.postgres.sql`
  - Purpose: store the grids with CHECK constraints on the stat vocabulary and the 199-length grid.
  - Verify: `yarn db:exec` then `yarn export:schema`; `SELECT column_name FROM information_schema.columns WHERE table_name='stat_outcome_ratio_quantiles'` returns the nine columns.
- [x] **Build script and job type**
  - Files: `repository/active/league/scripts/calculate-stat-outcome-ratio-quantiles.mjs`, `libs-shared/job-constants.mjs` (171)
  - Purpose: fit season N on N-3 to N-1 and replace it transactionally, reporting the zero share near each floor.
  - Verify: spec over a fixture asserts bucket counts, mean-1 grids and the window; then build 2026 and confirm `SELECT stat, position, count(*), min(sample_size) FROM stat_outcome_ratio_quantiles WHERE season_year=2026 GROUP BY 1,2` has every stat, no bucket under 80 rows.
- [x] **Holdout coverage**
  - File: `repository/active/league/scripts/measure-stat-outcome-ratio-coverage.mjs`
  - Purpose: fit 2022-2024, score 2025 per stat and bucket.
  - Verify: output saved to `scratch/nfl-betting/harden-loop/stat-error-grids/holdout.txt` with the command line at its head; every stat's pooled 80 percent coverage within 0.75-0.85, and any bucket outside 0.70-0.90 listed. A stat failing it is removed from the vocabulary before the kit step.
- [x] **Kit grid sampler** — landed 67ebb737df. `price --width grid --arms scale|none`, keyed seeding, and the tail guard reads the priced p10-p90. The twin matches the JS fixture to 1e-9. A `--width filed` re-price of PIT @ CLE and JAX @ CIN is byte-identical to the prior kit.
  - File: `cli/nfl/pricing-kit.py`, `cli/nfl/pricing-kit.test.py`
  - Purpose: Python twin of the sampler and bracketing; grid loading; median and mean normalization; floors; tail guard on the priced p10-p90; grid `built_at` in `rules_version`; the `--width` and arms switches.
  - Verify: `python3 cli/nfl/pricing-kit.test.py 2>&1 | grep -q '^OK$'`, with tests that match JS-written fixture quantiles to 1e-9, that a median-filed central's draw median equals the central, and that `--width filed` reproduces today's prices exactly on a stored work dir.
- [x] **Coverage on 2026 profile centrals** — 2026-10-06, weeks 3-4 (weeks 1-2 have no filed rows): grid 0.80 against filed 0.75 on 442 rows, +0.04 [+0.01, +0.08]; receptions alone runs 0.75 (`scratch/nfl-betting/harden-loop/stat-error-grids/profile-coverage.txt`).
  - Purpose: test the unchecked assumption that consensus-fitted widths suit profile centrals.
  - Verify: for graded 2026 weeks 1-3 player rows in grid stats, 80 percent coverage of central-times-grid against the filed bands; script and output in `scratch/nfl-betting/harden-loop/stat-error-grids/`; result written to the hardening task.
- [x] **Replay decision and cleanup** — operator ruled 2026-10-06 for grid width with no scale arms; ab6b8d8c69 deletes `--width`, `--arms` and the 0.93/1.07 arms, and `band-prob` reads the grid.
  - Purpose: once the hardening task's replay scores filed, grid with arms and grid alone on settled weeks, keep the winner and delete the other modes and the switches.
  - Verify: the operator's ruling recorded on the hardening task; `grep -n "width" cli/nfl/pricing-kit.py` shows no mode switch left; tests pass.

### Knowledge Base Updates

- [x] **League simulation doc**
  - `repository/active/league/docs/simulation-system.md`: the table, loader, build and holdout scripts; the fantasy-grid post-kickoff leak as a finding routed to the league lane.
  - Verify: `grep -n stat_outcome_ratio_quantiles docs/simulation-system.md` matches.
- [x] **Kit and betting docs** — ab6b8d8c69.
  - Kit docstring, `cli/nfl/ABOUT.md`, the distribution-sampler row of `text/nfl/nfl-betting/reference/betting-system-design.md`, and `████████████████████████████████████████` (as-of-kickoff consensus for fits).
  - Verify: `grep -rn "stat_outcome_ratio" cli/nfl/ABOUT.md text/nfl/nfl-betting/reference/betting-system-design.md` matches.
- [x] **Filing guideline, if grid mode wins** — ab6b8d8c69; the play-count floor stays, since the grids are player-grain only.
  - `guideline/nfl/betting/file-game-assessments.md`: filed bands stay graded but no longer set pricing width; the play-count floor is retired with them if the grids cover it.
  - Verify: `grep -n "pricing width" guideline/nfl/betting/file-game-assessments.md` matches, and the floor test is removed or kept to match.
