From 63092ded55a7c7d65b0f6586980b20d0d30f587d Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Sun, 30 Aug 2026 11:41:39 +0000
Subject: [PATCH 1/5] feat(reports): leftover-map incomplete item coverage on
graphic display (v2.41.0)
Caption the leftover-map graphic display with persisted leftover-map
incomplete item coverage (ADR 0284). After make seed, closest and
farthest leftover pairs sit above the member list with a plot caption
that matches leftover-map incomplete item coverage; click a post
marker or a pair button opens that post.
A missing, non-integer, or negative dropped count, or a dropped count
that contradicts usable item complete-case integers, omits that leftover-map
incomplete item caption. Dropped 0 is shown when that persisted dropped
count is a non-negative integer. Do not invent dropped criteria from
scored minus used, plotted criterion marker count, leftover-map
distance, leftover-map rank, leftover-map post coverage, leftover-map
item coverage, leftover-map incomplete post coverage, or the count of
unused axes. Never invent a leftover score or a theta. UI-only; no
new columns.
---
AGENTS.md | 13 +-
ARCHITECTURE.md | 9 +-
....41.0-leftover-map-plot-incomplete-item.md | 28 +++
CHANGELOG.md | 27 +++
CLAUDE.md | 2 +-
.../0284-leftover-map-plot-incomplete-item.md | 173 ++++++++++++++++++
docs/product-technical-gap-baseline.md | 54 ++++++
docs/storybook-inventory.md | 2 +-
frontend/package.json | 2 +-
frontend/src/App.test.tsx | 3 +
frontend/src/components/LeftoverMapPlot.css | 3 +-
.../src/components/LeftoverMapPlot.test.tsx | 71 ++++++-
frontend/src/components/LeftoverMapPlot.tsx | 21 ++-
.../src/components/LeftoverPairList.test.tsx | 3 +
frontend/src/components/LeftoverPairList.tsx | 3 +-
frontend/src/i18n.test.ts | 24 ++-
frontend/src/i18n.ts | 28 ++-
frontend/src/leftoverMapCoverage.test.ts | 40 ++++
frontend/src/leftoverMapCoverage.ts | 48 ++++-
frontend/src/leftoverMapPlotLayout.ts | 5 +-
pyproject.toml | 2 +-
21 files changed, 521 insertions(+), 40 deletions(-)
create mode 100644 CHANGELOG.d/2.41.0-leftover-map-plot-incomplete-item.md
create mode 100644 docs/adr/0284-leftover-map-plot-incomplete-item.md
diff --git a/AGENTS.md b/AGENTS.md
index a5d5d9996..c4b34cac2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -273,7 +273,7 @@ stops startup instead of leaving a healthy-looking partial schema, and
application code must not compensate for a missing table.
Period leftover pairs (ADR 0017 / 0018 / 0048 / 0049 / 0119 / 0158 / 0162 /
-0163 / 0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283) are computed in `lineageweave/leftover_pairs.py` from the
+0163 / 0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284) 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
@@ -354,6 +354,13 @@ scored post entering the map; a missing, non-integer, or negative dropped
count, or a dropped count that contradicts usable complete-case integers,
omits that leftover-map incomplete post caption and does not invent dropped
posts from scored minus used or from plotted marker count.
+ADR 0284 captions the leftover-map graphic display with persisted leftover-map
+incomplete item coverage so a `used N of M scored criteria` caption is not
+read as every scored criterion entering the map; a missing, non-integer, or
+negative dropped count, or a dropped count that contradicts usable item
+complete-case integers, omits that leftover-map incomplete item caption and
+does not invent dropped criteria from scored minus used or 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
@@ -369,7 +376,9 @@ and are not a leftover score. Complete-case coverage (ADR 0168) persists to
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. Incomplete post coverage (ADR 0283) captions the leftover-map
-graphic with how many scored posts stayed out of the factorization.
+graphic with how many scored posts stayed out of the factorization. Incomplete
+item coverage (ADR 0284) captions the leftover-map graphic with how many
+scored criteria stayed out of the factorization.
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 e38514fdd..6ceeeb4b2 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 / 0282 / 0283) 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 / 0283 / 0284) 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
@@ -661,7 +661,9 @@ expected `E`. ADR 0280 captions leftover-map pair segments with persisted leftov
rank. ADR 0281 captions the leftover-map graphic display with persisted leftover-map
complete-case coverage. ADR 0282 captions the leftover-map graphic display with
persisted leftover-map item complete-case coverage. ADR 0283 captions the leftover-map
-graphic display with persisted leftover-map incomplete post coverage. Click a post marker or a pair button
+graphic display with persisted leftover-map incomplete post coverage. ADR 0284
+captions the leftover-map graphic display with persisted leftover-map incomplete
+item 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
@@ -669,7 +671,8 @@ display contracts. Leftover-map axis share
0168) persists to `report_leftover_map_coverage` so readers see how
many scored posts entered the factorization, including on the graphic,
and how many scored criteria entered the factorization on the graphic,
-and how many scored posts stayed incomplete and out of the factorization. Results persist to
+and how many scored posts stayed incomplete and out of the factorization,
+and how many scored criteria stayed incomplete and out of the factorization. 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.41.0-leftover-map-plot-incomplete-item.md b/CHANGELOG.d/2.41.0-leftover-map-plot-incomplete-item.md
new file mode 100644
index 000000000..68543243e
--- /dev/null
+++ b/CHANGELOG.d/2.41.0-leftover-map-plot-incomplete-item.md
@@ -0,0 +1,28 @@
+## 2.41.0 — Leftover-map incomplete item coverage on the graphic display
+
+- Caption the leftover-map graphic display with persisted leftover-map
+ incomplete item coverage (ADR 0284). After `make seed`, closest and
+ farthest leftover pairs sit above the member list with the Gabriel biplot
+ of already-named coordinates, leftover-map axis share when finite, axis
+ ticks that match `ξ (x, y) ζ (x, y)` on the pair row, pair segments
+ that match `d` on the pair row, pair segments that match `R̂` on the
+ pair row, pair segments that match `R̂²/R²` on the pair row, pair
+ segments that match `U²/R²` on the pair row, pair segments that match
+ `2R̂U/R²` on the pair row, pair segments that match `U` on the pair
+ row, pair segments that match `R` on the pair row, pair segments that
+ match `Y` on the pair row, pair segments that match `E` on the pair
+ row, pair segments that match leftover-map rank on the pair row, a plot
+ caption that matches leftover-map complete-case coverage above the pair
+ list, a plot caption that matches leftover-map item complete-case
+ coverage, a plot caption that matches leftover-map incomplete post
+ coverage, and a plot caption that matches leftover-map incomplete item
+ coverage; click a post marker or a pair button opens that post.
+ A missing, non-integer, or negative dropped count, or a dropped count
+ that contradicts usable item complete-case integers, omits that leftover-map
+ incomplete item caption. Dropped `0` is shown when that persisted
+ dropped count is a non-negative integer. Do not invent dropped criteria
+ from scored minus used, plotted criterion marker count, leftover-map
+ distance, leftover-map rank, leftover-map post coverage, leftover-map
+ item coverage, leftover-map incomplete 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 df3f58696..568249bf9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,33 @@ 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 incomplete item coverage (ADR 0284 / v2.41.0). After
+ `make seed`, closest and farthest leftover pairs sit above the member
+ list with the Gabriel biplot of already-named coordinates, leftover-map
+ axis share when finite, axis ticks that match `ξ (x, y) ζ (x, y)` on
+ the pair row, pair segments that match `d` on the pair row, pair
+ segments that match `R̂` on the pair row, pair segments that match
+ `R̂²/R²` on the pair row, pair segments that match `U²/R²` on the pair
+ row, pair segments that match `2R̂U/R²` on the pair row, pair segments
+ that match `U` on the pair row, pair segments that match `R` on the
+ pair row, pair segments that match `Y` on the pair row, pair segments
+ that match `E` on the pair row, pair segments that match leftover-map
+ rank on the pair row, a plot caption that matches leftover-map
+ complete-case coverage above the pair list, a plot caption that matches
+ leftover-map item complete-case coverage, a plot caption that matches
+ leftover-map incomplete post coverage, and a plot caption that matches
+ leftover-map incomplete item coverage; click a post marker or a
+ pair button opens that post. A missing, non-integer, or negative
+ dropped count, or a dropped count that contradicts usable item
+ complete-case integers, omits that leftover-map incomplete item caption.
+ Dropped `0` is shown when that persisted dropped count is a non-negative
+ integer. Do not invent dropped criteria from scored minus used, plotted
+ criterion marker count, leftover-map distance, leftover-map rank,
+ leftover-map post coverage, leftover-map item coverage, leftover-map
+ incomplete 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 incomplete post coverage (ADR 0283 / v2.40.0). After
`make seed`, closest and farthest leftover pairs sit above the member
diff --git a/CLAUDE.md b/CLAUDE.md
index 70e509a8a..f6d2b42aa 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -49,7 +49,7 @@ cutoff. Global Ask optional `knowledge_cutoff` uses the same cover
Create/start endpoint rules (ADR 0017 / 0021), tie-vs-miss similarity
(ADR 0026), R&R catalog ids (ADR 0019 / 0027), leftover pairs
-(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283), occupational construct catalog search
+(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284), 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/0284-leftover-map-plot-incomplete-item.md b/docs/adr/0284-leftover-map-plot-incomplete-item.md
new file mode 100644
index 000000000..1eb30d18f
--- /dev/null
+++ b/docs/adr/0284-leftover-map-plot-incomplete-item.md
@@ -0,0 +1,173 @@
+# ADR 0284 — Name leftover-map incomplete item 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 incomplete post coverage on the graphic display
+([ADR 0283](0283-leftover-map-plot-incomplete.md)), leftover-map item
+complete-case coverage on the graphic display
+([ADR 0282](0282-leftover-map-plot-item-coverage.md)), leftover-map complete-case
+coverage on the graphic display
+([ADR 0281](0281-leftover-map-plot-coverage.md)), leftover-map rank on pair
+segments ([ADR 0280](0280-leftover-map-segment-rank.md)), leftover expected on pair
+segments ([ADR 0279](0279-leftover-map-segment-expected.md)), leftover observed on
+pair segments ([ADR 0278](0278-leftover-map-segment-observed.md)), leftover
+residual on pair segments ([ADR 0277](0277-leftover-map-segment-residual.md)),
+leftover-map unexplained leftover on pair segments
+([ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)), leftover-map
+cross share on pair segments ([ADR 0275](0275-leftover-map-segment-cross-share.md)),
+leftover-map unexplained leftover share on pair segments
+([ADR 0274](0274-leftover-map-segment-unexplained-share.md)), leftover-map
+explained leftover share on pair segments
+([ADR 0273](0273-leftover-map-segment-explained-share.md)), leftover-map
+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 `incomplete_item_count` on
+`report_leftover_map_coverage`. ADR 0282 already names persisted leftover-map
+item complete-case coverage on the leftover-map graphic display. ADR 0283
+already names persisted leftover-map incomplete post coverage on that
+graphic. The graphic still has no incomplete-item caption, so a buyer who
+reads `used N of M scored criteria (complete-case)` can treat those N
+criteria as the full scored census even after incomplete columns were
+dropped. Incomplete columns are dropped from Gabriel factorization; hiding
+dropped criteria on the plot lets a sparse criterion with one missing post
+vanish without a next action.
+
+This increment names leftover-map incomplete item coverage on the
+leftover-map graphic display. It does not add columns. It does not recompute
+dropped criteria from scored minus used, 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 **0284** so it does not collide with
+leftover-map incomplete post coverage on the graphic display (0283),
+leftover-map item complete-case coverage on the graphic display (0282),
+leftover-map complete-case coverage on the graphic display (0281), leftover-map
+rank on pair segments (0280), leftover 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 incomplete
+item coverage as `Leftover map dropped {dropped} incomplete criteria`, using
+the persisted `incomplete_item_count` integer. A missing coverage row, a
+non-integer dropped count, a negative dropped count, or a dropped count that
+contradicts usable item complete-case integers (`dropped !== scored − used`
+when `map_item_count` / `scored_item_count` are usable) omits that leftover-map
+incomplete item 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, leftover-map post coverage,
+leftover-map item coverage, or leftover-map incomplete post caption. Dropped
+`0` is shown when that persisted dropped count is a non-negative integer. Do
+not invent dropped criteria from scored minus used, plotted criterion marker
+count, leftover-map distance, leftover-map rank, leftover-map post coverage,
+leftover-map item coverage, leftover-map incomplete 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 incomplete item
+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, the plot names persisted leftover-map item
+complete-case coverage, the plot names persisted leftover-map incomplete
+post coverage, and the plot names persisted leftover-map incomplete item
+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 incomplete criteria when that dropped count 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, leftover-map complete-case coverage on the graphic display,
+leftover-map item complete-case coverage on the graphic display, and
+leftover-map incomplete post 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; incomplete item coverage names how many
+scored criteria stayed out of that factorization.)
diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md
index 583a02c33..37c969a3c 100644
--- a/docs/product-technical-gap-baseline.md
+++ b/docs/product-technical-gap-baseline.md
@@ -1,5 +1,59 @@
# Product & Technical Gap Baseline
+> Exact-head loop overlay: 2026-08-30 20:35 KST. Protected `main` is
+> `cb187cadee5fb6c46d8a944815ccc154a1e028d1` after unauthorized squash
+> of leftover-map coordinates (v2.24.0 / #782). Revert #808
+> (`revert-pr782-unauthorized` @ `1af3e53e`) restores `main` toward
+> `fc13acaa` (v2.23.0) and still needs independent APPROVE. GitHub
+> writes work (comment/close/create-PR/push). Token still has empty
+> `X-OAuth-Scopes`; repo permission is ADMIN. Open leftover stack
+> still lacks independent APPROVE. Do not self-approve.
+> #782 leftover-map coordinates through pair-segment distance
+> (v2.24.0–v2.28.0 / ADR 0267–0271) is `9bdd3e4d`. #802 explained
+> leftover share on pair segments (v2.30.0 / ADR 0273, includes v2.29
+> reconstruction) is `79ec22f0`. #803 unexplained leftover share on pair
+> segments (v2.31.0 / ADR 0274) is `089a1571`. #804 leftover-map cross
+> share on pair segments (v2.32.0 / ADR 0275) is `c513002b`. #805 leftover-map
+> unexplained leftover `U` on pair segments (v2.33.0 / ADR 0276) is
+> `e3fdfd74`. #806 leftover residual `R` on pair segments (v2.34.0 /
+> ADR 0277) is `33f6c3dd`. #809 leftover observed `Y` on pair segments
+> (v2.35.0 / ADR 0278) is `b334b00e`. #810 leftover expected `E` on pair
+> segments (v2.36.0 / ADR 0279) is `6e37757a`. #811 leftover-map rank on
+> pair segments (v2.37.0 / ADR 0280) is `e626a1d0`. #812 leftover-map
+> complete-case coverage on the graphic (v2.38.0 / ADR 0281) is `64964cb6`.
+> #813 leftover-map item complete-case coverage on the graphic (v2.39.0 /
+> ADR 0282) is `de2a8a8b`. #814 leftover-map incomplete post coverage on
+> the graphic (v2.40.0 / ADR 0283) is `77292872`. Leave
+> #782/#802/#803/#804/#805/#806/#808/#809/#810/#811/#812/#813/#814 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 incomplete item
+> coverage on the graphic display (ADR 0284 / v2.41.0) delivered locally on
+> `feat/leftover-map-plot-incomplete-item-v2410`. Caption the leftover-map
+> graphic with persisted leftover-map incomplete item coverage so a
+> `used N of M scored criteria` caption is not read as every scored
+> criterion entering the map. UI-only; no new columns. Missing,
+> non-integer, or negative dropped count, or a dropped count that
+> contradicts usable item complete-case integers, omits that leftover-map
+> incomplete item caption. Dropped `0` is shown when persisted. Do not
+> invent dropped criteria from scored minus used, plotted criterion
+> marker count, leftover-map distance, leftover-map rank, leftover-map
+> post coverage, leftover-map item coverage, leftover-map incomplete
+> post coverage, or the count of unused axes. Do not invent leftover
+> scores. Stack onto leftover branch `feat/leftover-map-plot-incomplete-v2400`
+> / #814; 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. Pair-list note stays post
+> coverage (ADR 0168). Grouping comparison strip (ADR 0149) does not
+> gain this caption.
+
> Exact-head loop overlay: 2026-08-30 20:20 KST. Protected `main` is
> `cb187cadee5fb6c46d8a944815ccc154a1e028d1` after unauthorized squash
> of leftover-map coordinates (v2.24.0 / #782). Revert #808
diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md
index 2c1d14bfd..78bcc406e 100644
--- a/docs/storybook-inventory.md
+++ b/docs/storybook-inventory.md
@@ -5,7 +5,7 @@ operator-facing control you can click before changing product CSS.
| Story | Operator next action | Token / module |
|---|---|---|
-| `Reports/LeftoverMapPlot` | Read the leftover-map graphic display of persisted `ξ` (posts) and `ζ` (criteria), match axis ticks to those coordinates, pair-segment `d` to leftover-map distance, pair-segment `R̂` to leftover-map reconstruction, pair-segment `R̂²/R²` to leftover-map explained leftover share, pair-segment `U²/R²` to leftover-map unexplained leftover share, pair-segment `2R̂U/R²` to leftover-map cross share, pair-segment `U` to leftover-map unexplained leftover, pair-segment `R` to leftover residual, pair-segment `Y` to leftover observed, pair-segment `E` to leftover expected, and pair-segment leftover-map rank, then click a post marker to open that post. Leftover-map axes name persisted leftover-map axis share when finite. `ClosestAndFarthest`, `RankZeroOrigin`, `MissingCoordinates`, and `MissingAxisShare` cover two-pair maps, rank-0 origin with 0% share, a `0` tick, `d 0.00`, `R̂ 0.00`, `R̂²/R² 0.00`, `U²/R² 0.00`, `2R̂U/R² 0.00`, `U 0.00`, `R 0.00`, `Y 0.00`, `E 0.00`, and `rank 0`, omitted plots, and missing share that keeps existing leftover-map axis text. The plot does not invent a leftover score. | `LeftoverMapPlot`, `leftoverMapPlotLayout`, `leftoverMapPlotAxisShare`, `--color-primary`, `--color-palette-blue-mid` |
+| `Reports/LeftoverMapPlot` | Read the leftover-map graphic display of persisted `ξ` (posts) and `ζ` (criteria), match axis ticks to those coordinates, pair-segment `d` to leftover-map distance, pair-segment `R̂` to leftover-map reconstruction, pair-segment `R̂²/R²` to leftover-map explained leftover share, pair-segment `U²/R²` to leftover-map unexplained leftover share, pair-segment `2R̂U/R²` to leftover-map cross share, pair-segment `U` to leftover-map unexplained leftover, pair-segment `R` to leftover residual, pair-segment `Y` to leftover observed, pair-segment `E` to leftover expected, and pair-segment leftover-map rank, then click a post marker to open that post. Leftover-map axes name persisted leftover-map axis share when finite. The plot names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. `ClosestAndFarthest`, `RankZeroOrigin`, `MissingCoordinates`, and `MissingAxisShare` cover two-pair maps, rank-0 origin with 0% share, a `0` tick, `d 0.00`, `R̂ 0.00`, `R̂²/R² 0.00`, `U²/R² 0.00`, `2R̂U/R² 0.00`, `U 0.00`, `R 0.00`, `Y 0.00`, `E 0.00`, and `rank 0`, omitted plots, and missing share that keeps existing leftover-map axis text. The plot does not invent a leftover score. | `LeftoverMapPlot`, `leftoverMapPlotLayout`, `leftoverMapPlotAxisShare`, `--color-primary`, `--color-palette-blue-mid` |
| `Reports/LeftoverPairList` | Read closest/farthest leftover pairs with named `R`, `Y`/`E`, rank, `U`, `s`, `e`, `x`, `R̂`, `ξ`/`ζ`, and `d`, then open that post. The leftover-map graphic display sits above the pair buttons when coordinates are finite, leftover-map axes name persisted leftover-map axis share, leftover-map axis ticks name persisted coordinates, pair segments name persisted leftover-map distance, pair segments name persisted leftover-map reconstruction, pair segments name persisted leftover-map explained leftover share, pair segments name persisted leftover-map unexplained leftover share, pair segments name persisted leftover-map cross share, pair segments name persisted leftover-map unexplained leftover, pair segments name persisted leftover residual, pair segments name persisted leftover observed, pair segments name persisted leftover expected, and pair segments name persisted leftover-map rank. | `LeftoverPairList`, `LeftoverMapPlot`, `ticket-list`, `post-badge` |
| `Workspace/OperationsDashboard` | Compare Event and post counts, inspect external-information coverage, then open the cited source behind a claim, handover, or repeat-issue fact. `EvidenceReady`, `NarrowViewport`, `AnalysisPendingAndMissingEvidence`, `AnalysisFailed`, and `LoadError` cover populated, mobile, unavailable-evidence, analysis-pending, retryable failure, and transport-error states. | `--color-dashboard-*`, `OperationsDashboard` |
| `Post/SimilarVocPanel` | Compare ontology/semantic similar VOC and prior action evidence, then open the source; unavailable states show no fabricated TEPP theta or weight. | `SimilarVocPanel.css`, `SimilarVocPanel` |
diff --git a/frontend/package.json b/frontend/package.json
index 7c43d0863..8dbd0fbda 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "2.40.0",
+ "version": "2.41.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 8af153295..d151aabe9 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -4163,6 +4163,9 @@ describe("App, authenticated", () => {
expect(screen.getByLabelText("Leftover-map graphic incomplete posts")).toHaveTextContent(
"Leftover map dropped 1 incomplete posts",
);
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 0 incomplete criteria",
+ );
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 7845798f1..45bfe213e 100644
--- a/frontend/src/components/LeftoverMapPlot.css
+++ b/frontend/src/components/LeftoverMapPlot.css
@@ -9,7 +9,8 @@
.leftover-map-plot-caption,
.leftover-map-plot-coverage,
.leftover-map-plot-item-coverage,
-.leftover-map-plot-incomplete-posts {
+.leftover-map-plot-incomplete-posts,
+.leftover-map-plot-incomplete-items {
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 aafe4a9fa..272ed9b1f 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, leftover-map item complete-case coverage, and leftover-map incomplete post 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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item 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();
@@ -92,6 +92,7 @@ describe("LeftoverMapPlot", () => {
expect(screen.queryByLabelText("Leftover-map graphic coverage")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Leftover-map graphic item coverage")).not.toBeInTheDocument();
expect(screen.queryByLabelText("Leftover-map graphic incomplete posts")).not.toBeInTheDocument();
+ expect(screen.queryByLabelText("Leftover-map graphic incomplete items")).not.toBeInTheDocument();
const postMarker = screen.getByRole("button", {
@@ -624,6 +625,9 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("Leftover-map graphic incomplete posts")).toHaveTextContent(
"Leftover map dropped 1 incomplete posts",
);
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 1 incomplete criteria",
+ );
});
it("omits leftover-map item coverage on the graphic when item coverage is missing or not usable", () => {
@@ -653,6 +657,9 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("Leftover-map graphic incomplete posts")).toHaveTextContent(
"Leftover map dropped 1 incomplete posts",
);
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 0 incomplete criteria",
+ );
});
it("names leftover-map item coverage 0 of M on a rank-0 origin when that persisted used count is a non-negative integer", () => {
@@ -701,6 +708,9 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("Leftover-map graphic incomplete posts")).toHaveTextContent(
"Leftover map dropped 3 incomplete posts",
);
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 2 incomplete criteria",
+ );
});
it("omits leftover-map incomplete posts on the graphic when the dropped count is missing or not usable", () => {
@@ -727,6 +737,9 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("Leftover-map graphic coverage")).toHaveTextContent(
"Leftover map used 2 of 3 scored posts (complete-case)",
);
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 0 incomplete criteria",
+ );
});
it("names leftover-map incomplete posts 0 when that persisted dropped count is a non-negative integer", () => {
@@ -751,5 +764,61 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("Leftover-map graphic coverage")).toHaveTextContent(
"Leftover map used 3 of 3 scored posts (complete-case)",
);
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 0 incomplete criteria",
+ );
+ });
+
+ it("omits leftover-map incomplete items on the graphic when the dropped count is missing or not usable", () => {
+ const { rerender } = render(
+ ,
+ );
+ expect(screen.queryByLabelText("Leftover-map graphic incomplete items")).not.toBeInTheDocument();
+ rerender(
+ ,
+ );
+ expect(screen.queryByLabelText("Leftover-map graphic incomplete items")).not.toBeInTheDocument();
+ expect(screen.getByLabelText("Leftover-map graphic item coverage")).toHaveTextContent(
+ "Leftover map used 2 of 2 scored criteria (complete-case)",
+ );
+ expect(screen.getByLabelText("Leftover-map graphic incomplete posts")).toHaveTextContent(
+ "Leftover map dropped 1 incomplete posts",
+ );
+ });
+
+ it("names leftover-map incomplete items 0 when that persisted dropped count is a non-negative integer", () => {
+ render(
+ ,
+ );
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 0 incomplete criteria",
+ );
+ expect(screen.getByLabelText("Leftover-map graphic item coverage")).toHaveTextContent(
+ "Leftover map used 2 of 2 scored criteria (complete-case)",
+ );
});
});
diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx
index 7b11b7b4e..97e2c9bf0 100644
--- a/frontend/src/components/LeftoverMapPlot.tsx
+++ b/frontend/src/components/LeftoverMapPlot.tsx
@@ -3,10 +3,13 @@ import { t, tf } from "../i18n";
import { formatLeftoverMapCoordinatePair } from "../leftoverMapCoordinates";
import {
leftoverMapCoverageCounts,
+ leftoverMapIncompleteItemCount,
leftoverMapIncompletePostCount,
leftoverMapItemCoverageCounts,
LEFTOVER_MAP_PLOT_COVERAGE,
LEFTOVER_MAP_PLOT_COVERAGE_LABEL,
+ LEFTOVER_MAP_PLOT_INCOMPLETE_ITEM,
+ LEFTOVER_MAP_PLOT_INCOMPLETE_ITEM_LABEL,
LEFTOVER_MAP_PLOT_INCOMPLETE_POST,
LEFTOVER_MAP_PLOT_INCOMPLETE_POST_LABEL,
LEFTOVER_MAP_PLOT_ITEM_COVERAGE,
@@ -75,9 +78,9 @@ 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, leftover-map item complete-case coverage, and leftover-map
- * incomplete post coverage on the figure when those persisted post and
- * criterion counts are usable.
+ * coverage, leftover-map item complete-case coverage, leftover-map
+ * incomplete post coverage, and leftover-map incomplete item 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
@@ -93,6 +96,8 @@ function leftoverMapPlotAxisText(
* Omit that leftover-map item coverage caption when item coverage is missing or
* not usable complete-case integers. Omit that leftover-map incomplete post
* caption when incomplete post coverage is missing or not a usable integer.
+ * Omit that leftover-map incomplete item caption when incomplete item
+ * coverage is missing or not a usable integer.
* Omit that axis badge when share is
* missing or non-finite and keep the existing leftover-map axis text.
* Omit the plot when no pair has four finite leftover-map coordinates.
@@ -112,6 +117,7 @@ export function LeftoverMapPlot({
const coverageCounts = leftoverMapCoverageCounts(leftoverMapCoverage);
const itemCoverageCounts = leftoverMapItemCoverageCounts(leftoverMapCoverage);
const incompletePostCount = leftoverMapIncompletePostCount(leftoverMapCoverage);
+ const incompleteItemCount = leftoverMapIncompleteItemCount(leftoverMapCoverage);
const openPost = (postId: string) => {
const pair = firstPlottablePairForPost(pairs, postId);
@@ -150,6 +156,15 @@ export function LeftoverMapPlot({
{tf(LEFTOVER_MAP_PLOT_INCOMPLETE_POST, incompletePostCount)}
) : null}
+ {incompleteItemCount !== null ? (
+
+ {tf(LEFTOVER_MAP_PLOT_INCOMPLETE_ITEM, incompleteItemCount)}
+
+ ) : null}
-
diff --git a/frontend/src/components/LeftoverPairList.test.tsx b/frontend/src/components/LeftoverPairList.test.tsx
index 90c7c47ee..230f10de2 100644
--- a/frontend/src/components/LeftoverPairList.test.tsx
+++ b/frontend/src/components/LeftoverPairList.test.tsx
@@ -215,6 +215,9 @@ describe("LeftoverPairList", () => {
expect(screen.getByLabelText("Leftover-map graphic incomplete posts")).toHaveTextContent(
"Leftover map dropped 1 incomplete posts",
);
+ expect(screen.getByLabelText("Leftover-map graphic incomplete items")).toHaveTextContent(
+ "Leftover map dropped 0 incomplete criteria",
+ );
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 4682a8de7..cae6e6c47 100644
--- a/frontend/src/components/LeftoverPairList.tsx
+++ b/frontend/src/components/LeftoverPairList.tsx
@@ -75,7 +75,8 @@ export type LeftoverPairListProps = {
* rank on those pair segments. ADR 0281 names persisted leftover-map
* complete-case coverage on the graphic. ADR 0282 names persisted leftover-map
* item complete-case coverage on the graphic. ADR 0283 names persisted leftover-map
- * incomplete post coverage on the graphic. Every badge still
+ * incomplete post coverage on the graphic. ADR 0284 names persisted leftover-map
+ * incomplete item 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 30bad8c60..cb1f77137 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -79,9 +79,11 @@ describe("i18n", () => {
"Leftover-map graphic coverage",
"Leftover-map graphic item coverage",
"Leftover-map graphic incomplete posts",
+ "Leftover-map graphic incomplete items",
"Leftover map used {used} of {scored} scored criteria (complete-case)",
"Leftover map dropped {dropped} incomplete posts",
- "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, leftover-map item complete-case coverage, and leftover-map incomplete post coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.",
+ "Leftover map dropped {dropped} incomplete criteria",
+ "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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item 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.",
@@ -345,22 +347,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 đủ của bài viết và tiêu chí cùng bài không đầ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í cùng bài không đầy đủ và tiêu chí không đầ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ư.",
],
] 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, leftover-map item complete-case coverage, and leftover-map incomplete post 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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. Click a post marker to open that post. The plot does not invent a leftover score.",
),
).toBe(expected);
});
@@ -395,6 +397,16 @@ describe("i18n", () => {
expect(t("Leftover-map graphic incomplete posts")).toBe(expected);
});
+ it.each([
+ ["ko", "잔여 지도 그림 불완전 기준"],
+ ["zh", "残差图图形不完整准则"],
+ ["ja", "残差マップ図の不完全基準"],
+ ["vi", "Tiêu chí không đầy đủ trên đồ họa bản đồ phần dư"],
+ ] as const)("formats leftover-map graphic incomplete items label in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(t("Leftover-map graphic incomplete items")).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 87ef9d354..14d919145 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -274,6 +274,8 @@ const TRANSLATIONS: Partial>> = {
"잔여 지도는 채점된 기준 {scored}개 중 {used}개를 사용했습니다(완전사례)",
"Leftover map dropped {dropped} incomplete posts":
"잔여 지도는 불완전 글 {dropped}개를 제외했습니다",
+ "Leftover map dropped {dropped} incomplete criteria":
+ "잔여 지도는 불완전 기준 {dropped}개를 제외했습니다",
"Event Lineage": "이벤트 계보",
"Related posts": "관련 글",
"Related to": "관련 대상:",
@@ -608,8 +610,9 @@ const TRANSLATIONS: Partial>> = {
"Leftover-map graphic coverage": "잔여 지도 그림 포함 범위",
"Leftover-map graphic item coverage": "잔여 지도 그림 기준 포함 범위",
"Leftover-map graphic incomplete posts": "잔여 지도 그림 불완전 글",
- "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, leftover-map item complete-case coverage, and leftover-map incomplete post 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 incomplete items": "잔여 지도 그림 불완전 기준",
+ "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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item 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.":
@@ -874,6 +877,8 @@ const TRANSLATIONS: Partial>> = {
"残差地图使用了 {scored} 个已评分准则中的 {used} 个(完全案例)",
"Leftover map dropped {dropped} incomplete posts":
"残差地图排除了 {dropped} 篇不完整帖文",
+ "Leftover map dropped {dropped} incomplete criteria":
+ "残差地图排除了 {dropped} 个不完整准则",
"Event Lineage": "事件谱系",
"Related posts": "相关文章",
"Related to": "相关对象:",
@@ -1204,8 +1209,9 @@ const TRANSLATIONS: Partial>> = {
"Leftover-map graphic coverage": "残差图图形覆盖范围",
"Leftover-map graphic item coverage": "残差图图形准则覆盖范围",
"Leftover-map graphic incomplete posts": "残差图图形不完整帖文",
- "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, leftover-map item complete-case coverage, and leftover-map incomplete post 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 incomplete items": "残差图图形不完整准则",
+ "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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item 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.":
@@ -1495,6 +1501,8 @@ const TRANSLATIONS: Partial>> = {
"残差マップは採点済み基準 {scored} 件のうち {used} 件を使いました(完全ケース)",
"Leftover map dropped {dropped} incomplete posts":
"残差マップは不完全な投稿 {dropped} 件を除外しました",
+ "Leftover map dropped {dropped} incomplete criteria":
+ "残差マップは不完全な基準 {dropped} 件を除外しました",
"Event Lineage": "イベント系譜",
"Related posts": "関連する投稿",
"Related to": "関連対象:",
@@ -1804,8 +1812,9 @@ const TRANSLATIONS: Partial>> = {
"Leftover-map graphic coverage": "残差マップ図の対象範囲",
"Leftover-map graphic item coverage": "残差マップ図の基準対象範囲",
"Leftover-map graphic incomplete posts": "残差マップ図の不完全投稿",
- "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, leftover-map item complete-case coverage, and leftover-map incomplete post 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 incomplete items": "残差マップ図の不完全基準",
+ "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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item 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.":
@@ -2095,6 +2104,8 @@ const TRANSLATIONS: Partial>> = {
"Bản đồ phần dư dùng {used} trên {scored} tiêu chí đã chấm (trường hợp đầy đủ)",
"Leftover map dropped {dropped} incomplete posts":
"Bản đồ phần dư loại {dropped} bài không đầy đủ",
+ "Leftover map dropped {dropped} incomplete criteria":
+ "Bản đồ phần dư loại {dropped} tiêu chí không đầy đủ",
"Event Lineage": "Dòng sự kiện",
"Related posts": "Bài viết liên quan",
"Related to": "Liên quan đến:",
@@ -2404,8 +2415,9 @@ const TRANSLATIONS: Partial>> = {
"Leftover-map graphic coverage": "Phạm vi đồ họa bản đồ phần dư",
"Leftover-map graphic item coverage": "Phạm vi đồ họa tiêu chí bản đồ phần dư",
"Leftover-map graphic incomplete posts": "Bài không đầy đủ trên đồ 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, leftover-map item complete-case coverage, and leftover-map incomplete post 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í cùng bài không đầ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 incomplete items": "Tiêu chí không đầy đủ trên đồ 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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item 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í cùng bài không đầy đủ và tiêu chí không đầ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ư.",
"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 57ef139c4..d5aa62c37 100644
--- a/frontend/src/leftoverMapCoverage.test.ts
+++ b/frontend/src/leftoverMapCoverage.test.ts
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import type { LeftoverMapCoverage } from "./api";
import {
leftoverMapCoverageCounts,
+ leftoverMapIncompleteItemCount,
leftoverMapIncompletePostCount,
leftoverMapItemCoverageCounts,
} from "./leftoverMapCoverage";
@@ -129,3 +130,42 @@ describe("leftoverMapIncompletePostCount", () => {
});
});
});
+
+describe("leftoverMapIncompleteItemCount", () => {
+ it("names persisted leftover-map incomplete item coverage without inventing a leftover score", () => {
+ expect(leftoverMapIncompleteItemCount(coverage())).toEqual({ dropped: 0 });
+ expect(
+ leftoverMapIncompleteItemCount(
+ coverage({ map_item_count: 0, scored_item_count: 2, incomplete_item_count: 2 }),
+ ),
+ ).toEqual({ dropped: 2 });
+ expect(
+ leftoverMapIncompleteItemCount(
+ coverage({ map_item_count: 1, scored_item_count: 2, incomplete_item_count: 1 }),
+ ),
+ ).toEqual({ dropped: 1 });
+ });
+
+ it("omits incomplete item coverage when the dropped count is missing or not a usable integer", () => {
+ expect(leftoverMapIncompleteItemCount(null)).toBeNull();
+ expect(leftoverMapIncompleteItemCount(undefined)).toBeNull();
+ expect(leftoverMapIncompleteItemCount(coverage({ incomplete_item_count: -1 }))).toBeNull();
+ expect(leftoverMapIncompleteItemCount(coverage({ incomplete_item_count: 1.5 }))).toBeNull();
+ expect(leftoverMapIncompleteItemCount(coverage({ incomplete_item_count: Number.NaN }))).toBeNull();
+ });
+
+ it("does not invent leftover-map incomplete items from scored minus used or from plotted criterion marker count", () => {
+ expect(
+ leftoverMapIncompleteItemCount(
+ coverage({
+ map_item_count: 2,
+ scored_item_count: 2,
+ incomplete_item_count: 1,
+ }),
+ ),
+ ).toBeNull();
+ expect(leftoverMapIncompleteItemCount(coverage({ incomplete_item_count: 0 }))).not.toEqual({
+ dropped: 1,
+ });
+ });
+});
diff --git a/frontend/src/leftoverMapCoverage.ts b/frontend/src/leftoverMapCoverage.ts
index ce4ab10d7..f6c2c6a67 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 / ADR 0282 / ADR 0283). */
+/** Leftover-map complete-case coverage after IRT main effects (ADR 0168 / ADR 0281 / ADR 0282 / ADR 0283 / ADR 0284). */
import type { LeftoverMapCoverage } from "./api";
@@ -17,15 +17,24 @@ export const LEFTOVER_MAP_PLOT_INCOMPLETE_POST_LABEL = "Leftover-map graphic inc
export const LEFTOVER_MAP_PLOT_INCOMPLETE_POST =
"Leftover map dropped {dropped} incomplete posts";
+export const LEFTOVER_MAP_PLOT_INCOMPLETE_ITEM_LABEL = "Leftover-map graphic incomplete items";
+
+export const LEFTOVER_MAP_PLOT_INCOMPLETE_ITEM =
+ "Leftover map dropped {dropped} incomplete criteria";
+
export type LeftoverMapCoverageCounts = {
used: number;
scored: number;
};
-export type LeftoverMapIncompletePostCount = {
+export type LeftoverMapIncompleteCount = {
dropped: number;
};
+export type LeftoverMapIncompletePostCount = LeftoverMapIncompleteCount;
+
+export type LeftoverMapIncompleteItemCount = LeftoverMapIncompleteCount;
+
function leftoverMapCompleteCaseCounts(
used: number,
scored: number,
@@ -39,6 +48,21 @@ function leftoverMapCompleteCaseCounts(
return { used, scored };
}
+function leftoverMapDroppedCount(
+ dropped: number,
+ used: number,
+ scored: number,
+): LeftoverMapIncompleteCount | null {
+ if (!Number.isInteger(dropped) || dropped < 0) {
+ return null;
+ }
+ const completeCase = leftoverMapCompleteCaseCounts(used, scored);
+ if (completeCase !== null && dropped !== completeCase.scored - completeCase.used) {
+ return null;
+ }
+ return { dropped };
+}
+
export function leftoverMapCoverageCounts(
coverage: LeftoverMapCoverage | null | undefined,
): LeftoverMapCoverageCounts | null {
@@ -63,16 +87,22 @@ export function leftoverMapIncompletePostCount(
if (coverage == null) {
return null;
}
- const dropped = coverage.incomplete_post_count;
- if (!Number.isInteger(dropped) || dropped < 0) {
- return null;
- }
- const completeCase = leftoverMapCompleteCaseCounts(
+ return leftoverMapDroppedCount(
+ coverage.incomplete_post_count,
coverage.map_post_count,
coverage.scored_post_count,
);
- if (completeCase !== null && dropped !== completeCase.scored - completeCase.used) {
+}
+
+export function leftoverMapIncompleteItemCount(
+ coverage: LeftoverMapCoverage | null | undefined,
+): LeftoverMapIncompleteItemCount | null {
+ if (coverage == null) {
return null;
}
- return { dropped };
+ return leftoverMapDroppedCount(
+ coverage.incomplete_item_count,
+ coverage.map_item_count,
+ coverage.scored_item_count,
+ );
}
diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts
index 243d06831..d1e58950d 100644
--- a/frontend/src/leftoverMapPlotLayout.ts
+++ b/frontend/src/leftoverMapPlotLayout.ts
@@ -11,7 +11,8 @@
* persisted leftover expected ``E`` (ADR 0279), and persisted leftover-map
* rank (ADR 0280). The plot names persisted leftover-map complete-case
* coverage (ADR 0281), leftover-map item complete-case coverage
- * (ADR 0282), and leftover-map incomplete post coverage (ADR 0283).
+ * (ADR 0282), leftover-map incomplete post coverage (ADR 0283), and
+ * leftover-map incomplete item coverage (ADR 0284).
*/
import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates";
@@ -29,7 +30,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, leftover-map item complete-case coverage, and leftover-map incomplete post 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, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item 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 4da603e66..d3f001d61 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "2.40.0"
+version = "2.41.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" }
From 2570e99e9cd3f39eae063015031b21a9c4af4c2f Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Sat, 5 Sep 2026 18:36:54 +0900
Subject: [PATCH 2/5] test(release): require v2.41 runtime identity
---
tests/test_package_version.py | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 tests/test_package_version.py
diff --git a/tests/test_package_version.py b/tests/test_package_version.py
new file mode 100644
index 000000000..cb85f136e
--- /dev/null
+++ b/tests/test_package_version.py
@@ -0,0 +1,20 @@
+"""Release identity stays consistent across Python and frontend artifacts."""
+
+from __future__ import annotations
+
+import json
+import tomllib
+from pathlib import Path
+
+from lineageweave import __version__
+
+
+def test_release_versions_are_synchronized() -> None:
+ """Runtime provenance must use the same version as shipped package metadata."""
+ repository_root = Path(__file__).resolve().parents[1]
+ project = tomllib.loads((repository_root / "pyproject.toml").read_text(encoding="utf-8"))
+ frontend = json.loads(
+ (repository_root / "frontend" / "package.json").read_text(encoding="utf-8")
+ )
+
+ assert __version__ == project["project"]["version"] == frontend["version"]
From 9edc673ed45258198324a32d4a4d89f8b3e19238 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Sat, 5 Sep 2026 18:38:00 +0900
Subject: [PATCH 3/5] fix(release): align runtime provenance with v2.41
---
lineageweave/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py
index 45c371fa7..226794a62 100644
--- a/lineageweave/__init__.py
+++ b/lineageweave/__init__.py
@@ -130,4 +130,4 @@
"serialize_lineage_analysis_result",
]
-__version__ = "2.20.0"
+__version__ = "2.41.0"
From f969cf9cf9af8a4ba0e7e126d1c61f6b0bef543e Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Sat, 5 Sep 2026 18:38:25 +0900
Subject: [PATCH 4/5] test(stack): inherit repaired #814 contracts
---
docs/index.md | 30 ++++++++++++++++
.../src/leftoverMapPlotSmallCanvas.test.ts | 35 +++++++++++++++++++
.../src/leftoverMapPlotVietnameseCopy.test.ts | 27 ++++++++++++++
3 files changed, 92 insertions(+)
create mode 100644 docs/index.md
create mode 100644 frontend/src/leftoverMapPlotSmallCanvas.test.ts
create mode 100644 frontend/src/leftoverMapPlotVietnameseCopy.test.ts
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 000000000..3b2deb73d
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,30 @@
+# LineageWeave
+
+LineageWeave reconstructs evidence-bound lineage, project journeys, and relationship context from scattered enterprise records so people can inspect how events, claims, posts, and decisions relate over time.
+
+## Start here
+
+Use LineageWeave when source systems contain related records without reliable explicit cross-record links and you need a browsable, evidence-preserving view of branching histories rather than a flat search result.
+
+The product combines deterministic lineage assembly with bounded retrieval/fusion and optional adjudication through shared ContextualWisdomLab components. It keeps source authority, authentication, tenant scope, measurement, and model-provider routing in their owning products and contracts.
+
+## Product surfaces
+
+- lineage reconstruction and project journeys;
+- operations/dashboard views over governed records;
+- evidence-aware Global Ask and related context navigation;
+- authenticated API and web client over durable PostgreSQL state;
+- integration with ThreadWeave, RankWeave, TEPP, contextual-orchestrator, and shared context contracts through explicit boundaries.
+
+## Documentation
+
+- [Repository overview and local stack](../README.md)
+- [Architecture](../ARCHITECTURE.md)
+- [Product requirements](product-requirements.md)
+- [Product/technical gap baseline](product-technical-gap-baseline.md)
+- [Architecture decisions](adr/)
+- [Research notes](lineage-bi-research-notes.md)
+
+## Status and evidence
+
+The repository has progressed beyond its original synthetic reconstruction demo into a broader authenticated product stack, while synthetic fixtures remain important for safe examples and tests. Treat protected-main behavior, current release evidence, and repository verification as authoritative; do not infer production readiness from a documentation page or an open pull request alone.
diff --git a/frontend/src/leftoverMapPlotSmallCanvas.test.ts b/frontend/src/leftoverMapPlotSmallCanvas.test.ts
new file mode 100644
index 000000000..75d18ab13
--- /dev/null
+++ b/frontend/src/leftoverMapPlotSmallCanvas.test.ts
@@ -0,0 +1,35 @@
+import { describe, expect, it } from "vitest";
+import { layoutLeftoverMapPlot, type LeftoverMapPlottablePair } from "./leftoverMapPlotLayout";
+
+const completeCaptionPair: LeftoverMapPlottablePair = {
+ pair_kind: "closest",
+ post_id: "post-small-canvas",
+ post_title: "Small canvas post",
+ criterion_code: "sales_lead_quality",
+ leftover_distance: 0.12,
+ leftover_map_reconstruction: 0.25,
+ leftover_map_explained_share: 0.76,
+ leftover_map_person_axis_1: -1,
+ leftover_map_person_axis_2: -1,
+ leftover_map_item_axis_1: 1,
+ leftover_map_item_axis_2: -1,
+};
+
+describe("layoutLeftoverMapPlot small-canvas caption bounds", () => {
+ it("keeps a complete three-caption stack inside the requested 20px height", () => {
+ const layout = layoutLeftoverMapPlot(
+ [completeCaptionPair],
+ (criterionCode) => criterionCode,
+ { height: 20 },
+ );
+
+ expect(layout).not.toBeNull();
+ expect(layout?.height).toBe(20);
+ const segment = layout?.segments[0];
+ expect(segment).toBeDefined();
+ expect(segment?.labelY).toBeGreaterThanOrEqual(12);
+ expect(segment?.reconstructionY).toBeGreaterThanOrEqual(segment?.labelY ?? 0);
+ expect(segment?.explainedShareY).toBeGreaterThanOrEqual(segment?.reconstructionY ?? 0);
+ expect(segment?.explainedShareY).toBeLessThanOrEqual(16);
+ });
+});
diff --git a/frontend/src/leftoverMapPlotVietnameseCopy.test.ts b/frontend/src/leftoverMapPlotVietnameseCopy.test.ts
new file mode 100644
index 000000000..16d29d5db
--- /dev/null
+++ b/frontend/src/leftoverMapPlotVietnameseCopy.test.ts
@@ -0,0 +1,27 @@
+import { afterEach, describe, expect, it } from "vitest";
+import { setLocale, tf } from "./i18n";
+
+const GRAPHIC_DISPLAY_COPY =
+ "Leftover map after IRT main effects. Axis ticks name persisted leftover-map coordinates. Pair segments name leftover-map distance d, leftover-map reconstruction R̂, leftover-map explained leftover share e, leftover-map unexplained leftover share s, leftover-map cross share x, leftover-map unexplained leftover U, leftover residual R, leftover observed Y, leftover expected E, and leftover-map rank. Click a post marker to open that post. The plot does not invent a leftover score.";
+
+afterEach(() => {
+ setLocale("en");
+});
+
+describe("leftover-map Vietnamese copy inheritance", () => {
+ it("keeps explained leftover share terminology as an explicit share", () => {
+ setLocale("vi");
+
+ expect(
+ tf("leftover-map explained leftover share {label}", { label: "R̂²/R² 0.76" }),
+ ).toBe("tỷ phần phần dư được giải thích trên bản đồ phần dư R̂²/R² 0.76");
+ });
+
+ it("keeps the expanded graphic description explicit about explained share e", () => {
+ setLocale("vi");
+
+ expect(tf(GRAPHIC_DISPLAY_COPY, {})).toContain(
+ "tỷ phần phần dư được giải thích e",
+ );
+ });
+});
From dd26eaf2169408af41e226164247949a29c69b6e Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Sun, 6 Sep 2026 15:53:09 +0900
Subject: [PATCH 5/5] test: keep ADR 0284 proposed while draft
---
tests/test_adr_0284_governance.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
create mode 100644 tests/test_adr_0284_governance.py
diff --git a/tests/test_adr_0284_governance.py b/tests/test_adr_0284_governance.py
new file mode 100644
index 000000000..531d3ed14
--- /dev/null
+++ b/tests/test_adr_0284_governance.py
@@ -0,0 +1,13 @@
+"""Governance regression for the draft ADR 0284 decision state."""
+
+from pathlib import Path
+
+
+ADR_PATH = Path(__file__).parents[1] / "docs/adr/0284-leftover-map-plot-incomplete-item.md"
+
+
+def test_draft_adr_0284_remains_proposed() -> None:
+ """Keep the unmerged decision proposed until independent acceptance exists."""
+ text = ADR_PATH.read_text(encoding="utf-8")
+ assert "**Decision status:** Proposed" in text
+ assert "**Decision status:** Accepted" not in text