-
Notifications
You must be signed in to change notification settings - Fork 1
feat: name leftover-map cosine on leftover pairs (v2.12.24) #532
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
459476c
53f8412
016c52d
3e34982
7f7be82
13e47ba
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,7 @@ | ||
| ## 2.12.24 — Leftover-map cosine | ||
|
|
||
| - Persist leftover-map cosine on leftover post–criterion pairs | ||
| (ADR 0180). After `make seed`, closest and farthest leftover pairs | ||
| sit above the member list with cosine next to leftover-map distance | ||
| `d`; click opens that post. Omit the badge when cosine is missing. | ||
| Never invent a leftover score. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # ADR 0180 — Name leftover-map cosine on period-report pair rows | ||
|
|
||
| **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 `d = ‖ξ_p − ζ_i‖` and | ||
| leftover residual `R = Y − E[Y|θ, item]` on `report_leftover_pair`. | ||
| ADR 0049 already renders closest and farthest pairs above the member | ||
| list and opens the named post. Distance is the Jeon et al. (2021, | ||
| eq. 3) map gap. Gabriel (1971) also names leftover-map alignment as | ||
| the cosine of the angle between `ξ_p` and `ζ_i`. Hiding that cosine | ||
| lets a buyer read a large reconstructed leftover cell, or a close | ||
| map pair, as leftover-map alignment without a scale-free value. | ||
|
|
||
| This increment does not persist leftover-map coordinates, does not | ||
| name leftover-map inner product, does not name observed `Y` / expected | ||
| `E`, does not name leftover-map rank, does not split leftover-map | ||
| distance onto two axes, and does not land Post quality on the leftover | ||
| criterion. | ||
|
|
||
| The unprotected-stack reconstructions for neighbouring leftover facts | ||
| use 0162–0179. This protected-main increment uses **0180** so it does | ||
| not collide with leftover-map inner product (0179), leftover residual | ||
| disclosure (0178), leftover observed `Y` / expected `E` (0177), | ||
| leftover-map rank (0172), two-axis leftover-map distance (0166), | ||
| leftover coverage (0168), leftover-map axis share (0148), or leftover | ||
| interaction-map persistence (0121). | ||
|
|
||
| ## Decision | ||
|
|
||
| Each leftover pair names `leftover_map_cosine` — the cosine of the | ||
| angle between leftover-map person and item coordinates that produced | ||
| leftover-map distance `d`. Migration `0180` is the single source of | ||
| the column on every install path, fresh or existing -- shipped | ||
| migrations (`0001` / `0012`) are never edited after the fact. The | ||
| column is nullable so older leftover rows keep distance and residual | ||
| without fabricating alignment. Fallback pairs that have no | ||
| complete-case leftover map, and pairs whose person or item vector | ||
| sits at the origin, omit the value rather than inventing one. | ||
|
|
||
| The pair button shows `cos {signed}` next to leftover-map distance | ||
| `d` when the value is finite. Next action: leftover-map cosine names | ||
| leftover-map alignment independent of distance; open this post to | ||
| read the named criterion. A missing or non-finite cosine omits the | ||
| badge and keeps the existing closest/farthest next action. Do not | ||
| invent a leftover score. Do not invent a theta. | ||
|
|
||
| ## Consequences | ||
|
|
||
| `GET /api/reports/{grouping}/{period}` returns `leftover_map_cosine`. | ||
| After `make seed`, closest and farthest leftover pairs sit above the | ||
| member list with named cosine next to `d`; click opens that post. | ||
| Hidden posts stay hidden. | ||
|
|
||
| ## Related | ||
|
|
||
| Independent of leftover interaction-map persistence, leftover-criterion | ||
| evaluation landing, leftover residual disclosure, leftover observed | ||
| `Y` / expected `E`, leftover-map complete-case coverage, leftover-map | ||
| axis share, leftover pairs on the grouping comparison strip, two-axis | ||
| leftover-map distance, leftover-map rank, and leftover-map inner | ||
| product. | ||
|
|
||
| ## 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { formatLeftoverMapCosine, formatSignedLeftoverValue } from "./leftoverMapCosine"; | ||
|
|
||
| describe("formatLeftoverMapCosine", () => { | ||
| it("names leftover-map cosine without inventing a leftover score", () => { | ||
| expect(formatLeftoverMapCosine(0.95)).toBe("cos +0.95"); | ||
| expect(formatLeftoverMapCosine(-1)).toBe("cos −1.00"); | ||
| expect(formatLeftoverMapCosine(0)).toBe("cos 0.00"); | ||
| expect(formatSignedLeftoverValue(0.95)).toBe("+0.95"); | ||
| expect(formatSignedLeftoverValue(-1)).toBe("−1.00"); | ||
| }); | ||
|
|
||
| it("omits the badge when cosine is missing or non-finite", () => { | ||
| expect(formatLeftoverMapCosine(null)).toBeNull(); | ||
| expect(formatLeftoverMapCosine(undefined)).toBeNull(); | ||
| expect(formatLeftoverMapCosine(Number.NaN)).toBeNull(); | ||
| expect(formatLeftoverMapCosine(Number.POSITIVE_INFINITY)).toBeNull(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /** Gabriel leftover-map cosine after IRT main effects. */ | ||
|
|
||
| export const LEFTOVER_MAP_COSINE_ACTION = | ||
| "Leftover-map cosine {value} names leftover-map alignment independent of distance. Open this post to read {criterion}."; | ||
|
|
||
| export function formatSignedLeftoverValue(value: number): string | null { | ||
| if (!Number.isFinite(value)) { | ||
| return null; | ||
| } | ||
| const magnitude = Math.abs(value).toFixed(2); | ||
| if (value > 0) { | ||
| return `+${magnitude}`; | ||
| } | ||
| if (value < 0) { | ||
| return `−${magnitude}`; | ||
| } | ||
| return magnitude; | ||
| } | ||
|
|
||
| export function formatLeftoverMapCosine(value: number | null | undefined): string | null { | ||
| if (value == null) { | ||
| return null; | ||
| } | ||
| const signed = formatSignedLeftoverValue(value); | ||
| return signed === null ? null : `cos ${signed}`; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -252,7 +252,7 @@ def leftover_pairs_for_fit( | |
| theta: np.ndarray, | ||
| fit: PolytomousFit, | ||
| ) -> tuple[LeftoverPair, ...]: | ||
| """Leftover pairs from the already-fitted GRM/GPCM main effects.""" | ||
| """Leftover pairs from the already-fitted GRM/GPCM main effects, with cosine.""" | ||
| probs = _category_probabilities(model, theta, fit) | ||
| expected = expected_category_matrix(matrix, probs) | ||
| return leftover_pairs_from_residual(post_ids, item_codes, matrix, expected) | ||
|
Comment on lines
+255
to
258
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. 🔍 Cosine feature is unimplemented beyond the migration and a util The PR claims to persist, fetch, and seed Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| -- ADR 0180: persist leftover-map cosine on leftover post–criterion | ||
| -- pairs. Distance stays Euclidean leftover-map d. Upgrade column is | ||
| -- nullable so older leftover rows keep distance and residual without | ||
| -- fabricating alignment. This migration is the single source of the | ||
| -- column on fresh and existing installations. | ||
|
|
||
| alter table report_leftover_pair | ||
| add column if not exists leftover_map_cosine numeric; | ||
|
Comment on lines
+7
to
+8
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. 🟡 Cosine migration is never applied on any install path The new migration is not added to the allowlist in Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| -- Reverse 0180. Leftover distance and residual stay on the pair row. | ||
|
|
||
| alter table report_leftover_pair | ||
| drop column if exists leftover_map_cosine; |
Uh oh!
There was an error while loading. Please reload this page.