---
title: Conform plain server-internal season vocabulary onto season_type
type: task
status: completed
description: >-
  Rename plain server-internal seas_type identifiers to season_type across libs-server internals and
  their callers, plus fold build_nflfastr_game_resolver onto season_year; leave the frozen data-view
  row-axis vocabulary untouched.
base_uri: user:task/league/conform-server-internal-season-vocabulary.md
created_at: '2026-08-26T00:44:38.279Z'
entity_id: 86c470ad-f474-45df-ae60-8dc1763ad414
finished_at: '2026-08-26T04:45:01.617Z'
observations:
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
public_read: true
tags:
  - user:tag/league-xo-football.md
  - user:tag/software-task.md
updated_at: '2026-08-26T04:45:16.073Z'
visibility_analyzed_at: '2026-11-06T00:17:43.976Z'
---

# Conform plain server-internal season-vocabulary onto season_type

## Plan

Conform the plain server-internal season vocabulary onto `season_year`/`season_type` — both `seas_type`→`season_type` and internal `year`→`season_year` — across `libs-server` internals and their callers, leaving the frozen data-view row-axis vocabulary (`year`/`seas_type` at params, row_axes, CTE aliases, saved views, short URLs) and the format modules untouched. Four reviewable batches, each its own commit gated on the call-site oracle, residual grep, `check:cluster`, eslint, and the full suite on private port 5440. All four landed: A (build_nflfastr fold-in) `403e8e73d`, B (plain libs-server + callers) `5a73574b5`, C (data-views runtime internals) `2ae2c60b8`, D (apply_scope_to_query internals + coupled callers, incl. `year_column`→`season_year_column`) `dc5e8defe`, plus a straggler pass `63cfc5a60` and a review polish `7111febd0`. Merged to master as `83b4f18e8` and deployed; the worktree and branch are gone.

## Overview

Rename `seas_type` identifiers that are PLAIN SERVER INTERNALS to `season_type`, across `libs-server` (the data-views runtime plumbing and the non-data-views modules) and the callers that reach them. This is the tail of the season-grain conform that already moved the physical DB columns and the REST API onto `season_year`/`season_type`. Scope is strictly the internal identifiers, not the frozen data-view row-axis vocabulary.

Also fold in `build_nflfastr_game_resolver`: rename its unconformed `year` destructured param to `season_year` and drop the one-key boundary mapping at its sole caller.

## Background

Current master (`7f1795537`), measured fresh. `libs-shared` contributes ZERO in-scope occurrences — every remaining `seas_type` there is an exempt module: `nfl-week-identifier`, the selection-id format pair + `selection-constants`, `season`/`season-dates`, the axis column params (`common-column-params`, `output-column-param`, `get-output-denominator-params`), and the `data-views-nfl-week-migration` (reads frozen legacy view-param keys). All confirmed STAYS.

The immutable row-axis vocabulary STAYS verbatim: `params.seas_type` / `column_params.seas_type` reads, `consumes_params` `'seas_type'` keys, emitted CTE row-axis aliases (e.g. `defensive_plays.seas_type`, `'seas_type'` column names), and object keys passed to the nfl-week vocabulary functions (`format_nfl_week_identifier`, `get_nfl_week_identifiers_for_year`, `decompose_nfl_weeks`) and to `format_standard_selection_id`. `get-data-view-results.mjs` is all STAYS except two caller-side KEYS (below). The external gamelog agreement check reads persisted external reference-site cache JSON (not DB rows) — STAYS. `nfl_seas_type` tokens are the shared constant — STAYS.

The MINE set, by batch (re-derived — materially smaller than the stale "218 in 44 files", which lumped axis-vocab data-views in with the internals):

