Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,13 @@ badge and does not invent leftover-map axis share from leftover-map singular
value, leftover pair count, or plotted marker count. Share `0` is shown when
persisted, including rank-0 unused axes. The strip does not gain leftover-map
singular values or the leftover-map graphic.
ADR 0305 returns persisted leftover-map rank on
`GET /api/reports/compare/{period}` leftover pairs so a buyer who
compares leftover pairs can match the pair-row rank badge on live
responses; a missing rank stays null and does not invent rank from
leftover-map coordinates, leftover-map distance, leftover-map
reconstruction, leftover-map axis share, or the count of unused axes.
The strip does not gain the leftover-map graphic.
When `R`, `R̂`, `U`, `x`,
`s`, and `e` are finite, `e + s + x = 1`. When `Y`, `E`, and `R` are
finite, `Y − E = R`. When `R`, `R̂`, and `U` are
Expand Down Expand Up @@ -536,6 +543,8 @@ Grouping comparison leftover-map coordinates payload (ADR 0303) returns persiste
coordinates `ξ` / `ζ` on `GET /api/reports/compare/{period}` leftover pairs.
Grouping comparison leftover-map axis share (ADR 0304) captions the grouping comparison
strip with persisted leftover-map axis share.
Grouping comparison leftover-map rank payload (ADR 0305) returns persisted leftover-map
rank on `GET /api/reports/compare/{period}` leftover pairs.
Item complete-case coverage
(ADR 0282) captions the leftover-map graphic with how many scored criteria
entered the map. Item complete-case coverage on the pair list (ADR 0285)
Expand Down
5 changes: 4 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,8 @@ ADR 0303 returns persisted leftover-map coordinates `ξ` / `ζ` on
`GET /api/reports/compare/{period}` leftover pairs.
ADR 0304 captions the grouping comparison strip with persisted leftover-map axis share
through leftoverMapCompareAxisShare.
ADR 0305 returns persisted leftover-map rank on
`GET /api/reports/compare/{period}` leftover pairs.
Click a post marker or a pair button
opens that post. Those ADRs are the normative mathematical, storage, and
display contracts. Leftover-map axis share
Expand Down Expand Up @@ -731,7 +733,8 @@ incomplete item coverage (ADR 0292), leftover-map reconstruction `R̂`
(ADR 0301), leftover-map coordinates `ξ` / `ζ`
(ADR 0302), leftover-map coordinates on the compare leftover-pair payload
(ADR 0303), and leftover-map axis share on the grouping comparison strip
(ADR 0304);
(ADR 0304), and leftover-map rank on the compare leftover-pair payload
(ADR 0305);
`POST .../rebuild` scores every grouping kind (post_admin). `make seed`
folds A-100/B-200 Event Lineage fixtures (and the Riverbend calendar
post) that already have constructed IRT cells into the same shared
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.d/2.62.0-leftover-map-compare-rank-payload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## 2.62.0 — Leftover-map rank on grouping comparison leftover-pair payload

- Return persisted leftover-map rank on `GET /api/reports/compare/{period}`
leftover pairs (ADR 0305). After `make seed`, grouping comparison leftover-pair
buttons that already caption formatLeftoverMapRank can match leftover-map rank
on the pair row from the live compare payload; click a post marker or a pair
button opens that post. A missing rank stays null and omits that leftover-map
comparison rank badge independently of leftover-map axis share, leftover-map
coordinates `ξ` / `ζ`, leftover expected `E`, leftover observed `Y`, leftover
residual `R`, leftover-map unexplained leftover `U`, leftover-map cross share
`x`, leftover-map unexplained leftover share `s`, leftover-map explained leftover
share `e`, leftover-map reconstruction `R̂`, leftover-map distance `d`, and the
strip coverage notes. Rank-0 origin cells still return `0` when that persisted
rank is stored. Do not invent leftover-map rank from leftover-map coordinates,
leftover-map distance, leftover-map reconstruction, leftover-map axis share, or
the count of unused axes. A finite negative leftover is shown, never clamped.
The strip does not gain the leftover-map graphic. Never invent a leftover
score. Never invent a theta. No new columns.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ All notable changes to this project are documented here. Format follows

### Added

