-
Notifications
You must be signed in to change notification settings - Fork 1
feat: name leftover-map rank on leftover pairs (v2.12.21) #529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1bd45fc
a30bb28
df18ed6
e05d63b
6fcf603
54f3f69
cb5b21f
435992d
dc508ae
a988ca6
13d95e1
de4ff43
5dbb0c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| ## 2.12.21 — Leftover-map rank | ||
|
|
||
| - Persist leftover-map rank on leftover post–criterion pairs (ADR 0172). | ||
| Rank is the number of Gabriel singular values above the floor. After | ||
| `make seed`, closest and farthest leftover pairs sit above the member | ||
| list with `rank {n}` next to leftover-map distance `d`; click opens | ||
| that post. Rank 0 names no leftover structure. Never invent a leftover | ||
| score or a theta. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| # ADR 0172 — Name leftover-map rank on leftover pairs | ||
|
|
||
| **Decision status:** Accepted | ||
| **Date:** 2026-08-24 | ||
|
|
||
| Amends [ADR 0048](0048-persist-lsirm-leftover-pairs.md) and | ||
| [ADR 0049](0049-leftover-pair-report-ui.md). | ||
|
|
||
| ## Context | ||
|
|
||
| ADR 0048 already persists leftover-map distance and leftover residual | ||
| `R = Y − E[Y|θ, item]` on `report_leftover_pair`. A rank-0 residual | ||
| still emits a stable closest/farthest pair so `make seed` is not empty; | ||
| the stored distance is then zero, not a fabricated interaction. ADR 0049 | ||
| renders those pairs above the member list. Without leftover-map rank, a | ||
| buyer cannot tell a Gabriel biplot with leftover structure (Jeon et al., | ||
| 2021, eq. 3) from an origin collapse that still shows `d 0.00`. | ||
|
|
||
| This increment does not persist leftover-map coordinates, does not name | ||
| observed `Y` / expected `E`, does not change leftover-map axis count, and | ||
| does not land Post quality on the leftover criterion. | ||
|
|
||
| The unprotected-stack ADR for the same buyer fact was 0164. This | ||
| protected-main reconstruction uses **0172** so it does not collide with | ||
| two-axis leftover-map distance (0166), leftover coverage (0168), | ||
| leftover-map axis share (0148), leftover observed Y / expected E | ||
| (0177), or analysis-run status same clock (0171). | ||
|
|
||
| ## Decision | ||
|
|
||
| Each leftover pair names `leftover_map_rank`: the number of Gabriel | ||
| singular values above the leftover singular floor on the complete-case | ||
| residual rectangle. Closest and farthest pairs on one period report share | ||
| that rank. A fallback pair that is not placed on a leftover map stores | ||
| rank `0`. Migration `0172` is the single source of the column on every | ||
| install path; shipped migrations (`0001` / `0012`) are never rewritten. | ||
| It adds a nullable column so older leftover rows keep distance and residual | ||
| without fabricating a rank. | ||
|
|
||
| The pair button shows `rank {n}` when the value is a finite | ||
| non-negative integer. Rank `0` next action: leftover map has no leftover | ||
| structure after IRT main effects; open this post. Rank `≥ 1` next action: | ||
| read leftover map rank after IRT main effects, then open this post. Omit | ||
| the rank badge when the value is missing. Do not invent a leftover score. | ||
| Do not invent a theta. | ||
|
|
||
| ## Consequences | ||
|
|
||
| `GET /api/reports/{grouping}/{period}` returns `leftover_map_rank`. After | ||
| `make seed`, closest and farthest leftover pairs sit above the member | ||
| list with leftover-map rank; click opens that post. Hidden posts stay | ||
| hidden. | ||
|
|
||
| ## Related | ||
|
|
||
| Independent of leftover interaction-map persistence, leftover-criterion | ||
| evaluation landing, leftover residual UI extraction, leftover-map | ||
| complete-case coverage, leftover-map axis share, leftover pairs on the | ||
| grouping comparison strip, two-axis leftover-map distance, and leftover | ||
| observed `Y` / expected `E`. | ||
|
|
||
| ## References | ||
|
|
||
| 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 | ||
|
|
||
| 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 |
|
devin-ai-integration[bot] marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { formatLeftoverMapRank } from "./leftoverMapRank"; | ||
|
|
||
| describe("formatLeftoverMapRank", () => { | ||
| it("names leftover-map rank without inventing leftover structure", () => { | ||
| expect(formatLeftoverMapRank(0)).toBe("rank 0"); | ||
| expect(formatLeftoverMapRank(1)).toBe("rank 1"); | ||
| expect(formatLeftoverMapRank(2)).toBe("rank 2"); | ||
| }); | ||
|
|
||
| it("omits the badge when rank is missing or not a non-negative integer", () => { | ||
| expect(formatLeftoverMapRank(null)).toBeNull(); | ||
| expect(formatLeftoverMapRank(undefined)).toBeNull(); | ||
| expect(formatLeftoverMapRank(-1)).toBeNull(); | ||
| expect(formatLeftoverMapRank(1.5)).toBeNull(); | ||
| expect(formatLeftoverMapRank(Number.NaN)).toBeNull(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| /** Leftover-map rank after IRT main effects (Gabriel singular values). */ | ||
|
|
||
| export const LEFTOVER_RANK_ZERO_ACTION = | ||
| "Leftover map has no leftover structure after IRT main effects. Open this post."; | ||
| export const LEFTOVER_RANK_STRUCTURE_ACTION = | ||
| "Leftover map rank {rank} after IRT main effects. Open this post."; | ||
|
Comment on lines
+3
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Rank next-action copy defined but never rendered
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| export function formatLeftoverMapRank(rank: number | null | undefined): string | null { | ||
| if (rank == null || !Number.isInteger(rank) || rank < 0) { | ||
| return null; | ||
| } | ||
| return `rank ${rank}`; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Info: 2.12.21 changelog entry placed out of version order
The new
## [2.12.21]section sits between## [2.12.19]and## [2.12.20](CHANGELOG.md:216). Newest-first order would put 2.12.21 above both. The surrounding entries are already non-monotonic (2.12.19 before 2.12.20), so this may reflect merge-order layout rather than a defect.Was this helpful? React with 👍 or 👎 to provide feedback.