diff --git a/AGENTS.md b/AGENTS.md
index 177393c2b..2e7d2fe57 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -273,7 +273,7 @@ stops startup instead of leaving a healthy-looking partial schema, and
application code must not compensate for a missing table.
Period leftover pairs (ADR 0017 / 0018 / 0048 / 0049 / 0119 / 0158 / 0162 /
-0163 / 0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281) are computed in `lineageweave/leftover_pairs.py` from the
+0163 / 0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282) 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
@@ -342,7 +342,13 @@ captions the leftover-map graphic display with persisted leftover-map
complete-case coverage so the pair-list `used N of M scored posts` note
matches the plot; a missing, non-integer, negative-used, non-positive-scored,
or used-greater-than-scored coverage omits that leftover-map coverage caption
-and does not invent coverage from plotted marker count. When `R`, `R̂`, `U`, `x`,
+and does not invent coverage from plotted marker count. ADR 0282 captions the
+leftover-map graphic display with persisted leftover-map item complete-case
+coverage so two criterion diamonds are not read as the scored-criterion census;
+a missing, non-integer, negative-used, non-positive-scored, or
+used-greater-than-scored item coverage omits that leftover-map item coverage
+caption and does not invent item coverage from plotted criterion marker count.
+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,
@@ -354,7 +360,9 @@ residual SVD axes 1 and 2 and persists to `report_leftover_map_axis`.
Rank-0 residuals emit two zero-share axes; the shares are report-level
and are not a leftover score. Complete-case coverage (ADR 0168) persists to
`report_leftover_map_coverage` and captions the pair list and the leftover-map
-graphic with how many scored posts entered the map.
+graphic with how many scored posts entered the map. Item complete-case coverage
+(ADR 0282) captions the leftover-map graphic with how many scored criteria
+entered the map.
Authorized occupational construct catalog search (ADR 0257) matches official
O*NET preferred labels or descriptions only when a source-eligible, ABAC-visible
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index eb4b5ca60..d1e13c158 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 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281) persist to `report_leftover_pair` with signed residual `R`,
+0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282) 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
@@ -659,13 +659,15 @@ residual `R`. ADR 0278 captions leftover-map pair segments with persisted leftov
observed `Y`. ADR 0279 captions leftover-map pair segments with persisted leftover
expected `E`. ADR 0280 captions leftover-map pair segments with persisted leftover-map
rank. ADR 0281 captions the leftover-map graphic display with persisted leftover-map
-complete-case coverage. Click a post marker or a pair button
+complete-case coverage. ADR 0282 captions the leftover-map graphic display with
+persisted leftover-map item complete-case coverage. 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
`report_leftover_map_axis`. Complete-case leftover-map coverage (ADR
0168) persists to `report_leftover_map_coverage` so readers see how
-many scored posts entered the factorization, including on the graphic. Results persist to
+many scored posts entered the factorization, including on the graphic,
+and how many scored criteria entered the factorization on the graphic. Results persist to
`report_period_score` / `report_member_score`.
`GET /api/reports/{grouping}` lists the trend;
`GET /api/reports/{grouping}/{period}` is ABAC-filtered;
diff --git a/CHANGELOG.d/2.39.0-leftover-map-plot-item-coverage.md b/CHANGELOG.d/2.39.0-leftover-map-plot-item-coverage.md
new file mode 100644
index 000000000..f0107c1b0
--- /dev/null
+++ b/CHANGELOG.d/2.39.0-leftover-map-plot-item-coverage.md
@@ -0,0 +1,24 @@
+## 2.39.0 — Leftover-map item complete-case coverage on the graphic display
+
+- Caption the leftover-map graphic display with persisted leftover-map
+ item complete-case coverage (ADR 0282). After `make seed`, closest and
+ farthest leftover pairs sit above the member list with the Gabriel biplot
+ of already-named coordinates, leftover-map axis share when finite, axis
+ ticks that match `ξ (x, y) ζ (x, y)` on the pair row, pair segments
+ that match `d` on the pair row, pair segments that match `R̂` on the
+ pair row, pair segments that match `R̂²/R²` on the pair row, pair
+ segments that match `U²/R²` on the pair row, pair segments that match
+ `2R̂U/R²` on the pair row, pair segments that match `U` on the pair
+ row, pair segments that match `R` on the pair row, pair segments that
+ match `Y` on the pair row, pair segments that match `E` on the pair
+ row, pair segments that match leftover-map rank on the pair row, a plot
+ caption that matches leftover-map complete-case coverage above the pair
+ list, and a plot caption that matches leftover-map item complete-case
+ coverage; click a post marker or a pair button opens that post.
+ A missing, non-integer, negative-used, non-positive-scored, or
+ used-greater-than-scored item coverage omits that leftover-map item
+ coverage caption. Coverage `0 of M` is shown when that persisted used
+ count is a non-negative integer. Do not invent item coverage from
+ plotted criterion marker count, leftover-map distance, leftover-map
+ rank, leftover-map post coverage, or the count of unused axes. Never
+ invent a leftover score. Never invent a theta. No new columns.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95d4743a6..18a6a0baf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,30 @@ All notable changes to this project are documented here. Format follows
### Added
+- Period leftover pairs now caption the leftover-map graphic display with
+ persisted leftover-map item complete-case coverage (ADR 0282 / v2.39.0). After
+ `make seed`, closest and farthest leftover pairs sit above the member
+ list with the Gabriel biplot of already-named coordinates, leftover-map
+ axis share when finite, axis ticks that match `ξ (x, y) ζ (x, y)` on
+ the pair row, pair segments that match `d` on the pair row, pair
+ segments that match `R̂` on the pair row, pair segments that match
+ `R̂²/R²` on the pair row, pair segments that match `U²/R²` on the pair
+ row, pair segments that match `2R̂U/R²` on the pair row, pair segments
+ that match `U` on the pair row, pair segments that match `R` on the
+ pair row, pair segments that match `Y` on the pair row, pair segments
+ that match `E` on the pair row, pair segments that match leftover-map
+ rank on the pair row, a plot caption that matches leftover-map
+ complete-case coverage above the pair list, and a plot caption that
+ matches leftover-map item complete-case coverage; click a post marker
+ or a pair button opens that post. A missing, non-integer, negative-used,
+ non-positive-scored, or used-greater-than-scored item coverage omits
+ that leftover-map item coverage caption. Coverage `0 of M` is shown
+ when that persisted used count is a non-negative integer. Do not invent
+ item coverage from plotted criterion marker count, leftover-map
+ distance, leftover-map rank, leftover-map post coverage, or the count
+ of unused axes. Never invent a leftover score or a theta. No new
+ columns.
+
- Period leftover pairs now caption the leftover-map graphic display with
persisted leftover-map complete-case coverage (ADR 0281 / v2.38.0). After
`make seed`, closest and farthest leftover pairs sit above the member
diff --git a/CLAUDE.md b/CLAUDE.md
index 532fcc899..a342ef2c6 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -49,7 +49,7 @@ cutoff. Global Ask optional `knowledge_cutoff` uses the same cover
Create/start endpoint rules (ADR 0017 / 0021), tie-vs-miss similarity
(ADR 0026), R&R catalog ids (ADR 0019 / 0027), leftover pairs
-(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281), occupational construct catalog search
+(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282), 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/0282-leftover-map-plot-item-coverage.md b/docs/adr/0282-leftover-map-plot-item-coverage.md
new file mode 100644
index 000000000..48affb306
--- /dev/null
+++ b/docs/adr/0282-leftover-map-plot-item-coverage.md
@@ -0,0 +1,159 @@
+# ADR 0282 — Name leftover-map item complete-case coverage on the graphic display
+
+**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 complete-case
+coverage ([ADR 0168](0168-leftover-map-complete-case-coverage.md)). Independent of
+leftover-map complete-case coverage on the graphic display
+([ADR 0281](0281-leftover-map-plot-coverage.md)), leftover-map rank on pair
+segments ([ADR 0280](0280-leftover-map-segment-rank.md)), leftover expected on pair
+segments ([ADR 0279](0279-leftover-map-segment-expected.md)), leftover observed on
+pair segments ([ADR 0278](0278-leftover-map-segment-observed.md)), leftover
+residual on pair segments ([ADR 0277](0277-leftover-map-segment-residual.md)),
+leftover-map unexplained leftover on pair segments
+([ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)), leftover-map
+cross share on pair segments ([ADR 0275](0275-leftover-map-segment-cross-share.md)),
+leftover-map unexplained leftover share on pair segments
+([ADR 0274](0274-leftover-map-segment-unexplained-share.md)), leftover-map
+explained leftover share on pair segments
+([ADR 0273](0273-leftover-map-segment-explained-share.md)), leftover-map
+reconstruction on pair segments
+([ADR 0272](0272-leftover-map-segment-reconstruction.md)), leftover-map
+distance on pair segments ([ADR 0271](0271-leftover-map-segment-distance.md)),
+leftover-map coordinate ticks ([ADR 0270](0270-leftover-map-coordinate-ticks.md)),
+leftover-map axis share on the graphic display
+([ADR 0269](0269-leftover-map-axis-share-plot.md)), leftover residual
+disclosure ([ADR 0162](0162-leftover-residual-disclosure.md)), leftover
+observed `Y` / expected `E` ([ADR 0163](0163-leftover-observed-expected.md)),
+leftover-map explained leftover share persistence
+([ADR 0266](0266-leftover-map-explained-share.md)), leftover-map
+unexplained leftover share persistence
+([ADR 0233](0233-leftover-map-unexplained-share.md)), leftover-map
+reconstruction persistence ([ADR 0201](0201-leftover-map-reconstruction.md)),
+leftover-map cross share persistence
+([ADR 0185](0185-leftover-map-cross-share.md)), leftover-map unexplained leftover
+persistence ([ADR 0182](0182-leftover-map-unexplained.md)), leftover-map rank
+persistence ([ADR 0164](0164-leftover-map-rank.md)), and leftover-map axis
+share persistence ([ADR 0148](0148-leftover-map-axis-share.md)).
+
+## Context
+
+ADR 0168 already persists `map_item_count` / `scored_item_count` on
+`report_leftover_map_coverage`. ADR 0281 already names persisted leftover-map
+post complete-case coverage on the leftover-map graphic display. The graphic
+still has no item coverage caption, so a buyer who reads two criterion
+diamonds can treat those markers as the scored-criterion census even after
+the pair-list note names post coverage. Incomplete columns are dropped from
+Gabriel factorization; hiding item coverage on the plot lets a sparse
+criterion with one missing post vanish without a next action.
+
+This increment names leftover-map item complete-case coverage on the
+leftover-map graphic display. It does not add columns. It does not recompute
+item coverage from plotted criterion marker count, leftover-map distance, or
+leftover-map rank. 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 **0282** so it does not collide with
+leftover-map complete-case coverage on the graphic display (0281), leftover-map
+rank on pair segments (0280), leftover expected on pair segments (0279), leftover
+observed on pair segments (0278), leftover residual on pair segments (0277),
+leftover-map unexplained leftover on pair segments (0276), leftover-map cross
+share on pair segments (0275), leftover-map unexplained leftover share on pair
+segments (0274), leftover-map explained leftover share on pair segments (0273),
+leftover-map reconstruction on pair segments (0272), leftover-map distance on
+pair segments (0271), leftover-map coordinate ticks (0270), leftover-map axis
+share on the graphic display (0269), leftover-map graphic display (0268),
+leftover-map coordinates (0267 / migration 0245), leftover-map explained leftover
+share persistence (0266 / migration 0244), leftover-map unexplained leftover
+share persistence (0233 / migration 0233), leftover-map reconstruction
+persistence (0201 / migration 0206), leftover-map cross share persistence (0185),
+leftover-map unexplained leftover persistence (0182), leftover residual
+disclosure (0162), leftover observed `Y` / expected `E` persistence (0163),
+leftover-map rank persistence (0164), leftover coverage persistence (0168),
+two-axis leftover-map distance persistence, 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 persisted leftover-map item
+complete-case coverage as `Leftover map used N of M scored criteria
+(complete-case)`, using the persisted `map_item_count` / `scored_item_count`
+integers. A missing coverage row, a non-integer count, a negative used count,
+a non-positive scored count, or used greater than scored omits that leftover-map
+item coverage caption and keeps the connecting lines and any leftover-map
+distance, reconstruction, explained leftover share, unexplained leftover share,
+leftover-map cross share, unexplained leftover, leftover residual, leftover
+observed, leftover expected, leftover-map rank, or leftover-map post coverage
+caption. Coverage `0 of M` is shown when that persisted used count is a
+non-negative integer and scored is a positive integer. Do not invent item
+coverage from plotted criterion marker count, leftover-map distance,
+leftover-map rank, leftover-map post coverage, or the count of unused axes.
+Click a post marker to open that post. Criterion markers are not post buttons.
+The grouping comparison strip (ADR 0149) stays on its reduced leftover payload
+and does not gain this leftover-map item coverage caption. The pair-list note
+(ADR 0168) stays post coverage.
+
+Do not add SQL. Do not edit shipped migrations. Do not persist inner
+product, cosine, or length as separate columns. Do not invent a leftover
+score. Do not invent a theta.
+
+## Consequences
+
+After `make seed`, closest and farthest leftover pairs sit above the member
+list with the leftover-map graphic display of persisted `ξ` and `ζ`,
+leftover-map axes name persisted Gabriel inertia share when finite,
+leftover-map axis ticks name the same coordinates shown on the pair row,
+pair segments name persisted leftover-map distance `d`, persisted leftover-map
+reconstruction `R̂`, persisted leftover-map explained leftover share `e`,
+persisted leftover-map unexplained leftover share `s`, persisted leftover-map
+cross share `x`, persisted leftover-map unexplained leftover `U`, persisted
+leftover residual `R`, persisted leftover observed `Y`, persisted leftover
+expected `E`, persisted leftover-map rank, the plot names persisted leftover-map
+complete-case coverage, and the plot names persisted leftover-map item
+complete-case coverage; click a post marker or a pair button opens that post.
+Hidden posts stay hidden. Rank-0 unused axes still plot at the origin and still
+name item coverage when that coverage is persisted.
+
+## Related
+
+Independent of leftover interaction-map persistence, leftover-criterion
+evaluation landing, leftover residual disclosure, leftover-map
+complete-case coverage persistence, leftover-map axis share persistence,
+leftover pairs on the grouping comparison strip, two-axis leftover-map
+distance persistence, leftover-map rank persistence, leftover-map inner
+product, leftover-map cosine, leftover-map length, leftover-map
+reconstruction persistence, leftover-map unexplained leftover persistence,
+leftover-map cross share persistence, leftover-map unexplained leftover
+share persistence, leftover-map explained leftover share persistence,
+leftover-map coordinate persistence, leftover-map graphic display,
+leftover-map axis share on the graphic display, leftover-map coordinate
+ticks, leftover-map distance on pair segments, leftover-map reconstruction
+on pair segments, leftover-map explained leftover share on pair segments,
+leftover-map unexplained leftover share on pair segments, leftover-map
+cross share on pair segments, leftover-map unexplained leftover on pair
+segments, leftover residual on pair segments, leftover observed on pair
+segments, leftover expected on pair segments, leftover-map rank on pair
+segments, and leftover-map complete-case coverage on the graphic display.
+
+## 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}`. Incomplete columns are dropped from the
+complete-case residual rectangle; item coverage names how many scored
+criteria entered that factorization.)
diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md
index aaad869ff..4370dfe60 100644
--- a/docs/product-technical-gap-baseline.md
+++ b/docs/product-technical-gap-baseline.md
@@ -1,5 +1,53 @@
# Product & Technical Gap Baseline
+> Exact-head loop overlay: 2026-08-30 19:48 KST. Protected `main` is
+> `cb187cadee5fb6c46d8a944815ccc154a1e028d1` after unauthorized squash
+> of leftover-map coordinates (v2.24.0 / #782). Revert #808
+> (`revert-pr782-unauthorized` @ `1af3e53e`) restores `main` toward
+> `fc13acaa` (v2.23.0) and still needs independent APPROVE. GitHub
+> writes work (comment/close/create-PR/push). Token still has empty
+> `X-OAuth-Scopes`; repo permission is ADMIN. Open leftover stack
+> still lacks independent APPROVE. Do not self-approve.
+> #782 leftover-map coordinates through pair-segment distance
+> (v2.24.0–v2.28.0 / ADR 0267–0271) is `9bdd3e4d`. #802 explained
+> leftover share on pair segments (v2.30.0 / ADR 0273, includes v2.29
+> reconstruction) is `79ec22f0`. #803 unexplained leftover share on pair
+> segments (v2.31.0 / ADR 0274) is `089a1571`. #804 leftover-map cross
+> share on pair segments (v2.32.0 / ADR 0275) is `c513002b`. #805 leftover-map
+> unexplained leftover `U` on pair segments (v2.33.0 / ADR 0276) is
+> `e3fdfd74`. #806 leftover residual `R` on pair segments (v2.34.0 /
+> ADR 0277) is `33f6c3dd`. #809 leftover observed `Y` on pair segments
+> (v2.35.0 / ADR 0278) is `b334b00e`. #810 leftover expected `E` on pair
+> segments (v2.36.0 / ADR 0279) is `6e37757a`. #811 leftover-map rank on
+> pair segments (v2.37.0 / ADR 0280) is `e626a1d0`. #812 leftover-map
+> complete-case coverage on the graphic (v2.38.0 / ADR 0281) is `64964cb6`.
+> Leave
+> #782/#802/#803/#804/#805/#806/#808/#809/#810/#811/#812 open for independent
+> review. Do not squash-merge stacked leftover PRs onto an unprotected leftover
+> base. Do not merge #808 without independent APPROVE. Strix on #782
+> failed closed at `Run Strix (quick)`; do not weaken fail-closed on
+> `Vulnerabilities [1-9]`. Org Strix `ModelBehaviorError` classifier is
+> already on ContextualWisdomLab/.github. Copilot review is not
+> independent APPROVE. Issues #79 and #87 stay open. #96 is already
+> closed. Only collaborator is `seonghobae`; no independent reviewer can
+> be requested from this token.
+>
+> Next buyer increment on this cycle: leftover-map item complete-case
+> coverage on the graphic display (ADR 0282 / v2.39.0) delivered locally on
+> `feat/leftover-map-plot-item-coverage-v2390`. Caption the leftover-map
+> graphic with persisted leftover-map item complete-case coverage so two
+> criterion diamonds are not read as the scored-criterion census. UI-only;
+> no new columns. Missing, non-integer, negative-used, non-positive-scored,
+> or used-greater-than-scored item coverage omits that leftover-map item
+> coverage caption. Coverage `0 of M` is shown when persisted. Do not
+> invent item coverage from plotted criterion marker count, leftover-map
+> distance, leftover-map rank, leftover-map post coverage, or the count of
+> unused axes. Do not invent leftover scores. Stack onto leftover branch
+> `feat/leftover-map-plot-coverage-v2380` / #812; 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 19:21 KST. Protected `main` is
> `cb187cadee5fb6c46d8a944815ccc154a1e028d1` after unauthorized squash
> of leftover-map coordinates (v2.24.0 / #782). Revert #808
diff --git a/frontend/package.json b/frontend/package.json
index 8b742284a..ae62f2d9c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "2.38.0",
+ "version": "2.39.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index d8776aab5..fb3afaf0c 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -4157,6 +4157,9 @@ describe("App, authenticated", () => {
expect(screen.getByLabelText("Leftover-map graphic coverage")).toHaveTextContent(
"Leftover map used 2 of 3 scored posts (complete-case)",
);
+ expect(screen.getByLabelText("Leftover-map graphic item coverage")).toHaveTextContent(
+ "Leftover map used 2 of 2 scored criteria (complete-case)",
+ );
const coverageCaption = screen.getByLabelText("Leftover map coverage");
const closestPair = screen.getByRole("button", { name: /open leftover closest pair: public post/i });
const farthestPair = screen.getByRole("button", {
diff --git a/frontend/src/components/LeftoverMapPlot.css b/frontend/src/components/LeftoverMapPlot.css
index f9563c758..177e46ada 100644
--- a/frontend/src/components/LeftoverMapPlot.css
+++ b/frontend/src/components/LeftoverMapPlot.css
@@ -7,7 +7,8 @@
}
.leftover-map-plot-caption,
-.leftover-map-plot-coverage {
+.leftover-map-plot-coverage,
+.leftover-map-plot-item-coverage {
margin: 0 0 var(--space-control-gap);
color: var(--color-text);
font-size: 0.85rem;
diff --git a/frontend/src/components/LeftoverMapPlot.test.tsx b/frontend/src/components/LeftoverMapPlot.test.tsx
index 610fbfbe1..98c7700f9 100644
--- a/frontend/src/components/LeftoverMapPlot.test.tsx
+++ b/frontend/src/components/LeftoverMapPlot.test.tsx
@@ -61,7 +61,7 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("Leftover-map graphic display")).toBeInTheDocument();
expect(
screen.getByText(
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.",
),
).toBeInTheDocument();
expect(screen.getByText("Post ξ")).toBeInTheDocument();
@@ -90,6 +90,7 @@ describe("LeftoverMapPlot", () => {
expect(screen.getAllByLabelText("leftover expected E 2.00")).toHaveLength(2);
expect(screen.getAllByLabelText("leftover-map rank rank 1")).toHaveLength(2);
expect(screen.queryByLabelText("Leftover-map graphic coverage")).not.toBeInTheDocument();
+ expect(screen.queryByLabelText("Leftover-map graphic item coverage")).not.toBeInTheDocument();
const postMarker = screen.getByRole("button", {
@@ -596,4 +597,99 @@ describe("LeftoverMapPlot", () => {
);
expect(screen.getByLabelText("leftover-map rank rank 0")).toBeInTheDocument();
});
+
+ it("names persisted leftover-map item complete-case coverage on the graphic without inventing a leftover score", () => {
+ render(
+ ,
+ );
+ expect(screen.getByLabelText("Leftover-map graphic item coverage")).toHaveTextContent(
+ "Leftover map used 1 of 2 scored criteria (complete-case)",
+ );
+ expect(screen.getByLabelText("Leftover-map graphic item coverage")).not.toHaveTextContent(
+ "Leftover map used 2 of 2 scored criteria (complete-case)",
+ );
+ });
+
+ it("omits leftover-map item coverage on the graphic when item coverage is missing or not usable", () => {
+ const { rerender } = render(
+ ,
+ );
+ expect(screen.queryByLabelText("Leftover-map graphic item coverage")).not.toBeInTheDocument();
+ rerender(
+ ,
+ );
+ expect(screen.queryByLabelText("Leftover-map graphic item coverage")).not.toBeInTheDocument();
+ expect(screen.getByLabelText("Leftover-map graphic coverage")).toHaveTextContent(
+ "Leftover map used 2 of 3 scored posts (complete-case)",
+ );
+ });
+
+ it("names leftover-map item coverage 0 of M on a rank-0 origin when that persisted used count is a non-negative integer", () => {
+ render(
+ ,
+ );
+ expect(screen.getByLabelText("Leftover-map graphic item coverage")).toHaveTextContent(
+ "Leftover map used 0 of 2 scored criteria (complete-case)",
+ );
+ expect(screen.getByLabelText("Leftover-map graphic coverage")).toHaveTextContent(
+ "Leftover map used 0 of 3 scored posts (complete-case)",
+ );
+ });
});
diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx
index 09563c0c3..23bf202c9 100644
--- a/frontend/src/components/LeftoverMapPlot.tsx
+++ b/frontend/src/components/LeftoverMapPlot.tsx
@@ -3,8 +3,11 @@ import { t, tf } from "../i18n";
import { formatLeftoverMapCoordinatePair } from "../leftoverMapCoordinates";
import {
leftoverMapCoverageCounts,
+ leftoverMapItemCoverageCounts,
LEFTOVER_MAP_PLOT_COVERAGE,
LEFTOVER_MAP_PLOT_COVERAGE_LABEL,
+ LEFTOVER_MAP_PLOT_ITEM_COVERAGE,
+ LEFTOVER_MAP_PLOT_ITEM_COVERAGE_LABEL,
} from "../leftoverMapCoverage";
import {
formatLeftoverMapPlotAxisShare,
@@ -69,7 +72,8 @@ function leftoverMapPlotAxisText(
* unexplained leftover ``U``, leftover residual ``R``, leftover
* observed ``Y``, leftover expected ``E``, and leftover-map rank so the
* pair-row badges match the graphic. Name leftover-map complete-case
- * coverage on the figure when those persisted post counts are usable.
+ * coverage and leftover-map item complete-case coverage on the figure when
+ * those persisted post and criterion counts are usable.
* 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
@@ -82,6 +86,8 @@ function leftoverMapPlotAxisText(
* caption when ``E`` is missing or non-finite. Omit that leftover-map rank
* caption when rank is missing, negative, or not an integer. Omit that leftover-map
* coverage caption when coverage is missing or not usable complete-case integers.
+ * Omit that leftover-map item coverage caption when item coverage is missing or
+ * not usable complete-case integers.
* 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.
@@ -99,6 +105,7 @@ export function LeftoverMapPlot({
return null;
}
const coverageCounts = leftoverMapCoverageCounts(leftoverMapCoverage);
+ const itemCoverageCounts = leftoverMapItemCoverageCounts(leftoverMapCoverage);
const openPost = (postId: string) => {
const pair = firstPlottablePairForPost(pairs, postId);
@@ -119,6 +126,15 @@ export function LeftoverMapPlot({
{tf(LEFTOVER_MAP_PLOT_COVERAGE, coverageCounts)}
) : null}
+ {itemCoverageCounts !== null ? (
+
+ {tf(LEFTOVER_MAP_PLOT_ITEM_COVERAGE, itemCoverageCounts)}
+
+ ) : null}
-
diff --git a/frontend/src/components/LeftoverPairList.test.tsx b/frontend/src/components/LeftoverPairList.test.tsx
index dc5d081d5..6b951ebd9 100644
--- a/frontend/src/components/LeftoverPairList.test.tsx
+++ b/frontend/src/components/LeftoverPairList.test.tsx
@@ -209,6 +209,9 @@ describe("LeftoverPairList", () => {
expect(screen.getByLabelText("Leftover-map graphic coverage")).toHaveTextContent(
"Leftover map used 2 of 3 scored posts (complete-case)",
);
+ expect(screen.getByLabelText("Leftover-map graphic item coverage")).toHaveTextContent(
+ "Leftover map used 2 of 2 scored criteria (complete-case)",
+ );
expect(onSelectPost).toHaveBeenCalledWith(
expect.objectContaining({
post_id: "post-demo-public",
diff --git a/frontend/src/components/LeftoverPairList.tsx b/frontend/src/components/LeftoverPairList.tsx
index 6cce67b57..5c6f8e780 100644
--- a/frontend/src/components/LeftoverPairList.tsx
+++ b/frontend/src/components/LeftoverPairList.tsx
@@ -73,7 +73,8 @@ export type LeftoverPairListProps = {
* observed ``Y`` on those pair segments. ADR 0279 names persisted leftover
* expected ``E`` on those pair segments. ADR 0280 names persisted leftover-map
* rank on those pair segments. ADR 0281 names persisted leftover-map
- * complete-case coverage on the graphic. Every badge still
+ * complete-case coverage on the graphic. ADR 0282 names persisted leftover-map
+ * item complete-case coverage on the graphic. 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 7b06271b3..a5ea3dc22 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -77,7 +77,9 @@ describe("i18n", () => {
"leftover expected {label}",
"leftover-map rank {label}",
"Leftover-map graphic coverage",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "Leftover-map graphic item coverage",
+ "Leftover map used {used} of {scored} scored criteria (complete-case)",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. 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.",
@@ -341,22 +343,22 @@ describe("i18n", () => {
it.each([
[
"ko",
- "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x, 잔여 지도 미설명 잔여 U, 잔여 R, 관측 Y, 기대 E, 잔여 지도 순위입니다. 저장된 완전사례 포함 범위가 있으면 그림이 그 범위를 표시합니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
+ "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x, 잔여 지도 미설명 잔여 U, 잔여 R, 관측 Y, 기대 E, 잔여 지도 순위입니다. 저장된 완전사례 포함 범위와 기준 포함 범위가 있으면 그림이 그 범위를 표시합니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
],
- ["zh", "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s、残差图交叉份额 x、残差图未解释残差 U、残差 R、观测 Y、期望 E 与残差图秩。图在已保存时标出完全案例覆盖范围。点击帖子标记打开该帖子。此图不会虚构残差分数。"],
+ ["zh", "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s、残差图交叉份额 x、残差图未解释残差 U、残差 R、观测 Y、期望 E 与残差图秩。图在已保存时标出完全案例覆盖范围与准则完全案例覆盖范围。点击帖子标记打开该帖子。此图不会虚构残差分数。"],
[
"ja",
- "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x、残差マップ未説明残差 U、残差 R、観測 Y、期待 E、残差マップ階数です。保存済みの完全ケース対象範囲があるときはその範囲を示します。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
+ "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x、残差マップ未説明残差 U、残差 R、観測 Y、期待 E、残差マップ階数です。保存済みの完全ケース対象範囲と基準の完全ケース対象範囲があるときはその範囲を示します。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
],
[
"vi",
- "Bản đồ phần dư sau hiệu ứng chính IRT. Vạch trục ghi tọa độ bản đồ phần dư đã lưu. Đoạn cặp ghi khoảng cách bản đồ phần dư d, tái dựng bản đồ phần dư R̂, 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, Y quan sát, E kỳ vọng và hạng bản đồ phần dư. Hình ghi phạm vi trường hợp đầy đủ khi đã lưu. 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, Y quan sát, E kỳ vọng và hạng bản đồ phần dư. Hình ghi phạm vi trường hợp đầy đủ của bài viết và tiêu chí khi đã lưu. 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, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.",
),
).toBe(expected);
});
@@ -371,6 +373,16 @@ describe("i18n", () => {
expect(t("Leftover-map graphic coverage")).toBe(expected);
});
+ it.each([
+ ["ko", "잔여 지도 그림 기준 포함 범위"],
+ ["zh", "残差图图形准则覆盖范围"],
+ ["ja", "残差マップ図の基準対象範囲"],
+ ["vi", "Phạm vi đồ họa tiêu chí bản đồ phần dư"],
+ ] as const)("formats leftover-map graphic item coverage label in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(t("Leftover-map graphic item coverage")).toBe(expected);
+ });
+
it.each([
["ko", "잔여 지도 축 1 눈금 +0.50"],
["zh", "残差图轴 1 刻度 +0.50"],
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 4e51ac34d..0292bb17e 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -270,6 +270,8 @@ const TRANSLATIONS: Partial>> = {
"Leftover map coverage": "잔여 지도 포함 범위",
"Leftover map used {used} of {scored} scored posts (complete-case)":
"잔여 지도는 채점된 글 {scored}개 중 {used}개를 사용했습니다(완전사례)",
+ "Leftover map used {used} of {scored} scored criteria (complete-case)":
+ "잔여 지도는 채점된 기준 {scored}개 중 {used}개를 사용했습니다(완전사례)",
"Event Lineage": "이벤트 계보",
"Related posts": "관련 글",
"Related to": "관련 대상:",
@@ -602,8 +604,9 @@ const TRANSLATIONS: Partial>> = {
"leftover expected {label}": "기대 {label}",
"leftover-map rank {label}": "잔여 지도 순위 {label}",
"Leftover-map graphic coverage": "잔여 지도 그림 포함 범위",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x, 잔여 지도 미설명 잔여 U, 잔여 R, 관측 Y, 기대 E, 잔여 지도 순위입니다. 저장된 완전사례 포함 범위가 있으면 그림이 그 범위를 표시합니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
+ "Leftover-map graphic item coverage": "잔여 지도 그림 기준 포함 범위",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.":
+ "IRT 주효과 이후 잔여 지도입니다. 축 눈금은 저장된 잔여 지도 좌표입니다. 쌍 선분은 잔여 지도 거리 d, 잔여 지도 재구성 R̂, 잔여 지도 설명 잔여 점유율 e, 잔여 지도 미설명 잔여 점유율 s, 잔여 지도 교차 점유율 x, 잔여 지도 미설명 잔여 U, 잔여 R, 관측 Y, 기대 E, 잔여 지도 순위입니다. 저장된 완전사례 포함 범위와 기준 포함 범위가 있으면 그림이 그 범위를 표시합니다. 글 표식을 눌러 그 글을 여세요. 이 그림은 잔여 점수를 만들어내지 않습니다.",
"Open leftover-map post {title} at ξ {person}":
"잔여 지도 글 {title} 열기 (ξ {person})",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.":
@@ -864,6 +867,8 @@ const TRANSLATIONS: Partial>> = {
"Leftover map coverage": "残差地图覆盖范围",
"Leftover map used {used} of {scored} scored posts (complete-case)":
"残差地图使用了 {scored} 篇已评分帖文中的 {used} 篇(完全案例)",
+ "Leftover map used {used} of {scored} scored criteria (complete-case)":
+ "残差地图使用了 {scored} 个已评分准则中的 {used} 个(完全案例)",
"Event Lineage": "事件谱系",
"Related posts": "相关文章",
"Related to": "相关对象:",
@@ -1192,8 +1197,9 @@ const TRANSLATIONS: Partial>> = {
"leftover expected {label}": "期望 {label}",
"leftover-map rank {label}": "残差图秩 {label}",
"Leftover-map graphic coverage": "残差图图形覆盖范围",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s、残差图交叉份额 x、残差图未解释残差 U、残差 R、观测 Y、期望 E 与残差图秩。图在已保存时标出完全案例覆盖范围。点击帖子标记打开该帖子。此图不会虚构残差分数。",
+ "Leftover-map graphic item coverage": "残差图图形准则覆盖范围",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.":
+ "IRT 主效应后的残差图。轴刻度标出已保存的残差图坐标。配对线段标出残差图距离 d、残差图重建 R̂、残差图已解释残差份额 e、残差图未解释残差份额 s、残差图交叉份额 x、残差图未解释残差 U、残差 R、观测 Y、期望 E 与残差图秩。图在已保存时标出完全案例覆盖范围与准则完全案例覆盖范围。点击帖子标记打开该帖子。此图不会虚构残差分数。",
"Open leftover-map post {title} at ξ {person}":
"打开残差图帖子 {title}(ξ {person})",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.":
@@ -1479,6 +1485,8 @@ const TRANSLATIONS: Partial>> = {
"Leftover map coverage": "残差マップの対象範囲",
"Leftover map used {used} of {scored} scored posts (complete-case)":
"残差マップは採点済み投稿 {scored} 件のうち {used} 件を使いました(完全ケース)",
+ "Leftover map used {used} of {scored} scored criteria (complete-case)":
+ "残差マップは採点済み基準 {scored} 件のうち {used} 件を使いました(完全ケース)",
"Event Lineage": "イベント系譜",
"Related posts": "関連する投稿",
"Related to": "関連対象:",
@@ -1786,8 +1794,9 @@ const TRANSLATIONS: Partial>> = {
"leftover expected {label}": "期待 {label}",
"leftover-map rank {label}": "残差マップ階数 {label}",
"Leftover-map graphic coverage": "残差マップ図の対象範囲",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.":
- "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x、残差マップ未説明残差 U、残差 R、観測 Y、期待 E、残差マップ階数です。保存済みの完全ケース対象範囲があるときはその範囲を示します。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
+ "Leftover-map graphic item coverage": "残差マップ図の基準対象範囲",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.":
+ "IRT主効果後の残差マップです。軸目盛は保存済みの残差マップ座標です。ペア線分は残差マップ距離 d、残差マップ再構成 R̂、残差マップ説明済み残差割合 e、残差マップ未説明残差割合 s、残差マップ交差割合 x、残差マップ未説明残差 U、残差 R、観測 Y、期待 E、残差マップ階数です。保存済みの完全ケース対象範囲と基準の完全ケース対象範囲があるときはその範囲を示します。投稿マーカーをクリックしてその投稿を開いてください。この図は残差スコアを作りません。",
"Open leftover-map post {title} at ξ {person}":
"残差マップの投稿 {title} を開く(ξ {person})",
"Read observed Y {observed} and expected E {expected} after IRT main effects, then open this post.":
@@ -2073,6 +2082,8 @@ const TRANSLATIONS: Partial>> = {
"Leftover map coverage": "Phạm vi bản đồ phần dư",
"Leftover map used {used} of {scored} scored posts (complete-case)":
"Bản đồ phần dư dùng {used} trên {scored} bài đã chấm (trường hợp đầy đủ)",
+ "Leftover map used {used} of {scored} scored criteria (complete-case)":
+ "Bản đồ phần dư dùng {used} trên {scored} tiêu chí đã chấm (trường hợp đầy đủ)",
"Event Lineage": "Dòng sự kiện",
"Related posts": "Bài viết liên quan",
"Related to": "Liên quan đến:",
@@ -2380,8 +2391,9 @@ const TRANSLATIONS: Partial>> = {
"leftover expected {label}": "kỳ vọng {label}",
"leftover-map rank {label}": "hạng bản đồ phần dư {label}",
"Leftover-map graphic coverage": "Phạm vi đồ họa bản đồ phần dư",
- "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. 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, Y quan sát, E kỳ vọng và hạng bản đồ phần dư. Hình ghi phạm vi trường hợp đầy đủ khi đã lưu. 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 graphic item coverage": "Phạm vi đồ họa tiêu chí bản đồ phần dư",
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. 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, Y quan sát, E kỳ vọng và hạng bản đồ phần dư. Hình ghi phạm vi trường hợp đầy đủ của bài viết và tiêu chí khi đã lưu. 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/leftoverMapCoverage.test.ts b/frontend/src/leftoverMapCoverage.test.ts
index b6299c690..f8ae4c802 100644
--- a/frontend/src/leftoverMapCoverage.test.ts
+++ b/frontend/src/leftoverMapCoverage.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import type { LeftoverMapCoverage } from "./api";
-import { leftoverMapCoverageCounts } from "./leftoverMapCoverage";
+import { leftoverMapCoverageCounts, leftoverMapItemCoverageCounts } from "./leftoverMapCoverage";
function coverage(overrides: Partial = {}): LeftoverMapCoverage {
return {
@@ -52,3 +52,41 @@ describe("leftoverMapCoverageCounts", () => {
});
});
});
+
+describe("leftoverMapItemCoverageCounts", () => {
+ it("names persisted leftover-map item complete-case coverage without inventing a leftover score", () => {
+ expect(leftoverMapItemCoverageCounts(coverage())).toEqual({ used: 2, scored: 2 });
+ expect(leftoverMapItemCoverageCounts(coverage({ map_item_count: 0, scored_item_count: 2 }))).toEqual({
+ used: 0,
+ scored: 2,
+ });
+ expect(
+ leftoverMapItemCoverageCounts(coverage({ map_item_count: 1, scored_item_count: 2, incomplete_item_count: 1 })),
+ ).toEqual({ used: 1, scored: 2 });
+ });
+
+ it("omits item coverage when counts are missing or not usable complete-case integers", () => {
+ expect(leftoverMapItemCoverageCounts(null)).toBeNull();
+ expect(leftoverMapItemCoverageCounts(undefined)).toBeNull();
+ expect(leftoverMapItemCoverageCounts(coverage({ map_item_count: -1 }))).toBeNull();
+ expect(leftoverMapItemCoverageCounts(coverage({ scored_item_count: 0 }))).toBeNull();
+ expect(leftoverMapItemCoverageCounts(coverage({ map_item_count: 3, scored_item_count: 2 }))).toBeNull();
+ expect(leftoverMapItemCoverageCounts(coverage({ map_item_count: 1.5 }))).toBeNull();
+ expect(leftoverMapItemCoverageCounts(coverage({ scored_item_count: Number.NaN }))).toBeNull();
+ });
+
+ it("does not invent leftover-map item coverage from plotted criterion marker count", () => {
+ expect(
+ leftoverMapItemCoverageCounts(
+ coverage({
+ map_item_count: 1,
+ scored_item_count: 2,
+ }),
+ ),
+ ).toEqual({ used: 1, scored: 2 });
+ expect(leftoverMapItemCoverageCounts(coverage({ map_item_count: 1, scored_item_count: 2 }))).not.toEqual({
+ used: 2,
+ scored: 2,
+ });
+ });
+});
diff --git a/frontend/src/leftoverMapCoverage.ts b/frontend/src/leftoverMapCoverage.ts
index 1aa778e41..373f0cc29 100644
--- a/frontend/src/leftoverMapCoverage.ts
+++ b/frontend/src/leftoverMapCoverage.ts
@@ -1,4 +1,4 @@
-/** Leftover-map complete-case coverage after IRT main effects (ADR 0168 / ADR 0281). */
+/** Leftover-map complete-case coverage after IRT main effects (ADR 0168 / ADR 0281 / ADR 0282). */
import type { LeftoverMapCoverage } from "./api";
@@ -7,19 +7,20 @@ export const LEFTOVER_MAP_PLOT_COVERAGE_LABEL = "Leftover-map graphic coverage";
export const LEFTOVER_MAP_PLOT_COVERAGE =
"Leftover map used {used} of {scored} scored posts (complete-case)";
+export const LEFTOVER_MAP_PLOT_ITEM_COVERAGE_LABEL = "Leftover-map graphic item coverage";
+
+export const LEFTOVER_MAP_PLOT_ITEM_COVERAGE =
+ "Leftover map used {used} of {scored} scored criteria (complete-case)";
+
export type LeftoverMapCoverageCounts = {
used: number;
scored: number;
};
-export function leftoverMapCoverageCounts(
- coverage: LeftoverMapCoverage | null | undefined,
+function leftoverMapCompleteCaseCounts(
+ used: number,
+ scored: number,
): LeftoverMapCoverageCounts | null {
- if (coverage == null) {
- return null;
- }
- const used = coverage.map_post_count;
- const scored = coverage.scored_post_count;
if (!Number.isInteger(used) || !Number.isInteger(scored)) {
return null;
}
@@ -28,3 +29,21 @@ export function leftoverMapCoverageCounts(
}
return { used, scored };
}
+
+export function leftoverMapCoverageCounts(
+ coverage: LeftoverMapCoverage | null | undefined,
+): LeftoverMapCoverageCounts | null {
+ if (coverage == null) {
+ return null;
+ }
+ return leftoverMapCompleteCaseCounts(coverage.map_post_count, coverage.scored_post_count);
+}
+
+export function leftoverMapItemCoverageCounts(
+ coverage: LeftoverMapCoverage | null | undefined,
+): LeftoverMapCoverageCounts | null {
+ if (coverage == null) {
+ return null;
+ }
+ return leftoverMapCompleteCaseCounts(coverage.map_item_count, coverage.scored_item_count);
+}
diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts
index 5249df4f7..7806e6a2d 100644
--- a/frontend/src/leftoverMapPlotLayout.ts
+++ b/frontend/src/leftoverMapPlotLayout.ts
@@ -10,7 +10,8 @@
* ``R`` (ADR 0277), persisted leftover observed ``Y`` (ADR 0278),
* persisted leftover expected ``E`` (ADR 0279), and persisted leftover-map
* rank (ADR 0280). The plot names persisted leftover-map complete-case
- * coverage (ADR 0281).
+ * coverage (ADR 0281) and leftover-map item complete-case coverage
+ * (ADR 0282).
*/
import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates";
@@ -28,7 +29,7 @@ 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, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.";
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. The plot names leftover-map complete-case coverage and leftover-map item complete-case coverage when persisted. 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}";
diff --git a/pyproject.toml b/pyproject.toml
index aa5c276d2..ea2ae9125 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "2.38.0"
+version = "2.39.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" }