From 3aac452eef781f3c1a5da258cd1c0c548e4930e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 31 Aug 2026 01:22:03 +0000 Subject: [PATCH] feat(reports): leftover-map singular values on grouping comparison graphic (v2.78.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caption persisted leftover-map singular values σ_k on the grouping comparison leftover-map graphic from already-named leftover-map axes (ADR 0321). Share and singular value omit independently. Rank-0 unused axes still name σ 0.00. Do not invent σ from leftover-map axis share. Synthetic fixtures only. --- AGENTS.md | 12 ++ ARCHITECTURE.md | 5 +- ...78.0-leftover-map-compare-plot-singular.md | 19 ++ CHANGELOG.md | 23 ++- CLAUDE.md | 2 +- .../0320-leftover-map-compare-plot-ticks.md | 7 +- ...0321-leftover-map-compare-plot-singular.md | 111 +++++++++++ docs/product-technical-gap-baseline.md | 18 ++ frontend/package.json | 2 +- frontend/src/App.test.tsx | 8 +- .../src/components/LeftoverMapPlot.test.tsx | 183 +++++++++++++----- frontend/src/components/LeftoverMapPlot.tsx | 32 ++- frontend/src/i18n.test.ts | 39 ++++ frontend/src/i18n.ts | 13 ++ frontend/src/leftoverMapPlotAxisShare.test.ts | 8 + .../src/leftoverMapPlotAxisSingular.test.ts | 93 +++++++++ frontend/src/leftoverMapPlotAxisSingular.ts | 44 +++++ frontend/src/leftoverMapPlotLayout.test.ts | 3 + frontend/src/leftoverMapPlotLayout.ts | 5 +- pyproject.toml | 2 +- 20 files changed, 561 insertions(+), 68 deletions(-) create mode 100644 CHANGELOG.d/2.78.0-leftover-map-compare-plot-singular.md create mode 100644 docs/adr/0321-leftover-map-compare-plot-singular.md create mode 100644 frontend/src/leftoverMapPlotAxisSingular.test.ts create mode 100644 frontend/src/leftoverMapPlotAxisSingular.ts diff --git a/AGENTS.md b/AGENTS.md index 424852d97..076369b1a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -637,6 +637,15 @@ that no persisted coordinate occupies. Do not invent leftover-map coordinate tic distance, leftover-map rank, leftover expected, leftover observed, leftover residual, leftover-map unexplained leftover, leftover-map reconstruction, leftover-map axis share, leftover-map post coverage, leftover-map item coverage, leftover-map incomplete post coverage, leftover-map incomplete item coverage, leftover pair count, or the count of unused axes. +ADR 0321 captions leftover-map singular values on that grouping comparison leftover-map +graphic from already-named leftover-map axes when that persisted `σ_k` is finite and +non-negative. Rank-0 unused axes still name leftover-map comparison graphic leftover-map +axis `σ 0.00`. Share and singular value omit independently. Do not invent leftover-map +singular values from leftover-map axis share, leftover-map coordinate ticks, leftover-map +distance, leftover-map rank, leftover expected, leftover observed, leftover residual, +leftover-map unexplained leftover, leftover-map reconstruction, leftover-map post coverage, +leftover-map item coverage, leftover-map incomplete post coverage, leftover-map incomplete +item coverage, leftover pair count, or the count of unused axes. When `R`, `R̂`, `U`, `x`, `s`, and `e` are finite, `e + s + x = 1`. When `Y`, `E`, and `R` are finite, `Y − E = R`. When `R`, `R̂`, and `U` are @@ -732,6 +741,9 @@ that grouping comparison leftover-map graphic with persisted leftover-map distan Grouping comparison leftover-map coordinate ticks (ADR 0320) captions that grouping comparison leftover-map graphic with persisted leftover-map coordinate ticks at the origin and at each unique finite `ξ` / `ζ` projection. +Grouping comparison leftover-map singular values (ADR 0321) captions +that grouping comparison leftover-map graphic with persisted leftover-map singular +values `σ_k` when leftoverSingularForAxis returns a usable finite non-negative value. Item complete-case coverage (ADR 0282) captions the leftover-map graphic with how many scored criteria entered the map. Item complete-case coverage on the pair list (ADR 0285) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 39c96edb6..d84a65f84 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -748,6 +748,8 @@ comparison leftover-map graphic when formatLeftoverMapDistance returns a usable badge. ADR 0320 captions leftover-map coordinate ticks on that grouping comparison leftover-map graphic from already-named leftover-map coordinates. +ADR 0321 captions leftover-map singular values on that grouping +comparison leftover-map graphic from already-named leftover-map axes. 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 @@ -797,7 +799,8 @@ graphic (ADR 0316), leftover expected on the grouping comparison leftover-map graphic (ADR 0317), leftover-map rank on the grouping comparison leftover-map graphic (ADR 0318), leftover-map distance on the grouping comparison leftover-map graphic (ADR 0319), leftover-map coordinate ticks on the grouping comparison leftover-map -graphic (ADR 0320); +graphic (ADR 0320), leftover-map singular values on the grouping comparison leftover-map +graphic (ADR 0321); `POST .../rebuild` scores every grouping kind (post_admin). `make seed` folds A-100/B-200 Event Lineage fixtures (and the Riverbend calendar post) that already have constructed IRT cells into the same shared diff --git a/CHANGELOG.d/2.78.0-leftover-map-compare-plot-singular.md b/CHANGELOG.d/2.78.0-leftover-map-compare-plot-singular.md new file mode 100644 index 000000000..14046ef95 --- /dev/null +++ b/CHANGELOG.d/2.78.0-leftover-map-compare-plot-singular.md @@ -0,0 +1,19 @@ +## 2.78.0 — Leftover-map singular values on grouping comparison leftover-map graphic + +- Caption leftover-map singular values on the grouping comparison leftover-map + graphic (ADR 0321). After `make seed`, closest and farthest leftover pairs + sit above the member list with leftover-map singular values `σ_k` on that + grouping comparison leftover-map graphic when those persisted singular values + are finite and non-negative; click a post marker or a pair button opens that + post. Rank-0 origin cells still name leftover-map comparison graphic leftover-map + axis `σ 0.00`. Comparison copy uses + `leftover map comparison graphic leftover-map axis {axis} σ {value}` so it + stays distinct from `leftover-map axis {axis} σ {value}`, from + `leftover axis {axis} σ {value}`, from + `leftover map comparison axis {axis} ({share}%)`, and from + `leftover map comparison graphic leftover-map axis {axis} tick {value}`. + Share and singular value omit independently. Do not invent `σ_k` from + leftover-map axis share. This increment does not caption leftover-map + singular values on leftover-axis report badges with a distinct name. + A finite negative leftover is shown, never clamped. Never invent a leftover + score. Never invent a theta. No new columns. diff --git a/CHANGELOG.md b/CHANGELOG.md index c1acf56f5..bd2315ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,24 @@ All notable changes to this project are documented here. Format follows ### Added +- Period leftover pairs now name leftover-map singular values on the grouping + comparison leftover-map graphic (ADR 0321 / v2.78.0). After `make seed`, + closest and farthest leftover pairs sit above the member list with leftover-map + singular values `σ_k` on that grouping comparison leftover-map graphic when + those persisted singular values are finite and non-negative; click a post + marker or a pair button opens that post. Rank-0 origin cells still name + leftover-map comparison graphic leftover-map axis `σ 0.00`. Comparison copy + uses `leftover map comparison graphic leftover-map axis {axis} σ {value}` so + it stays distinct from `leftover-map axis {axis} σ {value}`, from + `leftover axis {axis} σ {value}`, from + `leftover map comparison axis {axis} ({share}%)`, and from + `leftover map comparison graphic leftover-map axis {axis} tick {value}`. + Share and singular value omit independently. Do not invent `σ_k` from + leftover-map axis share. This increment does not caption leftover-map + singular values on leftover-axis report badges with a distinct name. + A finite negative leftover is shown, never clamped. Never invent a leftover + score or a theta. No new columns. + - Period leftover pairs now name leftover-map coordinate ticks on the grouping comparison leftover-map graphic (ADR 0320 / v2.77.0). After `make seed`, closest and farthest leftover pairs sit above the member list with leftover-map @@ -18,9 +36,8 @@ All notable changes to this project are documented here. Format follows `leftover map comparison graphic leftover-map axis {axis} tick {value}` so it stays distinct from `leftover-map axis {axis} tick {value}` and from `leftover map comparison axis {axis} ({share}%)`. Do not invent evenly spaced - ticks that no persisted coordinate occupies. This increment does not caption - leftover-map singular values on the comparison graphic with a distinct name. - Do not invent leftover-map coordinate ticks from leftover-map distance. + ticks that no persisted coordinate occupies. Leftover-map singular values on + the comparison graphic are ADR 0321. Do not invent leftover-map coordinate ticks from leftover-map distance. A finite negative leftover is shown, never clamped. Never invent a leftover score or a theta. No new columns. diff --git a/CLAUDE.md b/CLAUDE.md index cd8a6d9bf..8aa35a2b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,7 +49,7 @@ cutoff. Global Ask optional `knowledge_cutoff` uses the same cover Create/start endpoint rules (ADR 0017 / 0021), tie-vs-miss similarity (ADR 0026), R&R catalog ids (ADR 0019 / 0027), leftover pairs -(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284 / 0285 / 0286 / 0287 / 0288 / 0289 / 0290 / 0291 / 0292 / 0293 / 0294 / 0295 / 0296 / 0297 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320), occupational construct catalog search +(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284 / 0285 / 0286 / 0287 / 0288 / 0289 / 0290 / 0291 / 0292 / 0293 / 0294 / 0295 / 0296 / 0297 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321), 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/0320-leftover-map-compare-plot-ticks.md b/docs/adr/0320-leftover-map-compare-plot-ticks.md index b4ba99385..0ead801f4 100644 --- a/docs/adr/0320-leftover-map-compare-plot-ticks.md +++ b/docs/adr/0320-leftover-map-compare-plot-ticks.md @@ -3,6 +3,9 @@ **Decision status:** Accepted **Date:** 2026-08-31 +**Amended by:** [ADR 0321](0321-leftover-map-compare-plot-singular.md) +(leftover-map singular values on the grouping comparison leftover-map graphic) + Amends leftover-map coordinate ticks on graphic-display axes ([ADR 0270](0270-leftover-map-coordinate-ticks.md)), leftover-map distance on the grouping comparison leftover-map graphic @@ -12,7 +15,7 @@ on the grouping comparison leftover-map graphic display on the grouping comparison strip ([ADR 0304](0304-leftover-map-compare-graphic.md)), leftover-map coordinates ([ADR 0267](0267-leftover-map-coordinates.md)). Independent of leftover-map -singular values on the grouping comparison leftover-map graphic. +singular values on leftover-axis report badges. ## Context @@ -95,7 +98,7 @@ and distance are finite, `R̂ = ξ · ζ` and `d = ‖ξ − ζ‖`. ## Related -Independent of leftover-map singular values on the grouping comparison leftover-map graphic. +Independent of leftover-map singular values on leftover-axis report badges. ## References diff --git a/docs/adr/0321-leftover-map-compare-plot-singular.md b/docs/adr/0321-leftover-map-compare-plot-singular.md new file mode 100644 index 000000000..50d096056 --- /dev/null +++ b/docs/adr/0321-leftover-map-compare-plot-singular.md @@ -0,0 +1,111 @@ +# ADR 0321 — Name leftover-map singular values on the grouping comparison leftover-map graphic + +**Decision status:** Accepted +**Date:** 2026-08-31 + +Amends leftover-map coordinate ticks on the grouping comparison leftover-map +graphic ([ADR 0320](0320-leftover-map-compare-plot-ticks.md)), leftover-map +axis share on that comparison graphic +([ADR 0305](0305-leftover-map-compare-plot-axis-share.md)), leftover-map +graphic display on the grouping comparison strip +([ADR 0304](0304-leftover-map-compare-graphic.md)), leftover-map axis share +persistence ([ADR 0148](0148-leftover-map-axis-share.md)), leftover-map +coordinates ([ADR 0267](0267-leftover-map-coordinates.md)). Independent of +leftover-map singular values on leftover-axis report badges. + +## Context + +ADR 0148 already persists leftover-map singular values `σ_k` on +`report_leftover_map_axis` together with leftover-map axis share +`σ_k² / Σ_j σ_j²`. ADR 0305 already captions the grouping comparison +leftover-map graphic with `leftover map comparison axis {k} ({share}%)` +when that share is finite. ADR 0320 already ticks those comparison-graphic +axes at persisted `ξ` / `ζ` projections. The comparison graphic still +omits `σ_k`, so a buyer who compares leftover pairs can treat leftover-map +axis share, leftover-map distance `d`, leftover-map rank, or leftover-map +coordinate ticks as leftover-map structure without a next action. Gabriel +inertia share is `σ_k² / Σ_j σ_j²`; hiding the persisted singular value +lets share be read as a leftover score. A missing, non-finite, or negative +singular value is not a leftover score and must omit independently of +leftover-map axis share. Rank-0 unused axes still persist `σ_k = 0`. +A finite negative leftover is shown, never clamped. Do not invent `σ_k` +from leftover-map axis share. + +This increment captions leftover-map singular values on the grouping +comparison leftover-map graphic from already-named leftover-map axes. +Comparison copy uses the accessible names +`leftover map comparison graphic leftover-map axis {axis} σ {value}` and +`leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)` +so they stay distinct from hyphen `leftover-map axis {axis} σ {value}`, +from pair-list `leftover axis {axis} σ {value}`, from comparison axis-share +`leftover map comparison axis {axis} ({share}%)`, and from comparison +coordinate ticks +`leftover map comparison graphic leftover-map axis {axis} tick {value}`. +It does not add columns. Do not invent a leftover score. Do not invent a +theta. + +This protected increment uses **0321** so it does not collide with leftover-map +coordinate ticks on the grouping comparison leftover-map graphic (0320), +leftover-map distance on that graphic (0319), leftover-map axis share on that +graphic (0305), leftover-map singular values on leftover-axis report badges, +or the dashboard stacks. + +## Decision + +On the grouping comparison leftover-map graphic, caption leftover-map axis 1 +and leftover-map axis 2 with persisted leftover-map singular values `σ_k` +when that singular value is finite and non-negative. Share and singular +value omit independently: no share and no `σ_k` keeps +`leftover map comparison axis {k}`; share only stays +`leftover map comparison axis {k} ({share}%)`; `σ_k` only is +`leftover map comparison graphic leftover-map axis {k} σ {value}`; both is +`leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`. +A missing, non-finite, or negative singular value omits that `σ` badge and +does not invent `σ_k` from leftover-map axis share. Rank-0 unused axes +still name `σ 0.00`. Click a post marker to open that post. Criterion +markers are not post buttons. + +Leftover-map comparison graphic leftover-map singular values omit +independently of leftover-map comparison graphic leftover-map axis ticks +and leftover-map axis share. A missing singular value omits that leftover-map +comparison graphic leftover-map axis `σ` caption and keeps a usable +leftover-map comparison axis share caption, leftover-map comparison graphic +leftover-map axis ticks, leftover-map distance caption, leftover-map rank +caption, leftover expected `E`, leftover observed `Y`, leftover residual `R`, +leftover-map unexplained leftover `U`, leftover-map reconstruction `R̂`, +and leftover-map comparison graphic coverage notes. + +This increment does not caption leftover-map singular values on leftover-axis +report badges with a distinct comparison-strip name. Those leftover singular +values already persist on leftover-map axes through ADR 0148. This increment +does not persist leftover-map inner product, cosine, or length. + +Do not add SQL migrations. Do not edit shipped migrations. Do not invent a leftover +score. Do not invent a theta. + +## Consequences + +After `make seed`, grouping comparison leftover pairs that already show +the leftover-map graphic of persisted `ξ` / `ζ` also name leftover-map +singular values on that graphic when those persisted `σ_k` are finite and +non-negative. Rank-0 unused axes still plot at the origin and still name +leftover-map comparison graphic leftover-map axis `σ 0.00`. Click a post +marker or a pair button opens that post. When coordinates, reconstruction, +and distance are finite, `R̂ = ξ · ζ` and `d = ‖ξ − ζ‖`. When leftover-map +singular values and leftover-map axis share are finite, +leftover-map axis share is `σ_k² / Σ_j σ_j²`. + +## Related + +Independent of leftover-map singular values on leftover-axis report badges. + +## References + +Jeon, M., Jin, I. H., Schweinberger, M., & Baugh, S. (2021). Mapping +unobserved item–respondent interactions: A latent space item response +model with interaction map. *Psychometrika, 86*(2), 378–403. +https://doi.org/10.1007/s11336-021-09762-5 + +Gabriel, K. R. (1971). The biplot graphic display of matrices with +application to principal component analysis. *Biometrika, 58*(3), +453–467. https://doi.org/10.1093/biomet/58.3.453 diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 75be1a0d4..4b02b8eca 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,23 @@ # Product & Technical Gap Baseline +> Exact-head loop overlay: 2026-08-31 10:20 KST. Protected `main` is +> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not +> self-approve. #860 leftover-map coordinate ticks on the grouping comparison leftover-map +> graphic (v2.77.0 / ADR 0320) is `40fa5bd`, stacked on #859 @ `9ba8535`. Independent +> APPROVE is missing. Next buyer increment on this +> cycle: leftover-map singular values on the grouping comparison leftover-map +> graphic (ADR 0321 / v2.78.0) delivered locally on +> `feat/leftover-map-compare-plot-singular-v2780`. Caption persisted leftover-map +> singular values `σ_k` on that comparison graphic from already-named leftover-map +> axes. Distinct accessible name `leftover map comparison graphic leftover-map +> axis {axis} σ {value}`. Rank-0 unused axes still name `σ 0.00`. Share and +> singular value omit independently. Do not invent `σ_k` from leftover-map axis +> share. Do not caption leftover-map singular values on leftover-axis report +> badges with a distinct name this increment. Stack onto #860. +> Leave open for independent review. Do not squash-merge. Do not self-approve. +> Issues #79 and #87 stay open. Do not merge #808 without independent APPROVE. +> Never self-approve. + > Exact-head loop overlay: 2026-08-31 09:28 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not > self-approve. #859 leftover-map distance on the grouping comparison leftover-map diff --git a/frontend/package.json b/frontend/package.json index 2909efd65..336c33357 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.77.0", + "version": "2.78.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 313feda5b..4f3f774cb 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -4197,8 +4197,8 @@ describe("App, authenticated", () => { expect(screen.getByLabelText("Leftover map comparison graphic")).toBeInTheDocument(); expect(screen.getByText("leftover-map axis 1 (82%)")).toBeInTheDocument(); expect(screen.getByText("leftover-map axis 2 (18%)")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 2 (18%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 2 σ 0.86 (18%)")).toBeInTheDocument(); expect( screen.getAllByRole("button", { name: /open leftover-map post public post at ξ \(\+0\.50, \+0\.10\)/i, @@ -4322,12 +4322,12 @@ describe("App, authenticated", () => { ).not.toBeInTheDocument(); expect( within(screen.getByLabelText("Grouping comparison")).getByText( - "leftover map comparison axis 1 (82%)", + "leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)", ), ).toBeInTheDocument(); expect( within(screen.getByLabelText("Grouping comparison")).getByText( - "leftover map comparison axis 2 (18%)", + "leftover map comparison graphic leftover-map axis 2 σ 0.86 (18%)", ), ).toBeInTheDocument(); expect( diff --git a/frontend/src/components/LeftoverMapPlot.test.tsx b/frontend/src/components/LeftoverMapPlot.test.tsx index e3717a014..40162a36b 100644 --- a/frontend/src/components/LeftoverMapPlot.test.tsx +++ b/frontend/src/components/LeftoverMapPlot.test.tsx @@ -843,8 +843,8 @@ describe("LeftoverMapPlot", () => { "Leftover map comparison graphic of already-named coordinates. Click a post marker to open that post. The plot does not invent a leftover score.", ), ).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 2 (18%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 2 σ 0.86 (18%)")).toBeInTheDocument(); expect(screen.queryByText("leftover-map axis 1 (82%)")).not.toBeInTheDocument(); expect(screen.queryByText("leftover-map axis 2 (18%)")).not.toBeInTheDocument(); }); @@ -861,12 +861,95 @@ describe("LeftoverMapPlot", () => { variant="comparison" />, ); - expect(screen.getByText("leftover map comparison axis 1")).toBeInTheDocument(); + expect( + screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84"), + ).toBeInTheDocument(); expect(screen.getByText("leftover map comparison axis 2")).toBeInTheDocument(); + expect(screen.queryByText("leftover map comparison axis 1")).not.toBeInTheDocument(); expect(screen.queryByText(/leftover map comparison axis 1 \(/)).not.toBeInTheDocument(); expect(screen.queryByText(/leftover map comparison axis 2 \(/)).not.toBeInTheDocument(); expect(screen.queryByText("leftover-map axis 1")).not.toBeInTheDocument(); expect(screen.queryByText("leftover-map axis 2")).not.toBeInTheDocument(); + expect(screen.queryByText("leftover-map axis 1 σ 1.84")).not.toBeInTheDocument(); + }); + + it("names leftover-map singular values on the comparison graphic with a distinct name", () => { + render( + , + ); + expect( + screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)"), + ).toBeInTheDocument(); + expect( + screen.getByText("leftover map comparison graphic leftover-map axis 2 σ 0.86 (18%)"), + ).toBeInTheDocument(); + expect(screen.queryByText("leftover map comparison axis 1 (82%)")).not.toBeInTheDocument(); + expect(screen.queryByText("leftover-map axis 1 σ 1.84 (82%)")).not.toBeInTheDocument(); + expect(screen.queryByText("leftover axis 1 σ 1.84 82%")).not.toBeInTheDocument(); + expect(screen.queryByText("leftover-map axis 1 (82%)")).not.toBeInTheDocument(); + }); + + it("omits leftover-map comparison graphic singular values independently of leftover-map axis share", () => { + render( + , + ); + expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison axis 2 (18%)")).toBeInTheDocument(); + expect( + screen.queryByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)"), + ).not.toBeInTheDocument(); + expect( + screen.queryByText(/leftover map comparison graphic leftover-map axis 2 σ/), + ).not.toBeInTheDocument(); + }); + + it("names rank-0 zero leftover-map singular values on the comparison graphic", () => { + render( + , + ); + expect( + screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)"), + ).toBeInTheDocument(); + expect( + screen.getByText("leftover map comparison graphic leftover-map axis 2 σ 0.00 (0%)"), + ).toBeInTheDocument(); + expect(screen.queryByText("leftover map comparison axis 1 (0%)")).not.toBeInTheDocument(); + expect(screen.queryByText("leftover-map axis 1 σ 0.00 (0%)")).not.toBeInTheDocument(); }); it("names rank-0 zero leftover-map axis share on the comparison graphic", () => { @@ -890,8 +973,8 @@ describe("LeftoverMapPlot", () => { variant="comparison" />, ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 2 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 2 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map coverage on the comparison graphic with a distinct accessible name", () => { @@ -935,8 +1018,8 @@ describe("LeftoverMapPlot", () => { expect(screen.queryByLabelText("Leftover map comparison incomplete posts")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover-map graphic incomplete items")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover map comparison incomplete items")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 2 (18%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 2 σ 0.86 (18%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic coverage when coverage is missing or not usable", () => { @@ -954,7 +1037,7 @@ describe("LeftoverMapPlot", () => { ); expect(screen.queryByLabelText("Leftover map comparison graphic coverage")).not.toBeInTheDocument(); expect(screen.getByLabelText("Leftover map comparison graphic")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); rerender( { "Leftover map used 2 of 2 scored criteria (complete-case)", ); expect(screen.queryByLabelText("Leftover-map graphic coverage")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic item coverage when item coverage is missing or not usable", () => { @@ -1009,7 +1092,7 @@ describe("LeftoverMapPlot", () => { ); expect(screen.queryByLabelText("Leftover map comparison graphic item coverage")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover-map graphic item coverage")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); rerender( { ); expect(screen.queryByLabelText("Leftover map comparison graphic item coverage")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover-map graphic item coverage")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic incomplete posts when incomplete post coverage is missing or not usable", () => { @@ -1067,7 +1150,7 @@ describe("LeftoverMapPlot", () => { ); expect(screen.queryByLabelText("Leftover map comparison graphic incomplete posts")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover-map graphic incomplete posts")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); rerender( { ); expect(screen.queryByLabelText("Leftover map comparison graphic incomplete posts")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover-map graphic incomplete posts")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic incomplete items when incomplete item coverage is missing or not usable", () => { @@ -1128,7 +1211,7 @@ describe("LeftoverMapPlot", () => { ); expect(screen.queryByLabelText("Leftover map comparison graphic incomplete items")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover-map graphic incomplete items")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); rerender( { ); expect(screen.queryByLabelText("Leftover map comparison graphic incomplete items")).not.toBeInTheDocument(); expect(screen.queryByLabelText("Leftover-map graphic incomplete items")).not.toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names leftover-map comparison graphic coverage 0 of M on a rank-0 origin when that persisted used count is a non-negative integer", () => { @@ -1209,8 +1292,8 @@ describe("LeftoverMapPlot", () => { 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(); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 2 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 2 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map reconstruction on the comparison graphic with a distinct accessible name", () => { @@ -1246,7 +1329,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic incomplete items")).toHaveTextContent( "Leftover map dropped 0 incomplete criteria", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic reconstruction when R̂ is missing or not usable", () => { @@ -1280,7 +1363,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin reconstruction on the comparison graphic when that persisted value is finite", () => { @@ -1323,7 +1406,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 0 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map explained leftover share on the comparison graphic with a distinct accessible name", () => { @@ -1359,7 +1442,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic reconstruction R̂ +0.25"), ).toHaveTextContent("R̂ +0.25"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic explained leftover share when e is missing or not usable", () => { @@ -1396,7 +1479,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin explained leftover share on the comparison graphic when that persisted value is finite", () => { @@ -1443,7 +1526,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 0 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map unexplained leftover share on the comparison graphic with a distinct accessible name", () => { @@ -1482,7 +1565,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic reconstruction R̂ +0.25"), ).toHaveTextContent("R̂ +0.25"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic unexplained leftover share when s is missing or not usable", () => { @@ -1522,7 +1605,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin unexplained leftover share on the comparison graphic when that persisted value is finite", () => { @@ -1573,7 +1656,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 0 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map cross share on the comparison graphic with a distinct accessible name", () => { @@ -1615,7 +1698,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic reconstruction R̂ +0.25"), ).toHaveTextContent("R̂ +0.25"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic cross share when x is missing or not usable", () => { @@ -1658,7 +1741,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover-map cross share on the comparison graphic when that persisted value is finite", () => { @@ -1713,7 +1796,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 0 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map unexplained leftover on the comparison graphic with a distinct accessible name", () => { @@ -1758,7 +1841,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic reconstruction R̂ +0.25"), ).toHaveTextContent("R̂ +0.25"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic unexplained leftover when U is missing or not usable", () => { @@ -1804,7 +1887,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover-map unexplained leftover on the comparison graphic when that persisted value is finite", () => { @@ -1863,7 +1946,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 0 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover residual on the comparison graphic with a distinct accessible name", () => { @@ -1911,7 +1994,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic reconstruction R̂ +0.25"), ).toHaveTextContent("R̂ +0.25"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic leftover residual when R is missing or not usable", () => { @@ -1960,7 +2043,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover residual on the comparison graphic when that persisted value is finite", () => { @@ -2022,7 +2105,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 0 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover observed on the comparison graphic with a distinct accessible name", () => { @@ -2064,7 +2147,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic reconstruction R̂ +0.25"), ).toHaveTextContent("R̂ +0.25"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic leftover observed when Y is missing or not usable", () => { @@ -2106,7 +2189,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover observed on the comparison graphic when that persisted value is finite", () => { @@ -2163,7 +2246,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("Leftover map comparison graphic coverage")).toHaveTextContent( "Leftover map used 0 of 3 scored posts (complete-case)", ); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover expected on the comparison graphic with a distinct accessible name", () => { @@ -2198,7 +2281,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover residual R +0.40"), ).toHaveTextContent("R +0.40"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic leftover expected when E is missing or not usable", () => { @@ -2234,7 +2317,7 @@ describe("LeftoverMapPlot", () => { screen.getByLabelText("leftover map comparison graphic leftover residual R +0.40"), ).toHaveTextContent("R +0.40"); expect(screen.getByLabelText("leftover map comparison graphic leftover-map distance d 0.12")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover expected on the comparison graphic when that persisted value is finite", () => { @@ -2286,7 +2369,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover residual R 0.00"), ).toHaveTextContent("R 0.00"); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map rank on the comparison graphic with a distinct accessible name", () => { @@ -2324,7 +2407,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover residual R +0.40"), ).toHaveTextContent("R +0.40"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic leftover-map rank when rank is missing or not usable", () => { @@ -2363,7 +2446,7 @@ describe("LeftoverMapPlot", () => { screen.getByLabelText("leftover map comparison graphic leftover residual R +0.40"), ).toHaveTextContent("R +0.40"); expect(screen.getByLabelText("leftover map comparison graphic leftover-map distance d 0.12")).toBeInTheDocument(); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover-map rank on the comparison graphic when that persisted value is a non-negative integer", () => { @@ -2418,7 +2501,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover residual R 0.00"), ).toHaveTextContent("R 0.00"); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map distance on the comparison graphic with a distinct accessible name", () => { @@ -2456,7 +2539,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover observed Y 2.40"), ).toHaveTextContent("Y 2.40"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits leftover-map comparison graphic leftover-map distance when d is missing or not usable", () => { @@ -2494,7 +2577,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover residual R +0.40"), ).toHaveTextContent("R +0.40"); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover-map distance on the comparison graphic when that persisted value is finite", () => { @@ -2546,7 +2629,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover expected E 0.00"), ).toHaveTextContent("E 0.00"); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names leftover-map coordinate ticks on the comparison graphic with a distinct accessible name", () => { @@ -2588,7 +2671,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getAllByLabelText("leftover map comparison graphic leftover-map rank rank 1"), ).toHaveLength(2); - expect(screen.getByText("leftover map comparison axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); }); it("omits invented leftover-map comparison graphic leftover-map axis ticks that no persisted coordinate occupies", () => { @@ -2644,7 +2727,7 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover-map distance d 0.00"), ).toHaveTextContent("d 0.00"); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); it("names rank-0 origin leftover-map coordinate ticks on the comparison graphic", () => { @@ -2700,6 +2783,6 @@ describe("LeftoverMapPlot", () => { expect( screen.getByLabelText("leftover map comparison graphic leftover expected E 0.00"), ).toHaveTextContent("E 0.00"); - expect(screen.getByText("leftover map comparison axis 1 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover map comparison graphic leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); }); }); diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx index c0636c65c..a93f6eae8 100644 --- a/frontend/src/components/LeftoverMapPlot.tsx +++ b/frontend/src/components/LeftoverMapPlot.tsx @@ -27,6 +27,12 @@ import { LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE, LEFTOVER_MAP_PLOT_AXIS_SHARE, } from "../leftoverMapPlotAxisShare"; +import { + formatLeftoverMapPlotAxisSingular, + leftoverSingularForAxis, + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR, + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, +} from "../leftoverMapPlotAxisSingular"; import { firstPlottablePairForPost, layoutLeftoverMapPlot, @@ -84,10 +90,23 @@ function leftoverMapPlotAxisText( leftoverShareForAxis(leftoverMapAxes, axisIndex), ); if (variant === "comparison") { - if (percent === null) { + const singular = formatLeftoverMapPlotAxisSingular( + leftoverSingularForAxis(leftoverMapAxes, axisIndex), + ); + if (singular === null && percent === null) { return t(axisIndex === 1 ? LEFTOVER_MAP_COMPARE_PLOT_AXIS_1 : LEFTOVER_MAP_COMPARE_PLOT_AXIS_2); } - return tf(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE, { axis: axisIndex, share: percent }); + if (singular === null) { + return tf(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE, { axis: axisIndex, share: percent }); + } + if (percent === null) { + return tf(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR, { axis: axisIndex, value: singular }); + } + return tf(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, { + axis: axisIndex, + value: singular, + share: percent, + }); } if (percent === null) { return t(axisIndex === 1 ? "leftover-map axis 1" : "leftover-map axis 2"); @@ -129,8 +148,10 @@ function leftoverMapPlotAxisText( * 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 that axis singular-value badge when ``σ_k`` is missing, non-finite, + * or negative, independently of leftover-map axis share. Omit that axis badge when share is + * missing or non-finite and keep the existing leftover-map axis text, + * including any leftover-map comparison graphic leftover-map axis singular value. * Omit the plot when no pair has four finite leftover-map coordinates. * ADR 0304 reuses this graphic on the grouping comparison strip from * already-named leftover-map coordinates. ADR 0305 captions leftover-map axis @@ -181,6 +202,9 @@ function leftoverMapPlotAxisText( * labels. ADR 0320 captions leftover-map coordinate ticks on that * comparison graphic from already-named leftover-map coordinates * with distinct leftover map comparison graphic leftover-map axis tick + * labels. ADR 0321 captions leftover-map singular values on that + * comparison graphic from already-named leftover-map axes + * with distinct leftover map comparison graphic leftover-map axis σ * labels. * Never invent a leftover score. */ diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index d7ee7ef32..57b911fb0 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -105,6 +105,8 @@ describe("i18n", () => { "Leftover map comparison rank", "leftover map comparison graphic leftover-map distance {label}", "leftover map comparison graphic leftover-map axis {axis} tick {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)", "Leftover map comparison coordinates", "Leftover map comparison graphic", "Leftover map comparison", @@ -688,6 +690,43 @@ describe("i18n", () => { }, ); + it.each([ + ["ko", "잔여 지도 비교 그림 축 1 σ 1.84"], + ["zh", "残差地图比较图形轴 1 σ 1.84"], + ["ja", "残差マップの比較図軸 1 σ 1.84"], + ["vi", "trục đồ họa so sánh bản đồ phần dư 1 σ 1.84"], + ] as const)( + "formats leftover map comparison graphic leftover-map axis singular values in %s", + (locale, expected) => { + setLocale(locale); + expect( + tf("leftover map comparison graphic leftover-map axis {axis} σ {value}", { + axis: 1, + value: "1.84", + }), + ).toBe(expected); + }, + ); + + it.each([ + ["ko", "잔여 지도 비교 그림 축 1 σ 1.84 (82%)"], + ["zh", "残差地图比较图形轴 1 σ 1.84 (82%)"], + ["ja", "残差マップの比較図軸 1 σ 1.84 (82%)"], + ["vi", "trục đồ họa so sánh bản đồ phần dư 1 σ 1.84 (82%)"], + ] as const)( + "formats leftover map comparison graphic leftover-map axis singular share in %s", + (locale, expected) => { + setLocale(locale); + expect( + tf("leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)", { + axis: 1, + value: "1.84", + share: "82", + }), + ).toBe(expected); + }, + ); + it.each([ ["ko", "잔여 지도 비교 설명 잔여 점유율"], ["zh", "残差地图比较已解释残差份额"], diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index e1fa05892..203113342 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -297,6 +297,9 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison rank": "잔여 지도 비교 순위", "leftover map comparison graphic leftover-map distance {label}": "잔여 지도 비교 그림 거리 {label}", "leftover map comparison graphic leftover-map axis {axis} tick {value}": "잔여 지도 비교 그림 축 {axis} 눈금 {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value}": "잔여 지도 비교 그림 축 {axis} σ {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)": + "잔여 지도 비교 그림 축 {axis} σ {value} ({share}%)", "Leftover map comparison coordinates": "잔여 지도 비교 좌표", "Leftover map comparison graphic": "잔여 지도 비교 그림", "Leftover map comparison": "잔여 지도 비교", @@ -940,6 +943,9 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison rank": "残差地图比较秩", "leftover map comparison graphic leftover-map distance {label}": "残差地图比较图形距离 {label}", "leftover map comparison graphic leftover-map axis {axis} tick {value}": "残差地图比较图形轴 {axis} 刻度 {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value}": "残差地图比较图形轴 {axis} σ {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)": + "残差地图比较图形轴 {axis} σ {value} ({share}%)", "Leftover map comparison coordinates": "残差地图比较坐标", "Leftover map comparison graphic": "残差地图比较图形", "Leftover map comparison": "残差地图比较", @@ -1604,6 +1610,9 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison rank": "残差マップの比較階数", "leftover map comparison graphic leftover-map distance {label}": "残差マップの比較図距離 {label}", "leftover map comparison graphic leftover-map axis {axis} tick {value}": "残差マップの比較図軸 {axis} 目盛 {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value}": "残差マップの比較図軸 {axis} σ {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)": + "残差マップの比較図軸 {axis} σ {value} ({share}%)", "Leftover map comparison coordinates": "残差マップの比較座標", "Leftover map comparison graphic": "残差マップの比較図", "Leftover map comparison": "残差マップの比較", @@ -2247,6 +2256,10 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison rank": "Hạng so sánh bản đồ phần dư", "leftover map comparison graphic leftover-map distance {label}": "khoảng cách đồ họa so sánh bản đồ phần dư {label}", "leftover map comparison graphic leftover-map axis {axis} tick {value}": "vạch trục đồ họa so sánh bản đồ phần dư {axis} {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value}": + "trục đồ họa so sánh bản đồ phần dư {axis} σ {value}", + "leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)": + "trục đồ họa so sánh bản đồ phần dư {axis} σ {value} ({share}%)", "Leftover map comparison coordinates": "Tọa độ so sánh bản đồ phần dư", "Leftover map comparison graphic": "Đồ họa so sánh bản đồ phần dư", "Leftover map comparison": "So sánh bản đồ phần dư", diff --git a/frontend/src/leftoverMapPlotAxisShare.test.ts b/frontend/src/leftoverMapPlotAxisShare.test.ts index 6c99dac66..7315be18d 100644 --- a/frontend/src/leftoverMapPlotAxisShare.test.ts +++ b/frontend/src/leftoverMapPlotAxisShare.test.ts @@ -7,6 +7,10 @@ import { LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE, LEFTOVER_MAP_PLOT_AXIS_SHARE, } from "./leftoverMapPlotAxisShare"; +import { + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR, + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, +} from "./leftoverMapPlotAxisSingular"; describe("leftoverShareForAxis", () => { const axes = [ @@ -63,5 +67,9 @@ describe("leftover map comparison axis share labels", () => { expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE).not.toBe(LEFTOVER_MAP_PLOT_AXIS_SHARE); expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_1).not.toBe("leftover-map axis 1"); expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_2).not.toBe("leftover-map axis 2"); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE).not.toBe( + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, + ); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_1).not.toBe(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR); }); }); diff --git a/frontend/src/leftoverMapPlotAxisSingular.test.ts b/frontend/src/leftoverMapPlotAxisSingular.test.ts new file mode 100644 index 000000000..77cb55e73 --- /dev/null +++ b/frontend/src/leftoverMapPlotAxisSingular.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from "vitest"; +import { + formatLeftoverMapPlotAxisSingular, + leftoverSingularForAxis, + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR, + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, + LEFTOVER_MAP_PLOT_AXIS_SINGULAR, + LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE, +} from "./leftoverMapPlotAxisSingular"; +import { LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE } from "./leftoverMapPlotAxisShare"; +import { LEFTOVER_MAP_COMPARE_PLOT_TICK } from "./leftoverMapPlotLayout"; + +describe("leftoverSingularForAxis", () => { + const axes = [ + { axis_index: 1, leftover_singular_value: 1.84 }, + { axis_index: 2, leftover_singular_value: 0.86 }, + ]; + + it("reads persisted leftover-map singular values without inventing a leftover score", () => { + expect(leftoverSingularForAxis(axes, 1)).toBe(1.84); + expect(leftoverSingularForAxis(axes, 2)).toBe(0.86); + }); + + it("names a rank-0 zero leftover-map singular value", () => { + expect( + leftoverSingularForAxis( + [ + { axis_index: 1, leftover_singular_value: 0 }, + { axis_index: 2, leftover_singular_value: 0 }, + ], + 1, + ), + ).toBe(0); + }); + + it("omits a leftover-map axis when singular value is missing, non-finite, or negative", () => { + expect(leftoverSingularForAxis(undefined, 1)).toBeNull(); + expect(leftoverSingularForAxis([], 1)).toBeNull(); + expect(leftoverSingularForAxis([{ axis_index: 1, leftover_singular_value: null }], 1)).toBeNull(); + expect( + leftoverSingularForAxis([{ axis_index: 1, leftover_singular_value: Number.NaN }], 1), + ).toBeNull(); + expect( + leftoverSingularForAxis( + [{ axis_index: 1, leftover_singular_value: Number.POSITIVE_INFINITY }], + 1, + ), + ).toBeNull(); + expect(leftoverSingularForAxis([{ axis_index: 1, leftover_singular_value: -0.01 }], 1)).toBeNull(); + expect(leftoverSingularForAxis(axes, 3)).toBeNull(); + }); + + it("does not invent leftover-map singular values from leftover-map axis share", () => { + expect(leftoverSingularForAxis([{ axis_index: 1 }], 1)).toBeNull(); + }); +}); + +describe("formatLeftoverMapPlotAxisSingular", () => { + it("formats persisted leftover-map singular values without inventing a leftover score", () => { + expect(formatLeftoverMapPlotAxisSingular(1.84)).toBe("1.84"); + expect(formatLeftoverMapPlotAxisSingular(0.86)).toBe("0.86"); + expect(formatLeftoverMapPlotAxisSingular(0)).toBe("0.00"); + }); + + it("omits leftover-map singular values when the value is missing, non-finite, or negative", () => { + expect(formatLeftoverMapPlotAxisSingular(null)).toBeNull(); + expect(formatLeftoverMapPlotAxisSingular(undefined)).toBeNull(); + expect(formatLeftoverMapPlotAxisSingular(Number.NaN)).toBeNull(); + expect(formatLeftoverMapPlotAxisSingular(Number.POSITIVE_INFINITY)).toBeNull(); + expect(formatLeftoverMapPlotAxisSingular(Number.NEGATIVE_INFINITY)).toBeNull(); + expect(formatLeftoverMapPlotAxisSingular(-0.01)).toBeNull(); + }); +}); + +describe("leftover map comparison graphic leftover-map axis singular labels", () => { + it("stays distinct from leftover-map graphic axis singular copy and comparison axis share", () => { + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR).toBe( + "leftover map comparison graphic leftover-map axis {axis} σ {value}", + ); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE).toBe( + "leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)", + ); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR).not.toBe(LEFTOVER_MAP_PLOT_AXIS_SINGULAR); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE).not.toBe( + LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE, + ); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE).not.toBe( + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE, + ); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_PLOT_TICK); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR).not.toBe("leftover axis {axis} σ {value}"); + }); +}); diff --git a/frontend/src/leftoverMapPlotAxisSingular.ts b/frontend/src/leftoverMapPlotAxisSingular.ts new file mode 100644 index 000000000..a621fe351 --- /dev/null +++ b/frontend/src/leftoverMapPlotAxisSingular.ts @@ -0,0 +1,44 @@ +/** Caption leftover-map plot axes with persisted Gabriel singular values. */ + +import type { LeftoverMapAxis } from "./api"; + +export const LEFTOVER_MAP_PLOT_AXIS_SINGULAR = "leftover-map axis {axis} σ {value}"; + +export const LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE = + "leftover-map axis {axis} σ {value} ({share}%)"; + +export const LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR = + "leftover map comparison graphic leftover-map axis {axis} σ {value}"; + +export const LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE = + "leftover map comparison graphic leftover-map axis {axis} σ {value} ({share}%)"; + +export type LeftoverMapPlotAxisSingular = { + axis_index: LeftoverMapAxis["axis_index"]; + leftover_singular_value?: LeftoverMapAxis["leftover_singular_value"] | null; +}; + +export function leftoverSingularForAxis( + axes: ReadonlyArray | null | undefined, + axisIndex: number, +): number | null { + const axis = axes?.find((candidate) => candidate.axis_index === axisIndex); + if ( + axis == null || + axis.leftover_singular_value == null || + !Number.isFinite(axis.leftover_singular_value) || + axis.leftover_singular_value < 0 + ) { + return null; + } + return axis.leftover_singular_value; +} + +export function formatLeftoverMapPlotAxisSingular( + leftoverSingular: number | null | undefined, +): string | null { + if (leftoverSingular == null || !Number.isFinite(leftoverSingular) || leftoverSingular < 0) { + return null; + } + return leftoverSingular.toFixed(2); +} diff --git a/frontend/src/leftoverMapPlotLayout.test.ts b/frontend/src/leftoverMapPlotLayout.test.ts index d3d5481e0..6787cd828 100644 --- a/frontend/src/leftoverMapPlotLayout.test.ts +++ b/frontend/src/leftoverMapPlotLayout.test.ts @@ -1366,6 +1366,9 @@ describe("leftover map comparison graphic labels", () => { expect(LEFTOVER_MAP_COMPARE_PLOT_TICK).not.toBe( "leftover map comparison axis {axis} ({share}%)", ); + expect(LEFTOVER_MAP_COMPARE_PLOT_TICK).not.toBe( + "leftover map comparison graphic leftover-map axis {axis} σ {value}", + ); expect(LEFTOVER_MAP_COMPARE_PLOT_TICK).not.toBe( LEFTOVER_MAP_COMPARE_PLOT_SEGMENT_DISTANCE, ); diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts index 2c86013e5..c0dac3c23 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -48,7 +48,8 @@ * expected on that graphic (ADR 0317), and grouping comparison leftover-map * rank on that graphic (ADR 0318), grouping comparison leftover-map * distance on that graphic (ADR 0319), and grouping comparison leftover-map - * coordinate ticks on that graphic (ADR 0320) caption the pair list or the grouping comparison + * coordinate ticks on that graphic (ADR 0320), and grouping comparison leftover-map + * singular values on that graphic (ADR 0321) caption the pair list or the grouping comparison * strip. ADR 0304 reuses this graphic layout on the grouping comparison * strip. ADR 0305 captions leftover-map axis share on that comparison * graphic from already-named leftover-map axes. ADR 0306 captions leftover-map @@ -78,6 +79,8 @@ * already-named leftover-map distance. * ADR 0320 captions leftover-map coordinate ticks on that comparison graphic from * already-named leftover-map coordinates. + * ADR 0321 captions leftover-map singular values on that comparison graphic from + * already-named leftover-map axes. */ import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates"; diff --git a/pyproject.toml b/pyproject.toml index c1f5188d8..703556def 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.77.0" +version = "2.78.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" }