---
title: Repair wrong-domain season week reads in the stat generators
type: task
status: completed
description: >-
  COMPLETED: three stat generators defaulted their week to fantasy_season_week where an NFL
  regular-season week was meant. Repaired with a new nfl_regular_season_week member -- not with
  nfl_season_week, which would have broken the Super Bowl. The sweep for the wider class found eight
  more defects across production importers, and a cross-domain comparison gate now holds the line.
base_uri: user:task/league/repair-wrong-domain-season-week-reads.md
created_at: '2026-09-12T22:19:45.563Z'
entity_id: 98d5e3ac-86b0-4a93-85fd-e2e46ff53bda
finished_at: '2026-09-12T23:07:34.791Z'
owner_identity_uri: █████████████████████████
priority: High
public_read: true
relations:
  - relates [[███████████████████████-███████-██████-██████-█████████]]
  - relates [[user:task/league/fix-calculate-week-postseason-off-by-one.md]]
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-09-12T23:07:34.791Z'
visibility_analyzed_at: '2026-11-05T23:50:09.855Z'
---

## Resolution (2026-09-12) — league `2025f99ba`

All three sites repaired, but **not to `nfl_season_week`**, and two of the measured claims below turned out to be wrong. Details first, because both corrections change what the next reader should believe.

### The replacement is a NEW member, `nfl_regular_season_week`

All three sites resolve their rows against `nfl_games` filtered to `season_type = 'REG'`, so all three needed a REGULAR-season week. Neither existing member is one:

- `fantasy_season_week` is 0 in PRE, 0 in POST, and 0 for NFL week 18.
- `nfl_season_week` is a week within the CURRENT season type — it counts 1, 2, 3 through the preseason and restarts at 1 for wild card weekend.

So the mechanical swap this task warned against would have traded one defect for a worse one. At the Super Bowl `nfl_season_week` reads 4, a perfectly valid REG week 4, and the LAST_THREE window would have become REG weeks 3, 2, 1 — overwriting the end-of-season rolling values with September's, silently. `nfl_regular_season_week` is 0 whenever the clock is outside the NFL regular season, which fixes week 18 without opening the postseason.

### Both gamelog generators were the same defect, judged per site

`calculate_points` and `calculate_points_added` each filter `nfl_games` on `season_type = 'REG'` and `week`, and every caller in the two `*-player-logs.mjs` wrappers passes a week read back out of that same REG-filtered query. The fantasy SCORING of the rows does not change what the week SELECTS. So the fantasy member was wrong there too — not deliberate.

### Correction: the negative weeks are not reachable, and the real damage shape is different

The `week - i` values never reach the database as negative. `if (week > 0)` filters them out, leaving `query_weeks` EMPTY — and an empty list then skipped the `whereIn` entirely, so the query would have averaged the WHOLE season into a row labelled LAST_THREE. That is worse than a negative week, because it is a wrong value that reads exactly like a right one. It now throws instead.

In practice even that never fired: the live caller is gated on `week > 3`, and the fantasy member's 0 in PRE and POST meant the rolling variants simply never ran outside the regular season.

### Correction: `nfl_team_seasonlogs` has no week column, so step 4's premise was wrong

The table is keyed on `(nfl_team, stat_key, season_year)`. There are no week-0 or negative-week rows to look for. The answerable question is whether any rolling-window row was ever written, and the measurement says no:

- 960 rows per season for each of 2022-2025 (control: the table is populated).
- Zero rows carrying a `*_LAST_THREE`, `*_LAST_FOUR` or `*_LAST_EIGHT` stat_key, for any season.

**No data repair is needed.**

### Verification

- New spec pins `nfl_regular_season_week` across the whole calendar; both wrong members run as controls and each turns it red.
- New spec `test/scripts.generate-nfl-team-seasonlogs.spec.mjs` drives the window derivation through the CLOCK, so the choice of member is what is under test. `rolling_window_weeks` reads the clock itself for that reason — an anchor passed in by the caller would put the only thing that was ever wrong out of test reach. Both wrong members run as controls.
- `yarn test:isolated` 8581 passing. `check:cluster` 29 findings across 4 gates, measured IDENTICAL at HEAD with this work set aside, so none are mine. `check-season-member-resolution` green.

### Step 5 — the gate exists, and finding it found eight more defects

The shape sketched in the plan (a member read in a file that also writes an `nfl_*` table) was measured and rejected: 8 candidates, of which only 2 were defects. A 75% false-positive rate needs an allowlist, which is what makes a gate get weakened rather than obeyed.

The shape that works is narrower — **a comparison with an `nfl_` member on one side and a `fantasy_` member on the other**. The two domains measure different calendars, so such a comparison is meaningless whatever it returns, and correct code has no reason to contain one. Measured tree-wide: 7 instances, **all seven genuine defects**, zero false positives.

