---
title: Investigate the 2018 preseason play enrichment export gap
type: task
status: completed
description: >-
  Closed as a false finding. The published 2018 preseason CSV was a faithful export, read while a
  sibling session's play-enrichment backfill was rewriting the same partitions, so the CSV, the
  oracle and every later measurement each saw a different database. Landed a db_stability check on
  the payload oracles so a moving baseline cannot be misread as a lossy export again.
base_uri: user:task/league/repair-2018-preseason-enrichment-export-gap.md
created_at: '2026-09-22T04:51:33.860Z'
entity_id: 8977045e-09e5-4c7a-9b97-b814b9f7de38
finished_at: '2026-09-22T05:26:31.363Z'
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-09-22T05:26:31.363Z'
visibility_analyzed_at: '2026-11-05T23:50:02.551Z'
---

## What this turned out to be

**There is no export defect.** `nfl/plays/2018/PRE.csv` was a faithful copy of the database at the instant it was written. The finding was an artifact of measuring a fixed CSV against a database that was being rewritten while every measurement was taken.

A sibling session was running `scripts/backfill-play-canonical-columns.mjs` against `league_production`, and it re-runs the full play enrichment pipeline game by game, season by season. Its log pins the whole thing:

| time (UTC) | what happened |
| --- | --- |
| 04:25:31 | backfill starts at 2016 |
| 04:40:41 | 2017 PRE finished |
| 04:42:04 | 2018 PRE week 0 written — the Hall of Fame game, the ONLY game enriched at that moment |
| 04:42:13 | 2018 PRE week 1 written |
| 04:47:38 | the oracle measures 2018/PRE and reads 5,679 of 11,033 populated |
| 04:51:40 | the dataset commit |
| 05:10:27 | 2019 PRE finished |

The export read 2018/PRE inside the nine seconds between week 0 and week 1. That is why exactly one game carried values, and why that game looked like "the first by esbid" — preseason week 0 is the Hall of Fame game, so the backfill wrote it first and the esbid ordering was a coincidence.

The task's premise figures came from the same moving target. `8,342 of 11,585` was a mid-backfill reading; the settled number is 11,033. "2018 is the only preseason year carrying enrichment" was true before 04:25 and false by 05:10 — 2016 through 2019 all carry it now.

## How it was verified

- A fresh `export-data-nfl-plays.mjs --year 2018` at 05:11 produced **65 of 65** games enriched, 11,033 non-blank `is_fumble`, matching the database exactly. Tallied independently of the oracle with a standalone CSV parser.
- The published 2018/REG and 2018/POST copies also disagree with the database now, on the same twelve columns, and 2018/REG **passed** the population check at publish time. A partition cannot go from agreeing to disagreeing because of an export bug in code that has not changed.
- The twelve columns that differ are exactly the ones `libs-server/play-enrichment/play-stat-column-map.mjs` writes.
- `updated` is useless as evidence here: it still reads 2023-12-18 on rows the backfill rewrote today, because the enrichment writer does not bump it.

## What was done about it

`check-published-payload-oracles.mjs` now carries a `db_stability` check (league `9cd048b82`). Every play partition's populated counts are read a second time, one full pass after the first, and a partition whose counts moved FAILS — a dataset cannot be published against a table being rewritten under it. This is the check whose absence let `csv_missing` be read as an export defect.

Proven as a pair: perturbing the sweep's second read turned all three 2021 partitions FAIL with `db_stability.ok false`; reverting it returned all three to PASS in the same run shape. Unit controls are in `test/published-dataset-oracles.spec.mjs`.

It is blind to a writer that changes VALUES without changing how many rows are populated, and that blind spot is measured rather than assumed — a full 75-partition run against the same backfill, by then rewriting seasons that already carried enrichment, correctly reported every partition stable.

## What is left, and it is not this task

The published dataset is now stale against the database for every partition the backfill touches, and it will keep growing until the backfill finishes. As of 05:24 the failing set was 2018/PRE, 2018/REG, 2019/PRE. A republish is the remedy, it should happen only after the backfill completes, and it is an operator gate — a ~2GB force-push over published history.

Separately, `nfl/plays/2024/PRE.csv` fails `score_reconstruction` on esbid `2024080151`, and that one is real but database-side: the game's 129 plays carry no `home_score` or `away_score` at all, while the schedule row holds a 21-17 final. The CSV is faithful. It failed at publish time too and was accepted.