- Period leftover pairs now return persisted leftover-map rank on
`GET /api/reports/compare/{period}` leftover pairs (ADR 0305 / v2.62.0). After
`make seed`, grouping comparison leftover-pair buttons that already caption
formatLeftoverMapRank can match leftover-map rank on the pair row from the live
compare payload, leftover-map axis share badges match leftover-axis report
badges from leftover_map_axes, leftover-map coordinates `ξ` / `ζ` match the
pair-row badge, leftover-map reconstruction `R̂`, leftover-map explained leftover
share `e`, leftover-map unexplained leftover share `s`, leftover-map cross share
`x`, leftover-map unexplained leftover `U`, leftover residual `R`, leftover
observed `Y`, leftover expected `E`, leftover-map distance `d`, and the strip
coverage notes stay on the grouping comparison strip; click a post marker or a
pair button opens that post. A missing rank stays null and omits that leftover-map
comparison rank badge. Rank-0 origin cells still return `0` when that persisted
rank is stored. Do not invent leftover-map rank from leftover-map coordinates,
leftover-map distance, leftover-map reconstruction, leftover-map axis share,
leftover-map post coverage, leftover-map item coverage, leftover-map incomplete
post coverage, leftover-map incomplete item coverage, or the count of unused
axes. A finite negative leftover is shown, never clamped. The strip does not
gain the leftover-map graphic. Never invent a leftover score or a theta. No new
columns.

- Period leftover pairs now return persisted leftover-map axis share on
`GET /api/reports/compare/{period}` grouping rows and caption the grouping
comparison strip through leftoverMapCompareAxisShare (ADR 0304 / v2.61.0).
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ cutoff. Global Ask optional `knowledge_cutoff` uses the same cover