### Batch A — build_nflfastr fold-in
- `libs-server/nflfastr/resolve-nflfastr-game.mjs`: `({ year })` -> `({ season_year })`, `.where({ season_year: year })` -> `.where({ season_year })`.
- `scripts/import-plays-nflfastr.mjs`: call `{ year: season_year }` -> `{ season_year }`; delete the 3-line boundary comment (878-880).
- `test/libs-server.resolve-nflfastr-game.spec.mjs`: 7 call sites `{ year: 2021 }` -> `{ season_year: 2021 }`.

### Batch B — plain libs-server internals + their callers
Param/local renames (MINE):
- `get-projections.mjs` param `seas_type` (27,28,32,33,51) — callers: `scripts/process-projections.mjs:156,157,159`; `api/routes/projections.mjs:206` (no key change); `test/libs-server.get-player-projections-week-floor.spec.mjs:92,96,127` (key `seas_type:` -> `season_type:`).
- `4for4.mjs` param `seas_type` (20,25,30,37) — caller `scripts/import-4for4-projections.mjs:68-71` (+ its own locals 61,64,71,115,139,171).
- `check-projections-index-floor.mjs` param `seas_type` (21,31,43) — callers in 6 import scripts pass `seas_type:` result key -> `season_type:` (import-espn/fftoday/sleeper/fbg/fantasysharks/cbs projections), each ~2 sites.
- `simulation/load-nfl-schedule.mjs` params `seas_type` (56,59,63,74,80,87) — callers pass no seas_type (default REG), no caller change.
- Locals only (no cross-file param): `submit-reserve.mjs` `fallback_seas_type` (62,97); `verify-reserve-status.mjs` (39,74); `get-players.mjs` (113,148); `draftkings/draftkings-formatters.mjs` `seas_type` (31,47,58,67); `apply-play-by-play-column-params-to-query.mjs` locals `seas_types`, `covers_full_year_seas_type` (118,121,136,138-139).
- `validators.mjs` `seas_type_schema`/`seas_type_validator` (40,46) — no callers.

### Batch C — data-views runtime MINE (not coupled to apply_scope_to_query)
- `resolve-view-scope.mjs`: helper `expand_year_seas_type` param `seas_type` (30,35,36,40) + internal call key (64).
- `get-play-by-play-default-params.mjs`: local `seas_type` (11) + shorthand object key (16,19) -> expand to `seas_type: season_type` (keep the vocab key).
- `resolve-single-nfl-week-id.mjs`: locals `seas_type_param` (110), `seas_type` (114,115,122).
- `resolve-nfl-week-id-from-year-param.mjs`: locals `seas_type_array` (27,33).
- `week-opponent-cte-tables.mjs`: param `seas_type='REG'` (6) used as `.where` value (14,22) -> `season_type`; callers `get-data-view-results.mjs:1789,1814` switch KEY `seas_type:` -> `season_type:` (values unchanged).
- `identity-bridges/player-year-to-team-year.mjs`: internal helper `resolve_seas_type` (14,78).
- `join-practice-current-week.mjs` (9,16), `join-nfl-games-offset-week.mjs` (13,25), `join-nfl-games-current-week.mjs` (9,23): destructured local `seas_type` -> `{ ..., seas_type: season_type }` binding (source key stays), used as `.andOn(... season_type', [season_type])` value.

### Batch D — apply_scope_to_query internal params + coupled callers
- `apply-scope-to-query.mjs`: internal params `has_seas_type` (127,137,150,152-153) -> `has_season_type`, `seas_type_column` (130,155) -> `season_type_column`, locals `sorted_seas_types` (139) -> `sorted_season_types`, `resolved_seas_type_column` (154,155,157-158) -> `resolved_season_type_column`. `params.seas_type`, `column_params.seas_type`, and the `seas_type: [...]` scope keys STAY.
- Coupled callers (must update keys/locals): `output-aggregator/build-period-cte.mjs` `has_seas_type` local (73) + `seas_type_column:` key (83) + `has_seas_type:` keys (307,526); `period-denominator/per-player-play.mjs` `has_seas_type: false` (134); `period-denominator/per-player-route.mjs` `has_seas_type: false` (123).
- `physical-season-columns.mjs` itself is the off-limits boundary module — its exported `physical_seas_type_column` / `physical_has_seas_type` names STAY.