`db/gates/check-season-domain-comparisons.mjs`, in the `check:cluster` manifest at league `2e0013265`. Ten always-on controls, each must-report case paired with a decoy, plus two that pin the marker lookback window at both edges. Verified end to end by reinstating a real repaired defect and confirming the gate names the file and line. Landed green with zero adjudications, after the findings were repaired — the cluster's finding count is 29 both before and after, so it added no debt.

### The eight importer defects — league `2f13fe015`

All seven cross-domain comparisons were the same dead expression, `fantasy_season_week > nfl_regular_season_final_week`, which can never be true:

- **Six projection importers** (cbs, espn, fantasysharks, fbg, ffn, fftoday) used it as "do not import after the season has ended", so none ever stopped. Now `is_fantasy_season_over`. **This is a live behavior change** — they will now skip once the fantasy championship resolves.
- **`import-espn-line-win-rates.mjs`** paired it with a `=== 0` lower bound that also catches NFL week 18, so it skipped the final week of every regular season while ESPN was still publishing. Now `is_nfl_regular_season`.

Plus one found by the co-occurrence sweep rather than the comparison rule:

- **`import-plays-nflfastr-ftn.mjs`** threw "Season has not started yet" on a falsy fantasy week, refusing the current season's week 18 and all postseason FTN charting. Now gates on `nfl_season_type === 'PRE'`.

The other six co-occurrence candidates were adjudicated as correct — they compare fantasy weeks against fantasy boundaries, and `api/routes/scoreboard.mjs` branches on season type explicitly.

### What the gate still cannot see

Only DIRECT member-to-member comparisons. A member bound to a local and compared later is invisible, and so is a wrong-domain read that is never compared at all — **including the parameter defaults that started this task**. That class remains uncovered by any gate, which is why the specs carry it.

## Problem

`f6ce81b42` gave every `Season` member a domain prefix so that no caller could pick a domain by accident. Where a member was renamed into the WRONG domain at a call site, the result still resolves — so nothing throws, nothing lints, and `db/gates/check-season-member-resolution.mjs` is structurally blind to it. That gate answers "does this member exist"; this task is the other half, "is it the right one".

Three sites default an NFL-domain week to the fantasy member:

| Site | Reads | Should read |
| --- | --- | --- |
| `scripts/generate-nfl-team-seasonlogs.mjs:348,353,358,688` | `current_season.fantasy_season_week` | `nfl_season_week` |
| `scripts/generate-scoring-format-player-gamelogs.mjs:23` | `current_season.fantasy_season_week` | judge per site |
| `scripts/generate-league-format-player-gamelogs.mjs:23` | `current_season.fantasy_season_week` | judge per site |

## Why it matters — measured, not reasoned

Both members walked across injected clocks on the 2026 constants (2026-09-12):

| Week offset from anchor | `fantasy_season_week` | `nfl_season_week` | `nfl_season_type` |
| --- | --- | --- | --- |
| 0 | 0 | 3 | PRE |
| 1 | 1 | 1 | REG |
| 17 | 17 | 17 | REG |
| 18 | **0** | 18 | REG |
| 19 | **0** | 1 | POST |
| 25 | **0** | 4 | POST |

`fantasy_season_week` collapses to 0 from NFL week 18 onward and reads 0 through the preseason. `generate-nfl-team-seasonlogs.mjs` derives its rolling windows as `week - i`, so across the whole postseason those resolve to **0, -1, -2** — negative weeks against a table that has none.

The file already carries a comment at line 684 noting the weeks "are nonsensical", so the symptom was seen without the cause being named.

## Judge the two gamelog generators per site, do not sweep

`generate-scoring-format-player-gamelogs.mjs` and `generate-league-format-player-gamelogs.mjs` produce FANTASY-scored gamelogs. The fantasy member may be deliberate there even though the row grain is an NFL week — decide from what the generated rows are keyed on, not from the file name. Mechanically substituting one replacement across all three is exactly what this codebase's season history warns against.

## Plan

1. Confirm the postseason breakage by executing, not by reading: run `generate-nfl-team-seasonlogs.mjs` against an injected POST clock and assert on the week values it computes. A diff review cannot distinguish 0 from 18 here.
2. Repair `generate-nfl-team-seasonlogs.mjs` to `nfl_season_week` and delete the now-false line-684 comment.
3. Decide the two gamelog generators per site and record the reasoning in the commit.
4. Check whether rows were already written at week 0 or negative weeks in `nfl_team_seasonlogs`, and repair the data if so — the code fix does not retract what a prior postseason run wrote.
5. Consider whether a gate can catch the wrong-DOMAIN class at all. A useful shape: a member read inside a file that also writes an `nfl_*` table is suspect. Land it green with adjudications, per `docs/guides/gates.md`.

## Related

- `███████████████████████████████████████████████████████` — the rename that introduced this. Completed, and these gaps postdate its close.
- `███████████████████████████████████████████████████████` — the resolution half, completed 2026-09-12.
- `user:task/league/fix-calculate-week-postseason-off-by-one.md` — Planned, and adjacent: `calculate_nfl_week` postseason behavior, the same POST window this task's defect lives in.
