---
title: Repair the 2024 preseason kickoff possession inversion
type: task
status: completed
description: >-
  156 KOFF rows in the 2024 preseason name the receiving team in possession_nfl_team against a
  kicking-team convention, from a missing swap in the sportradar importer whose own comment states
  the convention backwards.
base_uri: user:task/league/repair-2024-preseason-kickoff-possession.md
created_at: '2026-09-11T02:43:04.077Z'
entity_id: bbb3127e-fe83-4f00-9b4a-59c4f0161499
finished_at: '2026-09-11T03:23:51.066Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
tags:
  - user:tag/league-xo-football.md
  - user:tag/software-task.md
updated_at: '2026-09-11T03:23:51.066Z'
visibility_analyzed_at: '2026-11-05T23:50:02.899Z'
---

# Implementation Plan: Repair the 2024 preseason kickoff possession inversion

## Overview

- 156 `KOFF` rows in the 2024 PRESEASON name the RECEIVING team in `possession_nfl_team`, against a database convention of the KICKING team. Because `offense_nfl_team` is derived from possession, those rows attribute the kickoff to the receiving team as offense too.
- The writer is attributable: `scripts/import-plays-sportradar.mjs` has no kickoff possession swap, while `scripts/import-plays-nflfastr.mjs` and `scripts/import-plays-nfl-v1.mjs` each carry one.
- Its own `KOFF` comment (around line 590) states the convention BACKWARDS — "Database uses receiving team as off, Sportradar uses kicking team" — which is plausibly why the swap was never added. Fixing the comment is part of the fix, not a tidy-up.

## Background

Measured against `league_production` on 2026-09-11 by [[user:task/league/reconcile-plays-team-column-consumers.md]], which forbade repairing it in place and required it get its own task.

- On `KOFF` rows carrying both fields, `possession_nfl_team` matches `yard_line_side` at 1.000 in every full season except 2024, which reads **0.9535** — 156 mismatches of 3,355 — plus small dips in 2008, 2013, 2014 and 2025.
- **All 156 are PRESEASON**, weeks 2 and 3 only, across 17 games. Zero in REG or POST.
- `play_description` settles the direction: esbid 2024082552 reads "W.Lutz kicks 65 yards from DEN 35" with `yard_line_side` DEN and `possession_nfl_team` ARI. Possession names the receiving team and is wrong; `yard_line_side` is right.

### Assumptions

- The 156 are the whole class. UNCHECKED at other cutoffs — sweep `KOFF` rows across every season for a possession that disagrees with `yard_line_side` before defining the population, and define it by that signature rather than by the season.
- The small dips in 2008, 2013, 2014 and 2025 are a different cause or noise. UNCHECKED. Measure them before assuming this repair does not cover them, and say which they are.
- `yard_line_side` is trustworthy on these rows. Checked once against `play_description`; widen it before repairing on it.

## Design

**Fix the writer, then the data, then the comment — and the comment is load-bearing.** A repair that corrects 156 rows and leaves a source comment asserting the inverse convention re-mints the defect on the next importer change. The comment is the reason the swap is missing.

**Repair in its own dated adhoc file**, scoped by the measured signature and setting all three team columns together — `nfl_plays_offense_matches_possession` requires offense to equal possession, and `nfl_plays_defense_differs_from_offense` requires the two sides to differ.

**The kickoff convention is already documented on the columns.** `db/adhoc/2026-09-11-comment-plays-team-columns-kickoff-convention.sql` put it on all three, and `possession_nfl_team` explicitly names which importers swap and which does not. Read those comments from `pg_attribute` before writing anything.

### Knowledge Base Impact

- No new tag. No scratch slug expected.
- `docs/guides/data.md` already carries the kickoff convention; extend only if the repair changes it.

## Notes

- Do NOT widen this into a general kickoff audit. The 2024 preseason class is attributable to one importer with one missing swap; the other seasons' dips are a measurement to report, not necessarily this task's repair.
- Dry-run the repair outside `db/adhoc/` with a forced `RAISE EXCEPTION` rollback, per `db/adhoc/README.md`.

## Plan

### Measurement

- [ ] **Sweep the signature across every season** — `KOFF` rows where `possession_nfl_team` disagrees with `yard_line_side`, by season and season type. Define the population by the signature, not by 2024.
- [ ] **Classify the 2008 / 2013 / 2014 / 2025 dips** — same cause, different cause, or noise. Report the reading.

### Core Implementation

- [ ] **Add the kickoff possession swap to `scripts/import-plays-sportradar.mjs`**
  - Verify: a spec that fails without the swap and passes with it, over a fixture whose description names the kicking team.
- [ ] **Correct the backwards `KOFF` comment** (around line 590) in the same commit.
- [ ] **Author the repair** — `db/adhoc/<date>-repair-2024-preseason-kickoff-possession.sql`, setting possession, offense and defense together.
  - Verify: post-condition asserting `possession_nfl_team` matches `yard_line_side` on every repaired row, and that no row outside the signature moved. Prove the control fires on a deliberately over-reaching variant.

### Testing & Validation

- [ ] **Run the suite BEFORE applying** — `yarn test:isolated`, read the summary line and the exit code rather than a tail window.
- [ ] **Confirm `plays-team-attribution-self-consistency` grades clean before and after.**