## Design

Approach: per-occurrence edits, NOT a mechanical `seas_type`->`season_type` replace. The token appears in two roles that must stay literally "seas_type" (frozen vocabulary) and one that must change (internal). Where a renamed internal becomes a shorthand object key that must keep the `seas_type` name, expand the shorthand (`{ seas_type }` -> `{ seas_type: season_type }`). Shorthand outputs that ARE vocabulary keys keep `seas_type` and expand only the value binding.

The landed gate `db/gates/check-call-site-param-contracts.mjs` is the caller-enumeration oracle: it resolves each cross-file call's import edge and compares call-site object keys against the callee's destructured parameters. After every param rename it mechanically lists stale callers (those still passing the old key, which the permissive default would otherwise swallow). Use it to drive each batch's caller updates; run it and require zero findings before committing.

Batch order maximizes independence and keeps the tree green at every commit:
1. **Batch A** (fold-in) — standalone.
2. **Batch B** (plain libs-server) — the projection-param renames share no function with Batch C/D.
3. **Batch C** (data-views runtime non-coupled) — independent of apply_scope_to_query.
4. **Batch D** (apply_scope_to_query + coupled callers) — largest cross-file surface; done last, oracle-verified.

Each batch is its own commit (billed CI run) with explicit pathspecs. Never `git add -A`.

### Verification recipe (per batch)
Every check must first be demonstrated able to go RED (verification rule):
1. **Residual grep**: after editing, `grep -n "seas_type"` each edited file must show ONLY the STAYS tokens (params.seas_type reads, vocab keys, CTE aliases, consumes_params). Demonstrate red: run the grep against a known-STAYS file (e.g. `physical-season-columns.mjs` still shows `seas_type`) and against a MINE line before renaming it — pattern returns the token, confirming the grep can match.
2. **Call-site oracle**: `node db/gates/check-call-site-param-contracts.mjs` — show it can go red by confirming it flags a stale key before the caller update, then require zero findings.
3. **`yarn check:cluster`** — the consistency gate cluster (each gate carries its own negative control per check-cluster-gates.mjs).
4. **`yarn lint`** (eslint; returns the no-bare-container-jsdoc ratchet).
5. **Suite on a PRIVATE port**: copy `compose.test.yaml`, distinct container name and host port, absolute `init-roles.sql` bind, `LEAGUE_DB_PORT` set. **Assigned port 5440** (5433-5439 taken). Never the shared `:5433`. Tear down only my own project.
6. **Targeted specs**: `test/libs-server.resolve-nflfastr-game.spec.mjs`, `test/libs-server.get-player-projections-week-floor.spec.mjs`, and `yarn test:local` on 5440.

### Knowledge-base impact
No new domain entities. The conform touches no user-facing capability; the one stale doc is `scratch/league/season-vocabulary-lanes/lane-6-server-internal-conform.md` (the previous research, already superseded; left in place, no promotion). Affected ABOUT.md: none.

### Scratch
No new scratch slug. The prior research already lives at `scratch/league/season-vocabulary-lanes/`.

### Orchestrator shape
Bounded to a single task; no orchestrator. The partition + batches above are the whole of it.

## Assumptions
- CLI flags stay as-is; only internal identifiers conform (already true — no argv touches `seas_type`).
- `year_column` in `apply_scope_to_query` IS renamed, along with the `has_year` / `sorted_years` / `resolved_year_column` siblings in the same destructure. This does not open a general `year`->`season_year` sweep: it stops at the emitter's own parameter list.
- `physical-season-columns.mjs` and both format modules are untouchable.

