---
title: Fix player_league_extended_salary rejecting array-shaped lid and year params
type: task
status: completed
description: >-
  The extended-salary emitter reads lid and year as scalars, but explicit column params are
  normalized to arrays, so the column errors whenever a user sets either one. Works only on
  defaults.
base_uri: user:task/league/data-views/fix-extended-salary-array-params.md
created_at: '2026-07-31T18:02:55.499Z'
entity_id: 7a2ec6a7-8772-444c-a14d-0f3ca201d657
finished_at: '2026-07-31T18:23:34.364Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Low
public_read: true
relations:
  - relates [[█████████████████████-█████████-██████-████████-█████████████]]
  - follows [[████████████████████-████████-██████-████-████████-██████-█████████]]
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-09-22T07:24:29.621Z'
visibility_analyzed_at: '2026-11-05T23:48:51.025Z'
---

# `player_league_extended_salary` errors on array-shaped params

**Shipped 2026-07-31 in league `82492e1fe`, deployed to both hosts and confirmed against production.** Both `player_extended_salary_join` and `player_extended_salary_table_alias` now derive `lid`/`year` through one shared `get_scope` helper built on `get_single_value` from `#libs-server/data-views/param-utils.mjs`. The alias is also exported now, so `player_league_extended_salary_over_market` shares the join group rather than copying the extension ladder.

The defect turned out to be a broader class that three other columns still carry — see the observations.

## The original defect

`player_extended_salary_join` read `params.lid` and `params.year` as scalars. `get_data_view_results` normalizes explicit column params to arrays, so an array reached the emitter and Postgres rejected it:

```
invalid input syntax for type smallint: "{"2026"}"
```

The column works only when it falls back to its `lid 1` / `current_season.year` defaults, which is what the fantasy league table on xo.football does — so the live page-load path is unaffected and this has never been reported. It breaks for any user who sets an explicit `lid` or `year` on the column in a data view.

Found 2026-07-31 by the session that fixed the post-deadline ladder double-application (`███████████████████████████████████████████████████████████████████`). Pre-existing on both the pre-fix and post-fix emitters; that session correctly left it alone as out of scope.

## Why it matters beyond this one column

`█████████████████████████████████████████████████████████████` planned a contract-versus-market column that shares this join by design, to avoid a second copy of the extension ladder. That constraint is now gone: the sibling session dropped it from its plan and shipped `player_league_extended_salary_over_market` in league `f16c0b7d4`, which imports the exported alias and join from this file.

## Plan

- [x] **Establish the existing convention and the true scope**
  - Find how other column definitions handle the normalized array shape; this is solved elsewhere in the codebase
  - Decide whether the right layer is this emitter or the normalization. A scalar-expecting emitter receiving an array is plausibly a broader class than one column — if it is, report the class rather than silently fixing only this one
  - Verify: candidate sites listed with `file:line`, and the chosen layer stated with its reason
  - Done: convention is `get_single_value` (`libs-server/data-views/param-utils.mjs:323`), which `get_scoring_format_id` and `get_league_format_id` both wrap. Emitter chosen over normalization — the array shape is load-bearing for every column that fans out over `year`. Class confirmed by execution, see observations.

- [x] **Apply the fix**
  - File: `libs-server/data-views-column-definitions/player-extended-salary-column-definitions.mjs` (or the normalization layer, per the step above)
  - Match the existing convention rather than special-casing this emitter
  - Verify: `yarn lint` clean; generated SQL inspected for both scalar and array param shapes
  - Done in `82492e1fe`. `yarn lint` 0 errors; emitted SQL carries `"rp"."lid" = 1 and "rp"."year" = 2020` on both param shapes.

- [x] **Verify both emitter branches**
  - The column emits different SQL depending on whether the season's EXTENSION transactions exist. A fix verified on one branch is half-verified
  - Exercise explicit `lid` and `year` params against lid 1 / 2026 (extensions processed) and lid 1 / 2020 (not processed); assert both return rows rather than erroring, and that the values still match the pre-fix defaults case where the params name the default scope
  - Verify: both queries run via `NODE_ENV=production LEAGUE_DB_HOST=127.0.0.1 LEAGUE_DB_PORT=15432`, row counts reported
  - Done: 233 rows on lid 1 / 2026, 205 on lid 1 / 2020, array params byte-identical to both the scalar and defaults cases on each branch. Positive control at pre-fix HEAD errors on both.

- [x] **Gates**
  - `db/adhoc/check-data-view-sql-validity.mjs` from a clean worktree at HEAD; full suite on the `:5433` container in its own database
  - ~~Verify: gate exits 0; suite green; no golden touched~~ — gate OK (1124 statements / 562 columns) and suite green (2429 passing, 0 failing), both from a clean worktree at HEAD. **Diverged on "no golden touched":** no existing golden was regenerated, but a NEW one was added — `test/data-view-queries/league-extended-salary-with-explicit-lid-and-year-params.json`, pinned to lid 1 / 2020 so it does not move with the clock. See the `[scope-change]` observation.

- [x] **Deploy and confirm on production**
  - Deploy, then set an explicit `lid` and `year` on the column against production and confirm it returns data
  - Verify: production response carries rows, not an error
  - Done: backend deployed, both hosts on `f16c0b7d4`. `POST /api/data-views/search` on a novel payload returns 200 with rows for both lid 1 / 2026 (Jacobs 60, Allen 55, Jackson 55) and lid 1 / 2020 (Allen 60, McCaffrey 54, Brown 48), matching host-side exactly.
