diff --git a/AGENTS.md b/AGENTS.md
index 019f0e89c..dfd852c61 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -273,7 +273,7 @@ stops startup instead of leaving a healthy-looking partial schema, and
application code must not compensate for a missing table.
Period leftover pairs (ADR 0017 / 0018 / 0048 / 0049 / 0119 / 0158 / 0162 /
-0163 / 0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275) 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) 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
@@ -317,8 +317,22 @@ the pair-row `U²/R²` badge matches the graphic; a missing or non-finite
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. When `R`, `R̂`, `U`, `x`,
-`s`, and `e` are finite, `e + s + x = 1`. When coordinates,
+`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. 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 e1e86008d..8b6968f6b 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 / 0274 / 0275) 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) 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
@@ -653,7 +653,10 @@ captions leftover-map pair segments with persisted leftover-map
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`. Click a post marker or a pair button
+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`. 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.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.md b/CHANGELOG.md
index 944e3a14c..7915ecc4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,57 @@ All notable changes to this project are documented here. Format follows
### Added
+- Period leftover pairs now caption 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
diff --git a/CLAUDE.md b/CLAUDE.md
index 107079ed7..81b568490 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -49,7 +49,7 @@ cutoff. Global Ask optional `knowledge_cutoff` uses the same cover
Create/start endpoint rules (ADR 0017 / 0021), tie-vs-miss similarity
(ADR 0026), R&R catalog ids (ADR 0019 / 0027), leftover pairs
-(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275), occupational construct catalog search
+(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278), 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 f94e7d097..45d41417a 100644
--- a/docs/adr/0048-persist-lsirm-leftover-pairs.md
+++ b/docs/adr/0048-persist-lsirm-leftover-pairs.md
@@ -18,7 +18,10 @@
[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 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 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)
## Context
@@ -84,7 +87,12 @@ leftover-map pair segments with persisted leftover-map explained leftover
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.
+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.
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 b3ae6fea1..34320cb62 100644
--- a/docs/adr/0049-leftover-pair-report-ui.md
+++ b/docs/adr/0049-leftover-pair-report-ui.md
@@ -19,7 +19,10 @@
[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 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 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)
## Context
@@ -51,7 +54,10 @@ 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). Pair segments name persisted leftover-map
unexplained leftover share `s` (ADR 0274). Pair segments name persisted leftover-map
-cross share `x` (ADR 0275). The pair renders every available finite measurement.
+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). 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
@@ -92,6 +98,12 @@ 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).
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/0268-leftover-map-graphic-display.md b/docs/adr/0268-leftover-map-graphic-display.md
index c11c16c7d..fce18867f 100644
--- a/docs/adr/0268-leftover-map-graphic-display.md
+++ b/docs/adr/0268-leftover-map-graphic-display.md
@@ -16,7 +16,13 @@
[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)
+(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)
Amends [ADR 0049](0049-leftover-pair-report-ui.md) and
[ADR 0267](0267-leftover-map-coordinates.md). Independent of leftover-map
@@ -95,7 +101,10 @@ 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). Pair segments name
persisted leftover-map unexplained leftover share `s` (ADR 0274). Pair segments name
-persisted leftover-map cross share `x` (ADR 0275). When coordinates, reconstruction, and
+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). 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/0275-leftover-map-segment-cross-share.md b/docs/adr/0275-leftover-map-segment-cross-share.md
index c5e062c43..c80b19fb6 100644
--- a/docs/adr/0275-leftover-map-segment-cross-share.md
+++ b/docs/adr/0275-leftover-map-segment-cross-share.md
@@ -2,6 +2,8 @@
**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
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/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md
index c6b291c2e..b2667a86e 100644
--- a/docs/product-technical-gap-baseline.md
+++ b/docs/product-technical-gap-baseline.md
@@ -1,5 +1,113 @@
# Product & Technical Gap Baseline
+> Exact-head loop overlay: 2026-08-30 13:06 KST. Protected `main` is
+> `cb187cadee5fb6c46d8a944815ccc154a1e028d1` after unauthorized squash
+> of leftover-map coordinates (v2.24.0 / #782). Revert #808
+> (`revert-pr782-unauthorized` @ `a10c2101`) restores `main` to
+> `fc13acaa` (v2.23.0) and still needs independent APPROVE. GitHub
+> writes work (comment/close/create-PR/push). Token still has empty
+> `X-OAuth-Scopes`; repo permission is ADMIN. Open leftover stack
+> still lacks independent APPROVE. Do not self-approve.
+> #782 leftover-map coordinates through pair-segment distance
+> (v2.24.0–v2.28.0 / ADR 0267–0271) is `9bdd3e4d`. #802 explained
+> leftover share on pair segments (v2.30.0 / ADR 0273, includes v2.29
+> reconstruction) is `79ec22f0`. #803 unexplained leftover share on pair
+> segments (v2.31.0 / ADR 0274) is `089a1571`. #804 leftover-map cross
+> share on pair segments (v2.32.0 / ADR 0275) is `c513002b`. #805 leftover-map
+> unexplained leftover `U` on pair segments (v2.33.0 / ADR 0276) is
+> `e3fdfd74`. #806 leftover residual `R` on pair segments (v2.34.0 /
+> ADR 0277) is `33f6c3dd`. Leave #782/#802/#803/#804/#805/#806/#808
+> open for independent review. Do not squash-merge stacked leftover PRs
+> onto an unprotected leftover base. Do not merge #808 without
+> independent APPROVE. Strix on #782 failed closed at `Run Strix (quick)`;
+> do not weaken fail-closed on `Vulnerabilities [1-9]`. Org Strix
+> `ModelBehaviorError` classifier is already on ContextualWisdomLab/.github.
+> Copilot review is not independent APPROVE. Issues #79 and #87 stay open.
+> #96 closed as weaker duplicate of #91. Only collaborator is
+> `seonghobae`; no independent reviewer can be requested from this token.
+>
+> Next buyer increment on this cycle: leftover observed `Y` on
+> graphic-display pair segments (ADR 0278 / v2.35.0) delivered locally on
+> `feat/leftover-map-segment-observed-v2350`. Caption each
+> closest/farthest segment with persisted leftover observed so the
+> pair-row `Y` badge matches the graphic and `Y − E = R` is
+> graphic-auditable. UI-only; no new columns. Missing/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. Do not invent leftover scores.
+> Stack onto leftover branch `feat/leftover-map-segment-residual-v2340`
+> / #806; leave the PR open for independent review. Do not squash-merge
+> onto the leftover base. Do not persist leftover-map inner product,
+> cosine, or length as separate columns.
+
+> Exact-head loop overlay: 2026-08-30 06:20 KST. Protected `main` is
+> `fc13acaa20adca11968238e398d4aafcf62b6cee` (v2.23.0 leftover-map
+> explained leftover share). GitHub writes work (comment/close/create-PR).
+> Token still has empty `X-OAuth-Scopes`; repo permission is ADMIN.
+> Open leftover stack still lacks independent APPROVE. Do not self-approve.
+> #782 leftover-map coordinates through pair-segment distance
+> (v2.24.0–v2.28.0 / ADR 0267–0271) is `9bdd3e4d`. #802 explained
+> leftover share on pair segments (v2.30.0 / ADR 0273, includes v2.29
+> reconstruction) is `79ec22f0`. #803 unexplained leftover share on pair
+> segments (v2.31.0 / ADR 0274) is `089a1571`. #804 leftover-map cross
+> share on pair segments (v2.32.0 / ADR 0275) is `c513002b`. #805 leftover-map
+> unexplained leftover `U` on pair segments (v2.33.0 / ADR 0276) is
+> `e3fdfd74`. Leave #782/#802/#803/#804/#805 open for independent review.
+> Do not squash-merge stacked leftover PRs onto an unprotected leftover
+> base. Strix on #782 failed closed at `Run Strix (quick)`; do not weaken
+> fail-closed on `Vulnerabilities [1-9]`. Org Strix `ModelBehaviorError`
+> classifier is already on ContextualWisdomLab/.github. Copilot review is
+> not independent APPROVE. Issues #79 and #87 stay open. Only collaborator
+> is `seonghobae`; no independent reviewer can be requested from this token.
+>
+> Next buyer increment on this cycle: leftover residual `R` on
+> graphic-display pair segments (ADR 0277 / v2.34.0) delivered locally on
+> `feat/leftover-map-segment-residual-v2340`. Caption each
+> closest/farthest segment with persisted leftover residual so the
+> pair-row `R` badge matches the graphic and `U + R̂ = R` is
+> graphic-auditable. UI-only; no new columns. Missing/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. Do not invent leftover
+> scores. Stack onto leftover branch
+> `feat/leftover-map-segment-unexplained-leftover-v2330` / #805; leave the
+> PR open for independent review. Do not squash-merge onto the leftover
+> base. Do not persist leftover-map inner product, cosine, or length as
+> separate columns.
+
+> Exact-head loop overlay: 2026-08-30 02:09 KST. Protected `main` is
+> `fc13acaa20adca11968238e398d4aafcf62b6cee` (v2.23.0 leftover-map
+> explained leftover share). GitHub writes work (comment/close/create-PR).
+> Token still has empty `X-OAuth-Scopes`; repo permission is ADMIN.
+> Open leftover stack still lacks independent APPROVE. Do not self-approve.
+> #782 leftover-map coordinates through pair-segment distance
+> (v2.24.0–v2.28.0 / ADR 0267–0271) is `9bdd3e4d`. #802 explained
+> leftover share on pair segments (v2.30.0 / ADR 0273, includes v2.29
+> reconstruction) is `79ec22f0`. #803 unexplained leftover share on pair
+> segments (v2.31.0 / ADR 0274) is `089a1571`. #804 leftover-map cross
+> share on pair segments (v2.32.0 / ADR 0275) is `c513002b`. Leave
+> #782/#802/#803/#804 open for independent review. Do not squash-merge
+> stacked leftover PRs onto an unprotected leftover base. Strix on #782
+> failed closed at `Run Strix (quick)`; do not weaken fail-closed on
+> `Vulnerabilities [1-9]`. Org Strix `ModelBehaviorError` classifier is
+> already on ContextualWisdomLab/.github. Copilot review is not
+> independent APPROVE. Issues #79 and #87 stay open.
+>
+> Next buyer increment on this cycle: leftover-map unexplained leftover
+> `U` on graphic-display pair segments (ADR 0276 / v2.33.0) delivered
+> locally on `feat/leftover-map-segment-unexplained-leftover-v2330`.
+> Caption each closest/farthest segment with persisted leftover-map
+> unexplained leftover so the pair-row `U` badge matches the graphic
+> and `U + R̂ = R` is graphic-auditable. UI-only; no new columns.
+> Missing/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. Do not invent
+> leftover scores. Stack onto leftover branch
+> `feat/leftover-map-segment-cross-share-v2320` / #804; leave the PR open
+> for independent review. Do not squash-merge onto the leftover base.
+> Do not persist leftover-map inner product, cosine, or length as
+> separate columns.
+
> Exact-head loop overlay: 2026-08-29 23:10 KST. Protected `main` is
> `fc13acaa20adca11968238e398d4aafcf62b6cee` (v2.23.0 leftover-map
> explained leftover share, #775). Open ready PRs still lack independent
diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md
index d63417657..f7b4558b3 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, pair-segment `R̂²/R²` to leftover-map explained leftover share, pair-segment `U²/R²` to leftover-map unexplained leftover share, and pair-segment `2R̂U/R²` to leftover-map cross 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`, `R̂²/R² 0.00`, `U²/R² 0.00`, and `2R̂U/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, pair segments name persisted leftover-map explained leftover share, pair segments name persisted leftover-map unexplained leftover share, and pair segments name persisted leftover-map cross 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, and pair-segment `Y` to leftover observed, 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`, and `Y 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, 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, and pair segments name persisted leftover observed. | `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 fbd3aeb86..0f972aef9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "2.32.0",
+ "version": "2.35.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/LeftoverMapPlot.stories.tsx b/frontend/src/components/LeftoverMapPlot.stories.tsx
index 915ff4223..d79587245 100644
--- a/frontend/src/components/LeftoverMapPlot.stories.tsx
+++ b/frontend/src/components/LeftoverMapPlot.stories.tsx
@@ -84,6 +84,7 @@ export const RankZeroOrigin: Story = {
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 82efd959f..7da06f2b4 100644
--- a/frontend/src/components/LeftoverMapPlot.test.tsx
+++ b/frontend/src/components/LeftoverMapPlot.test.tsx
@@ -12,10 +12,12 @@ const PAIRS: LeftoverPair[] = [
criterion_code: "sales_lead_quality",
leftover_distance: 0.12,
leftover_residual: 0.4,
+ observed_response: 2.4,
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_person_axis_1: 0.5,
leftover_map_person_axis_2: 0.1,
leftover_map_item_axis_1: 0.5,
@@ -28,10 +30,12 @@ const PAIRS: LeftoverPair[] = [
criterion_code: "negative_sentiment",
leftover_distance: 1.84,
leftover_residual: -1.1,
+ observed_response: 0.9,
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_person_axis_1: 0.9,
leftover_map_person_axis_2: 0.8,
leftover_map_item_axis_1: -0.7,
@@ -53,7 +57,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̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, and leftover-map cross share x. 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, and leftover observed Y. Click a post marker to open that post. The plot does not invent a leftover score.",
),
).toBeInTheDocument();
expect(screen.getByText("Post ξ")).toBeInTheDocument();
@@ -73,6 +77,12 @@ describe("LeftoverMapPlot", () => {
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();
const postMarker = screen.getByRole("button", {
name: "Open leftover-map post Public post at ξ (+0.50, +0.10)",
@@ -118,11 +128,13 @@ describe("LeftoverMapPlot", () => {
criterion_code: "sales_lead_quality",
leftover_distance: 0,
leftover_residual: 0,
+ observed_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,
@@ -149,6 +161,9 @@ describe("LeftoverMapPlot", () => {
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();
});
it("captions leftover-map axes with persisted leftover-map axis share", () => {
@@ -321,4 +336,97 @@ describe("LeftoverMapPlot", () => {
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();
+ });
});
diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx
index 0f4de1959..f518efb91 100644
--- a/frontend/src/components/LeftoverMapPlot.tsx
+++ b/frontend/src/components/LeftoverMapPlot.tsx
@@ -14,7 +14,10 @@ import {
LEFTOVER_MAP_PLOT_SEGMENT_CROSS_SHARE,
LEFTOVER_MAP_PLOT_SEGMENT_DISTANCE,
LEFTOVER_MAP_PLOT_SEGMENT_EXPLAINED_SHARE,
+ LEFTOVER_MAP_PLOT_SEGMENT_OBSERVED,
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";
@@ -54,14 +57,18 @@ function leftoverMapPlotAxisText(
* pair row match the plot. Pair segments name persisted leftover-map
* distance ``d``, leftover-map reconstruction ``R̂``, leftover-map
* explained leftover share ``e``, leftover-map unexplained leftover
- * share ``s``, and leftover-map cross share ``x`` so the pair-row
- * badges match the graphic.
+ * share ``s``, leftover-map cross share ``x``, leftover-map
+ * unexplained leftover ``U``, leftover residual ``R``, and leftover
+ * observed ``Y`` 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 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 axis badge when share is
+ * ``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 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.
@@ -215,6 +222,45 @@ export function LeftoverMapPlot({
{segment.crossShareLabel}
) : null}
+ {segment.unexplainedLeftoverLabel !== null ? (
+
+ {segment.unexplainedLeftoverLabel}
+
+ ) : null}
+ {segment.residualLabel !== null ? (
+
+ {segment.residualLabel}
+
+ ) : null}
+ {segment.observedLabel !== null ? (
+
+ {segment.observedLabel}
+
+ ) : null}
))}
{layout.items.map((marker) => (
diff --git a/frontend/src/components/LeftoverPairList.tsx b/frontend/src/components/LeftoverPairList.tsx
index ae7fce7c8..8f3bec764 100644
--- a/frontend/src/components/LeftoverPairList.tsx
+++ b/frontend/src/components/LeftoverPairList.tsx
@@ -66,7 +66,10 @@ export type LeftoverPairListProps = {
* 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. Every badge still
+ * 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 eade0358e..748f644f9 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -71,7 +71,10 @@ describe("i18n", () => {
"leftover-map explained leftover share {label}",
"leftover-map unexplained leftover share {label}",
"leftover-map cross 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̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, and leftover-map cross share x. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "leftover-map unexplained leftover {label}",
+ "leftover residual {label}",
+ "leftover observed {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, and leftover observed Y. 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.",
@@ -335,22 +338,22 @@ describe("i18n", () => {
it.each([
[
"ko",
- "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
+ "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x, 잔여 지도 미설명 잔여 U, 잔여 R, 관측 Y입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
],
- ["zh", "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s 与残差图交叉份额 x。点击帖子标记打开该帖子。此图不会虚构残差分数。"],
+ ["zh", "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s、残差图交叉份额 x、残差图未解释残差 U、残差 R 与观测 Y。点击帖子标记打开该帖子。此图不会虚构残差分数。"],
[
"ja",
- "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
+ "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x、残差マップ未説明残差 U、残差 R、観測 Y です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
],
[
"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̂, phần dư giải thích e, phần dư chưa giải thích s và phần giao x. 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̂, phần dư 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 và Y quan sát. 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̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, and leftover-map cross share x. 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, and leftover observed Y. Click a post marker to open that post. The plot does not invent a leftover score.",
),
).toBe(expected);
});
@@ -419,6 +422,36 @@ describe("i18n", () => {
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", "잔여 지도 축 1 (82%)"],
["zh", "残差图轴 1 (82%)"],
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index a16a3b7a7..6b8ef0a54 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -596,8 +596,11 @@ const TRANSLATIONS: Partial>> = {
"leftover-map explained leftover share {label}": "잔여 지도 설명 잔여 점유율 {label}",
"leftover-map unexplained leftover share {label}": "잔여 지도 미설명 잔여 점유율 {label}",
"leftover-map cross 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̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, and leftover-map cross share x. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
+ "leftover-map unexplained leftover {label}": "잔여 지도 미설명 잔여 {label}",
+ "leftover residual {label}": "잔여 {label}",
+ "leftover observed {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, and leftover observed Y. 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입니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
"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.":
@@ -1180,8 +1183,11 @@ const TRANSLATIONS: Partial>> = {
"leftover-map explained leftover share {label}": "残差图已解释残差份额 {label}",
"leftover-map unexplained leftover share {label}": "残差图未解释残差份额 {label}",
"leftover-map cross 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̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, and leftover-map cross share x. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s 与残差图交叉份额 x。点击帖子标记打开该帖子。此图不会虚构残差分数。",
+ "leftover-map unexplained leftover {label}": "残差图未解释残差 {label}",
+ "leftover residual {label}": "残差 {label}",
+ "leftover observed {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, and leftover observed Y. 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。点击帖子标记打开该帖子。此图不会虚构残差分数。",
"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.":
@@ -1768,8 +1774,11 @@ const TRANSLATIONS: Partial>> = {
"leftover-map explained leftover share {label}": "残差マップ説明済み残差割合 {label}",
"leftover-map unexplained leftover share {label}": "残差マップ未説明残差割合 {label}",
"leftover-map cross 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̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, and leftover-map cross share x. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
+ "leftover-map unexplained leftover {label}": "残差マップ未説明残差 {label}",
+ "leftover residual {label}": "残差 {label}",
+ "leftover observed {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, and leftover observed Y. 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 です。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
"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.":
@@ -2356,8 +2365,11 @@ const TRANSLATIONS: Partial>> = {
"leftover-map explained leftover share {label}": "phần dư giải thích bản đồ phần dư {label}",
"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 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, and leftover-map cross share x. 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̂, phần dư giải thích e, phần dư chưa giải thích s và phần giao x. 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 {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 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, and leftover observed Y. 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̂, phần dư 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 và Y quan sát. 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/leftoverMapPlotLayout.test.ts b/frontend/src/leftoverMapPlotLayout.test.ts
index 8d6e79224..574fd4115 100644
--- a/frontend/src/leftoverMapPlotLayout.test.ts
+++ b/frontend/src/leftoverMapPlotLayout.test.ts
@@ -27,6 +27,8 @@ function pair(
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,
};
}
@@ -616,11 +618,325 @@ describe("layoutLeftoverMapPlot", () => {
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");
diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts
index 215d3a7dc..6ed932965 100644
--- a/frontend/src/leftoverMapPlotLayout.ts
+++ b/frontend/src/leftoverMapPlotLayout.ts
@@ -4,20 +4,27 @@
* Pair segments name persisted leftover-map distance ``d`` (ADR 0271),
* 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), and persisted
- * leftover-map cross share ``x`` (ADR 0275).
+ * 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), and persisted leftover observed ``Y`` (ADR 0278).
*/
import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates";
import { formatLeftoverMapCrossShare } from "./leftoverMapCrossShare";
import { formatLeftoverMapExplainedShare } from "./leftoverMapExplainedShare";
import { formatLeftoverMapReconstruction } from "./leftoverMapReconstruction";
+import {
+ formatLeftoverMapUnexplained,
+ formatSignedLeftoverValue,
+} from "./leftoverMapUnexplained";
import { formatLeftoverMapUnexplainedShare } from "./leftoverMapUnexplainedShare";
-import { formatSignedLeftoverValue } from "./leftoverMapUnexplained";
+import { 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̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, and leftover-map cross share x. 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, and leftover observed Y. 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}";
@@ -40,6 +47,15 @@ export const LEFTOVER_MAP_PLOT_SEGMENT_UNEXPLAINED_SHARE =
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 PLOT_WIDTH = 480;
export const PLOT_HEIGHT = 320;
export const PLOT_PADDING = 40;
@@ -59,6 +75,9 @@ export type LeftoverMapPlottablePair = {
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;
leftover_map_person_axis_1?: number | null;
leftover_map_person_axis_2?: number | null;
leftover_map_item_axis_1?: number | null;
@@ -88,6 +107,9 @@ export type LeftoverMapPlotSegment = {
explainedShareLabel: string | null;
unexplainedShareLabel: string | null;
crossShareLabel: string | null;
+ unexplainedLeftoverLabel: string | null;
+ residualLabel: string | null;
+ observedLabel: string | null;
labelX: number;
labelY: number;
reconstructionX: number;
@@ -98,6 +120,12 @@ export type LeftoverMapPlotSegment = {
unexplainedShareY: number;
crossShareX: number;
crossShareY: number;
+ unexplainedLeftoverX: number;
+ unexplainedLeftoverY: number;
+ residualX: number;
+ residualY: number;
+ observedX: number;
+ observedY: number;
};
export type LeftoverMapPlotTick = {
@@ -325,6 +353,11 @@ export function layoutLeftoverMapPlot(
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 labelPosition = leftoverMapSegmentLabelPosition(
personPos.x,
personPos.y,
@@ -352,6 +385,33 @@ export function layoutLeftoverMapPlot(
(explainedShareLabel !== null ? 1 : 0) +
(unexplainedShareLabel !== null ? 1 : 0),
);
+ const unexplainedLeftoverY = leftoverMapStackedCaptionY(
+ labelPosition.labelY,
+ (distanceLabel !== null ? 1 : 0) +
+ (reconstructionLabel !== null ? 1 : 0) +
+ (explainedShareLabel !== null ? 1 : 0) +
+ (unexplainedShareLabel !== null ? 1 : 0) +
+ (crossShareLabel !== null ? 1 : 0),
+ );
+ const residualY = leftoverMapStackedCaptionY(
+ labelPosition.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),
+ );
+ const observedY = leftoverMapStackedCaptionY(
+ labelPosition.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),
+ );
segments.push({
pairKind: pair.pair_kind === "farthest" ? "farthest" : "closest",
postId: pair.post_id,
@@ -365,6 +425,9 @@ export function layoutLeftoverMapPlot(
explainedShareLabel,
unexplainedShareLabel,
crossShareLabel,
+ unexplainedLeftoverLabel,
+ residualLabel,
+ observedLabel,
reconstructionX: labelPosition.labelX,
reconstructionY,
explainedShareX: labelPosition.labelX,
@@ -373,6 +436,12 @@ export function layoutLeftoverMapPlot(
unexplainedShareY,
crossShareX: labelPosition.labelX,
crossShareY,
+ unexplainedLeftoverX: labelPosition.labelX,
+ unexplainedLeftoverY,
+ residualX: labelPosition.labelX,
+ residualY,
+ observedX: labelPosition.labelX,
+ observedY,
...labelPosition,
});
}
diff --git a/frontend/src/leftoverObservedExpected.test.ts b/frontend/src/leftoverObservedExpected.test.ts
index 56008273a..58f9c0e60 100644
--- a/frontend/src/leftoverObservedExpected.test.ts
+++ b/frontend/src/leftoverObservedExpected.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { formatLeftoverObservedExpected } from "./leftoverObservedExpected";
+import { formatLeftoverMapObserved, formatLeftoverObservedExpected } from "./leftoverObservedExpected";
describe("formatLeftoverObservedExpected", () => {
it("names observed Y and expected E without inventing a leftover score", () => {
@@ -15,3 +15,19 @@ 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();
+ });
+});
diff --git a/frontend/src/leftoverObservedExpected.ts b/frontend/src/leftoverObservedExpected.ts
index 50e225280..89d0a5132 100644
--- a/frontend/src/leftoverObservedExpected.ts
+++ b/frontend/src/leftoverObservedExpected.ts
@@ -14,3 +14,10 @@ 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)}`;
+}
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/pyproject.toml b/pyproject.toml
index e9de742c7..1a8eaabc0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "2.32.0"
+version = "2.35.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 24dddc5f9..ee8b86fda 100644
--- a/uv.lock
+++ b/uv.lock
@@ -685,7 +685,7 @@ wheels = [
[[package]]
name = "lineageweave"
-version = "2.32.0"
+version = "2.33.0"
source = { editable = "." }
dependencies = [
{ name = "certifi" },