## Decisions (both resolved by the operator, before sweeping)
1. **Batch D included**, done last and oracle-verified. Resolved: yes.
2. **`year_column` symmetry included**. The operator ruled to advance to the full clean canonical end state and expand scope as appropriate, which settles this toward symmetry: an asymmetric destructure is not the end state.

## Tasks
- [x] A: nflfastr fold-in (3 files, incl. spec) — committed `403e8e73d`.
- [x] B: plain libs-server internals + script/spec callers — committed `5a73574b5`.
- [x] C: data-views runtime MINE set — committed `2ae2c60b8`. Gates re-run independently of the predecessor's transcript: prettier clean, eslint identical to baseline, call-site oracle GATE OK, residual grep shows only STAYS tokens.
- [x] D: apply_scope_to_query internals + coupled callers — committed `dc5e8defe`. Oracle demonstrated RED on 9 stale call sites across 3 files, then GATE OK after the updates.
- [x] Stragglers: two `const { seas_types } = decompose_nfl_weeks(...)` locals the batch enumeration missed — `get-data-view-results.mjs:2126` and `add-team-stats-play-by-play-with-statement.mjs:354` — committed `63cfc5a60`.
- [x] Final: reviewed the whole lane diff, merged, validated and deployed. Merged to master as `83b4f18e8`; CI green on the merge commit. Deployed from a clean worktree pinned to `origin/master` at `d031fd1cc` — both hosts confirmed on that revision, served bundle hash matches the build, API and both workers reloaded and online.

## Outcome

Batch D took the operator's symmetry ruling to its conclusion: `has_year`/`sorted_years`/`resolved_year_column` moved alongside `year_column` so the whole destructure reads in one vocabulary. `has_nfl_week_id` stayed — `nfl_week_id` is already the canonical physical name, not a legacy one.

Review of the merged tree surfaced one defect unrelated to this lane, fixed here because it blocked a clean deploy: `league_player_projection_values.week` was narrowed to `smallint` by `0a846bbdb`, whose message records "no companion code change", but the data-view emitter still wrapped the join in `CAST(... AS VARCHAR)`. That compared `smallint` to `character varying` and failed with 42883 on every query carrying `player_week_projected_points_added*`. `check-data-view-sql-validity` was red on one generated statement and is clean across all 4590 after the fix. The sibling flag site for `league_format_player_projection_values` stays — that table's `week` really is still `character varying(10)`.

Two items outside the enumerated partition were repaired because the rename made them wrong rather than merely inconsistent:

- `physical-season-columns.mjs` threw a runtime error instructing callers to `pass has_seas_type: false`, a key that no longer exists. The message now names `has_season_type`. Only the string changed; every exported name in that boundary module is untouched.
- `docs/guides/data.md` described an emitter omitting `year_column`; it now says `season_year_column`. The neighbouring `param-utils.mjs` mention of `year_column` refers to a different contract and was left alone.

### Deliberately left alone

- `per-game.mjs` `sorted_years` — a plain local in a module the partition never enumerated. Conforming it means opening a general `year` sweep across `libs-server`, which is a separate lane.
- `identities.mjs` / `param-utils.mjs` / `select-string.mjs` `year_column` and `join_year_column` — the data-view identity and source key-column contracts, a different axis that merely shares the token.
- `libs-server/data-views-column-definitions/` — roughly 18 bindings destructured off `get_default_params()` across seven modules. The partition never enumerated this directory, and the bindings sit in the view-param read role rather than the plain-internal one, so conforming them is a scope call the operator should make rather than a straggler this lane dropped. One is off house style regardless of vocabulary: `player-seasonlogs-column-definitions.mjs:22` binds `seas_type: seasType` in camelCase.

### Pre-existing cluster findings (not this lane's)

`yarn check:cluster` reports findings from three gates — ts-check-ratchet, league-schema-consumers, conflated-player-rows — on the base ref as well as on the branch. None of their files overlap anything this lane touched, and master has since landed fixes for some of them.
