---
title: Recalibrate the spread of weekly defense projections
type: task
status: completed
description: >-
  Weekly defense projections are compressed: the top third outscore projection by 17-28% and the
  bottom third fall 35-53% short in every season 2021-2025. Locate where the compression enters the
  projection pipeline and fix it there.
base_uri: user:task/league/recalibrate-weekly-defense-projection-spread.md
created_at: '2026-09-29T02:35:53.692Z'
entity_id: 728237a8-e366-4ce9-b9da-e9df1d5b48c0
finished_at: '2026-09-29T04:45:07.294Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-10-30T01:43:36.568Z'
visibility_analyzed_at: '2026-11-05T23:49:55.032Z'
---

## Objective

Make weekly defense projections spread as widely as defenses actually score. The top third of projected defenses outscore their projection by 17-28% in every season 2021-2025, and the bottom third fall 35-53% short. League 1 starts defenses under-projected by 0.4-1.2 points a week in every full season since 2022.

## Findings

- **The compression is stable.** Per-season actual over projected points, projected defenses in league 1's format, projection thirds:

| Season | Bottom | Middle | Top |
| --- | --- | --- | --- |
| 2021 | 0.64 | 0.86 | 1.28 |
| 2022 | 0.91 | 1.05 | 1.20 |
| 2023 | 0.51 | 0.99 | 1.26 |
| 2024 | 0.58 | 0.97 | 1.22 |
| 2025 | 0.47 | 0.97 | 1.17 |

- **The simulation cannot absorb it.** Keeping each ratio grid's own mean was measured and rejected; see [[user:task/league/keep-projection-bias-in-simulated-player-draws.md]]. The fix belongs where the projection is made.
- **The compression is four scored components that no projection carries.** No source projects three-and-outs, fourth-down stops, points against or yards against, so the consensus holds them at 0.0 (2021-2025) or null (2026) and they score nothing. The vendor-supplied components (sacks, turnovers, touchdowns, safeties, blocks) run 0.95-1.05 of projection in most season-thirds. The missing four net about +1.5 points for the top third and -1.7 for the bottom, which is the whole spread. Two exceptions remain: the bottom third's vendor components ran 0.70 in 2023 and 0.69 in 2025.
- **The game line predicts the missing four.** Against the opponent's implied team total, points against tracks it with slope 1.01 (residual sd 8.7), yards against at 8.9 yards a point (residual sd 77), and three-and-outs and stops fall with it. Their net league 1 score runs from +4.3 at an implied 13 to -4.7 at 31, slope -0.39 to -0.57 in every season 2020-2025.
- **A prior-season linear fit on the implied total removes most of the compression.** Adding it to the stored projection flattens the thirds to 1.01-1.19 in 2021 and 0.73-0.99 in 2025, and cuts weekly root-mean-square error in every season (all seasons 6.43 to 6.12). 2023 and 2024 bottom thirds stay short (0.49, 0.60) from the vendor components.
- **Offense is not affected the same way.** Top-quarter QB, RB, WR and TE run 0.98-1.02 of projection from 2023.
- **This is not the valuation work.** [[████████████████████-███-██████████-██████████-███-███████████]] fixed defense `pts_added`, not the weekly points.

## Design

Operator ruling 2026-09-29: approved as recommended.

- **A derived game-line source projects the four missing stats.** Each defense-week gets points against, yards against, three-and-outs and fourth-down stops from the opponent's implied team total, by linear fits on the prior season. It writes `projections_index` and `projections_history` like a real source, so the consensus and the as-of-kickoff replay take it with no special path. A week with no line stays null.
- **The projected path scores threshold rules as expected values.** Points and yards allowed beyond the threshold are scored as their expectation under a normal residual (sd 8.7 points, 77 yards), per game, rather than the rule at the mean.
- **2021-2025 history is backfilled** from closing lines stamped at kickoff, so the harness can measure it.
- **Rejected:** a points-only offset on `projected_points_total`, which leaves the stored stats disagreeing with the points.