Create/start endpoint rules (ADR 0017 / 0021), tie-vs-miss similarity
(ADR 0026), R&R catalog ids (ADR 0019 / 0027), leftover pairs
(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284 / 0285 / 0286 / 0287 / 0288 / 0289 / 0290 / 0291 / 0292 / 0293 / 0294 / 0295 / 0296 / 0297 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304), occupational construct catalog search
(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284 / 0285 / 0286 / 0287 / 0288 / 0289 / 0290 / 0291 / 0292 / 0293 / 0294 / 0295 / 0296 / 0297 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305), occupational construct catalog search
(ADR 0257), the text-channel embedding swap and cosine
clamp (ADR 0190), per-edge channel-score persistence (ADR 0195),
token-backed status notices (ADR 0220),
Expand Down
8 changes: 7 additions & 1 deletion backend/app/report_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,8 @@ async def fetch_period_comparison(
f"""
select lp.grouping_kind, lp.grouping_key, lp.pair_kind, lp.post_id,
lp.criterion_code, lp.leftover_distance, lp.leftover_residual,
lp.leftover_map_reconstruction, lp.leftover_map_person_axis_1,
lp.leftover_map_reconstruction, lp.leftover_map_rank,
lp.leftover_map_person_axis_1,
lp.leftover_map_person_axis_2, lp.leftover_map_item_axis_1,
lp.leftover_map_item_axis_2,
p.post_title, p.visibility_code, p.corporate_entity_id,
Expand Down Expand Up @@ -1140,6 +1141,11 @@ async def fetch_period_comparison(
if pair["leftover_map_reconstruction"] is None
else float(pair["leftover_map_reconstruction"])
),
"leftover_map_rank": (
None
if pair["leftover_map_rank"] is None
else int(pair["leftover_map_rank"])
),
"leftover_map_person_axis_1": (
None
if pair["leftover_map_person_axis_1"] is None
Expand Down
13 changes: 13 additions & 0 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5845,6 +5845,19 @@ def test_seed_period_report_surfaces_on_get_reports(client, demo_analyst_token,
assert leftover_compare_axes[0]["leftover_share"] != leftover_compare_axes[0][
"leftover_singular_value"
] or leftover_compare_axes[0]["leftover_share"] in {0, 1}
assert all(
"leftover_map_rank" in pair for pair in leftover_thread.get("leftover_pairs", [])
)
assert all(
pair["leftover_map_rank"] is None
or (
isinstance(pair["leftover_map_rank"], int)
and pair["leftover_map_rank"] >= 0
and pair["leftover_map_rank"] != pair["leftover_distance"]
)
or pair["leftover_map_rank"] in {0, 1}
for pair in leftover_thread.get("leftover_pairs", [])
)
assert leftover_compare_coverage["map_item_count"] <= leftover_compare_coverage["scored_item_count"]
assert leftover_compare_coverage["map_post_count"] != len(
leftover_thread.get("leftover_pairs", [])
Expand Down
140 changes: 140 additions & 0 deletions docs/adr/0305-leftover-map-compare-rank-payload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# ADR 0305 — Return leftover-map rank on grouping comparison leftover pairs

**Decision status:** Accepted
**Date:** 2026-08-31

Amends leftover pairs on the grouping comparison strip
([ADR 0149](0149-leftover-pairs-on-comparison-strip.md)), leftover-map rank
([ADR 0164](0164-leftover-map-rank.md)), leftover-map rank on grouping comparison
strip pair rows ([ADR 0301](0301-leftover-map-compare-rank.md)), leftover-map
axis share on the grouping comparison strip
([ADR 0304](0304-leftover-map-compare-axis-share.md)), leftover-map coordinates
on grouping comparison leftover-pair payload
([ADR 0303](0303-leftover-map-compare-coordinates-payload.md)), leftover-map
coordinates on grouping comparison strip pair rows
([ADR 0302](0302-leftover-map-compare-coordinates.md)), leftover expected on
grouping comparison strip pair rows
([ADR 0300](0300-leftover-map-compare-expected.md)), leftover observed on grouping
comparison strip pair rows
([ADR 0299](0299-leftover-map-compare-observed.md)), leftover residual on grouping
comparison strip pair rows
([ADR 0298](0298-leftover-map-compare-residual.md)), leftover-map rank on pair
segments ([ADR 0280](0280-leftover-map-segment-rank.md)), leftover residual
disclosure ([ADR 0162](0162-leftover-residual-disclosure.md)), leftover observed
`Y` / expected `E` ([ADR 0163](0163-leftover-observed-expected.md)), leftover-map
reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.md)),
and leftover-map graphic display ([ADR 0268](0268-leftover-map-graphic-display.md)).

## Context

ADR 0164 already persists leftover-map rank on leftover pair rows. ADR 0301 already
captions that persisted rank on grouping comparison leftover-pair buttons through
formatLeftoverMapRank. The period-report detail payload already returns
`leftover_map_rank`. `GET /api/reports/compare/{period}` still omits that column,
so every live grouping comparison leftover-pair button fail-closes the leftover-map
comparison rank badge even after the pair list and graphic already name `rank k`.
Hiding rank on the live strip lets leftover-map coordinates, leftover expected `E`,
leftover observed `Y`, leftover residual `R`, leftover-map reconstruction `R̂`, or
leftover-map distance `d` be read as leftover-map structure without a next action.

This increment returns persisted leftover-map rank on each grouping comparison
leftover pair, matching the detail-report payload. It does not add columns. It
does not invent leftover-map rank from leftover-map coordinates, leftover-map
distance, leftover expected, leftover observed, leftover residual, leftover-map
reconstruction, leftover-map axis share, leftover-map post coverage, leftover-map
item coverage, leftover-map incomplete post coverage, leftover-map incomplete item
coverage, or the count of unused axes. It does not persist leftover-map inner
product, cosine, or length as separate columns. Do not invent a leftover score.
Do not invent a theta.

The dashboard stack already used neighbouring leftover facts under other numbers.
This protected increment uses **0305** so it does not collide with leftover-map
axis share on the grouping comparison strip (0304), leftover-map coordinates on
grouping comparison leftover-pair payload (0303), leftover-map coordinates on
grouping comparison strip pair rows (0302), leftover-map rank on grouping
comparison strip pair rows (0301), leftover-map rank on pair segments (0280),
leftover-map rank persistence (0164), leftover-map graphic display (0268), or the
dashboard stacks.

## Decision

On `GET /api/reports/compare/{period}`, include persisted `leftover_map_rank` on
each leftover pair, converting a stored integer the same way as
`GET /api/reports/{grouping}/{period}` and preserving nulls. A missing, negative,
or non-integer rank stays null (the formatter omits that leftover-map comparison
rank badge) and does not invent rank from leftover-map coordinates, leftover-map
distance, leftover expected, leftover observed, leftover residual, leftover-map
reconstruction, leftover-map unexplained leftover, leftover-map explained leftover
share, leftover-map unexplained leftover share, leftover-map cross share, leftover-map
axis share, leftover-map post coverage, leftover-map item coverage, leftover-map
incomplete post coverage, leftover-map incomplete item coverage, or the count of
unused axes. Rank-0 origin cells still return `0` when that persisted rank is stored.
A finite negative leftover on neighbouring fields is shown, never clamped. Do not add
the leftover-map graphic to the strip. Click a leftover pair on the strip to open
that post.

Do not add SQL migrations. Do not edit shipped migrations. Do not persist inner
product, cosine, or length as separate columns. Do not invent a leftover score.
Do not invent a theta.

## Consequences

After `make seed`, `GET /api/reports/compare/{period}` leftover pairs include
persisted leftover-map rank when that rank was stored, so grouping comparison
leftover-pair buttons that already caption formatLeftoverMapRank can match the
pair-row rank badge on live responses. Closest and farthest leftover pairs still
sit above the member list with the leftover-map graphic display; click a post
marker or a pair button opens that post.
Hidden posts stay hidden. When `Y`, `E`, and `R` are finite, `Y − E = R`.
When `R`, `R̂`, and `U` are finite, `U + R̂ = R`.
When `R`, `R̂`, `U`, `x`, `s`, and `e` are finite, `e + s + x = 1`.
When coordinates, reconstruction, and distance are finite, `R̂ = ξ · ζ`
and `d = ‖ξ − ζ‖`.

## Related

Independent of leftover interaction-map persistence, leftover-criterion
evaluation landing, leftover residual disclosure, leftover-map complete-case
coverage persistence, leftover-map axis share persistence, leftover pairs on
the grouping comparison strip, leftover-map complete-case coverage on the
grouping comparison strip, leftover-map item complete-case coverage on the
grouping comparison strip, leftover-map incomplete post coverage on the grouping
comparison strip, leftover-map incomplete item coverage on the grouping
comparison strip, leftover-map reconstruction on grouping comparison strip pair
rows, leftover-map explained leftover share on grouping comparison strip pair
rows, leftover-map unexplained leftover share on grouping comparison strip pair
rows, leftover-map cross share on grouping comparison strip pair rows, leftover-map
unexplained leftover on grouping comparison strip pair rows, leftover residual on
grouping comparison strip pair rows, leftover observed on grouping comparison
strip pair rows, leftover expected on grouping comparison strip pair rows,
leftover-map rank on grouping comparison strip pair rows, leftover-map coordinates
on grouping comparison strip pair rows, leftover-map coordinates on grouping
comparison leftover-pair payload, leftover-map axis share on the grouping
comparison strip, leftover-map inner product, leftover-map cosine, leftover-map
length, leftover-map graphic display, leftover-map rank on pair segments, leftover-map
rank persistence, leftover-map coordinate persistence, leftover-map coordinate
ticks, leftover-map reconstruction persistence, leftover residual on pair segments,
leftover observed on pair segments, leftover expected on pair segments, leftover-map
item complete-case coverage on the graphic display, leftover-map item complete-case
coverage on the pair list, leftover-map incomplete post coverage on the graphic
display, leftover-map incomplete post coverage on the pair list, leftover-map
incomplete item coverage on the graphic display, leftover-map incomplete item
coverage on the pair list, and leftover-map post complete-case coverage fail-closed
on the pair list.

## References

Jeon, M., Jin, I. H., Schweinberger, M., & Baugh, S. (2021). Mapping
unobserved item–respondent interactions: A latent space item response
model with interaction map. *Psychometrika, 86*(2), 378–403.
https://doi.org/10.1007/s11336-021-09762-5
(LSIRM interaction `−γ‖ξ_j − ζ_i‖` after main effects
`α_j − β_i`; typically `p = 2` for the interaction map. Leftover-map rank is
the number of residual SVD axes with nonzero singular value. Grouping comparison
leftover-map rank returns that persisted rank on `GET /api/reports/compare/{period}`
leftover pairs only when the stored rank is present. Rank-0 origin cells still
return `0` when that rank is stored.)

Gabriel, K. R. (1971). The biplot graphic display of matrices with
application to principal component analysis. *Biometrika, 58*(3),
453–467. https://doi.org/10.1093/biomet/58.3.453
25 changes: 25 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Product & Technical Gap Baseline

> Exact-head loop overlay: 2026-08-31 03:50 KST. Protected `main` is
> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work (close/comment/create-PR/push).
> Token still has empty `X-OAuth-Scopes`; GitHub App user-to-server writes succeed.
> Do not self-approve. #842 leftover-map axis share on grouping comparison
> strip (v2.61.0 / ADR 0304) is `12e40c93acb8473075199d8c8124b85b3d39adff`,
> stacked on #840 @ `cbf9944`. Competing #828 leftover-map axis share
> (v2.50.0 / ADR 0293) sits on the incomplete-item dead-end; reconstruction
> already owns 0293 on this stack. Copilot review cannot be requested (org
> has one collaborator, `seonghobae`). Next buyer increment on this cycle:
> leftover-map rank on `GET /api/reports/compare/{period}` leftover pairs
> (ADR 0305 / v2.62.0) delivered locally on
> `feat/leftover-map-compare-rank-payload-v2620`. Return persisted
> leftover_map_rank on each compare leftover pair, preserving nulls and
> converting stored integers the same way as the detail report. Rank-0 origin
> cells still return `0` when stored. Missing rank stays null independently of
> leftover-map axis share, leftover-map coordinates, leftover expected `E`,
> leftover observed `Y`, leftover residual `R`, leftover-map reconstruction
> `R̂`, leftover-map distance `d`, and coverage notes. Do not invent leftover-map
> rank from leftover-map coordinates, leftover-map distance, leftover-map
> reconstruction, leftover-map axis share, or the count of unused axes. Stack
> onto #842. Leave open for independent review. Do not squash-merge. Issues
> #79 and #87 stay open. #96 is already closed. Do not merge #808 without
> independent APPROVE.


> Exact-head loop overlay: 2026-08-31 03:10 KST. Protected `main` is
> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work (close/comment/create-PR/push).
> Token still has empty `X-OAuth-Scopes`; GitHub App user-to-server writes succeed.
Expand Down
Loading