diff --git a/AGENTS.md b/AGENTS.md
index 266618122..7ae4a5426 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) 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) 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
@@ -309,8 +309,38 @@ coordinates. ADR 0273 captions leftover-map pair segments with persisted
leftover-map explained leftover share `e` so the pair-row `R̂²/R²`
badge matches the graphic; a missing or non-finite `e` omits that
explained leftover share caption and does not invent `e` from `R̂` and
-`R` or from plotted coordinates. When `R`, `R̂`, `U`, `x`,
-`s`, and `e` are finite, `e + s + x = 1`. When coordinates,
+`R` or from plotted coordinates. ADR 0274 captions leftover-map pair
+segments with persisted leftover-map unexplained leftover share `s` so
+the pair-row `U²/R²` badge matches the graphic; a missing or non-finite
+`s` omits that unexplained leftover share caption and does not invent
+`s` from `U` and `R` or from plotted coordinates. ADR 0275 captions leftover-map pair
+segments with persisted leftover-map cross share `x` so
+the pair-row `2R̂U/R²` badge matches the graphic; a missing or non-finite
+`x` omits that leftover-map cross share caption and does not invent
+`x` from `R̂`, `U`, and `R` or from plotted coordinates. ADR 0276 captions leftover-map pair
+segments with persisted leftover-map unexplained leftover `U` so
+the pair-row `U` badge matches the graphic; a missing or non-finite
+`U` omits that unexplained leftover caption and does not invent
+`U` from `R` and `R̂` or from plotted coordinates. ADR 0277 captions leftover-map pair
+segments with persisted leftover residual `R` so the pair-row residual
+badge matches the graphic; a missing or non-finite `R` omits that leftover
+residual caption and does not invent `R` from `Y` and `E`, from `U` and
+`R̂`, or from plotted coordinates. ADR 0278 captions leftover-map pair
+segments with persisted leftover observed `Y` so the pair-row `Y` badge
+matches the graphic; a missing or non-finite `Y` omits that leftover
+observed caption and does not invent `Y` from `R` and `E` or from plotted
+coordinates. ADR 0279 captions leftover-map pair
+segments with persisted leftover expected `E` so the pair-row `E` badge
+matches the graphic; a missing or non-finite `E` omits that leftover
+expected caption and does not invent `E` from `Y` and `R` or from plotted
+coordinates. ADR 0280 captions leftover-map pair
+segments with persisted leftover-map rank so the pair-row `rank` badge
+matches the graphic; a missing, negative, or non-integer rank omits that
+leftover-map rank caption and does not invent rank from plotted
+coordinates, leftover-map distance, or the count of unused axes. 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
+finite, `U + R̂ = R`. When coordinates,
reconstruction, and distance are finite, `R̂ = ξ · ζ` and
`d = ‖ξ − ζ‖`. The pairs sit above the member
list so a click opens that post with the leftover criterion current
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index a071f7a8b..4827ec7df 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -637,7 +637,7 @@ information at the group's mean θ (Lord, 1980 max-info CAT). Rankings
persist to `report_item_information`. After those IRT main effects,
residual SVD leftover pairs on two Gabriel axes (Jeon et al., 2021;
ADR 0017 / 0048 / 0049 / 0119 / 0148 / 0158 / 0162 / 0163 / 0164 / 0168 /
-0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273) persist to `report_leftover_pair` with signed residual `R`,
+0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280) persist to `report_leftover_pair` with signed residual `R`,
observed `Y`, expected `E[Y|θ, item]`, full leftover-map rank, unexplained
leftover, ADR 0201 reconstruction evidence, ADR 0185 cross-share evidence,
ADR 0233 unexplained leftover share `s`, ADR 0266 explained leftover
@@ -650,7 +650,15 @@ badge matches the plot. ADR 0271 captions leftover-map pair segments
with persisted leftover-map distance `d`. ADR 0272 captions leftover-map
pair segments with persisted leftover-map reconstruction `R̂`. ADR 0273
captions leftover-map pair segments with persisted leftover-map
-explained leftover share `e`. Click a post marker or a pair button
+explained leftover share `e`. ADR 0274 captions leftover-map pair
+segments with persisted leftover-map unexplained leftover share `s`.
+ADR 0275 captions leftover-map pair segments with persisted leftover-map
+cross share `x`. ADR 0276 captions leftover-map pair segments with persisted leftover-map
+unexplained leftover `U`. ADR 0277 captions leftover-map pair segments with persisted leftover
+residual `R`. ADR 0278 captions leftover-map pair segments with persisted leftover
+observed `Y`. ADR 0279 captions leftover-map pair segments with persisted leftover
+expected `E`. ADR 0280 captions leftover-map pair segments with persisted leftover-map
+rank. 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
(Gabriel inertia of residual SVD axes 1 and 2; ADR 0148) persists to
diff --git a/CHANGELOG.d/2.31.0-leftover-map-segment-unexplained-share.md b/CHANGELOG.d/2.31.0-leftover-map-segment-unexplained-share.md
new file mode 100644
index 000000000..9560197a4
--- /dev/null
+++ b/CHANGELOG.d/2.31.0-leftover-map-segment-unexplained-share.md
@@ -0,0 +1,15 @@
+## 2.31.0 — Leftover-map unexplained leftover share on graphic-display pair segments
+
+- Caption leftover-map graphic-display pair segments with persisted
+ leftover-map unexplained leftover share `s = U² / R²` (ADR 0274). 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, and pair segments that match `U²/R²` on the
+ pair row; click a post marker or a pair button opens that post. A
+ missing or non-finite `s` omits that unexplained leftover share
+ caption. A share greater than 1 is shown, never clamped. Do not invent
+ `s` from `U` and `R` or from plotted coordinates. Never invent a
+ leftover score. Never invent a theta. No new columns.
diff --git a/CHANGELOG.d/2.32.0-leftover-map-segment-cross-share.md b/CHANGELOG.d/2.32.0-leftover-map-segment-cross-share.md
new file mode 100644
index 000000000..6a661293a
--- /dev/null
+++ b/CHANGELOG.d/2.32.0-leftover-map-segment-cross-share.md
@@ -0,0 +1,16 @@
+## 2.32.0 — Leftover-map cross share on graphic-display pair segments
+
+- Caption leftover-map graphic-display pair segments with persisted
+ leftover-map cross share `x = 2 R̂ U / R²` (ADR 0275). 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, and pair segments that match `2R̂U/R²` on the pair row;
+ click a post marker or a pair button opens that post. A missing or
+ non-finite `x` omits that leftover-map cross share caption. A finite
+ negative share is shown; do not clamp to nonnegative. Do not invent
+ `x` from `R̂`, `U`, and `R` or from plotted coordinates. Never invent
+ a leftover score. Never invent a theta. No new columns.
diff --git a/CHANGELOG.d/2.33.0-leftover-map-segment-unexplained-leftover.md b/CHANGELOG.d/2.33.0-leftover-map-segment-unexplained-leftover.md
new file mode 100644
index 000000000..8d0bc4a91
--- /dev/null
+++ b/CHANGELOG.d/2.33.0-leftover-map-segment-unexplained-leftover.md
@@ -0,0 +1,17 @@
+## 2.33.0 — Leftover-map unexplained leftover on graphic-display pair segments
+
+- Caption leftover-map graphic-display pair segments with persisted
+ leftover-map unexplained leftover `U = R − R̂` (ADR 0276). 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, and
+ pair segments that match `U` on the pair row; click a post marker or
+ a pair button opens that post. A missing or non-finite `U` omits that
+ unexplained leftover caption. A finite negative leftover is shown;
+ do not clamp to nonnegative. Do not invent `U` from `R` and `R̂` or
+ from plotted coordinates. Never invent a leftover score. Never invent
+ a theta. No new columns.
diff --git a/CHANGELOG.d/2.34.0-leftover-map-segment-residual.md b/CHANGELOG.d/2.34.0-leftover-map-segment-residual.md
new file mode 100644
index 000000000..eae4ccf0c
--- /dev/null
+++ b/CHANGELOG.d/2.34.0-leftover-map-segment-residual.md
@@ -0,0 +1,18 @@
+## 2.34.0 — Leftover residual on graphic-display pair segments
+
+- Caption leftover-map graphic-display pair segments with persisted
+ leftover residual `R = Y − E[Y|θ, item]` (ADR 0277). 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, and pair segments that match
+ `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 residual caption.
+ A finite negative leftover is shown; do not clamp to nonnegative. Do
+ not invent `R` from `Y` and `E`, from `U` and `R̂`, or from plotted
+ coordinates. Never invent a leftover score. Never invent a theta. No
+ new columns.
diff --git a/CHANGELOG.d/2.35.0-leftover-map-segment-observed.md b/CHANGELOG.d/2.35.0-leftover-map-segment-observed.md
new file mode 100644
index 000000000..c65d06db4
--- /dev/null
+++ b/CHANGELOG.d/2.35.0-leftover-map-segment-observed.md
@@ -0,0 +1,18 @@
+## 2.35.0 — Leftover observed Y on graphic-display pair segments
+
+- Caption leftover-map graphic-display pair segments with persisted
+ leftover observed `Y` (ADR 0278). 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, and pair segments that match `Y` on the pair row; click a
+ post marker or a pair button opens that post. A missing or
+ non-finite `Y` omits that leftover observed caption. A finite
+ negative observed is shown; do not clamp to nonnegative. Do not
+ invent `Y` from `R` and `E` or from plotted coordinates. Never invent
+ a leftover score. Never invent a theta. No new columns.
diff --git a/CHANGELOG.d/2.36.0-leftover-map-segment-expected.md b/CHANGELOG.d/2.36.0-leftover-map-segment-expected.md
new file mode 100644
index 000000000..cf5231f02
--- /dev/null
+++ b/CHANGELOG.d/2.36.0-leftover-map-segment-expected.md
@@ -0,0 +1,19 @@
+## 2.36.0 — Leftover expected E on graphic-display pair segments
+
+- Caption leftover-map graphic-display pair segments with persisted
+ leftover expected `E[Y|θ, item]` (ADR 0279). 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, and pair
+ segments that match `E` on the pair row; click a post marker or a
+ pair button opens that post. A missing or non-finite `E` omits that
+ leftover expected caption. A finite negative expected is shown; do
+ not clamp to nonnegative. Do not invent `E` from `Y` and `R` or from
+ plotted coordinates. Never invent a leftover score. Never invent a
+ theta. No new columns.
diff --git a/CHANGELOG.d/2.37.0-leftover-map-segment-rank.md b/CHANGELOG.d/2.37.0-leftover-map-segment-rank.md
new file mode 100644
index 000000000..ca9d3351a
--- /dev/null
+++ b/CHANGELOG.d/2.37.0-leftover-map-segment-rank.md
@@ -0,0 +1,20 @@
+## 2.37.0 — Leftover-map rank on graphic-display pair segments
+
+- Caption leftover-map graphic-display pair segments with persisted
+ leftover-map rank (ADR 0280). 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, and pair segments that match leftover-map rank on the pair row;
+ click a post marker or a pair button opens that post. A missing,
+ negative, or non-integer rank omits that leftover-map rank caption.
+ Rank `0` is shown when that persisted rank is a non-negative integer;
+ do not hide a rank-0 collapse. Do not invent rank from plotted
+ coordinates, leftover-map distance, or the count of unused axes.
+ Never invent a leftover score. Never invent a theta. No new columns.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 271c89353..e35505006 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,125 @@ All notable changes to this project are documented here. Format follows
### Added
+- Period leftover pairs now caption leftover-map graphic-display pair
+ segments with persisted leftover-map rank (ADR 0280 / v2.37.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, and pair segments that match leftover-map
+ rank on the pair row; click a post marker or a pair button opens that
+ post. A missing, negative, or non-integer rank omits that leftover-map
+ rank caption. Rank `0` is shown when that persisted rank is a
+ non-negative integer; do not hide a rank-0 collapse. Do not invent rank
+ from plotted coordinates, leftover-map distance, or the count of unused
+ axes. Never invent a leftover score or a theta. No new columns.
+
+- Period leftover pairs now caption leftover-map graphic-display pair
+ segments with persisted leftover expected `E[Y|θ, item]` (ADR 0279 /
+ v2.36.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, and pair segments that match `E` on the pair row;
+ click a post marker or a pair button opens that post. A missing or
+ non-finite `E` omits that leftover expected caption. A finite
+ negative expected is shown; do not clamp to nonnegative. Do not
+ invent `E` from `Y` and `R` or from plotted coordinates. Never invent
+ a leftover score or a theta. No new columns.
+
+- Period leftover pairs now caption leftover-map graphic-display pair
+ segments with persisted leftover observed `Y` (ADR 0278 / v2.35.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, and pair segments that match
+ `Y` on the pair row; click a post marker or a pair button opens that
+ post. A missing or non-finite `Y` omits that leftover observed
+ caption. A finite negative observed is shown; do not clamp to
+ nonnegative. Do not invent `Y` from `R` and `E` or from plotted
+ coordinates. Never invent a leftover score or a theta. No new columns.
+
+- Period leftover pairs now caption leftover-map graphic-display pair
+ segments with persisted leftover residual `R = Y − E[Y|θ, item]`
+ (ADR 0277 / v2.34.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, and pair segments that match `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 residual caption. A finite negative leftover is
+ shown; do not clamp to nonnegative. Do not invent `R` from `Y` and
+ `E`, from `U` and `R̂`, or from plotted coordinates. Never invent a
+ leftover score or a theta. No new columns.
+
+- Period leftover pairs now caption leftover-map graphic-display pair
+ segments with persisted leftover-map unexplained leftover
+ `U = R − R̂` (ADR 0276 / v2.33.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, and pair
+ segments that match `U` on the pair row; click a post marker or a
+ pair button opens that post. A missing or non-finite `U` omits that
+ unexplained leftover caption. A finite negative leftover is shown;
+ do not clamp to nonnegative. Do not invent `U` from `R` and `R̂` or
+ from plotted coordinates. Never invent a leftover score or a theta.
+ No new columns.
+
+- Period leftover pairs now caption leftover-map graphic-display pair
+ segments with persisted leftover-map cross share
+ `x = 2 R̂ U / R²` (ADR 0275 / v2.32.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, and
+ pair segments that match `2R̂U/R²` on the pair row; click a post
+ marker or a pair button opens that post. A missing or non-finite `x`
+ omits that leftover-map cross share caption. A finite negative share
+ is shown; do not clamp to nonnegative. Do not invent `x` from `R̂`,
+ `U`, and `R` or from plotted coordinates. Never invent a leftover
+ score or a theta. No new columns.
+
+- Period leftover pairs now caption leftover-map graphic-display pair
+ segments with persisted leftover-map unexplained leftover share
+ `s = U² / R²` (ADR 0274 / v2.31.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, and pair segments that match `U²/R²` on the pair
+ row; click a post marker or a pair button opens that post. A
+ missing or non-finite `s` omits that unexplained leftover share
+ caption. A share greater than 1 is shown, never clamped. Do not
+ invent `s` from `U` and `R` or from plotted coordinates. Never
+ invent a leftover score or a theta. No new columns.
+
- Period leftover pairs now caption leftover-map graphic-display pair
segments with persisted leftover-map explained leftover share
`e = R̂² / R²` (ADR 0273 / v2.30.0). After `make seed`, closest and
diff --git a/CLAUDE.md b/CLAUDE.md
index c4dad11ee..75cc2391a 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), 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), 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/0048-persist-lsirm-leftover-pairs.md b/docs/adr/0048-persist-lsirm-leftover-pairs.md
index 3c0adc8a5..032fe8e0e 100644
--- a/docs/adr/0048-persist-lsirm-leftover-pairs.md
+++ b/docs/adr/0048-persist-lsirm-leftover-pairs.md
@@ -16,7 +16,14 @@
[ADR 0270](0270-leftover-map-coordinate-ticks.md) (leftover-map coordinate ticks);
[ADR 0271](0271-leftover-map-segment-distance.md) (leftover-map distance on pair segments);
[ADR 0272](0272-leftover-map-segment-reconstruction.md) (leftover-map reconstruction on pair segments);
-[ADR 0273](0273-leftover-map-segment-explained-share.md) (leftover-map explained leftover share on pair segments)
+[ADR 0273](0273-leftover-map-segment-explained-share.md) (leftover-map explained leftover share on pair segments);
+[ADR 0274](0274-leftover-map-segment-unexplained-share.md) (leftover-map unexplained leftover share on pair segments);
+[ADR 0275](0275-leftover-map-segment-cross-share.md) (leftover-map cross share on pair segments);
+[ADR 0276](0276-leftover-map-segment-unexplained-leftover.md) (leftover-map unexplained leftover on pair segments);
+[ADR 0277](0277-leftover-map-segment-residual.md) (leftover residual on pair segments);
+[ADR 0278](0278-leftover-map-segment-observed.md) (leftover observed Y on pair segments);
+[ADR 0279](0279-leftover-map-segment-expected.md) (leftover expected E on pair segments);
+[ADR 0280](0280-leftover-map-segment-rank.md) (leftover-map rank on pair segments)
## Context
@@ -79,7 +86,18 @@ pair segments with persisted leftover-map distance `d`; it adds no
columns. ADR 0272 captions leftover-map pair segments with persisted
leftover-map reconstruction `R̂`; it adds no columns. ADR 0273 captions
leftover-map pair segments with persisted leftover-map explained leftover
-share `e`; it adds no columns.
+share `e`; it adds no columns. ADR 0274 captions leftover-map pair
+segments with persisted leftover-map unexplained leftover share `s`; it
+adds no columns. ADR 0275 captions leftover-map pair segments with
+persisted leftover-map cross share `x`; it adds no columns. ADR 0276
+captions leftover-map pair segments with persisted leftover-map
+unexplained leftover `U`; it adds no columns. ADR 0277 captions leftover-map
+pair segments with persisted leftover residual `R`; it adds no columns.
+ADR 0278 captions leftover-map pair segments with persisted leftover
+observed `Y`; it adds no columns. ADR 0279 captions leftover-map pair
+segments with persisted leftover expected `E`; it adds no columns. ADR 0280
+captions leftover-map pair segments with persisted leftover-map rank; it
+adds no columns.
Cascade the rows with `report_period_score`. A leftover post must
also be a `report_member_score` row, and the leftover criterion
diff --git a/docs/adr/0049-leftover-pair-report-ui.md b/docs/adr/0049-leftover-pair-report-ui.md
index 39cca48bd..bc59a5026 100644
--- a/docs/adr/0049-leftover-pair-report-ui.md
+++ b/docs/adr/0049-leftover-pair-report-ui.md
@@ -17,7 +17,14 @@
[ADR 0270](0270-leftover-map-coordinate-ticks.md) (leftover-map coordinate ticks);
[ADR 0271](0271-leftover-map-segment-distance.md) (leftover-map distance on pair segments);
[ADR 0272](0272-leftover-map-segment-reconstruction.md) (leftover-map reconstruction on pair segments);
-[ADR 0273](0273-leftover-map-segment-explained-share.md) (leftover-map explained leftover share on pair segments)
+[ADR 0273](0273-leftover-map-segment-explained-share.md) (leftover-map explained leftover share on pair segments);
+[ADR 0274](0274-leftover-map-segment-unexplained-share.md) (leftover-map unexplained leftover share on pair segments);
+[ADR 0275](0275-leftover-map-segment-cross-share.md) (leftover-map cross share on pair segments);
+[ADR 0276](0276-leftover-map-segment-unexplained-leftover.md) (leftover-map unexplained leftover on pair segments);
+[ADR 0277](0277-leftover-map-segment-residual.md) (leftover residual on pair segments);
+[ADR 0278](0278-leftover-map-segment-observed.md) (leftover observed Y on pair segments);
+[ADR 0279](0279-leftover-map-segment-expected.md) (leftover expected E on pair segments);
+[ADR 0280](0280-leftover-map-segment-rank.md) (leftover-map rank on pair segments)
## Context
@@ -47,7 +54,14 @@ when finite (ADR 0269). Leftover-map axis ticks name persisted `ξ` /
`ζ` coordinates (ADR 0270). Pair segments name persisted leftover-map
distance `d` (ADR 0271). Pair segments name persisted leftover-map
reconstruction `R̂` (ADR 0272). Pair segments name persisted leftover-map
-explained leftover share `e` (ADR 0273). The pair renders every available finite measurement.
+explained leftover share `e` (ADR 0273). Pair segments name persisted leftover-map
+unexplained leftover share `s` (ADR 0274). Pair segments name persisted leftover-map
+cross share `x` (ADR 0275). Pair segments name persisted leftover-map
+unexplained leftover `U` (ADR 0276). Pair segments name persisted leftover
+residual `R` (ADR 0277). Pair segments name persisted leftover observed
+`Y` (ADR 0278). Pair segments name persisted leftover expected
+`E` (ADR 0279). Pair segments name persisted leftover-map rank
+(ADR 0280). The pair renders every available finite measurement.
The next action uses the first available value in the priority below; no
amendment hides another badge, rank 0
explicitly names no leftover structure, and unexplained leftover names
@@ -84,6 +98,20 @@ Leftover-map reconstruction on pair segments is
[ADR 0272](0272-leftover-map-segment-reconstruction.md).
Leftover-map explained leftover share on pair segments is
[ADR 0273](0273-leftover-map-segment-explained-share.md).
+Leftover-map unexplained leftover share on pair segments is
+[ADR 0274](0274-leftover-map-segment-unexplained-share.md).
+Leftover-map cross share on pair segments is
+[ADR 0275](0275-leftover-map-segment-cross-share.md).
+Leftover-map unexplained leftover on pair segments is
+[ADR 0276](0276-leftover-map-segment-unexplained-leftover.md).
+Leftover residual on pair segments is
+[ADR 0277](0277-leftover-map-segment-residual.md).
+Leftover observed Y on pair segments is
+[ADR 0278](0278-leftover-map-segment-observed.md).
+Leftover expected E on pair segments is
+[ADR 0279](0279-leftover-map-segment-expected.md).
+Leftover-map rank on pair segments is
+[ADR 0280](0280-leftover-map-segment-rank.md).
After `make seed`, closest and farthest leftover pairs sit above the
member list. Click a pair to open that post with the leftover
diff --git a/docs/adr/0182-leftover-map-unexplained.md b/docs/adr/0182-leftover-map-unexplained.md
index 13ab5a573..1e96d8f81 100644
--- a/docs/adr/0182-leftover-map-unexplained.md
+++ b/docs/adr/0182-leftover-map-unexplained.md
@@ -4,7 +4,9 @@
**Date:** 2026-08-24
**Amended by:** [ADR 0201](0201-leftover-map-reconstruction.md)
-(two-axis reconstruction R̂)
+(two-axis reconstruction R̂);
+[ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)
+(leftover-map unexplained leftover on graphic-display pair segments)
Amends [ADR 0048](0048-persist-lsirm-leftover-pairs.md) and
[ADR 0049](0049-leftover-pair-report-ui.md).
@@ -73,7 +75,9 @@ score. Do not invent a theta.
`GET /api/reports/{grouping}/{period}` returns
`leftover_map_unexplained`. After `make seed`, closest and farthest
leftover pairs sit above the member list with named `U` next to `d`;
-click opens that post. Hidden posts stay hidden.
+click opens that post. Hidden posts stay hidden. ADR 0276
+captions leftover-map graphic-display pair segments with the same
+persisted leftover-map unexplained leftover; it adds no columns.
## Related
diff --git a/docs/adr/0185-leftover-map-cross-share.md b/docs/adr/0185-leftover-map-cross-share.md
index 755523b43..56d030df7 100644
--- a/docs/adr/0185-leftover-map-cross-share.md
+++ b/docs/adr/0185-leftover-map-cross-share.md
@@ -2,6 +2,8 @@
**Decision status:** Draft
**Date:** 2026-08-24
+**Amended by:** [ADR 0275](0275-leftover-map-segment-cross-share.md)
+(leftover-map cross share on graphic-display pair segments)
Amends [ADR 0048](0048-persist-lsirm-leftover-pairs.md) and
[ADR 0049](0049-leftover-pair-report-ui.md).
@@ -83,7 +85,9 @@ invent a leftover score. Do not invent a theta.
`GET /api/reports/{grouping}/{period}` returns
`leftover_map_cross_share`. After `make seed`, closest and farthest
leftover pairs sit above the member list with named `2R̂U/R²` next
-to `d`; click opens that post. Hidden posts stay hidden.
+to `d`; click opens that post. Hidden posts stay hidden. ADR 0275
+captions leftover-map graphic-display pair segments with that same
+persisted leftover-map cross share; it adds no columns.
## Related
diff --git a/docs/adr/0233-leftover-map-unexplained-share.md b/docs/adr/0233-leftover-map-unexplained-share.md
index ebbfe41b3..7d0a5efe8 100644
--- a/docs/adr/0233-leftover-map-unexplained-share.md
+++ b/docs/adr/0233-leftover-map-unexplained-share.md
@@ -4,7 +4,9 @@
**Date:** 2026-08-27
**Amended by:** [ADR 0266](0266-leftover-map-explained-share.md)
-(explained leftover share e)
+(explained leftover share e);
+[ADR 0274](0274-leftover-map-segment-unexplained-share.md)
+(leftover-map unexplained leftover share on graphic-display pair segments)
Amends [ADR 0048](0048-persist-lsirm-leftover-pairs.md) and
[ADR 0049](0049-leftover-pair-report-ui.md). Independent of leftover-map
diff --git a/docs/adr/0268-leftover-map-graphic-display.md b/docs/adr/0268-leftover-map-graphic-display.md
index 9182aab35..c91dc5b9c 100644
--- a/docs/adr/0268-leftover-map-graphic-display.md
+++ b/docs/adr/0268-leftover-map-graphic-display.md
@@ -12,7 +12,21 @@
[ADR 0272](0272-leftover-map-segment-reconstruction.md)
(leftover-map reconstruction on pair segments);
[ADR 0273](0273-leftover-map-segment-explained-share.md)
-(leftover-map explained leftover share on pair segments)
+(leftover-map explained leftover share on pair segments);
+[ADR 0274](0274-leftover-map-segment-unexplained-share.md)
+(leftover-map unexplained leftover share on pair segments);
+[ADR 0275](0275-leftover-map-segment-cross-share.md)
+(leftover-map cross share on pair segments);
+[ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)
+(leftover-map unexplained leftover on pair segments);
+[ADR 0277](0277-leftover-map-segment-residual.md)
+(leftover residual on pair segments);
+[ADR 0278](0278-leftover-map-segment-observed.md)
+(leftover observed Y on pair segments);
+[ADR 0279](0279-leftover-map-segment-expected.md)
+(leftover expected E on pair segments);
+[ADR 0280](0280-leftover-map-segment-rank.md)
+(leftover-map rank on pair segments)
Amends [ADR 0049](0049-leftover-pair-report-ui.md) and
[ADR 0267](0267-leftover-map-coordinates.md). Independent of leftover-map
@@ -89,7 +103,14 @@ leftover-map axes when finite (ADR 0269). Leftover-map axis ticks name
persisted `ξ` / `ζ` coordinates (ADR 0270). Pair segments name
persisted leftover-map distance `d` (ADR 0271). Pair segments name
persisted leftover-map reconstruction `R̂` (ADR 0272). Pair segments name
-persisted leftover-map explained leftover share `e` (ADR 0273). When coordinates, reconstruction, and
+persisted leftover-map explained leftover share `e` (ADR 0273). Pair segments name
+persisted leftover-map unexplained leftover share `s` (ADR 0274). Pair segments name
+persisted leftover-map cross share `x` (ADR 0275). Pair segments name
+persisted leftover-map unexplained leftover `U` (ADR 0276). Pair segments name
+persisted leftover residual `R` (ADR 0277). Pair segments name
+persisted leftover observed `Y` (ADR 0278). Pair segments name
+persisted leftover expected `E` (ADR 0279). Pair segments name
+persisted leftover-map rank (ADR 0280). 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.
diff --git a/docs/adr/0273-leftover-map-segment-explained-share.md b/docs/adr/0273-leftover-map-segment-explained-share.md
index 3330e3104..030f6d85e 100644
--- a/docs/adr/0273-leftover-map-segment-explained-share.md
+++ b/docs/adr/0273-leftover-map-segment-explained-share.md
@@ -3,6 +3,11 @@
**Decision status:** Accepted
**Date:** 2026-08-29
+**Amended by:** [ADR 0274](0274-leftover-map-segment-unexplained-share.md)
+(leftover-map unexplained leftover share on pair segments);
+[ADR 0275](0275-leftover-map-segment-cross-share.md)
+(leftover-map cross share on pair segments)
+
Amends [ADR 0268](0268-leftover-map-graphic-display.md) and
[ADR 0049](0049-leftover-pair-report-ui.md). Independent of leftover-map
reconstruction on pair segments
@@ -84,7 +89,9 @@ and `ζ`, leftover-map axes name persisted Gabriel inertia share when
finite, leftover-map axis ticks name the same coordinates shown on
the pair row, pair segments name persisted leftover-map distance `d`,
persisted leftover-map reconstruction `R̂`, and persisted leftover-map
-explained leftover share `e`; click a post marker or a pair button
+explained leftover share `e`, and pair segments name persisted leftover-map
+unexplained leftover share `s` (ADR 0274) and leftover-map cross share `x`
+(ADR 0275); click a post marker or a pair button
opens that post. Hidden posts stay hidden. Rank-0 unused axes still
plot at the origin, still name zero leftover-map axis share with a `0`
tick, still name `d 0.00` when that distance is persisted, still name
diff --git a/docs/adr/0274-leftover-map-segment-unexplained-share.md b/docs/adr/0274-leftover-map-segment-unexplained-share.md
new file mode 100644
index 000000000..53ce99e85
--- /dev/null
+++ b/docs/adr/0274-leftover-map-segment-unexplained-share.md
@@ -0,0 +1,136 @@
+# ADR 0274 — Name leftover-map unexplained leftover share on graphic-display pair segments
+
+**Decision status:** Accepted
+**Date:** 2026-08-29
+**Amended by:** [ADR 0275](0275-leftover-map-segment-cross-share.md)
+(leftover-map cross share on pair segments)
+
+Amends [ADR 0268](0268-leftover-map-graphic-display.md) and
+[ADR 0049](0049-leftover-pair-report-ui.md). Independent of leftover-map
+explained leftover share on pair segments
+([ADR 0273](0273-leftover-map-segment-explained-share.md)), leftover-map
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.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-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
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.md)),
+and leftover-map axis share persistence ([ADR 0148](0148-leftover-map-axis-share.md)).
+
+## Context
+
+ADR 0233 already persists leftover-map unexplained leftover share
+`s = U² / R²` of raw residual after two-axis Gabriel reconstruction.
+ADR 0182 already persists unexplained leftover `U`. ADR 0273 already
+names persisted leftover-map explained leftover share `e` on the
+leftover-map graphic-display pair segment. The segment still has no
+unexplained leftover share label, so a buyer who reads `U²/R² 0.02`
+on the pair row cannot match that square share to the graphic pair.
+Hiding `s` on the segment lets leftover-map reconstruction `R̂`,
+leftover residual `R`, unexplained leftover `U`, or leftover-map
+explained leftover share `e` be read as the leftover the truncated map
+cannot reconstruct even after `e` is named on the segment. When `R`,
+`R̂`, `U`, `x`, `s`, and `e` are finite, `e + s + x = 1`; the graphic
+must name the same persisted `s` the pair row already shows.
+
+This increment names leftover-map graphic-display pair segments with
+persisted leftover-map unexplained leftover share. It does not add
+columns. It does not recompute `s` from `U` and `R` or from plotted
+coordinates. It does not persist leftover-map inner product, cosine, or
+length as separate columns. It does not land Post quality on the leftover
+criterion. Leftover-map distance stays two-axis Euclidean. 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 **0274** so it does not
+collide with leftover-map explained leftover share on pair segments
+(0273), leftover-map reconstruction on pair segments (0272), leftover-map
+distance on pair segments (0271), leftover-map coordinate ticks (0270),
+leftover-map axis share on the graphic display (0269), leftover-map
+graphic display (0268), leftover-map coordinates (0267 / migration 0245),
+leftover-map explained leftover share persistence (0266 / migration 0244),
+leftover-map unexplained leftover share persistence (0233 / migration
+0233), leftover-map reconstruction persistence (0201 / migration 0206),
+leftover-map cross share (0185), leftover residual disclosure, leftover
+observed `Y` / expected `E`, leftover-map rank, two-axis leftover-map
+distance persistence, leftover coverage, leftover-map axis share
+persistence (0148), leftover interaction-map persistence, occupational
+construct catalog search (0265), or the dashboard stacks.
+
+## Decision
+
+On the leftover-map graphic display, caption each closest or farthest
+pair segment with the same persisted leftover-map unexplained leftover
+share formatter as the pair-row `U²/R²` badge. A missing or
+non-finite `s` omits that unexplained leftover share caption and keeps
+the connecting line and any leftover-map distance, reconstruction, or
+explained leftover share caption. Rank-0 origin cells still name
+`U²/R² 0.00` when that persisted share is finite. A finite share
+greater than 1 is shown, never clamped. Do not invent `s` from `U` and
+`R` or from plotted coordinates. Click a post marker to open that post.
+Criterion markers are not post buttons. The grouping comparison strip
+(ADR 0149) stays on its reduced leftover payload and does not gain these
+unexplained leftover share captions.
+
+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`, closest and farthest leftover pairs sit above the
+member list with the leftover-map graphic display of persisted `ξ`
+and `ζ`, leftover-map axes name persisted Gabriel inertia share when
+finite, leftover-map axis ticks name the same coordinates shown on
+the pair row, pair segments name persisted leftover-map distance `d`,
+persisted leftover-map reconstruction `R̂`, persisted leftover-map
+explained leftover share `e`, and persisted leftover-map unexplained
+leftover share `s`, and pair segments name persisted leftover-map
+cross share `x` (ADR 0275); click a post marker or a pair button opens that
+post. Hidden posts stay hidden. Rank-0 unused axes still plot at the
+origin, still name zero leftover-map axis share with a `0` tick, still
+name `d 0.00` when that distance is persisted, still name `R̂ 0.00`
+when that reconstruction is persisted, still name `R̂²/R² 0.00` when
+that explained leftover share is persisted, and still name `U²/R² 0.00`
+when that unexplained leftover share is persisted, and still name `2R̂U/R² 0.00`
+when that leftover-map cross share is persisted. When `R`, `R̂`, `U`,
+`x`, `s`, and `e` are all finite, `e + s + x = 1` remains the same
+identity already persisted by ADR 0233 and ADR 0266.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover residual disclosure, leftover observed
+`Y` / expected `E`, leftover-map complete-case coverage, leftover-map
+axis share persistence, leftover pairs on the grouping comparison
+strip, two-axis leftover-map distance persistence, leftover-map rank,
+leftover-map inner product, leftover-map cosine, leftover-map length,
+leftover-map reconstruction persistence, leftover-map unexplained
+leftover, leftover-map cross share, leftover-map unexplained leftover
+share persistence, leftover-map explained leftover share persistence,
+leftover-map coordinate persistence, leftover-map graphic display,
+leftover-map axis share on the graphic display, leftover-map coordinate
+ticks, leftover-map distance on pair segments, leftover-map reconstruction
+on pair segments, leftover-map explained leftover share on pair segments,
+and leftover-map cross share on pair segments.
+
+## 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}`. Unexplained leftover share is the square share
+`s = U² / R²` of the leftover the truncated map cannot reconstruct.)
diff --git a/docs/adr/0275-leftover-map-segment-cross-share.md b/docs/adr/0275-leftover-map-segment-cross-share.md
new file mode 100644
index 000000000..c80b19fb6
--- /dev/null
+++ b/docs/adr/0275-leftover-map-segment-cross-share.md
@@ -0,0 +1,143 @@
+# ADR 0275 — Name leftover-map cross share on graphic-display pair segments
+
+**Decision status:** Accepted
+**Date:** 2026-08-29
+**Amended by:** [ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)
+(leftover-map unexplained leftover on pair segments)
+
+Amends [ADR 0268](0268-leftover-map-graphic-display.md),
+[ADR 0049](0049-leftover-pair-report-ui.md), and leftover-map cross share
+persistence ([ADR 0185](0185-leftover-map-cross-share.md)). Independent of
+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
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.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-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
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.md)),
+and leftover-map axis share persistence ([ADR 0148](0148-leftover-map-axis-share.md)).
+
+## Context
+
+ADR 0185 already persists leftover-map cross share
+`x = 2 R̂ U / R²` of raw residual after two-axis Gabriel reconstruction.
+ADR 0274 already names persisted leftover-map unexplained leftover share
+`s` on the leftover-map graphic-display pair segment. The segment still
+has no leftover-map cross share label, so a buyer who reads `2R̂U/R² 0.12`
+on the pair row cannot match that identity remainder to the graphic pair.
+Hiding `x` on the segment lets leftover-map reconstruction `R̂`, leftover
+residual `R`, unexplained leftover `U`, leftover-map explained leftover
+share `e`, or leftover-map unexplained leftover share `s` be read as a
+complete leftover partition even after `e` and `s` are named on the
+segment. When `R`, `R̂`, `U`, `x`, `s`, and `e` are finite,
+`e + s + x = 1`; the graphic must name the same persisted `x` the pair
+row already shows.
+
+This increment names leftover-map graphic-display pair segments with
+persisted leftover-map cross share. It does not add columns. It does not
+recompute `x` from `R̂`, `U`, and `R` or from plotted coordinates. It
+does not persist leftover-map inner product, cosine, or length as
+separate columns. It does not land Post quality on the leftover
+criterion. Leftover-map distance stays two-axis Euclidean. 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 **0275** so it does not
+collide with leftover-map unexplained leftover share on pair segments
+(0274), leftover-map explained leftover share on pair segments (0273),
+leftover-map reconstruction on pair segments (0272), leftover-map
+distance on pair segments (0271), leftover-map coordinate ticks (0270),
+leftover-map axis share on the graphic display (0269), leftover-map
+graphic display (0268), leftover-map coordinates (0267 / migration 0245),
+leftover-map explained leftover share persistence (0266 / migration 0244),
+leftover-map unexplained leftover share persistence (0233 / migration
+0233), leftover-map reconstruction persistence (0201 / migration 0206),
+leftover-map cross share persistence (0185), leftover residual disclosure,
+leftover observed `Y` / expected `E`, leftover-map rank, two-axis leftover-map
+distance persistence, leftover coverage, leftover-map axis share
+persistence (0148), leftover interaction-map persistence, occupational
+construct catalog search (0265), or the dashboard stacks.
+
+## Decision
+
+On the leftover-map graphic display, caption each closest or farthest
+pair segment with the same persisted leftover-map cross share formatter
+as the pair-row `2R̂U/R²` badge. A missing or non-finite `x` omits that
+leftover-map cross share caption and keeps the connecting line and any
+leftover-map distance, reconstruction, explained leftover share, or
+unexplained leftover share caption. Rank-0 origin cells still name
+`2R̂U/R² 0.00` when that persisted share is finite. A finite share
+greater than 1 is shown, never clamped. A finite negative share is
+shown; do not clamp to nonnegative. Do not invent `x` from `R̂`, `U`,
+and `R` or from plotted coordinates. Click a post marker to open that
+post. Criterion markers are not post buttons. The grouping comparison
+strip (ADR 0149) stays on its reduced leftover payload and does not gain
+these leftover-map cross share captions.
+
+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`, closest and farthest leftover pairs sit above the
+member list with the leftover-map graphic display of persisted `ξ`
+and `ζ`, leftover-map axes name persisted Gabriel inertia share when
+finite, leftover-map axis ticks name the same coordinates shown on
+the pair row, pair segments name persisted leftover-map distance `d`,
+persisted leftover-map reconstruction `R̂`, persisted leftover-map
+explained leftover share `e`, persisted leftover-map unexplained
+leftover share `s`, and persisted leftover-map cross share `x`; click
+a post marker or a pair button opens that post. Hidden posts stay
+hidden. Rank-0 unused axes still plot at the origin, still name zero
+leftover-map axis share with a `0` tick, still name `d 0.00` when that
+distance is persisted, still name `R̂ 0.00` when that reconstruction is
+persisted, still name `R̂²/R² 0.00` when that explained leftover share
+is persisted, still name `U²/R² 0.00` when that unexplained leftover
+share is persisted, and still name `2R̂U/R² 0.00` when that leftover-map
+cross share is persisted. When `R`, `R̂`, `U`, `x`, `s`, and `e` are
+all finite, `e + s + x = 1` remains the same identity already persisted
+by ADR 0185, ADR 0233, and ADR 0266.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover residual disclosure, leftover observed
+`Y` / expected `E`, leftover-map complete-case coverage, leftover-map
+axis share persistence, leftover pairs on the grouping comparison
+strip, two-axis leftover-map distance persistence, leftover-map rank,
+leftover-map inner product, leftover-map cosine, leftover-map length,
+leftover-map reconstruction persistence, leftover-map unexplained
+leftover, leftover-map cross share persistence, leftover-map unexplained
+leftover share persistence, leftover-map explained leftover share
+persistence, leftover-map coordinate persistence, leftover-map graphic
+display, leftover-map axis share on the graphic display, leftover-map
+coordinate ticks, leftover-map distance on pair segments, leftover-map
+reconstruction on pair segments, leftover-map explained leftover share
+on pair segments, and leftover-map unexplained leftover share on pair
+segments.
+
+## 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}`. Leftover-map cross share is the identity
+remainder `x = 2 R̂ U / R²` of raw residual after two-axis
+reconstruction, with `U = R − R̂`.)
diff --git a/docs/adr/0276-leftover-map-segment-unexplained-leftover.md b/docs/adr/0276-leftover-map-segment-unexplained-leftover.md
new file mode 100644
index 000000000..79ee88b01
--- /dev/null
+++ b/docs/adr/0276-leftover-map-segment-unexplained-leftover.md
@@ -0,0 +1,147 @@
+# ADR 0276 — Name leftover-map unexplained leftover on graphic-display pair segments
+
+**Decision status:** Accepted
+**Date:** 2026-08-30
+
+Amends [ADR 0268](0268-leftover-map-graphic-display.md),
+[ADR 0049](0049-leftover-pair-report-ui.md), and leftover-map unexplained
+leftover persistence ([ADR 0182](0182-leftover-map-unexplained.md)). Independent of
+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
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.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-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
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.md)),
+leftover-map cross share persistence
+([ADR 0185](0185-leftover-map-cross-share.md)), and leftover-map axis share
+persistence ([ADR 0148](0148-leftover-map-axis-share.md)).
+
+## Context
+
+ADR 0182 already persists unexplained leftover `U = R − R̂` after
+two-axis Gabriel reconstruction. ADR 0275 already names persisted
+leftover-map cross share `x` on the leftover-map graphic-display pair
+segment. The segment still has no unexplained leftover label, so a
+buyer who reads `U +0.05` on the pair row cannot match that signed
+leftover to the graphic pair. Hiding `U` on the segment lets leftover
+residual `R`, leftover-map reconstruction `R̂`, leftover-map explained
+leftover share `e`, leftover-map unexplained leftover share `s`, or
+leftover-map cross share `x` be read as the leftover the truncated map
+cannot reconstruct even after `e`, `s`, and `x` are named on the
+segment. When `R`, `R̂`, and `U` are finite, `U + R̂ = R`; the graphic
+must name the same persisted `U` the pair row already shows.
+
+This increment names leftover-map graphic-display pair segments with
+persisted leftover-map unexplained leftover. It does not add columns.
+It does not recompute `U` from `R` and `R̂` or from plotted coordinates.
+It does not persist leftover-map inner product, cosine, or length as
+separate columns. It does not land Post quality on the leftover
+criterion. Leftover-map distance stays two-axis Euclidean. 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 **0276** so it does not
+collide with leftover-map cross share on pair segments (0275), leftover-map
+unexplained leftover share on pair segments (0274), leftover-map explained
+leftover share on pair segments (0273), leftover-map reconstruction on pair
+segments (0272), leftover-map distance on pair segments (0271), leftover-map
+coordinate ticks (0270), leftover-map axis share on the graphic display
+(0269), leftover-map graphic display (0268), leftover-map coordinates
+(0267 / migration 0245), leftover-map explained leftover share persistence
+(0266 / migration 0244), leftover-map unexplained leftover share
+persistence (0233 / migration 0233), leftover-map reconstruction
+persistence (0201 / migration 0206), leftover-map cross share persistence
+(0185), leftover-map unexplained leftover persistence (0182), leftover
+residual disclosure, leftover observed `Y` / expected `E`, leftover-map
+rank, two-axis leftover-map distance persistence, leftover coverage,
+leftover-map axis share persistence (0148), leftover interaction-map
+persistence, occupational construct catalog search (0265), or the
+dashboard stacks.
+
+## Decision
+
+On the leftover-map graphic display, caption each closest or farthest
+pair segment with the same persisted leftover-map unexplained leftover
+formatter as the pair-row `U` badge. A missing or non-finite `U` omits
+that unexplained leftover caption and keeps the connecting line and any
+leftover-map distance, reconstruction, explained leftover share,
+unexplained leftover share, or leftover-map cross share caption. Rank-0
+origin cells still name `U 0.00` when that persisted leftover is finite.
+A finite negative leftover is shown; do not clamp to nonnegative. Do not
+invent `U` from `R` and `R̂` or from plotted coordinates. Click a post
+marker to open that post. Criterion markers are not post buttons. The
+grouping comparison strip (ADR 0149) stays on its reduced leftover
+payload and does not gain these unexplained leftover captions.
+
+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`, closest and farthest leftover pairs sit above the
+member list with the leftover-map graphic display of persisted `ξ`
+and `ζ`, leftover-map axes name persisted Gabriel inertia share when
+finite, leftover-map axis ticks name the same coordinates shown on
+the pair row, pair segments name persisted leftover-map distance `d`,
+persisted leftover-map reconstruction `R̂`, persisted leftover-map
+explained leftover share `e`, persisted leftover-map unexplained leftover
+share `s`, persisted leftover-map cross share `x`, and persisted leftover-map
+unexplained leftover `U`; click a post marker or a pair button opens that
+post. Hidden posts stay hidden. Rank-0 unused axes still plot at the
+origin, still name zero leftover-map axis share with a `0` tick, still
+name `d 0.00` when that distance is persisted, still name `R̂ 0.00`
+when that reconstruction is persisted, still name `R̂²/R² 0.00` when
+that explained leftover share is persisted, still name `U²/R² 0.00`
+when that unexplained leftover share is persisted, still name
+`2R̂U/R² 0.00` when that leftover-map cross share is persisted, and
+still name `U 0.00` when that unexplained leftover is persisted. When
+`R`, `R̂`, and `U` are all finite, `U + R̂ = R` remains the same identity
+already persisted by ADR 0182 and ADR 0201. When `R`, `R̂`, `U`, `x`,
+`s`, and `e` are all finite, `e + s + x = 1` remains the same identity
+already persisted by ADR 0185, ADR 0233, and ADR 0266.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover residual disclosure, leftover observed
+`Y` / expected `E`, leftover-map complete-case coverage, leftover-map
+axis share persistence, leftover pairs on the grouping comparison
+strip, two-axis leftover-map distance persistence, leftover-map rank,
+leftover-map inner product, leftover-map cosine, leftover-map length,
+leftover-map reconstruction persistence, leftover-map unexplained leftover
+persistence, leftover-map cross share persistence, leftover-map unexplained
+leftover share persistence, leftover-map explained leftover share
+persistence, leftover-map coordinate persistence, leftover-map graphic
+display, leftover-map axis share on the graphic display, leftover-map
+coordinate ticks, leftover-map distance on pair segments, leftover-map
+reconstruction on pair segments, leftover-map explained leftover share
+on pair segments, leftover-map unexplained leftover share on pair
+segments, and leftover-map cross share on pair segments.
+
+## 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}`. Unexplained leftover is the signed remainder
+`U = R − R̂` after that two-axis reconstruction.)
diff --git a/docs/adr/0277-leftover-map-segment-residual.md b/docs/adr/0277-leftover-map-segment-residual.md
new file mode 100644
index 000000000..0f43a4227
--- /dev/null
+++ b/docs/adr/0277-leftover-map-segment-residual.md
@@ -0,0 +1,152 @@
+# ADR 0277 — Name leftover residual on graphic-display pair segments
+
+**Decision status:** Accepted
+**Date:** 2026-08-30
+
+Amends [ADR 0268](0268-leftover-map-graphic-display.md),
+[ADR 0049](0049-leftover-pair-report-ui.md), and leftover residual
+disclosure ([ADR 0162](0162-leftover-residual-disclosure.md)). Independent of
+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
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.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-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
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.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 observed
+`Y` / expected `E` ([ADR 0163](0163-leftover-observed-expected.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 0276 already names persisted
+leftover-map unexplained leftover `U` on the leftover-map graphic-display
+pair segment. The segment still has no residual label, so a buyer who
+reads `+0.40` on the pair row cannot match that signed leftover to the
+graphic pair. Hiding `R` on the segment lets leftover-map reconstruction
+`R̂` or unexplained leftover `U` be read as the leftover that entered the
+truncated map even after `U` is named on the segment. When `R`, `R̂`, and
+`U` are finite, `U + R̂ = R`; the graphic must name the same persisted `R`
+the pair row already shows.
+
+This increment names leftover-map graphic-display pair segments with
+persisted leftover residual. It does not add columns. It does not
+recompute `R` from `Y` and `E`, from `U` and `R̂`, or from plotted
+coordinates. It does not persist leftover-map inner product, cosine, or
+length as separate columns. It does not land Post quality on the leftover
+criterion. Leftover-map distance stays two-axis Euclidean. 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 **0277** so it does not
+collide with leftover-map unexplained leftover on pair segments (0276), leftover-map
+cross share on pair segments (0275), leftover-map unexplained leftover share
+on pair segments (0274), leftover-map explained leftover share on pair
+segments (0273), leftover-map reconstruction on pair segments (0272), leftover-map
+distance on pair segments (0271), leftover-map coordinate ticks (0270), leftover-map
+axis share on the graphic display (0269), leftover-map graphic display (0268), leftover-map
+coordinates (0267 / migration 0245), leftover-map explained leftover share persistence
+(0266 / migration 0244), leftover-map unexplained leftover share
+persistence (0233 / migration 0233), leftover-map reconstruction
+persistence (0201 / migration 0206), leftover-map cross share persistence
+(0185), leftover-map unexplained leftover persistence (0182), leftover
+residual disclosure (0162), leftover observed `Y` / expected `E`, leftover-map
+rank, two-axis leftover-map distance persistence, leftover coverage,
+leftover-map axis share persistence (0148), leftover interaction-map
+persistence, occupational construct catalog search (0265), or the
+dashboard stacks.
+
+## Decision
+
+On the leftover-map graphic display, caption each closest or farthest
+pair segment with persisted leftover residual `R` as `R ±n.nn`, using
+the same signed leftover formatter as the pair-row residual badge. A
+missing or non-finite `R` omits that leftover residual caption and keeps
+the connecting line and any leftover-map distance, reconstruction,
+explained leftover share, unexplained leftover share, leftover-map cross
+share, or unexplained leftover caption. Rank-0 origin cells still name
+`R 0.00` when that persisted leftover is finite. A finite negative
+leftover is shown; do not clamp to nonnegative. Do not invent `R` from
+`Y` and `E`, from `U` and `R̂`, or from plotted coordinates. Click a post
+marker to open that post. Criterion markers are not post buttons. The
+grouping comparison strip (ADR 0149) stays on its reduced leftover
+payload and does not gain these leftover residual captions.
+
+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`, closest and farthest leftover pairs sit above the
+member list with the leftover-map graphic display of persisted `ξ`
+and `ζ`, leftover-map axes name persisted Gabriel inertia share when
+finite, leftover-map axis ticks name the same coordinates shown on
+the pair row, pair segments name persisted leftover-map distance `d`,
+persisted leftover-map reconstruction `R̂`, persisted leftover-map
+explained leftover share `e`, persisted leftover-map unexplained leftover
+share `s`, persisted leftover-map cross share `x`, persisted leftover-map
+unexplained leftover `U`, and persisted leftover residual `R`; click a
+post marker or a pair button opens that post. Hidden posts stay hidden.
+Rank-0 unused axes still plot at the origin, still name zero leftover-map
+axis share with a `0` tick, still name `d 0.00` when that distance is
+persisted, still name `R̂ 0.00` when that reconstruction is persisted,
+still name `R̂²/R² 0.00` when that explained leftover share is persisted,
+still name `U²/R² 0.00` when that unexplained leftover share is persisted,
+still name `2R̂U/R² 0.00` when that leftover-map cross share is persisted,
+still name `U 0.00` when that unexplained leftover is persisted, and still
+name `R 0.00` when that leftover residual is persisted. When `R`, `R̂`,
+and `U` are all finite, `U + R̂ = R` remains the same identity already
+persisted by ADR 0162, ADR 0182, and ADR 0201. When `R`, `R̂`, `U`, `x`,
+`s`, and `e` are all finite, `e + s + x = 1` remains the same identity
+already persisted by ADR 0185, ADR 0233, and ADR 0266.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover observed `Y` / expected `E`, leftover-map
+complete-case coverage, leftover-map axis share persistence, leftover
+pairs on the grouping comparison strip, two-axis leftover-map distance
+persistence, leftover-map rank, leftover-map inner product, leftover-map
+cosine, leftover-map length, leftover-map reconstruction persistence,
+leftover-map unexplained leftover persistence, leftover-map cross share
+persistence, leftover-map unexplained leftover share persistence,
+leftover-map explained leftover share persistence, leftover-map
+coordinate persistence, leftover-map graphic display, leftover-map axis
+share on the graphic display, leftover-map coordinate ticks, leftover-map
+distance on pair segments, leftover-map reconstruction on pair segments,
+leftover-map explained leftover share on pair segments, leftover-map
+unexplained leftover share on pair segments, leftover-map cross share on
+pair segments, and leftover-map unexplained leftover on pair segments.
+
+## 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}`. Leftover residual is the signed remainder
+`R = Y − E[Y|θ, item]` after IRT main effects. Unexplained leftover is
+the signed remainder `U = R − R̂` after that two-axis reconstruction.)
diff --git a/docs/adr/0278-leftover-map-segment-observed.md b/docs/adr/0278-leftover-map-segment-observed.md
new file mode 100644
index 000000000..c15d02b84
--- /dev/null
+++ b/docs/adr/0278-leftover-map-segment-observed.md
@@ -0,0 +1,160 @@
+# ADR 0278 — Name leftover observed Y on graphic-display pair segments
+
+**Decision status:** Accepted
+**Date:** 2026-08-30
+
+Amends [ADR 0268](0268-leftover-map-graphic-display.md),
+[ADR 0049](0049-leftover-pair-report-ui.md), and leftover observed `Y` /
+expected `E` ([ADR 0163](0163-leftover-observed-expected.md)). Independent of
+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
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.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-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
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.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)), and leftover-map axis share
+persistence ([ADR 0148](0148-leftover-map-axis-share.md)).
+
+## Context
+
+ADR 0163 already persists observed category `Y` and expected
+`E[Y|θ, item]` on leftover pair rows. ADR 0277 already names persisted
+leftover residual `R` on the leftover-map graphic-display pair segment.
+The segment still has no observed-category label, so a buyer who reads
+`Y 2.40` on the pair row cannot match that observed response to the
+graphic pair. Hiding `Y` on the segment lets leftover residual `R` be
+read as the category that entered the truncated map even after `R` is
+named on the segment. When `Y`, `E`, and `R` are finite, `Y − E = R`;
+the graphic must name the same persisted `Y` the pair row already shows.
+
+This increment names leftover-map graphic-display pair segments with
+persisted leftover observed `Y`. It does not add columns. It does not
+recompute `Y` from `R` and `E` or from plotted coordinates. It does not
+persist leftover-map inner product, cosine, or length as separate
+columns. It does not land Post quality on the leftover criterion.
+Leftover-map distance stays two-axis Euclidean. Do not invent a leftover
+score. Do not invent a theta. Expected `E` on pair segments stays a later
+increment.
+
+The dashboard stack already used neighbouring leftover facts under
+other numbers. This protected increment uses **0278** so it does not
+collide with leftover residual on pair segments (0277), leftover-map
+unexplained leftover on pair segments (0276), leftover-map cross share
+on pair segments (0275), leftover-map unexplained leftover share
+on pair segments (0274), leftover-map explained leftover share on pair
+segments (0273), leftover-map reconstruction on pair segments (0272), leftover-map
+distance on pair segments (0271), leftover-map coordinate ticks (0270), leftover-map
+axis share on the graphic display (0269), leftover-map graphic display (0268), leftover-map
+coordinates (0267 / migration 0245), leftover-map explained leftover share persistence
+(0266 / migration 0244), leftover-map unexplained leftover share
+persistence (0233 / migration 0233), leftover-map reconstruction
+persistence (0201 / migration 0206), leftover-map cross share persistence
+(0185), leftover-map unexplained leftover persistence (0182), leftover
+residual disclosure (0162), leftover observed `Y` / expected `E` persistence
+(0163), leftover-map rank, two-axis leftover-map distance persistence, leftover coverage,
+leftover-map axis share persistence (0148), leftover interaction-map
+persistence, occupational construct catalog search (0265), or the
+dashboard stacks.
+
+## Decision
+
+On the leftover-map graphic display, caption each closest or farthest
+pair segment with persisted leftover observed `Y` as `Y n.nn`, using
+the same observed formatter as the pair-row `Y` badge. A missing or
+non-finite `Y` omits that leftover observed caption and keeps the
+connecting line and any leftover-map distance, reconstruction,
+explained leftover share, unexplained leftover share, leftover-map cross
+share, unexplained leftover, or leftover residual caption. Rank-0 origin
+cells still name `Y 0.00` when that persisted observed is finite. A
+finite negative observed is shown; do not clamp to nonnegative. Do not
+invent `Y` from `R` and `E` or from plotted coordinates. Click a post
+marker to open that post. Criterion markers are not post buttons. The
+grouping comparison strip (ADR 0149) stays on its reduced leftover
+payload and does not gain these leftover observed captions.
+
+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`, closest and farthest leftover pairs sit above the
+member list with the leftover-map graphic display of persisted `ξ`
+and `ζ`, leftover-map axes name persisted Gabriel inertia share when
+finite, leftover-map axis ticks name the same coordinates shown on
+the pair row, pair segments name persisted leftover-map distance `d`,
+persisted leftover-map reconstruction `R̂`, persisted leftover-map
+explained leftover share `e`, persisted leftover-map unexplained leftover
+share `s`, persisted leftover-map cross share `x`, persisted leftover-map
+unexplained leftover `U`, persisted leftover residual `R`, and persisted
+leftover observed `Y`; click a post marker or a pair button opens that
+post. Hidden posts stay hidden. Rank-0 unused axes still plot at the
+origin, still name zero leftover-map axis share with a `0` tick, still
+name `d 0.00` when that distance is persisted, still name `R̂ 0.00` when
+that reconstruction is persisted, still name `R̂²/R² 0.00` when that
+explained leftover share is persisted, still name `U²/R² 0.00` when that
+unexplained leftover share is persisted, still name `2R̂U/R² 0.00` when
+that leftover-map cross share is persisted, still name `U 0.00` when that
+unexplained leftover is persisted, still name `R 0.00` when that leftover
+residual is persisted, and still name `Y 0.00` when that leftover
+observed is persisted. When `Y`, `E`, and `R` are all finite,
+`Y − E = R` remains the same identity already persisted by ADR 0162 and
+ADR 0163. When `R`, `R̂`, and `U` are all finite, `U + R̂ = R` remains
+the same identity already persisted by ADR 0162, ADR 0182, and ADR 0201.
+When `R`, `R̂`, `U`, `x`, `s`, and `e` are all finite, `e + s + x = 1`
+remains the same identity already persisted by ADR 0185, ADR 0233, and
+ADR 0266.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover residual disclosure, leftover expected `E`
+on pair segments, leftover-map complete-case coverage, leftover-map
+axis share persistence, leftover pairs on the grouping comparison strip,
+two-axis leftover-map distance persistence, leftover-map rank,
+leftover-map inner product, leftover-map cosine, leftover-map length,
+leftover-map reconstruction persistence, leftover-map unexplained leftover
+persistence, leftover-map cross share persistence, leftover-map unexplained
+leftover share persistence, leftover-map explained leftover share
+persistence, leftover-map coordinate persistence, leftover-map graphic
+display, leftover-map axis share on the graphic display, leftover-map
+coordinate ticks, leftover-map distance on pair segments, leftover-map
+reconstruction on pair segments, leftover-map explained leftover share
+on pair segments, leftover-map unexplained leftover share on pair
+segments, leftover-map cross share on pair segments, leftover-map
+unexplained leftover on pair segments, and leftover residual on pair
+segments.
+
+## 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}`. Leftover residual is the signed remainder
+`R = Y − E[Y|θ, item]` after IRT main effects. Observed `Y` is the
+category that entered that residual.)
diff --git a/docs/adr/0279-leftover-map-segment-expected.md b/docs/adr/0279-leftover-map-segment-expected.md
new file mode 100644
index 000000000..f01adbd8d
--- /dev/null
+++ b/docs/adr/0279-leftover-map-segment-expected.md
@@ -0,0 +1,164 @@
+# ADR 0279 — Name leftover expected E on graphic-display pair segments
+
+**Decision status:** Accepted
+**Date:** 2026-08-30
+
+Amends [ADR 0268](0268-leftover-map-graphic-display.md),
+[ADR 0049](0049-leftover-pair-report-ui.md), and leftover observed `Y` /
+expected `E` ([ADR 0163](0163-leftover-observed-expected.md)). Independent of
+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
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.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-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
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.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)), and leftover-map axis share
+persistence ([ADR 0148](0148-leftover-map-axis-share.md)).
+
+## Context
+
+ADR 0163 already persists observed category `Y` and expected
+`E[Y|θ, item]` on leftover pair rows. ADR 0278 already names persisted
+leftover observed `Y` on the leftover-map graphic-display pair segment.
+The segment still has no expected-category label, so a buyer who reads
+`E 2.00` on the pair row cannot match that expected response to the
+graphic pair. Hiding `E` on the segment lets leftover observed `Y` or
+leftover residual `R` be read as the IRT main-effect expectation even
+after `Y` is named on the segment. When `Y`, `E`, and `R` are finite,
+`Y − E = R`; the graphic must name the same persisted `E` the pair row
+already shows.
+
+This increment names leftover-map graphic-display pair segments with
+persisted leftover expected `E[Y|θ, item]`. It does not add columns. It
+does not recompute `E` from `Y` and `R` or from plotted coordinates. It
+does not persist leftover-map inner product, cosine, or length as
+separate columns. It does not land Post quality on the leftover
+criterion. Leftover-map distance stays two-axis Euclidean. 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 **0279** so it does not
+collide with leftover observed on pair segments (0278), leftover residual
+on pair segments (0277), leftover-map unexplained leftover on pair
+segments (0276), leftover-map cross share on pair segments (0275),
+leftover-map unexplained leftover share on pair segments (0274),
+leftover-map explained leftover share on pair segments (0273),
+leftover-map reconstruction on pair segments (0272), leftover-map
+distance on pair segments (0271), leftover-map coordinate ticks (0270),
+leftover-map axis share on the graphic display (0269), leftover-map
+graphic display (0268), leftover-map coordinates (0267 / migration 0245),
+leftover-map explained leftover share persistence (0266 / migration 0244),
+leftover-map unexplained leftover share persistence (0233 / migration 0233),
+leftover-map reconstruction persistence (0201 / migration 0206), leftover-map
+cross share persistence (0185), leftover-map unexplained leftover
+persistence (0182), leftover residual disclosure (0162), leftover observed
+`Y` / expected `E` persistence (0163), leftover-map rank, two-axis
+leftover-map distance persistence, leftover coverage, leftover-map axis
+share persistence (0148), leftover interaction-map persistence,
+occupational construct catalog search (0265), or the dashboard stacks.
+
+## Decision
+
+On the leftover-map graphic display, caption each closest or farthest
+pair segment with persisted leftover expected `E[Y|θ, item]` as `E n.nn`,
+using the same expected formatter as the pair-row `E` badge. A missing or
+non-finite `E` omits that leftover expected caption and keeps the
+connecting line and any leftover-map distance, reconstruction,
+explained leftover share, unexplained leftover share, leftover-map cross
+share, unexplained leftover, leftover residual, or leftover observed
+caption. Rank-0 origin cells still name `E 0.00` when that persisted
+expected is finite. A finite negative expected is shown; do not clamp to
+nonnegative. Do not invent `E` from `Y` and `R` or from plotted
+coordinates. Click a post marker to open that post. Criterion markers
+are not post buttons. The grouping comparison strip (ADR 0149) stays on
+its reduced leftover payload and does not gain these leftover expected
+captions.
+
+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`, closest and farthest leftover pairs sit above the
+member list with the leftover-map graphic display of persisted `ξ`
+and `ζ`, leftover-map axes name persisted Gabriel inertia share when
+finite, leftover-map axis ticks name the same coordinates shown on
+the pair row, pair segments name persisted leftover-map distance `d`,
+persisted leftover-map reconstruction `R̂`, persisted leftover-map
+explained leftover share `e`, persisted leftover-map unexplained leftover
+share `s`, persisted leftover-map cross share `x`, persisted leftover-map
+unexplained leftover `U`, persisted leftover residual `R`, persisted
+leftover observed `Y`, and persisted leftover expected `E`; click a post
+marker or a pair button opens that post. Hidden posts stay hidden.
+Rank-0 unused axes still plot at the origin, still name zero leftover-map
+axis share with a `0` tick, still name `d 0.00` when that distance is
+persisted, still name `R̂ 0.00` when that reconstruction is persisted,
+still name `R̂²/R² 0.00` when that explained leftover share is persisted,
+still name `U²/R² 0.00` when that unexplained leftover share is persisted,
+still name `2R̂U/R² 0.00` when that leftover-map cross share is persisted,
+still name `U 0.00` when that unexplained leftover is persisted, still
+name `R 0.00` when that leftover residual is persisted, still name
+`Y 0.00` when that leftover observed is persisted, and still name
+`E 0.00` when that leftover expected is persisted. When `Y`, `E`, and
+`R` are all finite, `Y − E = R` remains the same identity already
+persisted by ADR 0162 and ADR 0163. When `R`, `R̂`, and `U` are all
+finite, `U + R̂ = R` remains the same identity already persisted by
+ADR 0162, ADR 0182, and ADR 0201. When `R`, `R̂`, `U`, `x`, `s`, and `e`
+are all finite, `e + s + x = 1` remains the same identity already
+persisted by ADR 0185, ADR 0233, and ADR 0266.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover residual disclosure, leftover-map
+complete-case coverage, leftover-map axis share persistence, leftover
+pairs on the grouping comparison strip, two-axis leftover-map distance
+persistence, leftover-map rank, leftover-map inner product, leftover-map
+cosine, leftover-map length, leftover-map reconstruction persistence,
+leftover-map unexplained leftover persistence, leftover-map cross share
+persistence, leftover-map unexplained leftover share persistence,
+leftover-map explained leftover share persistence, leftover-map
+coordinate persistence, leftover-map graphic display, leftover-map axis
+share on the graphic display, leftover-map coordinate ticks, leftover-map
+distance on pair segments, leftover-map reconstruction on pair segments,
+leftover-map explained leftover share on pair segments, leftover-map
+unexplained leftover share on pair segments, leftover-map cross share on
+pair segments, leftover-map unexplained leftover on pair segments,
+leftover residual on pair segments, and leftover observed on pair
+segments.
+
+## 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}`. Leftover residual is the signed remainder
+`R = Y − E[Y|θ, item]` after IRT main effects. Expected `E` is the
+already-fitted category that entered that residual.)
diff --git a/docs/adr/0280-leftover-map-segment-rank.md b/docs/adr/0280-leftover-map-segment-rank.md
new file mode 100644
index 000000000..0781bb0ff
--- /dev/null
+++ b/docs/adr/0280-leftover-map-segment-rank.md
@@ -0,0 +1,172 @@
+# ADR 0280 — Name leftover-map rank on graphic-display pair segments
+
+**Decision status:** Accepted
+**Date:** 2026-08-30
+
+Amends [ADR 0268](0268-leftover-map-graphic-display.md),
+[ADR 0049](0049-leftover-pair-report-ui.md), and leftover-map rank
+([ADR 0164](0164-leftover-map-rank.md)). Independent of 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
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.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
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.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)), and leftover-map axis
+share persistence ([ADR 0148](0148-leftover-map-axis-share.md)).
+
+## Context
+
+ADR 0164 already persists leftover-map rank — the number of Gabriel
+singular values above the leftover singular floor — on leftover pair
+rows. A rank-0 residual still emits a stable closest/farthest pair so
+`make seed` is not empty; the stored distance is then zero, not a
+fabricated interaction. ADR 0279 already names persisted leftover
+expected `E` on the leftover-map graphic-display pair segment. The
+segment still has no rank label, so a buyer who reads `rank 0` on the
+pair row cannot match that collapse to the graphic pair. Hiding rank on
+the segment lets a rank-0 origin cell with `d 0.00` and `E 0.00` be
+read as a two-axis leftover map even after the pair-row badge names no
+leftover structure. When leftover-map rank is a finite non-negative
+integer, the graphic must name the same persisted rank the pair row
+already shows.
+
+This increment names leftover-map graphic-display pair segments with
+persisted leftover-map rank. It does not add columns. It does not
+recompute rank from plotted coordinates, from leftover-map distance, or
+from the count of unused axes. It does not persist leftover-map inner
+product, cosine, or length as separate columns. It does not land Post
+quality on the leftover criterion. Leftover-map distance stays two-axis
+Euclidean. 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 **0280** so it does not
+collide with leftover expected on pair segments (0279), leftover observed
+on pair segments (0278), leftover residual on pair segments (0277),
+leftover-map unexplained leftover on pair segments (0276), leftover-map
+cross share on pair segments (0275), leftover-map unexplained leftover
+share on pair segments (0274), leftover-map explained leftover share on
+pair segments (0273), leftover-map reconstruction on pair segments
+(0272), leftover-map distance on pair segments (0271), leftover-map
+coordinate ticks (0270), leftover-map axis share on the graphic display
+(0269), leftover-map graphic display (0268), leftover-map coordinates
+(0267 / migration 0245), leftover-map explained leftover share
+persistence (0266 / migration 0244), leftover-map unexplained leftover
+share persistence (0233 / migration 0233), leftover-map reconstruction
+persistence (0201 / migration 0206), leftover-map cross share persistence
+(0185), leftover-map unexplained leftover persistence (0182), leftover
+residual disclosure (0162), leftover observed `Y` / expected `E`
+persistence (0163), leftover-map rank persistence (0164), two-axis
+leftover-map distance persistence, leftover coverage, leftover-map axis
+share persistence (0148), leftover interaction-map persistence,
+occupational construct catalog search (0265), or the dashboard stacks.
+
+## Decision
+
+On the leftover-map graphic display, caption each closest or farthest
+pair segment with persisted leftover-map rank as `rank n`, using the
+same rank formatter as the pair-row `rank` badge. A missing rank, a
+negative rank, or a non-integer rank omits that leftover-map rank
+caption and keeps the connecting line and any leftover-map distance,
+reconstruction, explained leftover share, unexplained leftover share,
+leftover-map cross share, unexplained leftover, leftover residual,
+leftover observed, or leftover expected caption. Rank-0 origin cells
+still name `rank 0` when that persisted rank is a non-negative integer.
+Do not invent rank from plotted coordinates, from leftover-map distance,
+or from the count of unused axes. Click a post marker to open that post.
+Criterion markers are not post buttons. The grouping comparison strip
+(ADR 0149) stays on its reduced leftover payload and does not gain these
+leftover-map rank captions.
+
+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`, closest and farthest leftover pairs sit above the
+member list with the leftover-map graphic display of persisted `ξ`
+and `ζ`, leftover-map axes name persisted Gabriel inertia share when
+finite, leftover-map axis ticks name the same coordinates shown on
+the pair row, pair segments name persisted leftover-map distance `d`,
+persisted leftover-map reconstruction `R̂`, persisted leftover-map
+explained leftover share `e`, persisted leftover-map unexplained leftover
+share `s`, persisted leftover-map cross share `x`, persisted leftover-map
+unexplained leftover `U`, persisted leftover residual `R`, persisted
+leftover observed `Y`, persisted leftover expected `E`, and persisted
+leftover-map rank; click a post marker or a pair button opens that post.
+Hidden posts stay hidden. Rank-0 unused axes still plot at the origin,
+still name zero leftover-map axis share with a `0` tick, still name
+`d 0.00` when that distance is persisted, still name `R̂ 0.00` when that
+reconstruction is persisted, still name `R̂²/R² 0.00` when that explained
+leftover share is persisted, still name `U²/R² 0.00` when that unexplained
+leftover share is persisted, still name `2R̂U/R² 0.00` when that leftover-map
+cross share is persisted, still name `U 0.00` when that unexplained leftover
+is persisted, still name `R 0.00` when that leftover residual is persisted,
+still name `Y 0.00` when that leftover observed is persisted, still name
+`E 0.00` when that leftover expected is persisted, and still name `rank 0`
+when that leftover-map rank is persisted. When `Y`, `E`, and `R` are all
+finite, `Y − E = R` remains the same identity already persisted by ADR
+0162 and ADR 0163. When `R`, `R̂`, and `U` are all finite, `U + R̂ = R`
+remains the same identity already persisted by ADR 0162, ADR 0182, and
+ADR 0201. When `R`, `R̂`, `U`, `x`, `s`, and `e` are all finite,
+`e + s + x = 1` remains the same identity already persisted by ADR 0185,
+ADR 0233, and ADR 0266.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover residual disclosure, leftover-map
+complete-case coverage, leftover-map axis share persistence, leftover
+pairs on the grouping comparison strip, two-axis leftover-map distance
+persistence, leftover-map rank persistence, leftover-map inner product,
+leftover-map cosine, leftover-map length, leftover-map reconstruction
+persistence, leftover-map unexplained leftover persistence, leftover-map
+cross share persistence, leftover-map unexplained leftover share
+persistence, leftover-map explained leftover share persistence,
+leftover-map coordinate persistence, leftover-map graphic display,
+leftover-map axis share on the graphic display, leftover-map coordinate
+ticks, leftover-map distance on pair segments, leftover-map reconstruction
+on pair segments, leftover-map explained leftover share on pair segments,
+leftover-map unexplained leftover share on pair segments, leftover-map
+cross share on pair segments, leftover-map unexplained leftover on pair
+segments, leftover residual on pair segments, leftover observed on pair
+segments, and leftover expected on pair segments.
+
+## 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}`. Leftover-map rank is the number of Gabriel
+singular values above the leftover singular floor on the complete-case
+residual rectangle. Rank `0` is an origin collapse, not a two-axis map.)
diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md
index 5db914a52..3eccb3fb5 100644
--- a/docs/product-technical-gap-baseline.md
+++ b/docs/product-technical-gap-baseline.md
@@ -1,5 +1,7 @@
# Product & Technical Gap Baseline
+> Exact-head convergence note: the v2.37 leftover-map successor composition keeps the #802 explained-share foundation while retaining persisted unexplained-share, cross-share, unexplained-U, residual-R, observed-Y, expected-E, and rank captions plus the Vietnamese explained-share terminology regression. This branch remains review-gated; no predecessor check or approval is inherited.
+
> Exact-head rendered-UX overlay: 2026-08-31 12:16 KST. PR #802
> implementation revision `1e972d7f6` retains persisted-only `d`, `R̂`, and
> `e` projection while moving dense segment captions off their plot segments
diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md
index e26bd5c3b..2c1d14bfd 100644
--- a/docs/storybook-inventory.md
+++ b/docs/storybook-inventory.md
@@ -5,8 +5,8 @@ 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, and pair-segment `R̂²/R²` to leftover-map explained leftover share, then click a post marker to open that post. Leftover-map axes name persisted leftover-map axis share when finite. `ClosestAndFarthest`, `RankZeroOrigin`, `MissingCoordinates`, and `MissingAxisShare` cover two-pair maps, rank-0 origin with 0% share, a `0` tick, `d 0.00`, `R̂ 0.00`, and `R̂²/R² 0.00`, omitted plots, and missing share that keeps existing leftover-map axis text. 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, and pair segments name persisted leftover-map explained leftover share. | `LeftoverPairList`, `LeftoverMapPlot`, `ticket-list`, `post-badge` |
+| `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. `ClosestAndFarthest`, `RankZeroOrigin`, `MissingCoordinates`, and `MissingAxisShare` 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, and missing share that keeps existing leftover-map axis text. 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. | `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 8a01b19f8..efb8af9b2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "2.30.0",
+ "version": "2.37.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/LeftoverMapPlot.stories.tsx b/frontend/src/components/LeftoverMapPlot.stories.tsx
index aad15812e..d79587245 100644
--- a/frontend/src/components/LeftoverMapPlot.stories.tsx
+++ b/frontend/src/components/LeftoverMapPlot.stories.tsx
@@ -82,6 +82,9 @@ export const RankZeroOrigin: Story = {
leftover_map_rank: 0,
leftover_map_reconstruction: 0,
leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
leftover_map_person_axis_1: 0,
leftover_map_person_axis_2: 0,
leftover_map_item_axis_1: 0,
diff --git a/frontend/src/components/LeftoverMapPlot.test.tsx b/frontend/src/components/LeftoverMapPlot.test.tsx
index 4e830bc8e..27e0d665e 100644
--- a/frontend/src/components/LeftoverMapPlot.test.tsx
+++ b/frontend/src/components/LeftoverMapPlot.test.tsx
@@ -12,8 +12,14 @@ const PAIRS: LeftoverPair[] = [
criterion_code: "sales_lead_quality",
leftover_distance: 0.12,
leftover_residual: 0.4,
+ observed_response: 2.4,
+ expected_response: 2.0,
leftover_map_reconstruction: 0.248,
leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ leftover_map_unexplained: 0.05,
+ leftover_map_rank: 1,
leftover_map_person_axis_1: 0.5,
leftover_map_person_axis_2: 0.1,
leftover_map_item_axis_1: 0.5,
@@ -26,8 +32,14 @@ const PAIRS: LeftoverPair[] = [
criterion_code: "negative_sentiment",
leftover_distance: 1.84,
leftover_residual: -1.1,
+ observed_response: 0.9,
+ expected_response: 2.0,
leftover_map_reconstruction: -0.95,
leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ leftover_map_unexplained: -0.25,
+ leftover_map_rank: 1,
leftover_map_person_axis_1: 0.9,
leftover_map_person_axis_2: 0.8,
leftover_map_item_axis_1: -0.7,
@@ -49,7 +61,7 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("Leftover-map graphic display")).toBeInTheDocument();
expect(
screen.getByText(
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.",
),
).toBeInTheDocument();
expect(screen.getByText("Post ξ")).toBeInTheDocument();
@@ -65,6 +77,18 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("leftover-map reconstruction R̂ −0.95")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map explained leftover share R̂²/R² 0.76")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map explained leftover share R̂²/R² 0.60")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover share U²/R² 0.02")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover share U²/R² 0.05")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map cross share 2R̂U/R² 0.12")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map cross share 2R̂U/R² -0.24")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover U +0.05")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover U −0.25")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover residual R +0.40")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover residual R −1.10")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover observed Y 2.40")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover observed Y 0.90")).toBeInTheDocument();
+ expect(screen.getAllByLabelText("leftover expected E 2.00")).toHaveLength(2);
+ expect(screen.getAllByLabelText("leftover-map rank rank 1")).toHaveLength(2);
const postMarker = screen.getByRole("button", {
name: "Open leftover-map post Public post at ξ (+0.50, +0.10)",
@@ -110,9 +134,14 @@ describe("LeftoverMapPlot", () => {
criterion_code: "sales_lead_quality",
leftover_distance: 0,
leftover_residual: 0,
+ observed_response: 0,
+ expected_response: 0,
leftover_map_rank: 0,
leftover_map_reconstruction: 0,
leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
leftover_map_person_axis_1: 0,
leftover_map_person_axis_2: 0,
leftover_map_item_axis_1: 0,
@@ -137,6 +166,13 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("leftover-map distance d 0.00")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map reconstruction R̂ 0.00")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map explained leftover share R̂²/R² 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover share U²/R² 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map cross share 2R̂U/R² 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover U 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover residual R 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover observed Y 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover expected E 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map rank rank 0")).toBeInTheDocument();
});
it("renders every distinct persisted tick when rounded labels match", () => {
@@ -269,4 +305,221 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("leftover-map reconstruction R̂ +0.25")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map distance d 0.12")).toBeInTheDocument();
});
+
+ it("omits leftover-map unexplained leftover share on a pair segment when s is missing", () => {
+ render(
+ ,
+ );
+ expect(screen.queryByLabelText(/leftover-map unexplained leftover share/)).not.toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map explained leftover share R̂²/R² 0.76")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map reconstruction R̂ +0.25")).toBeInTheDocument();
+ });
+
+ it("omits leftover-map cross share on a pair segment when x is missing", () => {
+ render(
+ ,
+ );
+ expect(screen.queryByLabelText(/leftover-map cross share/)).not.toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover share U²/R² 0.02")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map explained leftover share R̂²/R² 0.76")).toBeInTheDocument();
+ });
+
+ it("omits leftover-map unexplained leftover on a pair segment when U is missing", () => {
+ render(
+ ,
+ );
+ expect(screen.queryByLabelText(/^leftover-map unexplained leftover U/)).not.toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map cross share 2R̂U/R² 0.12")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover share U²/R² 0.02")).toBeInTheDocument();
+ });
+
+ it("omits leftover residual on a pair segment when R is missing", () => {
+ render(
+ ,
+ );
+ expect(screen.queryByLabelText(/^leftover residual R/)).not.toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover U +0.05")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map cross share 2R̂U/R² 0.12")).toBeInTheDocument();
+ });
+
+ it("omits leftover observed on a pair segment when Y is missing", () => {
+ render(
+ ,
+ );
+ expect(screen.queryByLabelText(/^leftover observed Y/)).not.toBeInTheDocument();
+ expect(screen.getByLabelText("leftover residual R +0.40")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map unexplained leftover U +0.05")).toBeInTheDocument();
+ });
+
+ it("omits leftover expected on a pair segment when E is missing", () => {
+ render(
+ ,
+ );
+ expect(screen.queryByLabelText(/^leftover expected E/)).not.toBeInTheDocument();
+ expect(screen.getByLabelText("leftover observed Y 2.40")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover residual R +0.40")).toBeInTheDocument();
+ });
+
+ it("omits leftover-map rank on a pair segment when rank is missing", () => {
+ render(
+ ,
+ );
+ expect(screen.queryByLabelText(/^leftover-map rank/)).not.toBeInTheDocument();
+ expect(screen.getByLabelText("leftover expected E 2.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover observed Y 2.40")).toBeInTheDocument();
+ });
});
diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx
index 97ca77115..72ea5aae8 100644
--- a/frontend/src/components/LeftoverMapPlot.tsx
+++ b/frontend/src/components/LeftoverMapPlot.tsx
@@ -11,9 +11,16 @@ import {
layoutLeftoverMapPlot,
LEFTOVER_MAP_PLOT_CAPTION,
LEFTOVER_MAP_PLOT_POST_ACTION,
+ LEFTOVER_MAP_PLOT_SEGMENT_CROSS_SHARE,
LEFTOVER_MAP_PLOT_SEGMENT_DISTANCE,
LEFTOVER_MAP_PLOT_SEGMENT_EXPLAINED_SHARE,
+ LEFTOVER_MAP_PLOT_SEGMENT_EXPECTED,
+ LEFTOVER_MAP_PLOT_SEGMENT_OBSERVED,
+ LEFTOVER_MAP_PLOT_SEGMENT_RANK,
LEFTOVER_MAP_PLOT_SEGMENT_RECONSTRUCTION,
+ LEFTOVER_MAP_PLOT_SEGMENT_RESIDUAL,
+ LEFTOVER_MAP_PLOT_SEGMENT_UNEXPLAINED,
+ LEFTOVER_MAP_PLOT_SEGMENT_UNEXPLAINED_SHARE,
LEFTOVER_MAP_PLOT_TICK,
} from "../leftoverMapPlotLayout";
import "./LeftoverMapPlot.css";
@@ -50,15 +57,26 @@ function leftoverMapPlotAxisText(
* Gabriel inertia share when finite, including rank-0 zero-share axes.
* Axis ticks name persisted leftover-map coordinates so ξ / ζ on the
* pair row match the plot. Pair segments name persisted leftover-map
- * distance ``d``, leftover-map reconstruction ``R̂``, and leftover-map
- * explained leftover share ``e`` so the pair-row badges match the graphic.
+ * distance ``d``, leftover-map reconstruction ``R̂``, leftover-map
+ * explained leftover share ``e``, leftover-map unexplained leftover
+ * share ``s``, leftover-map cross share ``x``, leftover-map
+ * unexplained leftover ``U``, leftover residual ``R``, leftover
+ * observed ``Y``, leftover expected ``E``, and leftover-map rank so the
+ * pair-row badges match the graphic.
* Omit that distance caption when ``d`` is missing or non-finite. Omit
* that reconstruction caption when ``R̂`` is missing or non-finite. Omit
* that explained leftover share caption when ``e`` is missing or
- * non-finite. Omit that axis badge when share is missing or non-finite
- * and keep the existing leftover-map axis text. Omit the plot when no
- * pair has four finite leftover-map coordinates. Never invent a leftover
- * score.
+ * non-finite. Omit that unexplained leftover share caption when ``s`` is
+ * missing or non-finite. Omit that leftover-map cross share caption when
+ * ``x`` is missing or non-finite. Omit that unexplained leftover caption
+ * when ``U`` is missing or non-finite. Omit that leftover residual
+ * caption when ``R`` is missing or non-finite. Omit that leftover observed
+ * caption when ``Y`` is missing or non-finite. Omit that leftover expected
+ * caption when ``E`` is missing or non-finite. Omit that leftover-map rank
+ * caption when rank is missing, negative, or not an integer. Omit that axis badge when share is
+ * missing or non-finite and keep the existing leftover-map axis text.
+ * Omit the plot when no pair has four finite leftover-map coordinates.
+ * Never invent a leftover score.
*/
export function LeftoverMapPlot({
pairs,
@@ -183,6 +201,97 @@ export function LeftoverMapPlot({
{segment.explainedShareLabel}
) : null}
+ {segment.unexplainedShareLabel !== null ? (
+
+ {segment.unexplainedShareLabel}
+
+ ) : null}
+ {segment.crossShareLabel !== null ? (
+
+ {segment.crossShareLabel}
+
+ ) : null}
+ {segment.unexplainedLeftoverLabel !== null ? (
+
+ {segment.unexplainedLeftoverLabel}
+
+ ) : null}
+ {segment.residualLabel !== null ? (
+
+ {segment.residualLabel}
+
+ ) : null}
+ {segment.observedLabel !== null ? (
+
+ {segment.observedLabel}
+
+ ) : null}
+ {segment.expectedLabel !== null ? (
+
+ {segment.expectedLabel}
+
+ ) : null}
+ {segment.rankLabel !== null ? (
+
+ {segment.rankLabel}
+
+ ) : null}
))}
{layout.items.map((marker) => (
diff --git a/frontend/src/components/LeftoverPairList.tsx b/frontend/src/components/LeftoverPairList.tsx
index 75e500821..8f3bec764 100644
--- a/frontend/src/components/LeftoverPairList.tsx
+++ b/frontend/src/components/LeftoverPairList.tsx
@@ -63,7 +63,13 @@ export type LeftoverPairListProps = {
* the plot. ADR 0271 names persisted leftover-map distance ``d`` on those
* pair segments. ADR 0272 names persisted leftover-map reconstruction
* ``R̂`` on those pair segments. ADR 0273 names persisted leftover-map
- * explained leftover share ``e`` on those pair segments. Every badge still
+ * explained leftover share ``e`` on those pair segments. ADR 0274 names
+ * persisted leftover-map unexplained leftover share ``s`` on those pair
+ * segments. ADR 0275 names persisted leftover-map cross share ``x`` on
+ * those pair segments. ADR 0276 names persisted leftover-map unexplained
+ * leftover ``U`` on those pair segments. ADR 0277 names persisted leftover
+ * residual ``R`` on those pair segments. ADR 0278 names persisted leftover
+ * observed ``Y`` on those pair segments. 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 4ef3a02a2..d5b56cf09 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -69,7 +69,14 @@ describe("i18n", () => {
"leftover-map distance {label}",
"leftover-map reconstruction {label}",
"leftover-map explained leftover share {label}",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "leftover-map unexplained leftover share {label}",
+ "leftover-map cross share {label}",
+ "leftover-map unexplained leftover {label}",
+ "leftover residual {label}",
+ "leftover observed {label}",
+ "leftover expected {label}",
+ "leftover-map rank {label}",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.",
"Open leftover-map post {title} at ξ {person}",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.",
"Leftover map has no leftover structure after IRT main effects. Open this post.",
@@ -333,22 +340,22 @@ describe("i18n", () => {
it.each([
[
"ko",
- "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
+ "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x, 잔여 지도 미설명 잔여 U, 잔여 R, 관측 Y, 기대 E, 잔여 지도 순위입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
],
- ["zh", "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂ 与残差图已解释残差份额 e。点击帖子标记打开该帖子。此图不会虚构残差分数。"],
+ ["zh", "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s、残差图交叉份额 x、残差图未解释残差 U、残差 R、观测 Y、期望 E 与残差图秩。点击帖子标记打开该帖子。此图不会虚构残差分数。"],
[
"ja",
- "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
+ "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x、残差マップ未説明残差 U、残差 R、観測 Y、期待 E、残差マップ階数です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
],
[
"vi",
- "Bản đồ phần dư sau hiệu ứng chính IRT. Vạch trục ghi tọa độ bản đồ phần dư đã lưu. Đoạn cặp ghi khoảng cách bản đồ phần dư d, tái dựng bản đồ phần dư R̂ và tỷ phần phần dư được giải thích e. Nhấn dấu bài viết để mở bài đó. Hình này không tạo ra điểm phần dư.",
+ "Bản đồ phần dư sau hiệu ứng chính IRT. Vạch trục ghi tọa độ bản đồ phần dư đã lưu. Đoạn cặp ghi khoảng cách bản đồ phần dư d, tái dựng bản đồ phần dư R̂, tỷ phần phần dư được giải thích e, phần dư chưa giải thích s, phần giao x, phần dư chưa giải thích U, phần dư R, Y quan sát, E kỳ vọng và hạng bản đồ phần dư. Nhấn dấu bài viết để mở bài đó. Hình này không tạo ra điểm phần dư.",
],
] as const)("formats leftover-map graphic display caption in %s", (locale, expected) => {
setLocale(locale);
expect(
t(
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.",
),
).toBe(expected);
});
@@ -395,6 +402,78 @@ describe("i18n", () => {
);
});
+ it.each([
+ ["ko", "잔여 지도 미설명 잔여 점유율 U²/R² 0.02"],
+ ["zh", "残差图未解释残差份额 U²/R² 0.02"],
+ ["ja", "残差マップ未説明残差割合 U²/R² 0.02"],
+ ["vi", "phần dư chưa giải thích bản đồ phần dư U²/R² 0.02"],
+ ] as const)("formats leftover-map segment unexplained leftover share in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover-map unexplained leftover share {label}", { label: "U²/R² 0.02" })).toBe(
+ expected,
+ );
+ });
+
+ it.each([
+ ["ko", "잔여 지도 교차 점유율 2R̂U/R² 0.12"],
+ ["zh", "残差图交叉份额 2R̂U/R² 0.12"],
+ ["ja", "残差マップ交差割合 2R̂U/R² 0.12"],
+ ["vi", "phần giao bản đồ phần dư 2R̂U/R² 0.12"],
+ ] as const)("formats leftover-map segment cross share in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover-map cross share {label}", { label: "2R̂U/R² 0.12" })).toBe(expected);
+ });
+
+ it.each([
+ ["ko", "잔여 지도 미설명 잔여 U +0.05"],
+ ["zh", "残差图未解释残差 U +0.05"],
+ ["ja", "残差マップ未説明残差 U +0.05"],
+ ["vi", "phần dư chưa giải thích U +0.05"],
+ ] as const)("formats leftover-map segment unexplained leftover in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover-map unexplained leftover {label}", { label: "U +0.05" })).toBe(expected);
+ });
+
+ it.each([
+ ["ko", "잔여 R +0.40"],
+ ["zh", "残差 R +0.40"],
+ ["ja", "残差 R +0.40"],
+ ["vi", "phần dư R +0.40"],
+ ] as const)("formats leftover-map segment leftover residual in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover residual {label}", { label: "R +0.40" })).toBe(expected);
+ });
+
+ it.each([
+ ["ko", "관측 Y 2.40"],
+ ["zh", "观测 Y 2.40"],
+ ["ja", "観測 Y 2.40"],
+ ["vi", "quan sát Y 2.40"],
+ ] as const)("formats leftover-map segment leftover observed in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover observed {label}", { label: "Y 2.40" })).toBe(expected);
+ });
+
+ it.each([
+ ["ko", "기대 E 2.00"],
+ ["zh", "期望 E 2.00"],
+ ["ja", "期待 E 2.00"],
+ ["vi", "kỳ vọng E 2.00"],
+ ] as const)("formats leftover-map segment leftover expected in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover expected {label}", { label: "E 2.00" })).toBe(expected);
+ });
+
+ it.each([
+ ["ko", "잔여 지도 순위 rank 1"],
+ ["zh", "残差图秩 rank 1"],
+ ["ja", "残差マップ階数 rank 1"],
+ ["vi", "hạng bản đồ phần dư rank 1"],
+ ] as const)("formats leftover-map segment leftover-map rank in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover-map rank {label}", { label: "rank 1" })).toBe(expected);
+ });
+
it.each([
["ko", "잔여 지도 축 1 (82%)"],
["zh", "残差图轴 1 (82%)"],
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index b4816f24c..84b2d10a6 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -594,8 +594,15 @@ const TRANSLATIONS: Partial>> = {
"leftover-map distance {label}": "잔여 지도 거리 {label}",
"leftover-map reconstruction {label}": "잔여 지도 재구성 {label}",
"leftover-map explained leftover share {label}": "잔여 지도 설명 잔여 점유율 {label}",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
+ "leftover-map unexplained leftover share {label}": "잔여 지도 미설명 잔여 점유율 {label}",
+ "leftover-map cross share {label}": "잔여 지도 교차 점유율 {label}",
+ "leftover-map unexplained leftover {label}": "잔여 지도 미설명 잔여 {label}",
+ "leftover residual {label}": "잔여 {label}",
+ "leftover observed {label}": "관측 {label}",
+ "leftover expected {label}": "기대 {label}",
+ "leftover-map rank {label}": "잔여 지도 순위 {label}",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.":
+ "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x, 잔여 지도 미설명 잔여 U, 잔여 R, 관측 Y, 기대 E, 잔여 지도 순위입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
"Open leftover-map post {title} at ξ {person}":
"잔여 지도 글 {title} 열기 (ξ {person})",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.":
@@ -1176,8 +1183,15 @@ const TRANSLATIONS: Partial>> = {
"leftover-map distance {label}": "残差图距离 {label}",
"leftover-map reconstruction {label}": "残差图重建 {label}",
"leftover-map explained leftover share {label}": "残差图已解释残差份额 {label}",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂ 与残差图已解释残差份额 e。点击帖子标记打开该帖子。此图不会虚构残差分数。",
+ "leftover-map unexplained leftover share {label}": "残差图未解释残差份额 {label}",
+ "leftover-map cross share {label}": "残差图交叉份额 {label}",
+ "leftover-map unexplained leftover {label}": "残差图未解释残差 {label}",
+ "leftover residual {label}": "残差 {label}",
+ "leftover observed {label}": "观测 {label}",
+ "leftover expected {label}": "期望 {label}",
+ "leftover-map rank {label}": "残差图秩 {label}",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.":
+ "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s、残差图交叉份额 x、残差图未解释残差 U、残差 R、观测 Y、期望 E 与残差图秩。点击帖子标记打开该帖子。此图不会虚构残差分数。",
"Open leftover-map post {title} at ξ {person}":
"打开残差图帖子 {title}(ξ {person})",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.":
@@ -1762,8 +1776,15 @@ const TRANSLATIONS: Partial>> = {
"leftover-map distance {label}": "残差マップ距離 {label}",
"leftover-map reconstruction {label}": "残差マップ再構成 {label}",
"leftover-map explained leftover share {label}": "残差マップ説明済み残差割合 {label}",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
+ "leftover-map unexplained leftover share {label}": "残差マップ未説明残差割合 {label}",
+ "leftover-map cross share {label}": "残差マップ交差割合 {label}",
+ "leftover-map unexplained leftover {label}": "残差マップ未説明残差 {label}",
+ "leftover residual {label}": "残差 {label}",
+ "leftover observed {label}": "観測 {label}",
+ "leftover expected {label}": "期待 {label}",
+ "leftover-map rank {label}": "残差マップ階数 {label}",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.":
+ "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x、残差マップ未説明残差 U、残差 R、観測 Y、期待 E、残差マップ階数です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
"Open leftover-map post {title} at ξ {person}":
"残差マップの投稿 {title} を開く(ξ {person})",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.":
@@ -2348,8 +2369,15 @@ const TRANSLATIONS: Partial>> = {
"leftover-map distance {label}": "khoảng cách bản đồ phần dư {label}",
"leftover-map reconstruction {label}": "tái dựng bản đồ phần dư {label}",
"leftover-map explained leftover share {label}": "tỷ phần phần dư được giải thích trên bản đồ phần dư {label}",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.":
- "Bản đồ phần dư sau hiệu ứng chính IRT. Vạch trục ghi tọa độ bản đồ phần dư đã lưu. Đoạn cặp ghi khoảng cách bản đồ phần dư d, tái dựng bản đồ phần dư R̂ và tỷ phần phần dư được giải thích e. Nhấn dấu bài viết để mở bài đó. Hình này không tạo ra điểm phần dư.",
+ "leftover-map unexplained leftover share {label}": "phần dư chưa giải thích bản đồ phần dư {label}",
+ "leftover-map cross share {label}": "phần giao bản đồ phần dư {label}",
+ "leftover-map unexplained leftover {label}": "phần dư chưa giải thích {label}",
+ "leftover residual {label}": "phần dư {label}",
+ "leftover observed {label}": "quan sát {label}",
+ "leftover expected {label}": "kỳ vọng {label}",
+ "leftover-map rank {label}": "hạng bản đồ phần dư {label}",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.":
+ "Bản đồ phần dư sau hiệu ứng chính IRT. Vạch trục ghi tọa độ bản đồ phần dư đã lưu. Đoạn cặp ghi khoảng cách bản đồ phần dư d, tái dựng bản đồ phần dư R̂, tỷ phần phần dư được giải thích e, phần dư chưa giải thích s, phần giao x, phần dư chưa giải thích U, phần dư R, Y quan sát, E kỳ vọng và hạng bản đồ phần dư. Nhấn dấu bài viết để mở bài đó. Hình này không tạo ra điểm phần dư.",
"Open leftover-map post {title} at ξ {person}":
"Mở bài viết bản đồ phần dư {title} tại ξ {person}",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.":
diff --git a/frontend/src/leftoverMapPlotDescendantLayout.test.ts b/frontend/src/leftoverMapPlotDescendantLayout.test.ts
new file mode 100644
index 000000000..a965db6e0
--- /dev/null
+++ b/frontend/src/leftoverMapPlotDescendantLayout.test.ts
@@ -0,0 +1,70 @@
+import { describe, expect, it } from "vitest";
+import { layoutLeftoverMapPlot, type LeftoverMapPlottablePair } from "./leftoverMapPlotLayout";
+
+function extendedPair(
+ overrides: Partial = {},
+): LeftoverMapPlottablePair {
+ return {
+ pair_kind: "closest",
+ post_id: "post-descendant-layout",
+ post_title: "Descendant layout post",
+ criterion_code: "sales_lead_quality",
+ leftover_distance: 0.12,
+ leftover_map_reconstruction: 0.25,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.24,
+ leftover_map_cross_share: -0.10,
+ leftover_map_unexplained: 0.50,
+ leftover_residual: 0.75,
+ observed_response: 2.40,
+ expected_response: 2.00,
+ leftover_map_rank: 0,
+ leftover_map_person_axis_1: -1,
+ leftover_map_person_axis_2: -1,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: -1,
+ ...overrides,
+ };
+}
+
+describe("leftover-map descendant layout inherits #802 bounds", () => {
+ it("preserves distinct persisted ticks even when their formatted labels round identically", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ extendedPair({
+ leftover_map_person_axis_1: 0.001,
+ leftover_map_item_axis_1: 0.004,
+ }),
+ ],
+ (criterionCode) => criterionCode,
+ );
+
+ const axis1 = layout?.ticks.filter((tick) => tick.axis === 1);
+ expect(axis1?.map((tick) => tick.value)).toEqual([0, 0.001, 0.004]);
+ expect(axis1?.map((tick) => tick.label)).toEqual(["0.00", "+0.00", "+0.00"]);
+ });
+
+ it("keeps all ten persisted captions inside an 80px canvas", () => {
+ const layout = layoutLeftoverMapPlot(
+ [extendedPair()],
+ (criterionCode) => criterionCode,
+ { height: 80 },
+ );
+
+ expect(layout).not.toBeNull();
+ expect(layout?.height).toBe(80);
+ const segment = layout?.segments[0];
+ expect(segment).toBeDefined();
+ expect(segment?.labelY).toBeGreaterThanOrEqual(12);
+ expect(segment?.reconstructionY).toBeGreaterThanOrEqual(segment?.labelY ?? 0);
+ expect(segment?.explainedShareY).toBeGreaterThanOrEqual(segment?.reconstructionY ?? 0);
+ expect(segment?.unexplainedShareY).toBeGreaterThanOrEqual(segment?.explainedShareY ?? 0);
+ expect(segment?.crossShareY).toBeGreaterThanOrEqual(segment?.unexplainedShareY ?? 0);
+ expect(segment?.unexplainedLeftoverY).toBeGreaterThanOrEqual(segment?.crossShareY ?? 0);
+ expect(segment?.residualY).toBeGreaterThanOrEqual(segment?.unexplainedLeftoverY ?? 0);
+ expect(segment?.observedY).toBeGreaterThanOrEqual(segment?.residualY ?? 0);
+ expect(segment?.expectedY).toBeGreaterThanOrEqual(segment?.observedY ?? 0);
+ expect(segment?.rankY).toBeGreaterThanOrEqual(segment?.expectedY ?? 0);
+ expect(segment?.rankY).toBeLessThanOrEqual(76);
+ });
+});
diff --git a/frontend/src/leftoverMapPlotLayout.test.ts b/frontend/src/leftoverMapPlotLayout.test.ts
index 51f266522..cc2b161d0 100644
--- a/frontend/src/leftoverMapPlotLayout.test.ts
+++ b/frontend/src/leftoverMapPlotLayout.test.ts
@@ -25,6 +25,10 @@ function pair(
leftover_distance: 0.12,
leftover_map_reconstruction: 0.248,
leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ leftover_map_unexplained: 0.05,
+ leftover_residual: 0.4,
...overrides,
};
}
@@ -401,6 +405,9 @@ describe("layoutLeftoverMapPlot", () => {
leftover_map_person_axis_2: -1,
leftover_map_item_axis_1: 1,
leftover_map_item_axis_2: -1,
+ observed_response: 2.4,
+ expected_response: 2.0,
+ leftover_map_rank: 1,
}),
pair({
pair_kind: "farthest",
@@ -410,14 +417,20 @@ describe("layoutLeftoverMapPlot", () => {
leftover_map_person_axis_2: 1,
leftover_map_item_axis_1: -1,
leftover_map_item_axis_2: 1,
+ observed_response: 2.4,
+ expected_response: 2.0,
+ leftover_map_rank: 1,
}),
],
criterionLabel,
);
- for (const segment of layout?.segments ?? []) {
+ expect(layout?.segments).toHaveLength(2);
+ for (const segment of layout!.segments) {
expect(segment.labelY).toBeGreaterThanOrEqual(12);
- expect(segment.explainedShareY).toBeLessThanOrEqual((layout?.height ?? 0) - 4);
+ expect(segment.rankLabel).toBe("rank 1");
+ expect(segment.rankY).toBeGreaterThan(segment.expectedY);
+ expect(segment.rankY).toBeLessThanOrEqual(layout!.height - 4);
}
});
@@ -479,6 +492,741 @@ describe("layoutLeftoverMapPlot", () => {
expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
});
+
+ it("names persisted leftover-map unexplained leftover share on pair segments without inventing a leftover score", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair(),
+ pair({
+ pair_kind: "farthest",
+ post_id: "post-demo-spec",
+ criterion_code: "negative_sentiment",
+ leftover_map_person_axis_1: 0.9,
+ leftover_map_person_axis_2: 0.8,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ leftover_distance: 1.84,
+ leftover_map_reconstruction: -0.95,
+ leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.unexplainedShareLabel)).toEqual([
+ "U\u00b2/R\u00b2 0.02",
+ "U\u00b2/R\u00b2 0.05",
+ ]);
+ expect(layout?.segments[0]?.unexplainedShareX).toBeCloseTo(layout?.segments[0]?.labelX ?? 0, 5);
+ expect(layout?.segments[0]?.unexplainedShareY).toBeGreaterThan(
+ layout?.segments[0]?.explainedShareY ?? 0,
+ );
+ });
+
+ it("omits a leftover-map unexplained leftover share caption when s is missing or non-finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ leftover_map_unexplained_share: null }),
+ pair({
+ pair_kind: "farthest",
+ leftover_map_unexplained_share: Number.NaN,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ criterion_code: "negative_sentiment",
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.unexplainedShareLabel)).toEqual([null, null]);
+ expect(layout?.segments.map((segment) => segment.explainedShareLabel)).toEqual([
+ "R\u0302\u00b2/R\u00b2 0.76",
+ "R\u0302\u00b2/R\u00b2 0.76",
+ ]);
+ });
+
+ it("does not invent leftover-map unexplained leftover share from unexplained leftover or residual", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 1,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: 0,
+ leftover_map_reconstruction: 1,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.02");
+ expect(layout?.segments[0]?.unexplainedShareLabel).not.toBe("U\u00b2/R\u00b2 1.00");
+ });
+
+ it("names rank-0 origin unexplained leftover share s 0.00 when that persisted value is finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 0,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 0,
+ leftover_map_item_axis_2: 0,
+ leftover_distance: 0,
+ leftover_map_reconstruction: 0,
+ leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.explainedShareLabel).toBe("R\u0302\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
+ expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
+ });
+
+ it("names persisted leftover-map cross share on pair segments without inventing a leftover score", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair(),
+ pair({
+ pair_kind: "farthest",
+ post_id: "post-demo-spec",
+ criterion_code: "negative_sentiment",
+ leftover_map_person_axis_1: 0.9,
+ leftover_map_person_axis_2: 0.8,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ leftover_distance: 1.84,
+ leftover_map_reconstruction: -0.95,
+ leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.crossShareLabel)).toEqual([
+ "2R\u0302U/R\u00b2 0.12",
+ "2R\u0302U/R\u00b2 -0.24",
+ ]);
+ expect(layout?.segments[0]?.crossShareX).toBeCloseTo(layout?.segments[0]?.labelX ?? 0, 5);
+ expect(layout?.segments[0]?.crossShareY).toBeGreaterThan(
+ layout?.segments[0]?.unexplainedShareY ?? 0,
+ );
+ });
+
+ it("omits a leftover-map cross share caption when x is missing or non-finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ leftover_map_cross_share: null }),
+ pair({
+ pair_kind: "farthest",
+ leftover_map_cross_share: Number.NaN,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ criterion_code: "negative_sentiment",
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.crossShareLabel)).toEqual([null, null]);
+ expect(layout?.segments.map((segment) => segment.unexplainedShareLabel)).toEqual([
+ "U\u00b2/R\u00b2 0.02",
+ "U\u00b2/R\u00b2 0.02",
+ ]);
+ });
+
+ it("does not invent leftover-map cross share from reconstruction or residual", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 1,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: 0,
+ leftover_map_reconstruction: 1,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.crossShareLabel).toBe("2R\u0302U/R\u00b2 0.12");
+ expect(layout?.segments[0]?.crossShareLabel).not.toBe("2R\u0302U/R\u00b2 1.00");
+ });
+
+ it("names rank-0 origin leftover-map cross share x 0.00 when that persisted value is finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 0,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 0,
+ leftover_map_item_axis_2: 0,
+ leftover_distance: 0,
+ leftover_map_reconstruction: 0,
+ leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.crossShareLabel).toBe("2R\u0302U/R\u00b2 0.00");
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).toBe("U 0.00");
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.explainedShareLabel).toBe("R\u0302\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
+ expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
+ });
+
+ it("names persisted leftover-map unexplained leftover on pair segments without inventing a leftover score", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair(),
+ pair({
+ pair_kind: "farthest",
+ post_id: "post-demo-spec",
+ criterion_code: "negative_sentiment",
+ leftover_map_person_axis_1: 0.9,
+ leftover_map_person_axis_2: 0.8,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ leftover_distance: 1.84,
+ leftover_map_reconstruction: -0.95,
+ leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ leftover_map_unexplained: -0.25,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.unexplainedLeftoverLabel)).toEqual([
+ "U +0.05",
+ "U \u22120.25",
+ ]);
+ expect(layout?.segments[0]?.unexplainedLeftoverX).toBeCloseTo(layout?.segments[0]?.labelX ?? 0, 5);
+ expect(layout?.segments[0]?.unexplainedLeftoverY).toBeGreaterThan(
+ layout?.segments[0]?.crossShareY ?? 0,
+ );
+ });
+
+ it("omits a leftover-map unexplained leftover caption when U is missing or non-finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ leftover_map_unexplained: null }),
+ pair({
+ pair_kind: "farthest",
+ leftover_map_unexplained: Number.NaN,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ criterion_code: "negative_sentiment",
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.unexplainedLeftoverLabel)).toEqual([null, null]);
+ expect(layout?.segments.map((segment) => segment.crossShareLabel)).toEqual([
+ "2R\u0302U/R\u00b2 0.12",
+ "2R\u0302U/R\u00b2 0.12",
+ ]);
+ });
+
+ it("does not invent leftover-map unexplained leftover from reconstruction or residual", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 1,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: 0,
+ leftover_map_reconstruction: 1,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ leftover_map_unexplained: 0.05,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).toBe("U +0.05");
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).not.toBe("U +0.15");
+ });
+
+ it("names rank-0 origin unexplained leftover U 0.00 when that persisted value is finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 0,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 0,
+ leftover_map_item_axis_2: 0,
+ leftover_distance: 0,
+ leftover_map_reconstruction: 0,
+ leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).toBe("U 0.00");
+ expect(layout?.segments[0]?.crossShareLabel).toBe("2R\u0302U/R\u00b2 0.00");
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.explainedShareLabel).toBe("R\u0302\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
+ expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
+ });
+
+ it("names persisted leftover residual on pair segments without inventing a leftover score", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair(),
+ pair({
+ pair_kind: "farthest",
+ post_id: "post-demo-spec",
+ criterion_code: "negative_sentiment",
+ leftover_map_person_axis_1: 0.9,
+ leftover_map_person_axis_2: 0.8,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ leftover_distance: 1.84,
+ leftover_map_reconstruction: -0.95,
+ leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ leftover_map_unexplained: -0.25,
+ leftover_residual: -1.1,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.residualLabel)).toEqual([
+ "R +0.40",
+ "R \u22121.10",
+ ]);
+ expect(layout?.segments[0]?.residualX).toBeCloseTo(layout?.segments[0]?.labelX ?? 0, 5);
+ expect(layout?.segments[0]?.residualY).toBeGreaterThan(
+ layout?.segments[0]?.unexplainedLeftoverY ?? 0,
+ );
+ });
+
+ it("omits a leftover residual caption when R is missing or non-finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ leftover_residual: null }),
+ pair({
+ pair_kind: "farthest",
+ leftover_residual: Number.NaN,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ criterion_code: "negative_sentiment",
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.residualLabel)).toEqual([null, null]);
+ expect(layout?.segments.map((segment) => segment.unexplainedLeftoverLabel)).toEqual([
+ "U +0.05",
+ "U +0.05",
+ ]);
+ });
+
+ it("does not invent leftover residual from unexplained leftover or reconstruction", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 1,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: 0,
+ leftover_map_reconstruction: 1,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ leftover_map_unexplained: 0.05,
+ leftover_residual: 0.4,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.residualLabel).toBe("R +0.40");
+ expect(layout?.segments[0]?.residualLabel).not.toBe("R +1.05");
+ });
+
+ it("names rank-0 origin leftover residual R 0.00 when that persisted value is finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 0,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 0,
+ leftover_map_item_axis_2: 0,
+ leftover_distance: 0,
+ leftover_map_reconstruction: 0,
+ leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
+ leftover_residual: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.residualLabel).toBe("R 0.00");
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).toBe("U 0.00");
+ expect(layout?.segments[0]?.crossShareLabel).toBe("2R\u0302U/R\u00b2 0.00");
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.explainedShareLabel).toBe("R\u0302\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
+ expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
+ });
+
+ it("names persisted leftover observed on pair segments without inventing a leftover score", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ observed_response: 2.4 }),
+ pair({
+ pair_kind: "farthest",
+ post_id: "post-demo-spec",
+ criterion_code: "negative_sentiment",
+ leftover_map_person_axis_1: 0.9,
+ leftover_map_person_axis_2: 0.8,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ leftover_distance: 1.84,
+ leftover_map_reconstruction: -0.95,
+ leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ leftover_map_unexplained: -0.25,
+ leftover_residual: -1.1,
+ observed_response: 0.9,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.observedLabel)).toEqual([
+ "Y 2.40",
+ "Y 0.90",
+ ]);
+ expect(layout?.segments[0]?.observedX).toBeCloseTo(layout?.segments[0]?.labelX ?? 0, 5);
+ expect(layout?.segments[0]?.observedY).toBeGreaterThan(
+ layout?.segments[0]?.residualY ?? 0,
+ );
+ });
+
+ it("omits a leftover observed caption when Y is missing or non-finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ observed_response: null }),
+ pair({
+ pair_kind: "farthest",
+ observed_response: Number.NaN,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ criterion_code: "negative_sentiment",
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.observedLabel)).toEqual([null, null]);
+ expect(layout?.segments.map((segment) => segment.residualLabel)).toEqual([
+ "R +0.40",
+ "R +0.40",
+ ]);
+ });
+
+ it("does not invent leftover observed from residual and expected", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 1,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: 0,
+ leftover_map_reconstruction: 1,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ leftover_map_unexplained: 0.05,
+ leftover_residual: 0.4,
+ observed_response: 9.9,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.observedLabel).toBe("Y 9.90");
+ expect(layout?.segments[0]?.observedLabel).not.toBe("Y 2.40");
+ });
+
+ it("names rank-0 origin leftover observed Y 0.00 when that persisted value is finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 0,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 0,
+ leftover_map_item_axis_2: 0,
+ leftover_distance: 0,
+ leftover_map_reconstruction: 0,
+ leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
+ leftover_residual: 0,
+ observed_response: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.observedLabel).toBe("Y 0.00");
+ expect(layout?.segments[0]?.residualLabel).toBe("R 0.00");
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).toBe("U 0.00");
+ expect(layout?.segments[0]?.crossShareLabel).toBe("2R\u0302U/R\u00b2 0.00");
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.explainedShareLabel).toBe("R\u0302\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
+ expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
+ });
+
+ it("names persisted leftover expected on pair segments without inventing a leftover score", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ observed_response: 2.4, expected_response: 2.0 }),
+ pair({
+ pair_kind: "farthest",
+ post_id: "post-demo-spec",
+ criterion_code: "negative_sentiment",
+ leftover_map_person_axis_1: 0.9,
+ leftover_map_person_axis_2: 0.8,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ leftover_distance: 1.84,
+ leftover_map_reconstruction: -0.95,
+ leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ leftover_map_unexplained: -0.25,
+ leftover_residual: -1.1,
+ observed_response: 0.9,
+ expected_response: 2.0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.expectedLabel)).toEqual([
+ "E 2.00",
+ "E 2.00",
+ ]);
+ expect(layout?.segments[0]?.expectedX).toBeCloseTo(layout?.segments[0]?.labelX ?? 0, 5);
+ expect(layout?.segments[0]?.expectedY).toBeGreaterThan(
+ layout?.segments[0]?.observedY ?? 0,
+ );
+ });
+
+ it("omits a leftover expected caption when E is missing or non-finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ observed_response: 2.4, expected_response: null }),
+ pair({
+ pair_kind: "farthest",
+ observed_response: 2.4,
+ expected_response: Number.NaN,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ criterion_code: "negative_sentiment",
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.expectedLabel)).toEqual([null, null]);
+ expect(layout?.segments.map((segment) => segment.observedLabel)).toEqual([
+ "Y 2.40",
+ "Y 2.40",
+ ]);
+ });
+
+ it("does not invent leftover expected from observed and residual", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 1,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: 0,
+ leftover_map_reconstruction: 1,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ leftover_map_unexplained: 0.05,
+ leftover_residual: 0.4,
+ observed_response: 2.4,
+ expected_response: 9.9,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.expectedLabel).toBe("E 9.90");
+ expect(layout?.segments[0]?.expectedLabel).not.toBe("E 2.00");
+ });
+
+ it("names rank-0 origin leftover expected E 0.00 when that persisted value is finite", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 0,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 0,
+ leftover_map_item_axis_2: 0,
+ leftover_distance: 0,
+ leftover_map_reconstruction: 0,
+ leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
+ leftover_residual: 0,
+ observed_response: 0,
+ expected_response: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.expectedLabel).toBe("E 0.00");
+ expect(layout?.segments[0]?.observedLabel).toBe("Y 0.00");
+ expect(layout?.segments[0]?.residualLabel).toBe("R 0.00");
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).toBe("U 0.00");
+ expect(layout?.segments[0]?.crossShareLabel).toBe("2R\u0302U/R\u00b2 0.00");
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.explainedShareLabel).toBe("R\u0302\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
+ expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
+ });
+
+ it("names persisted leftover-map rank on pair segments without inventing leftover structure", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ leftover_map_rank: 1, observed_response: 2.4, expected_response: 2.0 }),
+ pair({
+ pair_kind: "farthest",
+ post_id: "post-demo-spec",
+ criterion_code: "negative_sentiment",
+ leftover_map_person_axis_1: 0.9,
+ leftover_map_person_axis_2: 0.8,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ leftover_distance: 1.84,
+ leftover_map_reconstruction: -0.95,
+ leftover_map_explained_share: 0.6,
+ leftover_map_unexplained_share: 0.05,
+ leftover_map_cross_share: -0.24,
+ leftover_map_unexplained: -0.25,
+ leftover_residual: -1.1,
+ observed_response: 0.9,
+ expected_response: 2.0,
+ leftover_map_rank: 1,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.rankLabel)).toEqual([
+ "rank 1",
+ "rank 1",
+ ]);
+ expect(layout?.segments[0]?.rankX).toBeCloseTo(layout?.segments[0]?.labelX ?? 0, 5);
+ expect(layout?.segments[0]?.rankY).toBeGreaterThan(
+ layout?.segments[0]?.expectedY ?? 0,
+ );
+ });
+
+ it("omits a leftover-map rank caption when rank is missing or not a non-negative integer", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({ leftover_map_rank: null, expected_response: 2.0 }),
+ pair({
+ pair_kind: "farthest",
+ leftover_map_rank: Number.NaN,
+ leftover_map_item_axis_1: -0.7,
+ leftover_map_item_axis_2: -0.4,
+ criterion_code: "negative_sentiment",
+ expected_response: 2.0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments.map((segment) => segment.rankLabel)).toEqual([null, null]);
+ expect(layout?.segments.map((segment) => segment.expectedLabel)).toEqual([
+ "E 2.00",
+ "E 2.00",
+ ]);
+ });
+
+ it("does not invent leftover-map rank from plotted coordinates", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 1,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: 0,
+ leftover_map_reconstruction: 1,
+ leftover_map_explained_share: 0.76,
+ leftover_map_unexplained_share: 0.02,
+ leftover_map_cross_share: 0.12,
+ leftover_map_unexplained: 0.05,
+ leftover_residual: 0.4,
+ observed_response: 2.4,
+ expected_response: 2.0,
+ leftover_map_rank: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.rankLabel).toBe("rank 0");
+ expect(layout?.segments[0]?.rankLabel).not.toBe("rank 2");
+ expect(layout?.segments[0]?.rankLabel).not.toBe("rank 1");
+ });
+
+ it("names rank-0 origin leftover-map rank 0 when that persisted value is a non-negative integer", () => {
+ const layout = layoutLeftoverMapPlot(
+ [
+ pair({
+ leftover_map_person_axis_1: 0,
+ leftover_map_person_axis_2: 0,
+ leftover_map_item_axis_1: 0,
+ leftover_map_item_axis_2: 0,
+ leftover_distance: 0,
+ leftover_map_reconstruction: 0,
+ leftover_map_explained_share: 0,
+ leftover_map_unexplained_share: 0,
+ leftover_map_cross_share: 0,
+ leftover_map_unexplained: 0,
+ leftover_residual: 0,
+ observed_response: 0,
+ expected_response: 0,
+ leftover_map_rank: 0,
+ }),
+ ],
+ criterionLabel,
+ );
+ expect(layout?.segments[0]?.rankLabel).toBe("rank 0");
+ expect(layout?.segments[0]?.expectedLabel).toBe("E 0.00");
+ expect(layout?.segments[0]?.observedLabel).toBe("Y 0.00");
+ expect(layout?.segments[0]?.residualLabel).toBe("R 0.00");
+ expect(layout?.segments[0]?.unexplainedLeftoverLabel).toBe("U 0.00");
+ expect(layout?.segments[0]?.crossShareLabel).toBe("2R\u0302U/R\u00b2 0.00");
+ expect(layout?.segments[0]?.unexplainedShareLabel).toBe("U\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.explainedShareLabel).toBe("R\u0302\u00b2/R\u00b2 0.00");
+ expect(layout?.segments[0]?.reconstructionLabel).toBe("R\u0302 0.00");
+ expect(layout?.segments[0]?.distanceLabel).toBe("d 0.00");
+ });
});
describe("formatLeftoverMapDistance", () => {
diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts
index 638ad9513..cb1116c16 100644
--- a/frontend/src/leftoverMapPlotLayout.ts
+++ b/frontend/src/leftoverMapPlotLayout.ts
@@ -2,18 +2,32 @@
* Leftover-map axis share captions axes 1 and 2 when finite (ADR 0269).
* Axis ticks name persisted leftover-map coordinates (ADR 0270).
* Pair segments name persisted leftover-map distance ``d`` (ADR 0271),
- * persisted leftover-map reconstruction ``R̂`` (ADR 0272), and persisted
- * leftover-map explained leftover share ``e`` (ADR 0273).
+ * persisted leftover-map reconstruction ``R̂`` (ADR 0272), persisted
+ * leftover-map explained leftover share ``e`` (ADR 0273), persisted
+ * leftover-map unexplained leftover share ``s`` (ADR 0274), persisted
+ * leftover-map cross share ``x`` (ADR 0275), persisted leftover-map
+ * unexplained leftover ``U`` (ADR 0276), persisted leftover residual
+ * ``R`` (ADR 0277), persisted leftover observed ``Y`` (ADR 0278),
+ * persisted leftover expected ``E`` (ADR 0279), and persisted leftover-map
+ * rank (ADR 0280).
*/
import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates";
+import { formatLeftoverMapCrossShare } from "./leftoverMapCrossShare";
import { formatLeftoverMapExplainedShare } from "./leftoverMapExplainedShare";
+import { formatLeftoverMapRank } from "./leftoverMapRank";
import { formatLeftoverMapReconstruction } from "./leftoverMapReconstruction";
-import { formatSignedLeftoverValue } from "./leftoverMapUnexplained";
+import {
+ formatLeftoverMapUnexplained,
+ formatSignedLeftoverValue,
+} from "./leftoverMapUnexplained";
+import { formatLeftoverMapUnexplainedShare } from "./leftoverMapUnexplainedShare";
+import { formatLeftoverMapExpected, formatLeftoverMapObserved } from "./leftoverObservedExpected";
+import { formatLeftoverMapResidual } from "./leftoverResidual";
import type { LeftoverPair } from "./api";
export const LEFTOVER_MAP_PLOT_CAPTION =
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, and leftover-map explained leftover share e. Click a post marker to open that post. The plot does not invent a leftover score.";
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.";
export const LEFTOVER_MAP_PLOT_POST_ACTION =
"Open leftover-map post {title} at ξ {person}";
@@ -30,6 +44,27 @@ export const LEFTOVER_MAP_PLOT_SEGMENT_RECONSTRUCTION =
export const LEFTOVER_MAP_PLOT_SEGMENT_EXPLAINED_SHARE =
"leftover-map explained leftover share {label}";
+export const LEFTOVER_MAP_PLOT_SEGMENT_UNEXPLAINED_SHARE =
+ "leftover-map unexplained leftover share {label}";
+
+export const LEFTOVER_MAP_PLOT_SEGMENT_CROSS_SHARE =
+ "leftover-map cross share {label}";
+
+export const LEFTOVER_MAP_PLOT_SEGMENT_UNEXPLAINED =
+ "leftover-map unexplained leftover {label}";
+
+export const LEFTOVER_MAP_PLOT_SEGMENT_RESIDUAL =
+ "leftover residual {label}";
+
+export const LEFTOVER_MAP_PLOT_SEGMENT_OBSERVED =
+ "leftover observed {label}";
+
+export const LEFTOVER_MAP_PLOT_SEGMENT_EXPECTED =
+ "leftover expected {label}";
+
+export const LEFTOVER_MAP_PLOT_SEGMENT_RANK =
+ "leftover-map rank {label}";
+
export const PLOT_WIDTH = 480;
export const PLOT_HEIGHT = 320;
export const PLOT_PADDING = 40;
@@ -50,6 +85,13 @@ export type LeftoverMapPlottablePair = {
leftover_distance?: number | null;
leftover_map_reconstruction?: number | null;
leftover_map_explained_share?: number | null;
+ leftover_map_unexplained_share?: number | null;
+ leftover_map_cross_share?: number | null;
+ leftover_map_unexplained?: number | null;
+ leftover_residual?: number | null;
+ observed_response?: number | null;
+ expected_response?: number | null;
+ leftover_map_rank?: number | null;
leftover_map_person_axis_1?: number | null;
leftover_map_person_axis_2?: number | null;
leftover_map_item_axis_1?: number | null;
@@ -77,12 +119,33 @@ export type LeftoverMapPlotSegment = {
distanceLabel: string | null;
reconstructionLabel: string | null;
explainedShareLabel: string | null;
+ unexplainedShareLabel: string | null;
+ crossShareLabel: string | null;
+ unexplainedLeftoverLabel: string | null;
+ residualLabel: string | null;
+ observedLabel: string | null;
+ expectedLabel: string | null;
+ rankLabel: string | null;
labelX: number;
labelY: number;
reconstructionX: number;
reconstructionY: number;
explainedShareX: number;
explainedShareY: number;
+ unexplainedShareX: number;
+ unexplainedShareY: number;
+ crossShareX: number;
+ crossShareY: number;
+ unexplainedLeftoverX: number;
+ unexplainedLeftoverY: number;
+ residualX: number;
+ residualY: number;
+ observedX: number;
+ observedY: number;
+ expectedX: number;
+ expectedY: number;
+ rankX: number;
+ rankY: number;
};
export type LeftoverMapPlotTick = {
@@ -335,15 +398,35 @@ export function layoutLeftoverMapPlot(
const explainedShareLabel = formatLeftoverMapExplainedShare(
pair.leftover_map_explained_share,
);
+ const unexplainedShareLabel = formatLeftoverMapUnexplainedShare(
+ pair.leftover_map_unexplained_share,
+ );
+ const crossShareLabel = formatLeftoverMapCrossShare(pair.leftover_map_cross_share);
+ const unexplainedLeftoverLabel = formatLeftoverMapUnexplained(
+ pair.leftover_map_unexplained,
+ );
+ const residualLabel = formatLeftoverMapResidual(pair.leftover_residual);
+ const observedLabel = formatLeftoverMapObserved(pair.observed_response);
+ const expectedLabel = formatLeftoverMapExpected(pair.expected_response);
+ const rankLabel = formatLeftoverMapRank(pair.leftover_map_rank);
const labelPosition = leftoverMapSegmentLabelPosition(
personPos.x,
personPos.y,
itemPos.x,
itemPos.y,
);
- const captionCount = [distanceLabel, reconstructionLabel, explainedShareLabel].filter(
- (label) => label !== null,
- ).length;
+ const captionCount = [
+ distanceLabel,
+ reconstructionLabel,
+ explainedShareLabel,
+ unexplainedShareLabel,
+ crossShareLabel,
+ unexplainedLeftoverLabel,
+ residualLabel,
+ observedLabel,
+ expectedLabel,
+ rankLabel,
+ ].filter((label) => label !== null).length;
const captionSpacing = leftoverMapCaptionSpacing(height, captionCount);
const maximumLabelY = Math.max(
SEGMENT_LABEL_TOP_INSET,
@@ -365,6 +448,76 @@ export function layoutLeftoverMapPlot(
(distanceLabel !== null ? 1 : 0) + (reconstructionLabel !== null ? 1 : 0),
captionSpacing,
);
+ const unexplainedShareY = leftoverMapStackedCaptionY(
+ labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0),
+ captionSpacing,
+ );
+ const crossShareY = leftoverMapStackedCaptionY(
+ labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0) +
+ (unexplainedShareLabel !== null ? 1 : 0),
+ captionSpacing,
+ );
+ const unexplainedLeftoverY = leftoverMapStackedCaptionY(
+ labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0) +
+ (unexplainedShareLabel !== null ? 1 : 0) +
+ (crossShareLabel !== null ? 1 : 0),
+ captionSpacing,
+ );
+ const residualY = leftoverMapStackedCaptionY(
+ labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0) +
+ (unexplainedShareLabel !== null ? 1 : 0) +
+ (crossShareLabel !== null ? 1 : 0) +
+ (unexplainedLeftoverLabel !== null ? 1 : 0),
+ captionSpacing,
+ );
+ const observedY = leftoverMapStackedCaptionY(
+ labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0) +
+ (unexplainedShareLabel !== null ? 1 : 0) +
+ (crossShareLabel !== null ? 1 : 0) +
+ (unexplainedLeftoverLabel !== null ? 1 : 0) +
+ (residualLabel !== null ? 1 : 0),
+ captionSpacing,
+ );
+ const expectedY = leftoverMapStackedCaptionY(
+ labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0) +
+ (unexplainedShareLabel !== null ? 1 : 0) +
+ (crossShareLabel !== null ? 1 : 0) +
+ (unexplainedLeftoverLabel !== null ? 1 : 0) +
+ (residualLabel !== null ? 1 : 0) +
+ (observedLabel !== null ? 1 : 0),
+ captionSpacing,
+ );
+ const rankY = leftoverMapStackedCaptionY(
+ labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0) +
+ (unexplainedShareLabel !== null ? 1 : 0) +
+ (crossShareLabel !== null ? 1 : 0) +
+ (unexplainedLeftoverLabel !== null ? 1 : 0) +
+ (residualLabel !== null ? 1 : 0) +
+ (observedLabel !== null ? 1 : 0) +
+ (expectedLabel !== null ? 1 : 0),
+ captionSpacing,
+ );
segments.push({
pairKind: pair.pair_kind === "farthest" ? "farthest" : "closest",
postId: pair.post_id,
@@ -376,10 +529,31 @@ export function layoutLeftoverMapPlot(
distanceLabel,
reconstructionLabel,
explainedShareLabel,
+ unexplainedShareLabel,
+ crossShareLabel,
+ unexplainedLeftoverLabel,
+ residualLabel,
+ observedLabel,
+ expectedLabel,
+ rankLabel,
reconstructionX: labelPosition.labelX,
reconstructionY,
explainedShareX: labelPosition.labelX,
explainedShareY,
+ unexplainedShareX: labelPosition.labelX,
+ unexplainedShareY,
+ crossShareX: labelPosition.labelX,
+ crossShareY,
+ unexplainedLeftoverX: labelPosition.labelX,
+ unexplainedLeftoverY,
+ residualX: labelPosition.labelX,
+ residualY,
+ observedX: labelPosition.labelX,
+ observedY,
+ expectedX: labelPosition.labelX,
+ expectedY,
+ rankX: labelPosition.labelX,
+ rankY,
...labelPosition,
labelY,
});
diff --git a/frontend/src/leftoverMapPlotVietnameseCopy.test.ts b/frontend/src/leftoverMapPlotVietnameseCopy.test.ts
new file mode 100644
index 000000000..16d29d5db
--- /dev/null
+++ b/frontend/src/leftoverMapPlotVietnameseCopy.test.ts
@@ -0,0 +1,27 @@
+import { afterEach, describe, expect, it } from "vitest";
+import { setLocale, tf } from "./i18n";
+
+const GRAPHIC_DISPLAY_COPY =
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.";
+
+afterEach(() => {
+ setLocale("en");
+});
+
+describe("leftover-map Vietnamese copy inheritance", () => {
+ it("keeps explained leftover share terminology as an explicit share", () => {
+ setLocale("vi");
+
+ expect(
+ tf("leftover-map explained leftover share {label}", { label: "R̂²/R² 0.76" }),
+ ).toBe("tỷ phần phần dư được giải thích trên bản đồ phần dư R̂²/R² 0.76");
+ });
+
+ it("keeps the expanded graphic description explicit about explained share e", () => {
+ setLocale("vi");
+
+ expect(tf(GRAPHIC_DISPLAY_COPY, {})).toContain(
+ "tỷ phần phần dư được giải thích e",
+ );
+ });
+});
diff --git a/frontend/src/leftoverObservedExpected.test.ts b/frontend/src/leftoverObservedExpected.test.ts
index 56008273a..52ead7f6d 100644
--- a/frontend/src/leftoverObservedExpected.test.ts
+++ b/frontend/src/leftoverObservedExpected.test.ts
@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
-import { formatLeftoverObservedExpected } from "./leftoverObservedExpected";
+import {
+ formatLeftoverMapExpected,
+ formatLeftoverMapObserved,
+ formatLeftoverObservedExpected,
+} from "./leftoverObservedExpected";
describe("formatLeftoverObservedExpected", () => {
it("names observed Y and expected E without inventing a leftover score", () => {
@@ -15,3 +19,35 @@ describe("formatLeftoverObservedExpected", () => {
expect(formatLeftoverObservedExpected(2.4, Number.POSITIVE_INFINITY)).toBeNull();
});
});
+
+describe("formatLeftoverMapObserved", () => {
+ it("names leftover observed without inventing a leftover score", () => {
+ expect(formatLeftoverMapObserved(2.4)).toBe("Y 2.40");
+ expect(formatLeftoverMapObserved(0.9)).toBe("Y 0.90");
+ expect(formatLeftoverMapObserved(0)).toBe("Y 0.00");
+ expect(formatLeftoverMapObserved(-1.1)).toBe("Y -1.10");
+ });
+
+ it("omits the badge when leftover observed is missing or non-finite", () => {
+ expect(formatLeftoverMapObserved(null)).toBeNull();
+ expect(formatLeftoverMapObserved(undefined)).toBeNull();
+ expect(formatLeftoverMapObserved(Number.NaN)).toBeNull();
+ expect(formatLeftoverMapObserved(Number.POSITIVE_INFINITY)).toBeNull();
+ });
+});
+
+describe("formatLeftoverMapExpected", () => {
+ it("names leftover expected without inventing a leftover score", () => {
+ expect(formatLeftoverMapExpected(2.0)).toBe("E 2.00");
+ expect(formatLeftoverMapExpected(0.9)).toBe("E 0.90");
+ expect(formatLeftoverMapExpected(0)).toBe("E 0.00");
+ expect(formatLeftoverMapExpected(-1.1)).toBe("E -1.10");
+ });
+
+ it("omits the badge when leftover expected is missing or non-finite", () => {
+ expect(formatLeftoverMapExpected(null)).toBeNull();
+ expect(formatLeftoverMapExpected(undefined)).toBeNull();
+ expect(formatLeftoverMapExpected(Number.NaN)).toBeNull();
+ expect(formatLeftoverMapExpected(Number.POSITIVE_INFINITY)).toBeNull();
+ });
+});
diff --git a/frontend/src/leftoverObservedExpected.ts b/frontend/src/leftoverObservedExpected.ts
index 50e225280..86612b681 100644
--- a/frontend/src/leftoverObservedExpected.ts
+++ b/frontend/src/leftoverObservedExpected.ts
@@ -14,3 +14,17 @@ export function formatLeftoverObservedExpected(
}
return `Y ${observed.toFixed(2)} · E ${expected.toFixed(2)}`;
}
+
+export function formatLeftoverMapObserved(value: number | null | undefined): string | null {
+ if (value == null || !Number.isFinite(value)) {
+ return null;
+ }
+ return `Y ${value.toFixed(2)}`;
+}
+
+export function formatLeftoverMapExpected(value: number | null | undefined): string | null {
+ if (value == null || !Number.isFinite(value)) {
+ return null;
+ }
+ return `E ${value.toFixed(2)}`;
+}
diff --git a/frontend/src/leftoverResidual.test.ts b/frontend/src/leftoverResidual.test.ts
index bcdb831c3..342d3ad37 100644
--- a/frontend/src/leftoverResidual.test.ts
+++ b/frontend/src/leftoverResidual.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { formatLeftoverResidual } from "./leftoverResidual";
+import { formatLeftoverMapResidual, formatLeftoverResidual } from "./leftoverResidual";
describe("formatLeftoverResidual", () => {
it("keeps a signed residual without inventing a leftover score", () => {
@@ -9,3 +9,18 @@ describe("formatLeftoverResidual", () => {
expect(formatLeftoverResidual(Number.NaN)).toBe("—");
});
});
+
+describe("formatLeftoverMapResidual", () => {
+ it("names leftover residual without inventing a leftover score", () => {
+ expect(formatLeftoverMapResidual(0.4)).toBe("R +0.40");
+ expect(formatLeftoverMapResidual(-1.1)).toBe("R \u22121.10");
+ expect(formatLeftoverMapResidual(0)).toBe("R 0.00");
+ });
+
+ it("omits the badge when leftover residual is missing or non-finite", () => {
+ expect(formatLeftoverMapResidual(null)).toBeNull();
+ expect(formatLeftoverMapResidual(undefined)).toBeNull();
+ expect(formatLeftoverMapResidual(Number.NaN)).toBeNull();
+ expect(formatLeftoverMapResidual(Number.POSITIVE_INFINITY)).toBeNull();
+ });
+});
diff --git a/frontend/src/leftoverResidual.ts b/frontend/src/leftoverResidual.ts
index e21fe98d7..33886e308 100644
--- a/frontend/src/leftoverResidual.ts
+++ b/frontend/src/leftoverResidual.ts
@@ -12,3 +12,10 @@ export function formatLeftoverResidual(value: number): string {
}
return magnitude;
}
+
+export function formatLeftoverMapResidual(value: number | null | undefined): string | null {
+ if (value == null || !Number.isFinite(value)) {
+ return null;
+ }
+ return `R ${formatLeftoverResidual(value)}`;
+}
diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py
index ff0c6867e..217361388 100644
--- a/lineageweave/__init__.py
+++ b/lineageweave/__init__.py
@@ -130,4 +130,4 @@
"serialize_lineage_analysis_result",
]
-__version__ = "2.30.0"
+__version__ = "2.37.0"
diff --git a/pyproject.toml b/pyproject.toml
index 839a61c8d..cd9d2f8f8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "2.30.0"
+version = "2.37.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 b21381b50..0e7d2ce9d 100644
--- a/uv.lock
+++ b/uv.lock
@@ -685,7 +685,7 @@ wheels = [
[[package]]
name = "lineageweave"
-version = "2.30.0"
+version = "2.37.0"
source = { editable = "." }
dependencies = [
{ name = "certifi" },