From a4bf239f1cf07ea29bd49b28300f252be0ca7a19 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 15:26:11 +0000 Subject: [PATCH] feat(reports): leftover-map reconstruction on grouping comparison strip (v2.50.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caption persisted leftover-map reconstruction R̂ on GET /api/reports/compare/{period} leftover-pair buttons through formatLeftoverMapReconstruction (ADR 0293). Distinct aria-label Leftover map comparison reconstruction. Omit when R̂ is missing or non-finite. Do not invent R̂ from leftover-map distance or plotted coordinates. No SQL. No leftover score or theta. --- AGENTS.md | 10 +- ARCHITECTURE.md | 7 +- ...0.0-leftover-map-compare-reconstruction.md | 38 ++++ CHANGELOG.md | 37 ++++ CLAUDE.md | 2 +- ...293-leftover-map-compare-reconstruction.md | 178 ++++++++++++++++++ docs/product-technical-gap-baseline.md | 68 +++++++ frontend/package.json | 2 +- frontend/src/App.test.tsx | 46 +++++ frontend/src/App.tsx | 15 ++ frontend/src/components/LeftoverPairList.tsx | 4 +- frontend/src/i18n.test.ts | 11 ++ frontend/src/i18n.ts | 4 + frontend/src/leftoverMapPlotLayout.ts | 5 +- .../src/leftoverMapReconstruction.test.ts | 10 +- frontend/src/leftoverMapReconstruction.ts | 3 + pyproject.toml | 2 +- 17 files changed, 432 insertions(+), 10 deletions(-) create mode 100644 CHANGELOG.d/2.50.0-leftover-map-compare-reconstruction.md create mode 100644 docs/adr/0293-leftover-map-compare-reconstruction.md diff --git a/AGENTS.md b/AGENTS.md index 30380bc66..29b964d56 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) 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) 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 @@ -416,6 +416,12 @@ dropped count that contradicts usable item complete-case integers, omits that le comparison incomplete item note and does not invent dropped criteria from scored minus used or from leftover pair count. Dropped `0` is shown when persisted. The strip does not gain the leftover-map graphic. +ADR 0293 captions grouping comparison leftover-pair buttons with persisted leftover-map +reconstruction `R̂` through formatLeftoverMapReconstruction so a buyer who compares +leftover pairs can match the pair-row reconstruction badge; a missing or non-finite +`R̂` omits that leftover-map comparison reconstruction badge and does not invent `R̂` +from leftover-map distance or plotted coordinates. 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 @@ -438,6 +444,8 @@ Grouping comparison incomplete post coverage (ADR 0291) captions the grouping comparison strip with how many scored posts stayed out of each grouping's map. Grouping comparison incomplete item coverage (ADR 0292) captions the grouping comparison strip with how many scored criteria stayed out of each grouping's map. +Grouping comparison reconstruction (ADR 0293) captions grouping comparison leftover-pair +buttons with persisted leftover-map reconstruction `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 346afb687..460cfc592 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -675,6 +675,8 @@ ADR 0291 captions the grouping comparison strip with persisted leftover-map inco post coverage through leftoverMapIncompletePostCount. ADR 0292 captions the grouping comparison strip with persisted leftover-map incomplete item coverage through leftoverMapIncompleteItemCount. +ADR 0293 captions grouping comparison leftover-pair buttons with persisted leftover-map +reconstruction `R̂` through formatLeftoverMapReconstruction. 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 @@ -694,8 +696,9 @@ including on the pair list. Results persist to `GET /api/reports/compare/{period}` is the home-page grouping strip and carries the same ABAC-filtered leftover pairs (ADR 0149) plus persisted leftover-map complete-case coverage (ADR 0289), leftover-map item complete-case -coverage (ADR 0290), leftover-map incomplete post coverage (ADR 0291), and leftover-map -incomplete item coverage (ADR 0292); +coverage (ADR 0290), leftover-map incomplete post coverage (ADR 0291), leftover-map +incomplete item coverage (ADR 0292), and leftover-map reconstruction `R̂` +(ADR 0293); `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.d/2.50.0-leftover-map-compare-reconstruction.md b/CHANGELOG.d/2.50.0-leftover-map-compare-reconstruction.md new file mode 100644 index 000000000..c0419f96b --- /dev/null +++ b/CHANGELOG.d/2.50.0-leftover-map-compare-reconstruction.md @@ -0,0 +1,38 @@ +## 2.50.0 — Leftover-map reconstruction on grouping comparison strip pair rows + +- Caption leftover-map reconstruction `R̂` on grouping comparison strip + leftover-pair buttons through formatLeftoverMapReconstruction (ADR 0293). + 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, and grouping comparison leftover-pair buttons + that match leftover-map reconstruction `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 reconstruction badge and keeps + leftover-map distance `d` plus the strip coverage notes. Do not invent + `R̂` from leftover-map distance, plotted coordinates, leftover residual, + unexplained leftover, 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. The + strip does not gain the leftover-map graphic. Never invent a leftover + score. Never invent a theta. No new columns. diff --git a/CHANGELOG.md b/CHANGELOG.md index a416d2f8b..6baa70f54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,43 @@ 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-map reconstruction `R̂` through + formatLeftoverMapReconstruction (ADR 0293 / v2.50.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, and grouping + comparison leftover-pair buttons that match leftover-map reconstruction + `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 + reconstruction badge and keeps leftover-map distance `d` plus the strip + coverage notes. Do not invent `R̂` from leftover-map distance, plotted + coordinates, leftover residual, unexplained leftover, 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. 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 the grouping comparison strip with persisted leftover-map incomplete item coverage through leftoverMapIncompleteItemCount (ADR 0292 / v2.49.0). After `make seed`, diff --git a/CLAUDE.md b/CLAUDE.md index 3a6fbb514..1963b8114 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), 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), 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/0293-leftover-map-compare-reconstruction.md b/docs/adr/0293-leftover-map-compare-reconstruction.md new file mode 100644 index 000000000..4796af038 --- /dev/null +++ b/docs/adr/0293-leftover-map-compare-reconstruction.md @@ -0,0 +1,178 @@ +# ADR 0293 — Name leftover-map reconstruction on grouping comparison strip pair rows + +**Decision status:** Accepted +**Date:** 2026-08-30 + +Amends leftover pairs on the grouping comparison strip +([ADR 0149](0149-leftover-pairs-on-comparison-strip.md)), leftover-map +reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.md)), +leftover-map reconstruction on pair segments +([ADR 0272](0272-leftover-map-segment-reconstruction.md)), leftover-map +complete-case coverage on the grouping comparison strip +([ADR 0289](0289-leftover-map-compare-coverage.md)), leftover-map item +complete-case coverage on the grouping comparison strip +([ADR 0290](0290-leftover-map-compare-item-coverage.md)), leftover-map +incomplete post coverage on the grouping comparison strip +([ADR 0291](0291-leftover-map-compare-incomplete-post.md)), and leftover-map +incomplete item coverage on the grouping comparison strip +([ADR 0292](0292-leftover-map-compare-incomplete-item.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 +residual on pair segments ([ADR 0277](0277-leftover-map-segment-residual.md)), +leftover-map unexplained leftover on pair segments +([ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)), leftover-map +cross share on pair segments ([ADR 0275](0275-leftover-map-segment-cross-share.md)), +leftover-map unexplained leftover share on pair segments +([ADR 0274](0274-leftover-map-segment-unexplained-share.md)), leftover-map +explained leftover share on pair segments +([ADR 0273](0273-leftover-map-segment-explained-share.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 residual +disclosure ([ADR 0162](0162-leftover-residual-disclosure.md)), leftover +observed `Y` / expected `E` ([ADR 0163](0163-leftover-observed-expected.md)), +leftover-map explained leftover share persistence +([ADR 0266](0266-leftover-map-explained-share.md)), leftover-map +unexplained leftover share persistence +([ADR 0233](0233-leftover-map-unexplained-share.md)), leftover-map +cross share persistence +([ADR 0185](0185-leftover-map-cross-share.md)), leftover-map unexplained leftover +persistence ([ADR 0182](0182-leftover-map-unexplained.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 0201 already persists leftover-map reconstruction +`R̂ = ξ_{1:2} · ζ_{1:2}` on leftover pair rows. ADR 0049 already names +that reconstruction on the period-report pair list. ADR 0272 already names +it on leftover-map graphic-display pair segments. ADR 0149 already carries +the same ABAC-filtered leftover pairs onto the grouping comparison strip, +and ADR 0292 already captions leftover-map incomplete item coverage on +each grouping row. The strip pair button still names only leftover-map +distance `d`, so a buyer who compares leftover pairs can treat Euclidean +gap as the leftover the two-axis map reconstructs even after the pair +list and graphic already name `R̂`. Hiding reconstruction on the strip +lets leftover residual `R` or leftover-map distance `d` be read as +leftover-map reconstruction without a next action. + +This increment captions persisted leftover-map reconstruction on each +grouping comparison leftover-pair button through +formatLeftoverMapReconstruction. It does not add columns. It does not +recompute `R̂` from leftover-map distance, plotted coordinates, leftover +residual, unexplained leftover, or a pixel inner product. It does not +persist leftover-map inner product, cosine, or length as separate columns +(`R̂` already is the two-axis inner product). 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 **0293** so it does not collide with +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-map post complete-case coverage +fail-closed on the pair list (0288), leftover-map incomplete item coverage +on the pair list (0287), leftover-map incomplete post coverage on the pair +list (0286), leftover-map item complete-case coverage on the pair list +(0285), leftover-map incomplete item coverage on the graphic display +(0284), leftover-map incomplete post coverage on the graphic display +(0283), leftover-map item complete-case coverage on the graphic display +(0282), leftover-map complete-case coverage on the graphic display (0281), +leftover-map rank on pair segments (0280), 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-map reconstruction formatter as the pair-row +`R̂` badge, next to leftover-map distance `d`. Use the distinct accessible +name `Leftover map comparison reconstruction` so the strip badge is not +the graphic caption (`leftover-map reconstruction {label}`). A missing or +non-finite `R̂` omits that leftover-map comparison reconstruction badge and +keeps 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 reconstruction is finite. Do not invent `R̂` +from leftover-map distance, plotted coordinates, leftover residual, +unexplained leftover, 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` and +persisted leftover-map reconstruction `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 coordinates, reconstruction, and distance +are all finite, `R̂ = ξ_{1:2} · ζ_{1:2}` and `d = ‖ξ_{1:2} − ζ_{1:2}‖` +remain the same identities already persisted by ADR 0267. + +## 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 inner product, +leftover-map cosine, leftover-map length, leftover-map graphic display, +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. Gabriel +reconstruction of the leftover cell is the two-axis inner product +`R̂ = ξ_{1:2} · ζ_{1:2}`. Grouping comparison reconstruction names that +persisted inner product on the strip pair row only when +formatLeftoverMapReconstruction returns a usable signed badge.) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index f0befa176..3482516f4 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,73 @@ # Product & Technical Gap Baseline +> Exact-head loop overlay: 2026-08-31 00:20 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 +> `fc13acaa` (v2.23.0) and still needs independent APPROVE. GitHub +> writes work (comment/close/create-PR/push). Token still has empty +> `X-OAuth-Scopes`; repo permission is ADMIN. Open leftover stack +> still lacks independent APPROVE. Do not self-approve. +> #782 leftover-map coordinates through pair-segment distance +> (v2.24.0–v2.28.0 / ADR 0267–0271) is `9bdd3e4d`. #802 explained +> leftover share on pair segments (v2.30.0 / ADR 0273, includes v2.29 +> reconstruction) is `79ec22f0`. #803 unexplained leftover share on pair +> segments (v2.31.0 / ADR 0274) is `089a1571`. #804 leftover-map cross +> share on pair segments (v2.32.0 / ADR 0275) is `c513002b`. #805 leftover-map +> unexplained leftover `U` on pair segments (v2.33.0 / ADR 0276) is +> `e3fdfd74`. #806 leftover residual `R` on pair segments (v2.34.0 / +> ADR 0277) is `33f6c3dd`. #809 leftover observed `Y` on pair segments +> (v2.35.0 / ADR 0278) is `b334b00e`. #810 leftover expected `E` on pair +> segments (v2.36.0 / ADR 0279) is `6e37757a`. #811 leftover-map rank on +> pair segments (v2.37.0 / ADR 0280) is `e626a1d0`. #812 leftover-map +> complete-case coverage on the graphic (v2.38.0 / ADR 0281) is `64964cb6`. +> #813 leftover-map item complete-case coverage on the graphic (v2.39.0 / +> ADR 0282) is `de2a8a8b`. #814 leftover-map incomplete post coverage on +> the graphic (v2.40.0 / ADR 0283) is `77292872`. #815 leftover-map +> incomplete item coverage on the graphic (v2.41.0 / ADR 0284) is +> `63092de`. #816 leftover-map item complete-case coverage on the pair +> list (v2.42.0 / ADR 0285) is `1e3d13e`. #817 leftover-map incomplete +> post coverage on the pair list (v2.43.0 / ADR 0286) is `ef30930`. +> #818 leftover-map incomplete item coverage on the pair list +> (v2.44.0 / ADR 0287) is `aa32089`. #819 leftover-map post complete-case +> coverage fail-closed on the pair list (v2.45.0 / ADR 0288) is `a2c96551`. +> #821 leftover-map complete-case coverage on the grouping comparison +> strip (v2.46.0 / ADR 0289) is `11a78553`. #822 leftover-map item +> complete-case coverage on the grouping comparison strip (v2.47.0 / +> ADR 0290) is `6726353a`. #823 leftover-map singular values on leftover-axis +> badges (v2.47.0 / ADR 0290, plot stack) is `eb172c40`. #824 leftover-map +> incomplete post coverage on the grouping comparison strip (v2.48.0 / +> ADR 0291) is `8b2ed95`. #825 leftover-map incomplete item coverage on +> the grouping comparison strip (v2.49.0 / ADR 0292) is `bca6baa`. Leave +> #782/#802/#803/#804/#805/#806/#808/#809/#810/#811/#812/#813/#814/#815/#816/#817/#818/#819/#821/#822/#823/#824/#825 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 +> fail-closed on `Vulnerabilities [1-9]`. Org Strix `ModelBehaviorError` +> classifier is already on ContextualWisdomLab/.github. Copilot review is +> not independent APPROVE. Issues #79 and #87 stay open. #96 is already +> closed. Only collaborator is `seonghobae`; no independent reviewer can +> be requested from this token. +> +> Next buyer increment on this cycle: leftover-map reconstruction `R̂` on +> grouping comparison leftover-pair buttons (ADR 0293 / v2.50.0) delivered +> locally on `feat/leftover-map-compare-reconstruction-v2500`. Caption each +> strip leftover-pair button through formatLeftoverMapReconstruction with +> distinct aria-label `Leftover map comparison reconstruction` next to +> leftover-map distance `d`. UI-only; no new columns. Missing or non-finite +> `R̂` omits that leftover-map comparison reconstruction badge independently +> of `d` and coverage notes. Do not invent `R̂` from leftover-map distance, +> plotted coordinates, leftover residual, unexplained leftover, 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 caption the leftover-map graphic on the +> strip. Do not invent leftover scores. Stack onto leftover branch +> `feat/leftover-map-compare-incomplete-item-v2490` / #825; 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. + + > Exact-head loop overlay: 2026-08-30 23:28 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1` after unauthorized squash > of leftover-map coordinates (v2.24.0 / #782). Revert #808 diff --git a/frontend/package.json b/frontend/package.json index 8fa819fcf..497940a94 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.49.0", + "version": "2.50.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 283e671ac..ed531af9c 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -898,6 +898,16 @@ describe("App, authenticated", () => { incomplete_post_count: 0, incomplete_item_count: 0, }, + leftover_pairs: [ + { + pair_kind: "closest", + post_id: "post-2", + post_title: "Specification revision requested", + criterion_code: "general_sentiment_negative", + leftover_distance: 2.0, + leftover_residual: -1.1, + }, + ], }, { grouping_kind: "corporate_entity", @@ -922,6 +932,7 @@ describe("App, authenticated", () => { criterion_code: "sales_lead_specificity", leftover_distance: 0.12, leftover_residual: 0.4, + leftover_map_reconstruction: 0.248, }, ], leftover_map_coverage: { @@ -4323,6 +4334,31 @@ describe("App, authenticated", () => { "Leftover-map graphic incomplete items", ), ).not.toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).queryByLabelText( + /leftover-map reconstruction/, + ), + ).not.toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).getByLabelText( + "Leftover map comparison reconstruction", + ), + ).toHaveTextContent("R̂ +0.25"); + expect( + within(screen.getByLabelText("Grouping comparison")).getAllByLabelText( + "Leftover map comparison reconstruction", + ), + ).toHaveLength(1); + expect( + screen.getByRole("button", { + name: /open leftover closest pair from comparison: specification revision requested/i, + }), + ).toHaveTextContent("d 2.00"); + expect( + screen.getByRole("button", { + name: /open leftover closest pair from comparison: specification revision requested/i, + }), + ).not.toHaveTextContent("R̂"); expect( screen.getByRole("button", { name: "Compare Business unit (PU): Demo Report High, mean θ 0.81" }), ).toHaveTextContent("mean θ 0.81"); @@ -4337,6 +4373,16 @@ describe("App, authenticated", () => { name: /open leftover closest pair from comparison: public post/i, }), ).toHaveTextContent("Closest leftover: Public post · sales-lead"); + expect( + screen.getByRole("button", { + name: /open leftover closest pair from comparison: public post/i, + }), + ).toHaveTextContent("d 0.12"); + expect( + screen.getByRole("button", { + name: /open leftover closest pair from comparison: public post/i, + }), + ).toHaveTextContent("R̂ +0.25"); 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 1255ff43c..af3b76d56 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -137,6 +137,10 @@ import { LEFTOVER_MAP_PLOT_INCOMPLETE_POST, LEFTOVER_MAP_PLOT_ITEM_COVERAGE, } from "./leftoverMapCoverage"; +import { + formatLeftoverMapReconstruction, + LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL, +} from "./leftoverMapReconstruction"; import "./App.css"; const AdminPanel = lazy(() => import("./components/AdminPanel").then((module) => ({ default: module.AdminPanel }))); @@ -4059,6 +4063,9 @@ function ReportsPanel({ ? "Open this post to read the criterion it sat farthest from after main effects." : "Open this post to read the criterion it sat closest to after main effects."; const criterion = criterionShortLabel(pair.criterion_code); + const reconstruction = formatLeftoverMapReconstruction( + pair.leftover_map_reconstruction, + ); return (
  • {nextAction} d {pair.leftover_distance.toFixed(2)} + {reconstruction ? ( + + {reconstruction} + + ) : null}
  • ); diff --git a/frontend/src/components/LeftoverPairList.tsx b/frontend/src/components/LeftoverPairList.tsx index a6a097bc1..71b98b914 100644 --- a/frontend/src/components/LeftoverPairList.tsx +++ b/frontend/src/components/LeftoverPairList.tsx @@ -87,7 +87,9 @@ export type LeftoverPairListProps = { * item complete-case coverage on the grouping comparison strip, not this pair * list. ADR 0291 names persisted leftover-map incomplete post coverage on the * grouping comparison strip, not this pair list. ADR 0292 names persisted leftover-map - * incomplete item coverage on the grouping comparison strip, not this pair list. Every badge still + * incomplete item coverage on the grouping comparison strip, not this pair list. ADR 0293 + * names persisted leftover-map reconstruction on grouping comparison leftover-pair + * buttons, not this pair list. Every badge still * renders together before opening the named post. */ export function LeftoverPairList({ diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index 68a7ffc9f..20d72b60c 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -81,6 +81,7 @@ describe("i18n", () => { "Leftover map comparison item coverage", "Leftover map comparison incomplete posts", "Leftover map comparison incomplete items", + "Leftover map comparison reconstruction", "Leftover-map graphic item coverage", "Leftover map item coverage", "Leftover map incomplete posts", @@ -424,6 +425,16 @@ describe("i18n", () => { expect(t("Leftover map comparison incomplete items")).toBe(expected); }); + it.each([ + ["ko", "잔여 지도 비교 재구성"], + ["zh", "残差地图比较重建"], + ["ja", "残差マップの比較再構成"], + ["vi", "Tái dựng so sánh bản đồ phần dư"], + ] as const)("formats leftover map comparison reconstruction label in %s", (locale, expected) => { + setLocale(locale); + expect(t("Leftover map comparison reconstruction")).toBe(expected); + }); + it.each([ ["ko", "잔여 지도 그림 기준 포함 범위"], ["zh", "残差图图形准则覆盖范围"], diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 95d7f3e33..5559de700 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -272,6 +272,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison item coverage": "잔여 지도 비교 기준 포함 범위", "Leftover map comparison incomplete posts": "잔여 지도 비교 불완전 글", "Leftover map comparison incomplete items": "잔여 지도 비교 불완전 기준", + "Leftover map comparison reconstruction": "잔여 지도 비교 재구성", "Leftover map item coverage": "잔여 지도 기준 포함 범위", "Leftover map incomplete posts": "잔여 지도 불완전 글", "Leftover map incomplete items": "잔여 지도 불완전 기준", @@ -882,6 +883,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison item coverage": "残差地图比较准则覆盖范围", "Leftover map comparison incomplete posts": "残差地图比较不完整帖文", "Leftover map comparison incomplete items": "残差地图比较不完整准则", + "Leftover map comparison reconstruction": "残差地图比较重建", "Leftover map item coverage": "残差地图准则覆盖范围", "Leftover map incomplete posts": "残差地图不完整帖文", "Leftover map incomplete items": "残差地图不完整准则", @@ -1513,6 +1515,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison item coverage": "残差マップの比較基準対象範囲", "Leftover map comparison incomplete posts": "残差マップの比較不完全投稿", "Leftover map comparison incomplete items": "残差マップの比較不完全基準", + "Leftover map comparison reconstruction": "残差マップの比較再構成", "Leftover map item coverage": "残差マップの基準対象範囲", "Leftover map incomplete posts": "残差マップの不完全投稿", "Leftover map incomplete items": "残差マップの不完全基準", @@ -2123,6 +2126,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison item coverage": "Phạm vi so sánh tiêu chí bản đồ phần dư", "Leftover map comparison incomplete posts": "Bài không đầy đủ so sánh trên bản đồ phần dư", "Leftover map comparison incomplete items": "Tiêu chí không đầy đủ so sánh trên bản đồ phần dư", + "Leftover map comparison reconstruction": "Tái dựng 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 b96176152..1106e38cc 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -19,8 +19,9 @@ * coverage (ADR 0287), grouping comparison complete-case coverage * (ADR 0289), grouping comparison item complete-case coverage * (ADR 0290), grouping comparison incomplete post coverage - * (ADR 0291), and grouping comparison incomplete item coverage - * (ADR 0292) caption the pair list or the grouping comparison strip, + * (ADR 0291), grouping comparison incomplete item coverage + * (ADR 0292), and grouping comparison reconstruction + * (ADR 0293) caption the pair list or the grouping comparison strip, * not this graphic layout. */ diff --git a/frontend/src/leftoverMapReconstruction.test.ts b/frontend/src/leftoverMapReconstruction.test.ts index 5a35a5168..5fb107210 100644 --- a/frontend/src/leftoverMapReconstruction.test.ts +++ b/frontend/src/leftoverMapReconstruction.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { formatLeftoverMapReconstruction } from "./leftoverMapReconstruction"; +import { + formatLeftoverMapReconstruction, + LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL, +} from "./leftoverMapReconstruction"; describe("formatLeftoverMapReconstruction", () => { it("names leftover-map reconstruction without inventing a leftover score", () => { @@ -14,4 +17,9 @@ describe("formatLeftoverMapReconstruction", () => { expect(formatLeftoverMapReconstruction(Number.NaN)).toBeNull(); expect(formatLeftoverMapReconstruction(Number.POSITIVE_INFINITY)).toBeNull(); }); + + it("keeps the grouping comparison reconstruction label distinct from the graphic reconstruction label", () => { + expect(LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL).toBe("Leftover map comparison reconstruction"); + expect(LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL).not.toBe("leftover-map reconstruction {label}"); + }); }); diff --git a/frontend/src/leftoverMapReconstruction.ts b/frontend/src/leftoverMapReconstruction.ts index 895192933..9a32c66b7 100644 --- a/frontend/src/leftoverMapReconstruction.ts +++ b/frontend/src/leftoverMapReconstruction.ts @@ -5,6 +5,9 @@ import { formatSignedLeftoverValue } from "./leftoverMapUnexplained"; export const LEFTOVER_MAP_RECONSTRUCTION_ACTION = "Leftover map reconstructs R̂ {value} after IRT main effects. Open this post to read {criterion}."; +export const LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL = + "Leftover map comparison reconstruction"; + const RECONSTRUCTION_BADGE = "R\u0302"; export function formatLeftoverMapReconstruction(value: number | null | undefined): string | null { diff --git a/pyproject.toml b/pyproject.toml index 3da74d89c..1ed881f7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.49.0" +version = "2.50.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" }