From b2183deac5256fffae6a3ed084b28dc20a93b9a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 16:31:59 +0000 Subject: [PATCH] feat(reports): leftover residual on grouping comparison strip (v2.55.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caption persisted leftover residual R = Y − E on grouping comparison leftover-pair buttons through formatLeftoverMapResidual (ADR 0298). Distinct aria-label Leftover map comparison leftover residual. Missing or non-finite R omits that badge and keeps U, x, s, e, R̂, d, and coverage notes. Do not invent R from Y and E or from U and R̂. Rank-0 origin cells still name R 0.00 when finite. Finite negative residual is shown, never clamped. UI-only; no new columns. --- AGENTS.md | 10 +- ARCHITECTURE.md | 7 +- CHANGELOG.md | 51 +++++ CLAUDE.md | 2 +- ...-leftover-map-compare-leftover-residual.md | 215 ++++++++++++++++++ docs/product-technical-gap-baseline.md | 48 ++-- frontend/package.json | 2 +- frontend/src/App.test.tsx | 25 ++ frontend/src/App.tsx | 15 ++ frontend/src/i18n.test.ts | 14 ++ frontend/src/i18n.ts | 4 + frontend/src/leftoverMapPlotLayout.ts | 3 +- frontend/src/leftoverResidual.test.ts | 13 +- frontend/src/leftoverResidual.ts | 4 + pyproject.toml | 2 +- uv.lock | 2 +- 16 files changed, 385 insertions(+), 32 deletions(-) create mode 100644 docs/adr/0298-leftover-map-compare-leftover-residual.md diff --git a/AGENTS.md b/AGENTS.md index d58c5bdf2..0c3f3af1e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -273,7 +273,7 @@ stops startup instead of leaving a healthy-looking partial schema, and application code must not compensate for a missing table. Period leftover pairs (ADR 0017 / 0018 / 0048 / 0049 / 0119 / 0158 / 0162 / -0163 / 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) are computed in `lineageweave/leftover_pairs.py` from the +0163 / 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) are computed in `lineageweave/leftover_pairs.py` from the residual after a real GRM/GPCM score, never invented. Distances are Euclidean on the two-dimensional Gabriel leftover map; missing cells stay out of the factorization. Closest and farthest post–criterion pairs @@ -446,6 +446,12 @@ compares leftover pairs can match the pair-row `U` badge; a missing or non-finit `U` omits that leftover-map comparison unexplained leftover badge and does not invent `U` from `R` and `R̂` or from leftover-map distance. The strip does not gain the leftover-map graphic. +ADR 0298 captions grouping comparison leftover-pair buttons with persisted leftover +residual `R` through formatLeftoverMapResidual so a buyer who +compares leftover pairs can match the pair-row residual badge; a missing or non-finite +`R` omits that leftover-map comparison leftover residual badge and does not invent +`R` from `Y` and `E`, from `U` and `R̂`, or from leftover-map distance. 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 @@ -478,6 +484,8 @@ Grouping comparison leftover-map cross share (ADR 0296) captions grouping compar buttons with persisted leftover-map cross share `x`. Grouping comparison leftover-map unexplained leftover (ADR 0297) captions grouping comparison leftover-pair buttons with persisted leftover-map unexplained leftover `U`. +Grouping comparison leftover residual (ADR 0298) captions grouping comparison leftover-pair +buttons with persisted leftover residual `R`. 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) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ece6e3992..fe9027238 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -685,6 +685,8 @@ ADR 0296 captions grouping comparison leftover-pair buttons with persisted lefto cross share `x` through formatLeftoverMapCrossShare. ADR 0297 captions grouping comparison leftover-pair buttons with persisted leftover-map unexplained leftover `U` through formatLeftoverMapUnexplained. +ADR 0298 captions grouping comparison leftover-pair buttons with persisted leftover +residual `R` through formatLeftoverMapResidual. 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 @@ -709,8 +711,9 @@ incomplete item coverage (ADR 0292), leftover-map reconstruction `R̂` (ADR 0293), leftover-map explained leftover share `e` (ADR 0294), leftover-map unexplained leftover share `s` (ADR 0295), leftover-map cross share `x` -(ADR 0296), and leftover-map unexplained leftover `U` -(ADR 0297); +(ADR 0296), leftover-map unexplained leftover `U` +(ADR 0297), and leftover residual `R` +(ADR 0298); `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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 961e4f418..6dbcf74fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,57 @@ All notable changes to this project are documented here. Format follows ### Added +- Period leftover pairs now caption grouping comparison leftover-pair + buttons with persisted leftover residual `R` through + formatLeftoverMapResidual (ADR 0298 / v2.55.0). After `make seed`, + closest and farthest leftover pairs sit above the member list with the + Gabriel biplot of already-named coordinates, leftover-map axis share when + finite, axis ticks that match `ξ (x, y) ζ (x, y)` on the pair row, pair + segments that match `d` on the pair row, pair segments that match `R̂` on + the pair row, pair segments that match `R̂²/R²` on the pair row, pair + segments that match `U²/R²` on the pair row, pair segments that match + `2R̂U/R²` on the pair row, pair segments that match `U` on the pair row, + pair segments that match `R` on the pair row, pair segments that match + `Y` on the pair row, pair segments that match `E` on the pair row, pair + segments that match leftover-map rank on the pair row, a plot caption + that matches leftover-map complete-case coverage above the pair list, a + plot caption that matches leftover-map item complete-case coverage, a + plot caption that matches leftover-map incomplete post coverage, a plot + caption that matches leftover-map incomplete item coverage, a pair-list + note that matches leftover-map post complete-case coverage, a pair-list + note that matches leftover-map item complete-case coverage, a pair-list + note that matches leftover-map incomplete post coverage, a pair-list + note that matches leftover-map incomplete item coverage, a grouping + comparison note that matches leftover-map post complete-case coverage, + a grouping comparison note that matches leftover-map item + complete-case coverage, a grouping comparison note that matches + leftover-map incomplete post coverage, a grouping comparison note + that matches leftover-map incomplete item coverage, grouping comparison + leftover-pair buttons that match leftover-map reconstruction `R̂` on the + pair row, grouping comparison leftover-pair buttons that match + leftover-map explained leftover share `e` on the pair row, grouping + comparison leftover-pair buttons that match leftover-map unexplained + leftover share `s` on the pair row, grouping comparison leftover-pair + buttons that match leftover-map cross share `x` on the pair row, + grouping comparison leftover-pair buttons that match leftover-map + unexplained leftover `U` on the pair row, and grouping comparison + leftover-pair buttons that match leftover residual `R` on the pair row; + click a post marker or a pair button opens that post. A missing or + non-finite `R` omits that leftover-map comparison leftover residual badge + and keeps leftover-map unexplained leftover `U` when finite, leftover-map + cross share `x` when finite, leftover-map unexplained leftover share `s` + when finite, leftover-map explained leftover share `e` when finite, + leftover-map reconstruction `R̂` when finite, leftover-map distance `d`, + plus the strip coverage notes. Do not invent `R` from `Y` and `E`, from + `U` and `R̂`, leftover-map distance, plotted coordinates, leftover-map + reconstruction, leftover-map rank, 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 name `R 0.00` when that persisted residual is + finite. A finite negative residual 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 caption grouping comparison leftover-pair buttons with persisted leftover-map unexplained leftover `U` through formatLeftoverMapUnexplained (ADR 0297 / v2.54.0). After `make seed`, diff --git a/CLAUDE.md b/CLAUDE.md index 81123ac0a..50c56d7dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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), 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), 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), diff --git a/docs/adr/0298-leftover-map-compare-leftover-residual.md b/docs/adr/0298-leftover-map-compare-leftover-residual.md new file mode 100644 index 000000000..b8652292f --- /dev/null +++ b/docs/adr/0298-leftover-map-compare-leftover-residual.md @@ -0,0 +1,215 @@ +# ADR 0298 — Name leftover residual on grouping comparison strip pair rows + +**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 residual +disclosure ([ADR 0162](0162-leftover-residual-disclosure.md)), leftover residual +on pair segments +([ADR 0277](0277-leftover-map-segment-residual.md)), leftover-map unexplained +leftover on grouping comparison strip pair rows +([ADR 0297](0297-leftover-map-compare-unexplained-leftover.md)), leftover-map +cross share on grouping comparison strip pair rows +([ADR 0296](0296-leftover-map-compare-cross-share.md)), leftover-map +unexplained leftover share on grouping comparison strip pair rows +([ADR 0295](0295-leftover-map-compare-unexplained-share.md)), leftover-map +explained leftover share on grouping comparison strip pair rows +([ADR 0294](0294-leftover-map-compare-explained-share.md)), leftover-map +reconstruction on grouping comparison strip pair rows +([ADR 0293](0293-leftover-map-compare-reconstruction.md)), leftover-map +incomplete item coverage on the grouping comparison strip +([ADR 0292](0292-leftover-map-compare-incomplete-item.md)), leftover-map +incomplete post coverage on the grouping comparison strip +([ADR 0291](0291-leftover-map-compare-incomplete-post.md)), leftover-map item +complete-case coverage on the grouping comparison strip +([ADR 0290](0290-leftover-map-compare-item-coverage.md)), leftover-map +complete-case coverage on the grouping comparison strip +([ADR 0289](0289-leftover-map-compare-coverage.md)), leftover-map unexplained +leftover persistence ([ADR 0182](0182-leftover-map-unexplained.md)), leftover-map +unexplained leftover on pair segments +([ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)), leftover-map +cross share persistence ([ADR 0185](0185-leftover-map-cross-share.md)), leftover-map +cross share on pair segments +([ADR 0275](0275-leftover-map-segment-cross-share.md)), leftover-map unexplained +leftover share persistence ([ADR 0233](0233-leftover-map-unexplained-share.md)), +leftover-map unexplained leftover share on pair segments +([ADR 0274](0274-leftover-map-segment-unexplained-share.md)), leftover-map +explained leftover share persistence +([ADR 0266](0266-leftover-map-explained-share.md)), leftover-map explained +leftover share on pair segments +([ADR 0273](0273-leftover-map-segment-explained-share.md)), leftover-map +reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.md)), +and leftover-map reconstruction on pair segments +([ADR 0272](0272-leftover-map-segment-reconstruction.md)). Independent of leftover-map +post complete-case coverage fail-closed on the pair list +([ADR 0288](0288-leftover-map-list-post-coverage-helper.md)), leftover-map +incomplete item coverage on the pair list +([ADR 0287](0287-leftover-map-list-incomplete-item.md)), leftover-map incomplete +post coverage on the pair list +([ADR 0286](0286-leftover-map-list-incomplete-post.md)), leftover-map item +complete-case coverage on the pair list +([ADR 0285](0285-leftover-map-list-item-coverage.md)), leftover-map incomplete +item coverage on the graphic display +([ADR 0284](0284-leftover-map-plot-incomplete-item.md)), leftover-map incomplete +post coverage on the graphic display +([ADR 0283](0283-leftover-map-plot-incomplete.md)), leftover-map item +complete-case coverage on the graphic display +([ADR 0282](0282-leftover-map-plot-item-coverage.md)), leftover-map complete-case +coverage on the graphic display +([ADR 0281](0281-leftover-map-plot-coverage.md)), leftover-map rank on pair +segments ([ADR 0280](0280-leftover-map-segment-rank.md)), leftover expected on pair +segments ([ADR 0279](0279-leftover-map-segment-expected.md)), leftover observed on +pair segments ([ADR 0278](0278-leftover-map-segment-observed.md)), leftover-map +distance on pair segments ([ADR 0271](0271-leftover-map-segment-distance.md)), +leftover-map coordinate ticks ([ADR 0270](0270-leftover-map-coordinate-ticks.md)), +leftover-map axis share on the graphic display +([ADR 0269](0269-leftover-map-axis-share-plot.md)), leftover observed `Y` / +expected `E` ([ADR 0163](0163-leftover-observed-expected.md)), leftover-map rank +persistence ([ADR 0164](0164-leftover-map-rank.md)), and leftover-map axis share +persistence ([ADR 0148](0148-leftover-map-axis-share.md)). + +## Context + +ADR 0162 already discloses leftover residual `R = Y − E[Y|θ, item]` after +IRT main effects on leftover pair rows. ADR 0277 already names that residual +on leftover-map graphic-display pair segments. ADR 0297 already captions +persisted leftover-map unexplained leftover `U` on grouping comparison +leftover-pair buttons. The strip pair button still does not name `R`, so a +buyer who compares leftover pairs can treat 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̂`, or leftover-map distance `d` as the leftover that entered the truncated +map even after the pair list and graphic already name `R`. When `Y`, `E`, and +`R` are finite, `Y − E = R`; when `R`, `R̂`, and `U` are finite, `U + R̂ = R`. +Hiding `R` on the strip lets those identities vanish without a next action. + +This increment captions persisted leftover residual on each grouping +comparison leftover-pair button through formatLeftoverMapResidual. It does +not add columns. It does not recompute `R` from `Y` and `E`, from `U` and +`R̂`, leftover-map distance, plotted coordinates, leftover-map reconstruction, +leftover-map unexplained leftover, leftover-map explained leftover share, +leftover-map unexplained leftover share, or leftover-map cross share. 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 **0298** so it does not collide with +leftover-map unexplained leftover on grouping comparison strip pair rows +(0297), leftover-map cross share on grouping comparison strip pair rows +(0296), leftover-map unexplained leftover share on grouping comparison strip +pair rows (0295), leftover-map explained leftover share on grouping comparison +strip pair rows (0294), leftover-map reconstruction on grouping comparison +strip pair rows (0293), leftover-map incomplete item coverage on the grouping +comparison strip (0292), leftover-map incomplete post coverage on the grouping +comparison strip (0291), leftover-map item complete-case coverage on the +grouping comparison strip (0290), leftover-map complete-case coverage on the +grouping comparison strip (0289), leftover residual on pair segments (0277), +leftover residual disclosure (0162), leftover-map unexplained leftover on pair +segments (0276), leftover-map unexplained leftover persistence (0182), leftover-map +cross share on pair segments (0275), leftover-map cross share persistence +(0185), leftover-map unexplained leftover share on pair segments (0274), +leftover-map unexplained leftover share persistence (0233), leftover-map +explained leftover share on pair segments (0273), leftover-map explained +leftover share persistence (0266), leftover-map reconstruction on pair +segments (0272), leftover-map reconstruction persistence (0201), or the +dashboard stacks. + +## Decision + +On the grouping comparison strip, caption each leftover pair button with +the same persisted leftover residual formatter as the pair-row `R` badge, +next to 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̂`, and leftover-map distance `d`. +Use the distinct accessible name `Leftover map comparison leftover residual` +so the strip badge is not the graphic caption (`leftover residual {label}`). +A missing or non-finite `R` omits that leftover-map comparison leftover +residual badge and keeps leftover-map unexplained leftover `U` when finite, +leftover-map cross share `x` when finite, leftover-map unexplained leftover +share `s` when finite, leftover-map explained leftover share `e` when finite, +leftover-map reconstruction `R̂` when finite, leftover-map distance `d`, the +strip leftover-map post coverage note, leftover-map item coverage note, +leftover-map incomplete post note, leftover-map incomplete item note, leftover +pairs, and any leftover-map captions on the pair list and graphic. Rank-0 +origin cells still name `R 0.00` when that persisted residual is finite. A +finite negative residual is shown, never clamped. Do not invent `R` from `Y` +and `E`, from `U` and `R̂`, leftover-map distance, plotted coordinates, +leftover-map reconstruction, leftover-map unexplained leftover, leftover-map +explained leftover share, leftover-map unexplained leftover share, leftover-map +cross share, leftover-map rank, leftover-map post coverage, leftover-map item +coverage, leftover-map incomplete post coverage, leftover-map incomplete item +coverage, or the count of unused axes. 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. 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`, the grouping comparison strip names persisted leftover-map +post complete-case coverage, persisted leftover-map item complete-case +coverage, persisted leftover-map incomplete post coverage, and persisted +leftover-map incomplete item coverage on each grouping row when +leftoverMapCoverageCounts / leftoverMapItemCoverageCounts / +leftoverMapIncompletePostCount / leftoverMapIncompleteItemCount return usable +integers, then names leftover pairs with leftover-map distance `d`, persisted +leftover-map reconstruction `R̂` when finite, persisted leftover-map +explained leftover share `e` when finite, persisted leftover-map unexplained +leftover share `s` when finite, persisted leftover-map cross share `x` +when finite, persisted leftover-map unexplained leftover `U` when finite, +and persisted leftover residual `R` when finite. 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`. + +## 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-map inner product, +leftover-map cosine, leftover-map length, leftover-map graphic display, +leftover residual on pair segments, leftover-map unexplained leftover on pair +segments, leftover-map unexplained leftover persistence, leftover-map cross +share on pair segments, leftover-map cross share persistence, leftover-map +unexplained leftover share on pair segments, leftover-map unexplained leftover +share persistence, leftover-map explained leftover share on pair segments, +leftover-map explained leftover share persistence, leftover-map reconstruction +on pair segments, leftover-map reconstruction persistence, 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 + +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 +(LSIRM interaction `−γ‖ξ_j − ζ_i‖` after main effects +`α_j − β_i`; typically `p = 2` for the interaction map. Leftover residual of +the leftover cell is `R = Y − E[Y|θ, item]` after IRT main effects. +Grouping comparison leftover residual names that persisted remainder on the +strip pair row only when formatLeftoverMapResidual returns a usable badge. +When finite, `Y − E = R`. When finite, `U + R̂ = R`. When finite, +`e + s + x = 1`.) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 983fa4eb1..5f43d8ce1 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,6 +1,6 @@ # Product & Technical Gap Baseline -> Exact-head loop overlay: 2026-08-31 01:06 KST. Protected `main` is +> Exact-head loop overlay: 2026-08-31 01:28 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1` after unauthorized squash > of leftover-map coordinates (v2.24.0 / #782). Revert #808 > (`revert-pr782-unauthorized` @ `1af3e53e`) restores `main` toward @@ -45,8 +45,10 @@ > ADR 0294) is `ead81bd`. #829 leftover-map unexplained leftover share > `s` on grouping comparison leftover-pair buttons (v2.52.0 / ADR 0295) > is `4d82c28`. #831 leftover-map cross share `x` on grouping comparison -> leftover-pair buttons (v2.53.0 / ADR 0296) is `2cefc0b`. Leave -> #782/#802/#803/#804/#805/#806/#808/#809/#810/#811/#812/#813/#814/#815/#816/#817/#818/#819/#821/#822/#823/#824/#825/#826/#827/#829/#831 open for +> leftover-pair buttons (v2.53.0 / ADR 0296) is `2cefc0b`. #832 leftover-map +> unexplained leftover `U` on grouping comparison leftover-pair buttons +> (v2.54.0 / ADR 0297) is `1511166`. Leave +> #782/#802/#803/#804/#805/#806/#808/#809/#810/#811/#812/#813/#814/#815/#816/#817/#818/#819/#821/#822/#823/#824/#825/#826/#827/#829/#831/#832 open for > independent review. Do not squash-merge stacked leftover PRs onto an > unprotected leftover base. Do not merge #808 without independent APPROVE. > Strix on #782 failed closed at `Run Strix (quick)`; do not weaken @@ -56,27 +58,27 @@ > closed. Only collaborator is `seonghobae`; no independent reviewer can > be requested from this token. > -> Next buyer increment on this cycle: leftover-map unexplained leftover -> `U` on grouping comparison leftover-pair buttons (ADR 0297 / v2.54.0) -> delivered locally on `feat/leftover-map-compare-unexplained-leftover-v2540`. +> Next buyer increment on this cycle: leftover residual +> `R` on grouping comparison leftover-pair buttons (ADR 0298 / v2.55.0) +> delivered locally on `feat/leftover-map-compare-leftover-residual-v2550`. > Caption each strip leftover-pair button through -> formatLeftoverMapUnexplained with distinct aria-label -> `Leftover map comparison unexplained leftover` next to leftover-map cross -> share `x`, leftover-map unexplained leftover share `s`, leftover-map -> explained leftover share `e`, leftover-map reconstruction `R̂`, and -> leftover-map distance `d`. UI-only; no new columns. Missing or non-finite -> `U` omits that leftover-map comparison unexplained leftover badge -> independently of `x`, `s`, `e`, `R̂`, `d`, and coverage notes. Do not invent -> `U` from `R` and `R̂`, leftover-map distance, plotted coordinates, -> leftover-map reconstruction, leftover residual, leftover-map explained -> leftover share, leftover-map unexplained leftover share, leftover-map -> cross share, leftover-map rank, 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 name `U 0.00` when finite. A finite negative leftover is -> shown, never clamped. Do not caption the leftover-map graphic on the -> strip. Do not invent leftover scores. Stack onto leftover branch -> `feat/leftover-map-compare-cross-share-v2530` / #831; leave the PR +> formatLeftoverMapResidual with distinct aria-label +> `Leftover map comparison leftover residual` next to 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̂`, and leftover-map distance `d`. +> UI-only; no new columns. Missing or non-finite `R` omits that leftover-map +> comparison leftover residual badge independently of `U`, `x`, `s`, `e`, +> `R̂`, `d`, and coverage notes. Do not invent `R` from `Y` and `E`, from +> `U` and `R̂`, leftover-map distance, plotted coordinates, leftover-map +> reconstruction, leftover-map explained leftover share, leftover-map unexplained leftover share, leftover-map cross share, +> leftover-map rank, 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 +> name `R 0.00` when finite. A finite negative residual is shown, never +> clamped. Do not caption the leftover-map graphic on the strip. Do not +> invent leftover scores. Stack onto leftover branch +> `feat/leftover-map-compare-unexplained-leftover-v2540` / #832; leave the PR > open for independent review. Do not squash-merge onto the leftover > base. Do not persist leftover-map inner product, cosine, or length as > separate columns. diff --git a/frontend/package.json b/frontend/package.json index 52298d5f8..228735341 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.54.0", + "version": "2.55.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index ba5ac8600..5b203715e 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -4413,6 +4413,21 @@ describe("App, authenticated", () => { "Leftover map comparison unexplained leftover", ), ).toHaveLength(1); + expect( + within(screen.getByLabelText("Grouping comparison")).queryByLabelText( + "leftover residual {label}", + ), + ).not.toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).getAllByLabelText( + "Leftover map comparison leftover residual", + ), + ).toHaveLength(2); + expect( + within(screen.getByLabelText("Grouping comparison")).getAllByLabelText( + "Leftover map comparison leftover residual", + )[0], + ).toHaveTextContent("R \u22121.10"); expect( screen.getByRole("button", { name: /open leftover closest pair from comparison: specification revision requested/i, @@ -4443,6 +4458,11 @@ describe("App, authenticated", () => { name: /open leftover closest pair from comparison: specification revision requested/i, }), ).not.toHaveTextContent("U +"); + expect( + screen.getByRole("button", { + name: /open leftover closest pair from comparison: specification revision requested/i, + }), + ).toHaveTextContent("R \u22121.10"); expect( screen.getByRole("button", { name: "Compare Business unit (PU): Demo Report High, mean θ 0.81" }), ).toHaveTextContent("mean θ 0.81"); @@ -4487,6 +4507,11 @@ describe("App, authenticated", () => { name: /open leftover closest pair from comparison: public post/i, }), ).toHaveTextContent("U +0.05"); + expect( + screen.getByRole("button", { + name: /open leftover closest pair from comparison: public post/i, + }), + ).toHaveTextContent("R +0.40"); await waitFor(() => expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining("/api/reports/thread_group/2026-W02"), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0bb2c3f88..1babe1f65 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -157,6 +157,10 @@ import { formatLeftoverMapUnexplained, LEFTOVER_MAP_COMPARE_UNEXPLAINED_LABEL, } from "./leftoverMapUnexplained"; +import { + formatLeftoverMapResidual, + LEFTOVER_MAP_COMPARE_RESIDUAL_LABEL, +} from "./leftoverResidual"; import "./App.css"; const AdminPanel = lazy(() => import("./components/AdminPanel").then((module) => ({ default: module.AdminPanel }))); @@ -4094,6 +4098,9 @@ function ReportsPanel({ const unexplainedLeftover = formatLeftoverMapUnexplained( pair.leftover_map_unexplained, ); + const leftoverResidual = formatLeftoverMapResidual( + pair.leftover_residual, + ); return (
  • ) : null} + {leftoverResidual ? ( + + {leftoverResidual} + + ) : null}
  • ); diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index 52c8415f9..8238ca884 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -86,6 +86,7 @@ describe("i18n", () => { "Leftover map comparison unexplained leftover share", "Leftover map comparison cross share", "Leftover map comparison unexplained leftover", + "Leftover map comparison leftover residual", "Leftover-map graphic item coverage", "Leftover map item coverage", "Leftover map incomplete posts", @@ -491,6 +492,19 @@ describe("i18n", () => { }, ); + it.each([ + ["ko", "잔여 지도 비교 잔여"], + ["zh", "残差地图比较残差"], + ["ja", "残差マップの比較残差"], + ["vi", "Phần dư so sánh bản đồ phần dư"], + ] as const)( + "formats leftover map comparison leftover residual label in %s", + (locale, expected) => { + setLocale(locale); + expect(t("Leftover map comparison leftover residual")).toBe(expected); + }, + ); + it.each([ ["ko", "잔여 지도 그림 기준 포함 범위"], ["zh", "残差图图形准则覆盖范围"], diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 2f64fb582..259ef76f9 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -277,6 +277,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison unexplained leftover share": "잔여 지도 비교 미설명 잔여 점유율", "Leftover map comparison cross share": "잔여 지도 비교 교차 점유율", "Leftover map comparison unexplained leftover": "잔여 지도 비교 미설명 잔여", + "Leftover map comparison leftover residual": "잔여 지도 비교 잔여", "Leftover map item coverage": "잔여 지도 기준 포함 범위", "Leftover map incomplete posts": "잔여 지도 불완전 글", "Leftover map incomplete items": "잔여 지도 불완전 기준", @@ -892,6 +893,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison unexplained leftover share": "残差地图比较未解释残差份额", "Leftover map comparison cross share": "残差地图比较交叉份额", "Leftover map comparison unexplained leftover": "残差地图比较未解释残差", + "Leftover map comparison leftover residual": "残差地图比较残差", "Leftover map item coverage": "残差地图准则覆盖范围", "Leftover map incomplete posts": "残差地图不完整帖文", "Leftover map incomplete items": "残差地图不完整准则", @@ -1528,6 +1530,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison unexplained leftover share": "残差マップの比較未説明残差割合", "Leftover map comparison cross share": "残差マップの比較交差割合", "Leftover map comparison unexplained leftover": "残差マップの比較未説明残差", + "Leftover map comparison leftover residual": "残差マップの比較残差", "Leftover map item coverage": "残差マップの基準対象範囲", "Leftover map incomplete posts": "残差マップの不完全投稿", "Leftover map incomplete items": "残差マップの不完全基準", @@ -2143,6 +2146,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison unexplained leftover share": "Phần dư chưa giải thích so sánh bản đồ phần dư", "Leftover map comparison cross share": "Phần giao so sánh bản đồ phần dư", "Leftover map comparison unexplained leftover": "Phần dư chưa giải thích so sánh", + "Leftover map comparison leftover residual": "Phần dư so sánh bản đồ phần dư", "Leftover map item coverage": "Phạm vi tiêu chí bản đồ phần dư", "Leftover map incomplete posts": "Bài không đầy đủ trên bản đồ phần dư", "Leftover map incomplete items": "Tiêu chí không đầy đủ trên bản đồ phần dư", diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts index a71251029..4860f6adc 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -25,7 +25,8 @@ * (ADR 0294), grouping comparison unexplained leftover share * (ADR 0295), grouping comparison leftover-map cross share * (ADR 0296), and grouping comparison leftover-map unexplained leftover - * (ADR 0297) caption the pair list or the grouping comparison strip, + * (ADR 0297), and grouping comparison leftover residual + * (ADR 0298) caption the pair list or the grouping comparison strip, * not this graphic layout. */ diff --git a/frontend/src/leftoverResidual.test.ts b/frontend/src/leftoverResidual.test.ts index 342d3ad37..f70bf6c4c 100644 --- a/frontend/src/leftoverResidual.test.ts +++ b/frontend/src/leftoverResidual.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { formatLeftoverMapResidual, formatLeftoverResidual } from "./leftoverResidual"; +import { + formatLeftoverMapResidual, + formatLeftoverResidual, + LEFTOVER_MAP_COMPARE_RESIDUAL_LABEL, +} from "./leftoverResidual"; describe("formatLeftoverResidual", () => { it("keeps a signed residual without inventing a leftover score", () => { @@ -23,4 +27,11 @@ describe("formatLeftoverMapResidual", () => { expect(formatLeftoverMapResidual(Number.NaN)).toBeNull(); expect(formatLeftoverMapResidual(Number.POSITIVE_INFINITY)).toBeNull(); }); + + it("keeps the grouping comparison leftover residual label distinct from the graphic leftover residual label", () => { + expect(LEFTOVER_MAP_COMPARE_RESIDUAL_LABEL).toBe( + "Leftover map comparison leftover residual", + ); + expect(LEFTOVER_MAP_COMPARE_RESIDUAL_LABEL).not.toBe("leftover residual {label}"); + }); }); diff --git a/frontend/src/leftoverResidual.ts b/frontend/src/leftoverResidual.ts index 33886e308..fb7a18bb6 100644 --- a/frontend/src/leftoverResidual.ts +++ b/frontend/src/leftoverResidual.ts @@ -1,4 +1,8 @@ /** Signed leftover residual ``R = Y − E[Y|θ, item]`` after IRT main effects. */ + +export const LEFTOVER_MAP_COMPARE_RESIDUAL_LABEL = + "Leftover map comparison leftover residual"; + export function formatLeftoverResidual(value: number): string { if (!Number.isFinite(value)) { return "—"; diff --git a/pyproject.toml b/pyproject.toml index eb1f2076d..61be353ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.54.0" +version = "2.55.0" description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication." readme = "README.md" license = { text = "MIT" } diff --git a/uv.lock b/uv.lock index d5f0e8e22..116d23df0 100644 --- a/uv.lock +++ b/uv.lock @@ -685,7 +685,7 @@ wheels = [ [[package]] name = "lineageweave" -version = "2.54.0" +version = "2.55.0" source = { editable = "." } dependencies = [ { name = "certifi" },