---
title: Partition the row-axis share percentile by its period, not by position alone
type: task
status: completed
description: >-
  A week- or year-split view pools every period into one rank, so a week-3 back is ranked against a
  week-14 back; render_percentile's extra_partition exists and only the params.output path supplies
  it.
base_uri: user:task/league/partition-the-row-axis-percentile-by-period.md
created_at: '2026-09-24T21:55:37.461Z'
entity_id: 02c710fb-b004-450b-b2ae-2534570f6d81
finished_at: '2026-09-24T23:03:18.936Z'
owner_identity_uri: █████████████████████████
public_read: true
relations:
  - relates [[███████████████████████-████-████-███-████-██████-█████-███████████]]
tags:
  - user:tag/league-xo-football.md
  - user:tag/software-task.md
updated_at: '2026-09-24T23:45:24.979Z'
visibility_analyzed_at: '2026-11-05T23:49:47.730Z'
---

## Problem

The share percentile pools every period of a row-axis scan into ONE rank. A view
split by week ranks a week-3 back against a week-14 back; a view split by year
ranks 2023 against 2025. The number is well-formed, spans 0 to 100, sorts fine,
and answers a question nobody asked.

This is the exact failure `render_percentile`'s own `extra_partition` note in
`libs-server/data-views/measure/percentile.mjs` describes. The parameter exists and
only ONE caller supplies it — `libs-server/data-views/output-aggregator/build-period-cte.mjs:518`,
the `params.output` path. The row-axis path never does.

It is NOT a regression from the percentile lift (league `92956de53`). That commit
fixed the offset-window scope and left this one as it found it. The offset-window
scope has the same hole from the other direction: `recombine_percentile` in
`measure-contract.mjs` takes no `extra_partition` argument at all, so a week axis
combined with a `year_offset_range` cannot be partitioned even if the emitter
wanted to.

## Mechanism

`libs-server/data-views/add-player-stats-play-by-play-with-statement.mjs:147-160`
adds each row axis to the CTE's SELECT and GROUP BY. The percentile SQL was
already rendered upstream by `derive_measure(...).select`, which is the SEASON
render and carries no time key. Nothing connects the two, so the GROUP BY gains a
period and the PARTITION BY does not.

## Measured 2026-09-24, on `player_expected_fantasy_points_share_percentile_from_plays`

The tell is the count of rows reading exactly 100.00. PERCENT_RANK gives 1.0 to the
top row of EACH partition, so a correct partition yields one maximum per
(position, period) and a pooled one yields a maximum per position only.

| request | ranked rows | rows at 100.00 | expected if partitioned |
|---|---|---|---|
| `row_axes: [week]`, year 2024 | 3,738 over 18 weeks | 3 | 54 (3 positions x 18 weeks) |
| `row_axes: [year]`, years 2023-2025 | 961 over 3 years | 3 | 9 (3 positions x 3 years) |

Three in both cases, which is the position count — RB, WR and TE, the positions
carrying a floor in `QUALIFIED_ROLE_SHARE_FLOORS`. Per-week maxima confirm it
independently: 15 of 18 weeks top out at 99.x rather than 100.

## Scope

A data-view correctness fix in shared measure-contract code. No production write.
The column is the only percentile measure in the registry today, so the blast
radius is one column and whatever future measure declares `percentile`.

## Plan

- [ ] Reproduce both rows of the table above before changing anything, using the
  rows-at-100 count rather than eyeballing a spread — a per-period spread looks
  correct under pooling and is what hid this
- [ ] Decide where the period key enters. The row axes are known to the CTE
  builder, the percentile is rendered before it runs, so either the render moves
  or the axes reach it — name the choice rather than threading a parameter through
- [ ] Give `recombine_percentile` the same period key, so the offset-window scope
  cannot be partitioned one way and the period scope another
- [ ] Assert a NON-DEGENERATE result, per [[███████████████████████████████-█████████████]]:
  a test that counts maxima per partition, not one that greps the emitted SQL for
  the word PARTITION
- [ ] Re-run the data-view SQL validity gate and the golden set

## Verification

Rows reading exactly 100.00 must equal positions times periods, on both the week
and the year axis. A golden diff alone does not verify this — the pooled SQL is
valid and its goldens are green today.

## Origin

Found on 2026-09-24 by `repl-69f47b3c` reviewing league `92956de53`, the commit
that fixed the sibling scope. Confirmed empirically against production rather than
by reading the emitted SQL.
