From 52b3597e5c6b825f988a9c8a7c68ae1653535982 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 31 Aug 2026 01:47:18 +0000
Subject: [PATCH] feat(reports): leftover-map singular values on comparison
leftover-axis badges (v2.80.0)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Caption leftover-axis report badges on the grouping comparison strip with
persisted leftover-map singular values σ_k (ADR 0323). Comparison copy stays
leftover map comparison leftover axis {k} σ {value} {share}% so it stays
distinct from period leftover-axis leftover axis {k} σ {value} {share}%
(ADR 0322). Rank-0 unused axes still name σ 0.00. Share and singular value
omit independently. Do not invent σ_k from leftover-map axis share. Fail-closes
leftover-map comparison graphic TypeScript on omitted leftover-map singular
values. Never invent a leftover score. Never invent a theta.
---
AGENTS.md | 13 +-
ARCHITECTURE.md | 5 +-
...80.0-leftover-map-compare-axis-singular.md | 22 ++++
CHANGELOG.md | 22 +++-
CLAUDE.md | 2 +-
docs/adr/0148-leftover-map-axis-share.md | 4 +-
docs/adr/0322-leftover-map-axis-singular.md | 3 +-
...0323-leftover-map-compare-axis-singular.md | 116 ++++++++++++++++++
docs/product-technical-gap-baseline.md | 17 +++
docs/storybook-inventory.md | 2 +-
frontend/package.json | 2 +-
frontend/src/App.test.tsx | 20 +++
frontend/src/App.tsx | 34 +++++
.../src/components/LeftoverMapPlot.test.tsx | 3 +-
frontend/src/components/LeftoverMapPlot.tsx | 12 +-
frontend/src/components/LeftoverPairList.tsx | 3 +-
frontend/src/i18n.test.ts | 42 +++++++
frontend/src/i18n.ts | 30 +++++
frontend/src/leftoverMapAxisBadge.test.ts | 8 +-
frontend/src/leftoverMapAxisBadge.ts | 5 +-
.../src/leftoverMapPlotAxisSingular.test.ts | 74 +++++++++++
frontend/src/leftoverMapPlotAxisSingular.ts | 42 +++++++
frontend/src/leftoverMapPlotLayout.test.ts | 3 +
frontend/src/leftoverMapPlotLayout.ts | 2 +
pyproject.toml | 2 +-
25 files changed, 468 insertions(+), 20 deletions(-)
create mode 100644 CHANGELOG.d/2.80.0-leftover-map-compare-axis-singular.md
create mode 100644 docs/adr/0323-leftover-map-compare-axis-singular.md
diff --git a/AGENTS.md b/AGENTS.md
index 3039c75b6..f7ebeb9d3 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 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321 / 0322) 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 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321 / 0322 / 0323) 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
@@ -650,8 +650,12 @@ ADR 0322 captions leftover-axis report badges with persisted leftover-map singul
values `σ_k` when leftoverMapAxisBadgeSingular returns a usable finite non-negative
value; a missing, non-finite, or negative singular value omits that `σ` badge and
keeps `leftover axis {k} {share}%`. Do not invent leftover-map singular values from
-leftover-map axis share. The grouping comparison strip does not gain leftover-axis
-report badges with a distinct comparison-strip name.
+leftover-map axis share. ADR 0323 captions leftover-axis report badges on the grouping
+comparison strip with persisted leftover-map singular values `σ_k` when
+leftoverMapCompareAxisBadge returns a usable leftover-axis caption. Distinct accessible
+name `leftover map comparison leftover axis {axis} σ {value}`. Share and singular value
+omit independently. Rank-0 unused axes still name `σ 0.00`. Do not invent leftover-map
+singular values from leftover-map axis share.
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
@@ -753,6 +757,9 @@ values `σ_k` when leftoverSingularForAxis returns a usable finite non-negative
Leftover-axis report badge leftover-map singular values (ADR 0322) captions leftover-axis
report badges with persisted leftover-map singular values `σ_k` when leftoverMapAxisBadgeSingular
returns a usable finite non-negative value.
+Grouping comparison leftover-axis report badge leftover-map singular values (ADR 0323) captions
+leftover-axis report badges on the grouping comparison strip with persisted leftover-map
+singular values `σ_k` when leftoverMapCompareAxisBadge returns a usable leftover-axis caption.
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 7bfd22ea6..8321c5d25 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -752,6 +752,8 @@ ADR 0321 captions leftover-map singular values on that grouping
comparison leftover-map graphic from already-named leftover-map axes.
ADR 0322 captions leftover-axis report badges with persisted leftover-map
singular values `σ_k`.
+ADR 0323 captions leftover-axis report badges on the grouping comparison strip
+with persisted leftover-map singular values `σ_k`.
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
@@ -803,7 +805,8 @@ graphic (ADR 0318), leftover-map distance on the grouping comparison leftover-ma
graphic (ADR 0319), leftover-map coordinate ticks on the grouping comparison leftover-map
graphic (ADR 0320), leftover-map singular values on the grouping comparison leftover-map
graphic (ADR 0321), leftover-map singular values on leftover-axis report badges
-(ADR 0322);
+(ADR 0322), leftover-map singular values on leftover-axis report badges on the
+grouping comparison strip (ADR 0323);
`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.80.0-leftover-map-compare-axis-singular.md b/CHANGELOG.d/2.80.0-leftover-map-compare-axis-singular.md
new file mode 100644
index 000000000..305984458
--- /dev/null
+++ b/CHANGELOG.d/2.80.0-leftover-map-compare-axis-singular.md
@@ -0,0 +1,22 @@
+## 2.80.0 — Leftover-map singular values on leftover-axis report badges on the grouping comparison strip
+
+- Caption leftover-axis report badges on the grouping comparison strip with
+ persisted leftover-map singular values `σ_k` (ADR 0323). After `make seed`,
+ grouping comparison leftover-axis badges name persisted leftover-map singular
+ values when finite together with persisted leftover-map axis share; click a
+ leftover pair opens that post. A missing, non-finite, or negative singular
+ value omits that `σ` badge and keeps
+ `leftover map comparison leftover axis {k} {share}%`. Rank-0 unused axes still
+ name `σ 0.00`. Comparison leftover-axis badges stay
+ `leftover map comparison leftover axis {k} σ {value} {share}%` so they stay
+ distinct from period leftover-axis `leftover axis {k} σ {value} {share}%`,
+ from hyphen `leftover-map axis {k} σ {value} ({share}%)`, and from comparison
+ graphic `leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`.
+ Do not invent `σ_k` from leftover-map axis share. This increment does not
+ change leftover-map singular values on leftover-axis report badges in the
+ period-report panel. A finite negative leftover is shown, never clamped.
+ Never invent a leftover score. Never invent a theta. No new columns.
+ Fail-closes leftover-map comparison graphic TypeScript on omitted leftover-map
+ singular values (`leftover_singular_value: Number.NaN` and percent/singular
+ narrowing) inherited from the grouping comparison leftover-map graphic σ
+ increment.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d84ce590..9a0d1cec3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,26 @@ All notable changes to this project are documented here. Format follows
### Added
+- Period leftover pairs now caption leftover-axis report badges on the grouping
+ comparison strip with persisted leftover-map singular values `σ_k`
+ (ADR 0323 / v2.80.0). After `make seed`, grouping comparison leftover-axis
+ badges name persisted leftover-map singular values when finite together with
+ persisted leftover-map axis share; click a leftover pair opens that post. A
+ missing, non-finite, or negative singular value omits that `σ` badge and keeps
+ `leftover map comparison leftover axis {k} {share}%`. Rank-0 unused axes still
+ name `σ 0.00`. Comparison leftover-axis badges stay
+ `leftover map comparison leftover axis {k} σ {value} {share}%` so they stay
+ distinct from period leftover-axis `leftover axis {k} σ {value} {share}%`,
+ from hyphen `leftover-map axis {k} σ {value} ({share}%)`, and from comparison
+ graphic `leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`.
+ Do not invent `σ_k` from leftover-map axis share. This increment does not
+ change leftover-map singular values on leftover-axis report badges in the
+ period-report panel. A finite negative leftover is shown, never clamped.
+ Never invent a leftover score or a theta. No new columns.
+ Fail-closes leftover-map comparison graphic TypeScript on omitted leftover-map
+ singular values inherited from the grouping comparison leftover-map graphic σ
+ increment.
+
- Period leftover pairs now caption leftover-axis report badges with persisted
leftover-map singular values `σ_k` (ADR 0322 / v2.79.0). After `make seed`,
leftover-axis badges name persisted leftover-map singular values when finite
@@ -20,7 +40,7 @@ All notable changes to this project are documented here. Format follows
`leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`.
Do not invent `σ_k` from leftover-map axis share. This increment does not
caption leftover-map singular values on leftover-axis badges on the grouping
- comparison strip with a distinct name. A finite negative leftover is shown,
+ comparison strip with a distinct name (ADR 0323). A finite negative leftover is shown,
never clamped. Never invent a leftover score or a theta. No new columns.
- Period leftover pairs now name leftover-map singular values on the grouping
diff --git a/CLAUDE.md b/CLAUDE.md
index d4cbddbe7..4546003e0 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 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321 / 0322), occupational construct catalog search
+(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284 / 0285 / 0286 / 0287 / 0288 / 0289 / 0290 / 0291 / 0292 / 0293 / 0294 / 0295 / 0296 / 0297 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321 / 0322 / 0323), 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/0148-leftover-map-axis-share.md b/docs/adr/0148-leftover-map-axis-share.md
index 4eb1f32c2..74728c994 100644
--- a/docs/adr/0148-leftover-map-axis-share.md
+++ b/docs/adr/0148-leftover-map-axis-share.md
@@ -5,7 +5,9 @@
**Amended by:** [ADR 0269](0269-leftover-map-axis-share-plot.md)
(leftover-map axis share on the graphic display);
[ADR 0322](0322-leftover-map-axis-singular.md)
-(leftover-map singular values on leftover-axis report badges)
+(leftover-map singular values on leftover-axis report badges);
+[ADR 0323](0323-leftover-map-compare-axis-singular.md)
+(leftover-map singular values on leftover-axis report badges on the grouping comparison strip)
## Context
diff --git a/docs/adr/0322-leftover-map-axis-singular.md b/docs/adr/0322-leftover-map-axis-singular.md
index 9a783b770..bf1183f72 100644
--- a/docs/adr/0322-leftover-map-axis-singular.md
+++ b/docs/adr/0322-leftover-map-axis-singular.md
@@ -82,7 +82,8 @@ singular values (ADR 0321) remain.
Independent of leftover-map singular values on the grouping comparison
leftover-map graphic. This increment does not caption leftover-map singular
values on leftover-axis badges on the grouping comparison strip with a
-distinct name.
+distinct name
+([ADR 0323](0323-leftover-map-compare-axis-singular.md)).
## References
diff --git a/docs/adr/0323-leftover-map-compare-axis-singular.md b/docs/adr/0323-leftover-map-compare-axis-singular.md
new file mode 100644
index 000000000..6d294a869
--- /dev/null
+++ b/docs/adr/0323-leftover-map-compare-axis-singular.md
@@ -0,0 +1,116 @@
+# ADR 0323 — Name leftover-map singular values on leftover-axis report badges on the grouping comparison strip
+
+**Decision status:** Accepted
+**Date:** 2026-08-31
+
+Amends leftover-map singular values on leftover-axis report badges
+([ADR 0322](0322-leftover-map-axis-singular.md)), leftover-map singular
+values on the grouping comparison leftover-map graphic
+([ADR 0321](0321-leftover-map-compare-plot-singular.md)), leftover-map
+axis share persistence ([ADR 0148](0148-leftover-map-axis-share.md)), leftover-map
+graphic display on the grouping comparison strip
+([ADR 0304](0304-leftover-map-compare-graphic.md)). Independent of leftover-map
+singular values on leftover-axis report badges in the period-report panel
+(ADR 0322).
+
+## Context
+
+ADR 0148 already persists leftover-map singular values `σ_k` on
+`report_leftover_map_axis` together with leftover-map axis share
+`σ_k² / Σ_j σ_j²`. ADR 0322 already captions period-report leftover-axis
+badges with `leftover axis {axis} σ {value} {share}%` when that singular
+value is finite and non-negative. ADR 0321 already captions the grouping
+comparison leftover-map graphic with
+`leftover map comparison graphic leftover-map axis {axis} σ {value}`
+when that singular value is finite and non-negative. The grouping
+comparison strip still omits leftover-axis report badges, so a buyer
+who compares leftover pairs can treat leftover-map graphic `σ_k` as
+the leftover-axis report badge without a next action. Hiding persisted
+`σ_k` on leftover-axis report badges lets leftover-map axis share be
+read as a leftover score. A missing, non-finite, or negative singular
+value is not a leftover score and must omit independently of leftover-map
+axis share. Rank-0 unused axes still persist `σ_k = 0`. A finite negative
+leftover is shown, never clamped. Do not invent `σ_k` from leftover-map
+axis share.
+
+This increment captions leftover-map singular values on leftover-axis
+report badges on the grouping comparison strip from already-named
+leftover-map axes. Comparison copy uses the accessible names
+`leftover map comparison leftover axis {axis} σ {value}` and
+`leftover map comparison leftover axis {axis} σ {value} {share}%`
+so they stay distinct from hyphen `leftover-map axis {axis} σ {value}`,
+from pair-list and period-report leftover-axis
+`leftover axis {axis} σ {value} {share}%` (ADR 0322), from comparison
+graphic `leftover map comparison graphic leftover-map axis {axis} σ {value}`,
+and from comparison axis-share
+`leftover map comparison axis {axis} ({share}%)`. It does not add
+columns. Do not invent a leftover score. Do not invent a theta.
+
+This protected increment uses **0323** so it does not collide with leftover-map
+singular values on leftover-axis report badges in the period-report panel
+(0322), leftover-map singular values on the grouping comparison leftover-map
+graphic (0321), leftover-map coordinate ticks on that graphic (0320), leftover-map
+axis share on that graphic (0305), leftover-map graphic display on the grouping
+comparison strip (0304), leftover-map axis share persistence (0148), or the
+dashboard stacks.
+
+## Decision
+
+On the grouping comparison strip, caption leftover-map axis 1 and leftover-map
+axis 2 leftover-axis report badges with persisted leftover-map singular
+values `σ_k` when that singular value is finite and non-negative. Share
+and singular value omit independently: no share and no `σ_k` omits that
+leftover-axis report badge; share only is
+`leftover map comparison leftover axis {k} {share}%`; `σ_k` only is
+`leftover map comparison leftover axis {k} σ {value}`; both is
+`leftover map comparison leftover axis {k} σ {value} {share}%`.
+A missing, non-finite, or negative singular value omits that `σ` badge
+and does not invent `σ_k` from leftover-map axis share. Rank-0 unused
+axes still name `σ 0.00`. Click a leftover pair to open that post.
+
+Leftover-map comparison leftover-axis singular values omit independently
+of leftover-map comparison graphic leftover-map axis singular values and
+leftover-map axis share. A missing singular value omits that leftover-map
+comparison leftover-axis `σ` caption and keeps a usable leftover-map
+comparison leftover-axis share caption, leftover-map comparison graphic
+leftover-map axis `σ` caption, leftover-map comparison graphic leftover-map
+axis ticks, leftover-map distance caption, leftover-map rank caption,
+leftover expected `E`, leftover observed `Y`, leftover residual `R`,
+leftover-map unexplained leftover `U`, leftover-map reconstruction `R̂`,
+and leftover-map comparison graphic coverage notes.
+
+This increment does not change leftover-map singular values on leftover-axis
+report badges in the period-report panel. Those leftover-axis badges stay
+`leftover axis {axis} σ {value} {share}%` (ADR 0322). This increment does
+not persist leftover-map inner product, cosine, or length.
+
+Do not add SQL migrations. Do not edit shipped migrations. Do not invent a leftover
+score. Do not invent a theta.
+
+## Consequences
+
+After `make seed`, grouping comparison leftover pairs that already show
+the leftover-map graphic of persisted `ξ` / `ζ` also name leftover-map
+singular values on leftover-axis report badges when those persisted `σ_k`
+are finite and non-negative. Rank-0 unused axes still name leftover-map
+comparison leftover axis `σ 0.00`. Click a post marker or a pair button
+opens that post. When coordinates, reconstruction, and distance are finite,
+`R̂ = ξ · ζ` and `d = ‖ξ − ζ‖`. When leftover-map singular values and
+leftover-map axis share are finite, leftover-map axis share is
+`σ_k² / Σ_j σ_j²`.
+
+## Related
+
+Independent of leftover-map singular values on leftover-axis report badges
+in the period-report panel ([ADR 0322](0322-leftover-map-axis-singular.md)).
+
+## References
+
+Jeon, M., Jin, I. H., Schweinberger, M., & Baugh, S. (2021). Mapping
+unobserved item–respondent interactions: A latent space item response
+model with interaction map. *Psychometrika, 86*(2), 378–403.
+https://doi.org/10.1007/s11336-021-09762-5
+
+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
diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md
index 9fef7a938..19515f547 100644
--- a/docs/product-technical-gap-baseline.md
+++ b/docs/product-technical-gap-baseline.md
@@ -1,5 +1,22 @@
# Product & Technical Gap Baseline
+> Exact-head loop overlay: 2026-08-31 10:40 KST. Protected `main` is
+> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not
+> self-approve. #862 leftover-map singular values on leftover-axis report badges
+> (v2.79.0 / ADR 0322) is `f24b664`, stacked on #861 @ `3aac452`. Independent
+> APPROVE is missing. Next buyer increment on this
+> cycle: leftover-map singular values on leftover-axis report badges on the
+> grouping comparison strip (ADR 0323 / v2.80.0) delivered locally on
+> `feat/leftover-map-compare-axis-singular-v2800`. Caption persisted leftover-map
+> singular values `σ_k` on leftover-axis report badges from already-named leftover-map
+> axes. Distinct accessible name `leftover map comparison leftover axis {axis} σ {value}`.
+> Rank-0 unused axes still name `σ 0.00`. Share and singular value omit
+> independently. Do not invent `σ_k` from leftover-map axis share. Do not change
+> leftover-map singular values on leftover-axis report badges in the period-report
+> panel this increment. Stack onto #862. Leave open for independent review. Do not
+> squash-merge. Do not self-approve. Issues #79 and #87 stay open. Do not merge
+> #808 without independent APPROVE. Never self-approve.
+
> Exact-head loop overlay: 2026-08-31 10:35 KST. Protected `main` is
> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not
> self-approve. #861 leftover-map singular values on the grouping comparison leftover-map
diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md
index 26b00286f..6811f24df 100644
--- a/docs/storybook-inventory.md
+++ b/docs/storybook-inventory.md
@@ -6,7 +6,7 @@ operator-facing control you can click before changing product CSS.
| Story | Operator next action | Token / module |
|---|---|---|
| `Reports/LeftoverMapPlot` | Read the leftover-map graphic display of persisted `ξ` (posts) and `ζ` (criteria), match axis ticks to those coordinates, pair-segment `d` to leftover-map distance, pair-segment `R̂` to leftover-map reconstruction, pair-segment `R̂²/R²` to leftover-map explained leftover share, pair-segment `U²/R²` to leftover-map unexplained leftover share, pair-segment `2R̂U/R²` to leftover-map cross share, pair-segment `U` to leftover-map unexplained leftover, pair-segment `R` to leftover residual, pair-segment `Y` to leftover observed, pair-segment `E` to leftover expected, and pair-segment leftover-map rank, then click a post marker to open that post. Leftover-map axes name persisted leftover-map axis share when finite. The plot names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. The comparison variant names leftover-map complete-case coverage under `Leftover map comparison graphic coverage` and leftover-map item complete-case coverage under `Leftover map comparison graphic item coverage` and leftover-map incomplete post coverage under `Leftover map comparison graphic incomplete posts` and leftover-map incomplete item coverage under `Leftover map comparison graphic incomplete items`. `ClosestAndFarthest`, `RankZeroOrigin`, `MissingCoordinates`, `MissingAxisShare`, `Comparison`, `ComparisonMissingCoverage`, `ComparisonMissingItemCoverage`, `ComparisonMissingIncompletePosts`, and `ComparisonMissingIncompleteItems` cover two-pair maps, rank-0 origin with 0% share, a `0` tick, `d 0.00`, `R̂ 0.00`, `R̂²/R² 0.00`, `U²/R² 0.00`, `2R̂U/R² 0.00`, `U 0.00`, `R 0.00`, `Y 0.00`, `E 0.00`, and `rank 0`, omitted plots, missing share that keeps existing leftover-map axis text, grouping comparison leftover-map graphic coverage that matches leftover map used N of M scored posts, omitted comparison graphic coverage when leftoverMapCoverageCounts fail-closes, grouping comparison leftover-map graphic item coverage that matches leftover map used N of M scored criteria, omitted comparison graphic item coverage when leftoverMapItemCoverageCounts fail-closes, grouping comparison leftover-map graphic incomplete posts that matches leftover map dropped N incomplete posts, omitted comparison graphic incomplete posts when leftoverMapIncompletePostCount fail-closes, grouping comparison leftover-map graphic incomplete items that matches leftover map dropped N incomplete criteria, and omitted comparison graphic incomplete items when leftoverMapIncompleteItemCount fail-closes. The plot does not invent a leftover score. | `LeftoverMapPlot`, `leftoverMapPlotLayout`, `leftoverMapPlotAxisShare`, `--color-primary`, `--color-palette-blue-mid` |
-| `Reports/LeftoverPairList` | Read closest/farthest leftover pairs with named `R`, `Y`/`E`, rank, `U`, `s`, `e`, `x`, `R̂`, `ξ`/`ζ`, and `d`, then open that post. The leftover-map graphic display sits above the pair buttons when coordinates are finite, leftover-map axes name persisted leftover-map axis share, leftover-map axis ticks name persisted coordinates, pair segments name persisted leftover-map distance, pair segments name persisted leftover-map reconstruction, pair segments name persisted leftover-map explained leftover share, pair segments name persisted leftover-map unexplained leftover share, pair segments name persisted leftover-map cross share, pair segments name persisted leftover-map unexplained leftover, pair segments name persisted leftover residual, pair segments name persisted leftover observed, pair segments name persisted leftover expected, and pair segments name persisted leftover-map rank. The pair-list note names leftover-map complete-case coverage only when leftoverMapCoverageCounts returns usable complete-case integers, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. The grouping comparison strip names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage with distinct comparison labels when leftoverMapCoverageCounts / leftoverMapItemCoverageCounts / leftoverMapIncompletePostCount / leftoverMapIncompleteItemCount return usable integers. | `LeftoverPairList`, `LeftoverMapPlot`, `ticket-list`, `post-badge` |
+| `Reports/LeftoverPairList` | Read closest/farthest leftover pairs with named `R`, `Y`/`E`, rank, `U`, `s`, `e`, `x`, `R̂`, `ξ`/`ζ`, and `d`, then open that post. The leftover-map graphic display sits above the pair buttons when coordinates are finite, leftover-map axes name persisted leftover-map axis share, leftover-map axis ticks name persisted coordinates, pair segments name persisted leftover-map distance, pair segments name persisted leftover-map reconstruction, pair segments name persisted leftover-map explained leftover share, pair segments name persisted leftover-map unexplained leftover share, pair segments name persisted leftover-map cross share, pair segments name persisted leftover-map unexplained leftover, pair segments name persisted leftover residual, pair segments name persisted leftover observed, pair segments name persisted leftover expected, and pair segments name persisted leftover-map rank. The pair-list note names leftover-map complete-case coverage only when leftoverMapCoverageCounts returns usable complete-case integers, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. The grouping comparison strip names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, leftover-map incomplete item coverage, and leftover-axis report badges with persisted leftover-map singular values under `leftover map comparison leftover axis {axis} σ {value}` with distinct comparison labels when leftoverMapCoverageCounts / leftoverMapItemCoverageCounts / leftoverMapIncompletePostCount / leftoverMapIncompleteItemCount / leftoverMapCompareAxisBadge return usable values. | `LeftoverPairList`, `LeftoverMapPlot`, `ticket-list`, `post-badge` |
| `Workspace/OperationsDashboard` | Compare Event and post counts, inspect external-information coverage, then open the cited source behind a claim, handover, or repeat-issue fact. `EvidenceReady`, `NarrowViewport`, `AnalysisPendingAndMissingEvidence`, `AnalysisFailed`, and `LoadError` cover populated, mobile, unavailable-evidence, analysis-pending, retryable failure, and transport-error states. | `--color-dashboard-*`, `OperationsDashboard` |
| `Post/SimilarVocPanel` | Compare ontology/semantic similar VOC and prior action evidence, then open the source; unavailable states show no fabricated TEPP theta or weight. | `SimilarVocPanel.css`, `SimilarVocPanel` |
| `Post/Recorded perspectives` | Read the imported primary and every evidence-connected additional Voice with its recorded truth state instead of flattening them into one compound category. `CombinedEvidence`, `RejectedEvidence`, and `NarrowViewport` cover desktop, rejected-evidence, and narrow layouts. | `VoicePerspectiveList`, `ticket-list`, `post-meta` |
diff --git a/frontend/package.json b/frontend/package.json
index 304a03d4d..9b99eca3c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "2.79.0",
+ "version": "2.80.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 38e065235..085626a09 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -4179,6 +4179,11 @@ describe("App, authenticated", () => {
expect(screen.getAllByText(/CAT: sales-lead I=0\.70/).length).toBeGreaterThan(0);
expect(screen.getAllByText(/leftover axis 1 σ 1\.84 82%/).length).toBeGreaterThan(0);
expect(screen.getAllByText(/leftover axis 2 σ 0\.86 18%/).length).toBeGreaterThan(0);
+ expect(screen.getByText("leftover map comparison leftover axis 1 σ 1.84 82%")).toBeInTheDocument();
+ expect(screen.getByText("leftover map comparison leftover axis 2 σ 0.86 18%")).toBeInTheDocument();
+ expect(screen.getByLabelText("Leftover map comparison leftover axis")).toHaveTextContent(
+ "Open a leftover pair to read the post–criterion cell",
+ );
expect(screen.getByLabelText("Leftover-map axis share")).toHaveTextContent(
"Open a leftover pair to read the post–criterion cell",
);
@@ -4336,12 +4341,27 @@ describe("App, authenticated", () => {
expect(
within(screen.getByLabelText("Grouping comparison")).queryByText("leftover-map axis 2 (18%)"),
).not.toBeInTheDocument();
+ expect(
+ within(screen.getByLabelText("Grouping comparison")).getByText(
+ "leftover map comparison leftover axis 1 σ 1.84 82%",
+ ),
+ ).toBeInTheDocument();
+ expect(
+ within(screen.getByLabelText("Grouping comparison")).getByText(
+ "leftover map comparison leftover axis 2 σ 0.86 18%",
+ ),
+ ).toBeInTheDocument();
expect(
within(screen.getByLabelText("Grouping comparison")).queryByText("leftover axis 1 σ 1.84 82%"),
).not.toBeInTheDocument();
expect(
within(screen.getByLabelText("Grouping comparison")).queryByText("leftover axis 2 σ 0.86 18%"),
).not.toBeInTheDocument();
+ expect(
+ within(screen.getByLabelText("Grouping comparison")).getByLabelText(
+ "Leftover map comparison leftover axis",
+ ),
+ ).toHaveTextContent("Open a leftover pair to read the post–criterion cell");
expect(
within(screen.getByLabelText("Grouping comparison")).getByRole("button", {
name: "Open leftover-map post Public post at ξ (+0.50, +0.10)",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index ecd927785..8d36a1b5f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -143,6 +143,13 @@ import {
LEFTOVER_MAP_AXIS_BADGE_SHARE,
LEFTOVER_MAP_AXIS_BADGE_SINGULAR,
} from "./leftoverMapAxisBadge";
+import {
+ leftoverMapCompareAxisBadge,
+ leftoverSingularForAxis,
+ LEFTOVER_MAP_COMPARE_AXIS_CAPTION,
+ LEFTOVER_MAP_COMPARE_AXIS_LABEL,
+} from "./leftoverMapPlotAxisSingular";
+import { leftoverShareForAxis } from "./leftoverMapPlotAxisShare";
import {
formatLeftoverMapReconstruction,
LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL,
@@ -4101,6 +4108,33 @@ function ReportsPanel({
{tf(LEFTOVER_MAP_PLOT_INCOMPLETE_ITEM, comparisonIncompleteItemCount)}
) : null}
+ {row.leftover_map_axes?.map((axis) => {
+ const badge = leftoverMapCompareAxisBadge(
+ axis.axis_index,
+ leftoverSingularForAxis([axis], axis.axis_index),
+ leftoverShareForAxis([axis], axis.axis_index),
+ );
+ if (badge === null) {
+ return null;
+ }
+ return (
+
+ {tf(badge.key, badge.values)}
+
+ );
+ })}
+ {row.leftover_map_axes?.some(
+ (axis) =>
+ leftoverMapCompareAxisBadge(
+ axis.axis_index,
+ leftoverSingularForAxis([axis], axis.axis_index),
+ leftoverShareForAxis([axis], axis.axis_index),
+ ) !== null,
+ ) ? (
+
+ {t(LEFTOVER_MAP_COMPARE_AXIS_CAPTION)}
+
+ ) : null}
{row.leftover_pairs && row.leftover_pairs.length > 0 && (
<>
diff --git a/frontend/src/components/LeftoverMapPlot.test.tsx b/frontend/src/components/LeftoverMapPlot.test.tsx
index 40162a36b..ed9b89639 100644
--- a/frontend/src/components/LeftoverMapPlot.test.tsx
+++ b/frontend/src/components/LeftoverMapPlot.test.tsx
@@ -895,6 +895,7 @@ describe("LeftoverMapPlot", () => {
expect(screen.queryByText("leftover map comparison axis 1 (82%)")).not.toBeInTheDocument();
expect(screen.queryByText("leftover-map axis 1 σ 1.84 (82%)")).not.toBeInTheDocument();
expect(screen.queryByText("leftover axis 1 σ 1.84 82%")).not.toBeInTheDocument();
+ expect(screen.queryByText("leftover map comparison leftover axis 1 σ 1.84 82%")).not.toBeInTheDocument();
expect(screen.queryByText("leftover-map axis 1 (82%)")).not.toBeInTheDocument();
});
@@ -903,7 +904,7 @@ describe("LeftoverMapPlot", () => {
{
"leftover map comparison graphic leftover-map axis {axis} tick {value}",
"leftover map comparison graphic leftover-map axis {axis} σ {value}",
"leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)",
+ "leftover map comparison leftover axis {axis} σ {value}",
+ "leftover map comparison leftover axis {axis} σ {value} {share}%",
+ "leftover map comparison leftover axis {axis} {share}%",
+ "Leftover map comparison leftover axis",
+ "Leftover map comparison leftover-axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.",
"Leftover map comparison coordinates",
"Leftover map comparison graphic",
"Leftover map comparison",
@@ -740,6 +745,43 @@ describe("i18n", () => {
).toBe(expected);
});
+ it.each([
+ ["ko", "잔여 지도 비교 잔차 축 1 σ 1.84"],
+ ["zh", "残差地图比较残差轴 1 σ 1.84"],
+ ["ja", "残差マップの比較残差軸 1 σ 1.84"],
+ ["vi", "trục phần dư so sánh bản đồ phần dư 1 σ 1.84"],
+ ] as const)(
+ "formats leftover map comparison leftover-axis singular values in %s",
+ (locale, expected) => {
+ setLocale(locale);
+ expect(
+ tf("leftover map comparison leftover axis {axis} σ {value}", {
+ axis: 1,
+ value: "1.84",
+ }),
+ ).toBe(expected);
+ },
+ );
+
+ it.each([
+ ["ko", "잔여 지도 비교 잔차 축 1 σ 1.84 82%"],
+ ["zh", "残差地图比较残差轴 1 σ 1.84 82%"],
+ ["ja", "残差マップの比較残差軸 1 σ 1.84 82%"],
+ ["vi", "trục phần dư so sánh bản đồ phần dư 1 σ 1.84 82%"],
+ ] as const)(
+ "formats leftover map comparison leftover-axis singular share in %s",
+ (locale, expected) => {
+ setLocale(locale);
+ expect(
+ tf("leftover map comparison leftover axis {axis} σ {value} {share}%", {
+ axis: 1,
+ value: "1.84",
+ share: "82",
+ }),
+ ).toBe(expected);
+ },
+ );
+
it.each([
["ko", "잔여 지도 비교 설명 잔여 점유율"],
["zh", "残差地图比较已解释残差份额"],
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index de3e12910..52cf6a35e 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -300,6 +300,13 @@ const TRANSLATIONS: Partial>> = {
"leftover map comparison graphic leftover-map axis {axis} σ {value}": "잔여 지도 비교 그림 축 {axis} σ {value}",
"leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)":
"잔여 지도 비교 그림 축 {axis} σ {value} ({share}%)",
+ "leftover map comparison leftover axis {axis} σ {value}": "잔여 지도 비교 잔차 축 {axis} σ {value}",
+ "leftover map comparison leftover axis {axis} σ {value} {share}%":
+ "잔여 지도 비교 잔차 축 {axis} σ {value} {share}%",
+ "leftover map comparison leftover axis {axis} {share}%": "잔여 지도 비교 잔차 축 {axis} {share}%",
+ "Leftover map comparison leftover axis": "잔여 지도 비교 잔차 축",
+ "Leftover map comparison leftover-axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.":
+ "잔여 지도 비교 잔차 축 비율은 잔차 SVD 축 1과 2의 Gabriel 관성입니다. 글–기준 셀을 읽으려면 잔차 쌍을 여세요. 이 비율은 잔차 점수를 만들어내지 않습니다.",
"Leftover map comparison coordinates": "잔여 지도 비교 좌표",
"Leftover map comparison graphic": "잔여 지도 비교 그림",
"Leftover map comparison": "잔여 지도 비교",
@@ -947,6 +954,13 @@ const TRANSLATIONS: Partial>> = {
"leftover map comparison graphic leftover-map axis {axis} σ {value}": "残差地图比较图形轴 {axis} σ {value}",
"leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)":
"残差地图比较图形轴 {axis} σ {value} ({share}%)",
+ "leftover map comparison leftover axis {axis} σ {value}": "残差地图比较残差轴 {axis} σ {value}",
+ "leftover map comparison leftover axis {axis} σ {value} {share}%":
+ "残差地图比较残差轴 {axis} σ {value} {share}%",
+ "leftover map comparison leftover axis {axis} {share}%": "残差地图比较残差轴 {axis} {share}%",
+ "Leftover map comparison leftover axis": "残差地图比较残差轴",
+ "Leftover map comparison leftover-axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.":
+ "残差地图比较残差轴份额是残差 SVD 轴 1 和 2 的 Gabriel 惯性。打开残差对以阅读帖文–准则单元格。这些份额不编造残差分数。",
"Leftover map comparison coordinates": "残差地图比较坐标",
"Leftover map comparison graphic": "残差地图比较图形",
"Leftover map comparison": "残差地图比较",
@@ -1615,6 +1629,13 @@ const TRANSLATIONS: Partial>> = {
"leftover map comparison graphic leftover-map axis {axis} σ {value}": "残差マップの比較図軸 {axis} σ {value}",
"leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)":
"残差マップの比較図軸 {axis} σ {value} ({share}%)",
+ "leftover map comparison leftover axis {axis} σ {value}": "残差マップの比較残差軸 {axis} σ {value}",
+ "leftover map comparison leftover axis {axis} σ {value} {share}%":
+ "残差マップの比較残差軸 {axis} σ {value} {share}%",
+ "leftover map comparison leftover axis {axis} {share}%": "残差マップの比較残差軸 {axis} {share}%",
+ "Leftover map comparison leftover axis": "残差マップの比較残差軸",
+ "Leftover map comparison leftover-axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.":
+ "残差マップの比較残差軸の割合は残差 SVD 軸 1 と 2 の Gabriel 慣性です。投稿–基準セルを読むには残差ペアを開いてください。この割合は残差スコアを作りません。",
"Leftover map comparison coordinates": "残差マップの比較座標",
"Leftover map comparison graphic": "残差マップの比較図",
"Leftover map comparison": "残差マップの比較",
@@ -2263,6 +2284,15 @@ const TRANSLATIONS: Partial>> = {
"trục đồ họa so sánh bản đồ phần dư {axis} σ {value}",
"leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)":
"trục đồ họa so sánh bản đồ phần dư {axis} σ {value} ({share}%)",
+ "leftover map comparison leftover axis {axis} σ {value}":
+ "trục phần dư so sánh bản đồ phần dư {axis} σ {value}",
+ "leftover map comparison leftover axis {axis} σ {value} {share}%":
+ "trục phần dư so sánh bản đồ phần dư {axis} σ {value} {share}%",
+ "leftover map comparison leftover axis {axis} {share}%":
+ "trục phần dư so sánh bản đồ phần dư {axis} {share}%",
+ "Leftover map comparison leftover axis": "Trục phần dư so sánh bản đồ phần dư",
+ "Leftover map comparison leftover-axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.":
+ "Tỷ phần trục phần dư so sánh bản đồ phần dư là quán tính Gabriel của trục SVD phần dư 1 và 2. Mở cặp phần dư để đọc ô bài–tiêu chí. Các tỷ phần này không tạo ra điểm phần dư.",
"Leftover map comparison coordinates": "Tọa độ so sánh bản đồ phần dư",
"Leftover map comparison graphic": "Đồ họa so sánh bản đồ phần dư",
"Leftover map comparison": "So sánh bản đồ phần dư",
diff --git a/frontend/src/leftoverMapAxisBadge.test.ts b/frontend/src/leftoverMapAxisBadge.test.ts
index 84ec57a39..7261489cb 100644
--- a/frontend/src/leftoverMapAxisBadge.test.ts
+++ b/frontend/src/leftoverMapAxisBadge.test.ts
@@ -6,6 +6,9 @@ import {
LEFTOVER_MAP_AXIS_BADGE_SINGULAR,
} from "./leftoverMapAxisBadge";
import {
+ LEFTOVER_MAP_COMPARE_AXIS_SHARE,
+ LEFTOVER_MAP_COMPARE_AXIS_SINGULAR,
+ LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE,
LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR,
LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE,
LEFTOVER_MAP_PLOT_AXIS_SINGULAR,
@@ -51,7 +54,7 @@ describe("leftoverMapAxisBadgeSingular", () => {
expect(leftoverMapAxisBadgeSingular({ axis_index: 1 })).toBeNull();
});
- it("stays distinct from leftover-map graphic and comparison graphic leftover-map axis σ copy", () => {
+ it("stays distinct from leftover-map graphic and comparison leftover-axis σ copy", () => {
expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).toBe("leftover axis {axis} σ {value} {share}%");
expect(LEFTOVER_MAP_AXIS_BADGE_SHARE).toBe("leftover axis {axis} {share}%");
expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe(LEFTOVER_MAP_PLOT_AXIS_SINGULAR);
@@ -60,6 +63,9 @@ describe("leftoverMapAxisBadgeSingular", () => {
expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe(
LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE,
);
+ expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR);
+ expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE);
+ expect(LEFTOVER_MAP_AXIS_BADGE_SHARE).not.toBe(LEFTOVER_MAP_COMPARE_AXIS_SHARE);
expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe("leftover axis {axis} σ {value}");
});
});
diff --git a/frontend/src/leftoverMapAxisBadge.ts b/frontend/src/leftoverMapAxisBadge.ts
index 7d2b377df..71a7d9dc4 100644
--- a/frontend/src/leftoverMapAxisBadge.ts
+++ b/frontend/src/leftoverMapAxisBadge.ts
@@ -1,4 +1,7 @@
-/** Caption leftover-axis report badges with persisted Gabriel singular values. */
+/** Caption leftover-axis report badges with persisted Gabriel singular values.
+ * ADR 0323 captions leftover-axis report badges on the grouping comparison strip
+ * with a distinct leftover map comparison leftover axis name, not this helper.
+ */
import type { LeftoverMapAxis } from "./api";
import {
diff --git a/frontend/src/leftoverMapPlotAxisSingular.test.ts b/frontend/src/leftoverMapPlotAxisSingular.test.ts
index ce08b36a0..bd36ce16a 100644
--- a/frontend/src/leftoverMapPlotAxisSingular.test.ts
+++ b/frontend/src/leftoverMapPlotAxisSingular.test.ts
@@ -1,7 +1,13 @@
import { describe, expect, it } from "vitest";
import {
formatLeftoverMapPlotAxisSingular,
+ leftoverMapCompareAxisBadge,
leftoverSingularForAxis,
+ LEFTOVER_MAP_COMPARE_AXIS_CAPTION,
+ LEFTOVER_MAP_COMPARE_AXIS_LABEL,
+ LEFTOVER_MAP_COMPARE_AXIS_SHARE,
+ LEFTOVER_MAP_COMPARE_AXIS_SINGULAR,
+ LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE,
LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR,
LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE,
LEFTOVER_MAP_PLOT_AXIS_SINGULAR,
@@ -9,6 +15,7 @@ import {
} from "./leftoverMapPlotAxisSingular";
import { LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE } from "./leftoverMapPlotAxisShare";
import { LEFTOVER_MAP_COMPARE_PLOT_TICK } from "./leftoverMapPlotLayout";
+import { LEFTOVER_MAP_AXIS_BADGE_SINGULAR } from "./leftoverMapAxisBadge";
describe("leftoverSingularForAxis", () => {
const axes = [
@@ -92,5 +99,72 @@ describe("leftover map comparison graphic leftover-map axis singular labels", ()
expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE).not.toBe(
"leftover axis {axis} σ {value} {share}%",
);
+ expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR);
+ });
+});
+
+describe("leftover map comparison leftover-axis report badges", () => {
+ it("names persisted leftover-map singular values on leftover-axis report badges", () => {
+ expect(leftoverMapCompareAxisBadge(1, 1.84, 0.82)).toEqual({
+ key: LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE,
+ values: { axis: 1, value: "1.84", share: "82" },
+ });
+ expect(leftoverMapCompareAxisBadge(2, 0.86, 0.18)).toEqual({
+ key: LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE,
+ values: { axis: 2, value: "0.86", share: "18" },
+ });
+ });
+
+ it("names rank-0 zero leftover-map singular values on leftover-axis report badges", () => {
+ expect(leftoverMapCompareAxisBadge(1, 0, 0)).toEqual({
+ key: LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE,
+ values: { axis: 1, value: "0.00", share: "0" },
+ });
+ });
+
+ it("omits leftover-map singular values independently of leftover-map axis share", () => {
+ expect(leftoverMapCompareAxisBadge(1, Number.NaN, 0.82)).toEqual({
+ key: LEFTOVER_MAP_COMPARE_AXIS_SHARE,
+ values: { axis: 1, share: "82" },
+ });
+ expect(leftoverMapCompareAxisBadge(2, -0.01, 0.18)).toEqual({
+ key: LEFTOVER_MAP_COMPARE_AXIS_SHARE,
+ values: { axis: 2, share: "18" },
+ });
+ expect(leftoverMapCompareAxisBadge(1, 1.84, Number.NaN)).toEqual({
+ key: LEFTOVER_MAP_COMPARE_AXIS_SINGULAR,
+ values: { axis: 1, value: "1.84" },
+ });
+ expect(leftoverMapCompareAxisBadge(1, null, null)).toBeNull();
+ });
+
+ it("does not invent leftover-map singular values from leftover-map axis share", () => {
+ expect(leftoverMapCompareAxisBadge(1, undefined, 0.82)?.key).toBe(
+ LEFTOVER_MAP_COMPARE_AXIS_SHARE,
+ );
+ expect(leftoverMapCompareAxisBadge(1, undefined, 0.82)?.values.value).toBeUndefined();
+ });
+
+ it("stays distinct from leftover-axis, leftover-map graphic, and comparison graphic copy", () => {
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR).toBe(
+ "leftover map comparison leftover axis {axis} σ {value}",
+ );
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE).toBe(
+ "leftover map comparison leftover axis {axis} σ {value} {share}%",
+ );
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SHARE).toBe(
+ "leftover map comparison leftover axis {axis} {share}%",
+ );
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR);
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE).not.toBe(
+ LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE,
+ );
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR).not.toBe(LEFTOVER_MAP_PLOT_AXIS_SINGULAR);
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR).not.toBe("leftover axis {axis} σ {value}");
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE).not.toBe(LEFTOVER_MAP_AXIS_BADGE_SINGULAR);
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SHARE).not.toBe("leftover axis {axis} {share}%");
+ expect(LEFTOVER_MAP_COMPARE_AXIS_SHARE).not.toBe(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE);
+ expect(LEFTOVER_MAP_COMPARE_AXIS_LABEL).not.toBe("Leftover-map axis share");
+ expect(LEFTOVER_MAP_COMPARE_AXIS_CAPTION).toContain("Open a leftover pair");
});
});
diff --git a/frontend/src/leftoverMapPlotAxisSingular.ts b/frontend/src/leftoverMapPlotAxisSingular.ts
index a621fe351..70130898e 100644
--- a/frontend/src/leftoverMapPlotAxisSingular.ts
+++ b/frontend/src/leftoverMapPlotAxisSingular.ts
@@ -1,6 +1,7 @@
/** Caption leftover-map plot axes with persisted Gabriel singular values. */
import type { LeftoverMapAxis } from "./api";
+import { formatLeftoverMapPlotAxisShare } from "./leftoverMapPlotAxisShare";
export const LEFTOVER_MAP_PLOT_AXIS_SINGULAR = "leftover-map axis {axis} σ {value}";
@@ -13,11 +14,30 @@ export const LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR =
export const LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE =
"leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)";
+export const LEFTOVER_MAP_COMPARE_AXIS_SINGULAR =
+ "leftover map comparison leftover axis {axis} σ {value}";
+
+export const LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE =
+ "leftover map comparison leftover axis {axis} σ {value} {share}%";
+
+export const LEFTOVER_MAP_COMPARE_AXIS_SHARE =
+ "leftover map comparison leftover axis {axis} {share}%";
+
+export const LEFTOVER_MAP_COMPARE_AXIS_LABEL = "Leftover map comparison leftover axis";
+
+export const LEFTOVER_MAP_COMPARE_AXIS_CAPTION =
+ "Leftover map comparison leftover-axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.";
+
export type LeftoverMapPlotAxisSingular = {
axis_index: LeftoverMapAxis["axis_index"];
leftover_singular_value?: LeftoverMapAxis["leftover_singular_value"] | null;
};
+export type LeftoverMapCompareAxisBadge = {
+ key: string;
+ values: { axis: number; value?: string; share?: string };
+};
+
export function leftoverSingularForAxis(
axes: ReadonlyArray | null | undefined,
axisIndex: number,
@@ -42,3 +62,25 @@ export function formatLeftoverMapPlotAxisSingular(
}
return leftoverSingular.toFixed(2);
}
+
+export function leftoverMapCompareAxisBadge(
+ axisIndex: number,
+ leftoverSingular: number | null | undefined,
+ leftoverShare: number | null | undefined,
+): LeftoverMapCompareAxisBadge | null {
+ const singular = formatLeftoverMapPlotAxisSingular(leftoverSingular);
+ const percent = formatLeftoverMapPlotAxisShare(leftoverShare);
+ if (singular === null && percent === null) {
+ return null;
+ }
+ if (singular === null && percent !== null) {
+ return { key: LEFTOVER_MAP_COMPARE_AXIS_SHARE, values: { axis: axisIndex, share: percent } };
+ }
+ if (singular !== null && percent === null) {
+ return { key: LEFTOVER_MAP_COMPARE_AXIS_SINGULAR, values: { axis: axisIndex, value: singular } };
+ }
+ return {
+ key: LEFTOVER_MAP_COMPARE_AXIS_SINGULAR_SHARE,
+ values: { axis: axisIndex, value: singular as string, share: percent as string },
+ };
+}
diff --git a/frontend/src/leftoverMapPlotLayout.test.ts b/frontend/src/leftoverMapPlotLayout.test.ts
index 6787cd828..88d118208 100644
--- a/frontend/src/leftoverMapPlotLayout.test.ts
+++ b/frontend/src/leftoverMapPlotLayout.test.ts
@@ -1369,6 +1369,9 @@ describe("leftover map comparison graphic labels", () => {
expect(LEFTOVER_MAP_COMPARE_PLOT_TICK).not.toBe(
"leftover map comparison graphic leftover-map axis {axis} σ {value}",
);
+ expect(LEFTOVER_MAP_COMPARE_PLOT_TICK).not.toBe(
+ "leftover map comparison leftover axis {axis} σ {value}",
+ );
expect(LEFTOVER_MAP_COMPARE_PLOT_TICK).not.toBe(
LEFTOVER_MAP_COMPARE_PLOT_SEGMENT_DISTANCE,
);
diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts
index a6d4787f8..217a56bae 100644
--- a/frontend/src/leftoverMapPlotLayout.ts
+++ b/frontend/src/leftoverMapPlotLayout.ts
@@ -83,6 +83,8 @@
* already-named leftover-map axes.
* ADR 0322 captions leftover-axis report badges with persisted leftover-map
* singular values, not this graphic.
+ * ADR 0323 captions leftover-axis report badges on the grouping comparison strip
+ * with persisted leftover-map singular values, not this graphic.
*/
import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates";
diff --git a/pyproject.toml b/pyproject.toml
index da173ad89..95202a36c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "2.79.0"
+version = "2.80.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" }