## Plan

- [x] **Locate the compression.** Trace defense weekly projected points from `projections_index` to `scoring_format_player_projection_points`; measure the actual-over-projected ratio by projection third per source and per scoring component. Verify: the table above reproduces, and the component or source that carries the spread is named.
- [x] **Design gate with the operator.** Verify: the ruling is recorded in this task.
- [x] **Build and measure** with the calibration harness, seed-paired against master, reading `player_centre_by_position` for DST and Brier overall. Verify: the DST centre error from 2023 falls from +0.83 toward 0 and Brier is no worse than 0.2019.
  - Built on league branch `defense-line-projections` (`3e4fee662`). Source 30 history is backfilled for 2021-2025.
  - The harness reads stored consensus points, not history, so it was measured with an uncommitted overlay adding source 30's scored components to the stored DST points. That is exact for 2021-2025, whose stored consensus holds 0.0 for all four.
  - Results, seed 42, 10,000 draws, old ratio grids: DST centre from 2023 +0.83 to -0.23, all seasons +0.80 to -0.07; Brier 0.2019 to 0.2016; other positions unchanged; week co-movement 1.62 to 1.55. DST 50% coverage widened from 0.51 to 0.56, because the grids were fitted around the compressed projections.
- [x] **Merge and deploy.** Merged as `95e3eb42b` and deployed 2026-09-29 04:2xZ with `deploy:all` (12-commit range, every commit cleared by its owner or the operator). Both hosts and the bundle are at `95e3eb42b`, and the cron entry is installed. Source 30 wrote 68 games across 10 open 2026 weeks, and the 04:30Z process-projections run carried points against for all 32 week-4 defenses.
- [x] **Rebuild the stored 2021-2025 DST consensus.** Done 2026-09-29. Source 30's four stats went into the AVERAGE `projections_index` DST rows, and only DST rows of `scoring_format_player_projection_points` were re-scored: 2,654 stat rows and 22,016 point rows across every stored format. Re-scoring the old rows first reproduced every stored value exactly, so nothing else moved. Top thirds now run 1.02-1.20 of projection, down from 1.17-1.28.
- [x] **Refit the ratio grids for 2021-2025, after deploy.** Applied 2026-09-29 04:42Z; a harness pass on the stored grids reproduced the in-memory figures exactly. An in-memory refit measured over the paired 2021-2025 seasons (a 2026 week settled between passes, so 2026 is left out):

| Pass | DST centre | DST coverage 50 / 80 / 95 | Brier |
| --- | --- | --- | --- |
| Master | +0.85 | 0.513 / 0.803 / 0.936 | 0.2022 |
| Rebuilt consensus, old grids | -0.05 | 0.562 / 0.831 / 0.940 | 0.2018 |
| Rebuilt consensus, refitted grids | -0.05 | 0.523 / 0.841 / 0.937 | 0.2019 |

  Apply with `scripts/calculate-projection-outcome-ratio-quantiles.mjs --year <y>` for 2021-2025 once live 2026 DST projections carry source 30. The 2025 grid is what live forecasts read, so refitting before deploy would pair it with the still-compressed live board.

## Known limits

- **Weak defenses are still over-projected from 2023.** The vendors' own sacks, turnovers and touchdowns run about 0.70 of projection for the bottom third in 2023 and 2025, and the stored bottom third now runs 0.49-0.76. That is vendor error, not the missing components, and no task tracks it.
- **Rest-of-season rows leave the four stats out.** Two are per-game thresholds, and lines exist only for the next few weeks, so a rest-of-season sum would be unscoreable or partial. The rest-of-season DST board is therefore unchanged by this work.

## Notable context

- [[user:text/league/simulation-calibration-baseline.md]] — the harness command and current numbers
- `libs-shared/calculate-points.mjs` — defense scoring components
