diff --git a/AGENTS.md b/AGENTS.md index 076369b1a..3039c75b6 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 / 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) 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 / 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 / 0322) 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 @@ -646,6 +646,12 @@ distance, leftover-map rank, leftover expected, leftover observed, leftover resi 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. +ADR 0322 captions leftover-axis report badges with persisted leftover-map singular +values `σ_k` when leftoverMapAxisBadgeSingular returns a usable finite non-negative +value; a missing, non-finite, or negative singular value omits that `σ` badge and +keeps `leftover axis {k} {share}%`. Do not invent leftover-map singular values from +leftover-map axis share. The grouping comparison strip does not gain leftover-axis +report badges with a distinct comparison-strip name. 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 @@ -744,6 +750,9 @@ 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. +Leftover-axis report badge leftover-map singular values (ADR 0322) captions leftover-axis +report badges with persisted leftover-map singular values `σ_k` when leftoverMapAxisBadgeSingular +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 d84a65f84..7bfd22ea6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -750,6 +750,8 @@ 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. +ADR 0322 captions leftover-axis report badges with persisted leftover-map +singular values `σ_k`. 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 @@ -800,7 +802,8 @@ 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), leftover-map singular values on the grouping comparison leftover-map -graphic (ADR 0321); +graphic (ADR 0321), leftover-map singular values on leftover-axis report badges +(ADR 0322); `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.79.0-leftover-map-axis-singular.md b/CHANGELOG.d/2.79.0-leftover-map-axis-singular.md new file mode 100644 index 000000000..d83609121 --- /dev/null +++ b/CHANGELOG.d/2.79.0-leftover-map-axis-singular.md @@ -0,0 +1,17 @@ +## 2.79.0 — Leftover-map singular values on leftover-axis report badges + +- Caption leftover-axis report badges with persisted leftover-map singular + values `σ_k` (ADR 0322). After `make seed`, leftover-axis badges name + persisted leftover-map singular values when finite together with persisted + leftover-map axis share; click a leftover pair opens that post. A missing, + non-finite, or negative singular value omits that `σ` badge and keeps + `leftover axis {k} {share}%`. Rank-0 unused axes still name `σ 0.00`. + Pair-list leftover-axis badges stay `leftover axis {k} σ {value} {share}%` + so they stay distinct from hyphen `leftover-map axis {k} σ {value} ({share}%)` + and from comparison copy + `leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`. + Do not invent `σ_k` from leftover-map axis share. This increment does not + caption leftover-map singular values on leftover-axis badges on the grouping + comparison strip 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 bd2315ce4..7d84ce590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ All notable changes to this project are documented here. Format follows ### Added +- Period leftover pairs now caption leftover-axis report badges with persisted + leftover-map singular values `σ_k` (ADR 0322 / v2.79.0). After `make seed`, + leftover-axis badges name persisted leftover-map singular values when finite + together with persisted leftover-map axis share; click a leftover pair opens + that post. A missing, non-finite, or negative singular value omits that `σ` + badge and keeps `leftover axis {k} {share}%`. Rank-0 unused axes still name + `σ 0.00`. Pair-list leftover-axis badges stay + `leftover axis {k} σ {value} {share}%` so they stay distinct from hyphen + `leftover-map axis {k} σ {value} ({share}%)` and from comparison copy + `leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`. + Do not invent `σ_k` from leftover-map axis share. This increment does not + caption leftover-map singular values on leftover-axis badges on the grouping + comparison strip 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 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 @@ -22,7 +37,8 @@ All notable changes to this project are documented here. Format follows `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. + singular values on leftover-axis report badges with a distinct name + (ADR 0322). 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 8aa35a2b4..d4cbddbe7 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 / 0321), 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 / 0322), 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/0148-leftover-map-axis-share.md b/docs/adr/0148-leftover-map-axis-share.md index a9d554176..4eb1f32c2 100644 --- a/docs/adr/0148-leftover-map-axis-share.md +++ b/docs/adr/0148-leftover-map-axis-share.md @@ -3,7 +3,9 @@ **Decision status:** Accepted **Date:** 2026-08-24 **Amended by:** [ADR 0269](0269-leftover-map-axis-share-plot.md) -(leftover-map axis share on the graphic display) +(leftover-map axis share on the graphic display); +[ADR 0322](0322-leftover-map-axis-singular.md) +(leftover-map singular values on leftover-axis report badges) ## Context diff --git a/docs/adr/0321-leftover-map-compare-plot-singular.md b/docs/adr/0321-leftover-map-compare-plot-singular.md index 50d096056..0d386b4d0 100644 --- a/docs/adr/0321-leftover-map-compare-plot-singular.md +++ b/docs/adr/0321-leftover-map-compare-plot-singular.md @@ -11,7 +11,8 @@ 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. +leftover-map singular values on leftover-axis report badges +([ADR 0322](0322-leftover-map-axis-singular.md)). ## Context @@ -76,7 +77,8 @@ 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 +report badges with a distinct comparison-strip name. Those leftover-axis report +badges are ADR 0322. 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. @@ -97,7 +99,8 @@ leftover-map axis share is `σ_k² / Σ_j σ_j²`. ## Related -Independent of leftover-map singular values on leftover-axis report badges. +Independent of leftover-map singular values on leftover-axis report badges +([ADR 0322](0322-leftover-map-axis-singular.md)). ## References diff --git a/docs/adr/0322-leftover-map-axis-singular.md b/docs/adr/0322-leftover-map-axis-singular.md new file mode 100644 index 000000000..9a783b770 --- /dev/null +++ b/docs/adr/0322-leftover-map-axis-singular.md @@ -0,0 +1,98 @@ +# ADR 0322 — Name leftover-map singular values on leftover-axis report badges + +**Decision status:** Accepted +**Date:** 2026-08-31 + +Amends leftover-map axis share persistence +([ADR 0148](0148-leftover-map-axis-share.md)). Independent of leftover-map +singular values on the grouping comparison leftover-map graphic +([ADR 0321](0321-leftover-map-compare-plot-singular.md)), leftover-map +coordinate ticks on that comparison 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 +coordinates ([ADR 0267](0267-leftover-map-coordinates.md)), leftover-map +graphic display ([ADR 0268](0268-leftover-map-graphic-display.md)), and +leftover-map axis share on the graphic display +([ADR 0269](0269-leftover-map-axis-share-plot.md)). + +## Context + +ADR 0148 already persists leftover-map singular values `σ_k` and leftover-map +axis share `σ_k² / Σ_j σ_j²` and captions leftover-axis report badges with +share only. ADR 0321 already captions the grouping comparison leftover-map +graphic with persisted `σ_k`. Those leftover-axis badges still hide `σ_k`, so +Gabriel (1971) singular values appear on the comparison graphic and never on +the report-level leftover-axis badges that name the same axes. Hiding `σ_k` on +the badges lets leftover-map axis share be read as leftover-map structure even +when the buyer cannot tell whether axis 1 is large because `σ_1` is large or +because axis 2 collapsed. 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-axis report badges with already persisted +leftover-map singular values. Pair-list leftover-axis badges stay +`leftover axis {k} σ {value} {share}%` so they stay distinct from hyphen +`leftover-map axis {k} σ {value} ({share}%)` and from comparison copy +`leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`. +It does not add columns. Do not invent a leftover score. Do not invent a +theta. + +This protected increment uses **0322** so it does not collide with leftover-map +singular values on the grouping comparison leftover-map graphic (0321), +leftover-map coordinate ticks on that graphic (0320), leftover-map axis share +on that graphic (0305), leftover-map graphic display on the grouping +comparison strip (0304), leftover-map axis share persistence (0148), or the +dashboard stacks. + +## Decision + +On leftover-axis report badges, caption leftover-map axis `k` with persisted +leftover-map singular value `σ_k` when that singular value is finite and +non-negative, including rank-0 zero singular values. Pair-list labels stay +distinct from graphic labels: leftover-axis badges stay +`leftover axis {k} σ {value} {share}%`; leftover-map graphic axes stay +`leftover-map axis {k} ({share}%)` until a later increment captions `σ_k` +there; grouping comparison leftover-map graphic axes stay +`leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`. +A missing, non-finite, or negative singular value omits that `σ` badge and +keeps the existing `leftover axis {k} {share}%` text. Do not invent `σ_k` +from leftover-map axis share. Axis 1 and axis 2 stay independently named. +Click a leftover pair to open that post. The grouping comparison strip +(ADR 0149) stays on its reduced leftover payload and does not gain leftover-axis +report badges with a distinct comparison-strip name. + +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`, leftover-axis report badges name persisted leftover-map +singular values when finite together with persisted leftover-map axis share; +click a leftover pair opens that post. Hidden posts stay hidden. Rank-0 unused +axes still name `σ 0.00`. Grouping comparison leftover-map graphic leftover-map +singular values (ADR 0321) remain. + +## Related + +Independent of leftover-map singular values on the grouping comparison +leftover-map graphic. This increment does not caption leftover-map singular +values on leftover-axis badges on the grouping comparison strip with a +distinct name. + +## 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.) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 4b02b8eca..9fef7a938 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,24 @@ # Product & Technical Gap Baseline +> Exact-head loop overlay: 2026-08-31 10:35 KST. Protected `main` is +> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not +> self-approve. #861 leftover-map singular values on the grouping comparison leftover-map +> graphic (v2.78.0 / ADR 0321) is `3aac452`, stacked on #860 @ `40fa5bd`. Independent +> APPROVE is missing. Next buyer increment on this +> cycle: leftover-map singular values on leftover-axis report badges +> (ADR 0322 / v2.79.0) delivered locally on +> `feat/leftover-map-axis-singular-v2790`. Caption persisted leftover-map +> singular values `σ_k` on leftover-axis report badges from already-named leftover-map +> axes. Distinct accessible name `leftover axis {axis} σ {value} {share}%`. +> Rank-0 unused axes still name `σ 0.00`. A missing, non-finite, or negative +> singular value omits that `σ` badge and keeps `leftover axis {k} {share}%`. +> Do not invent `σ_k` from leftover-map axis share. Do not caption leftover-map +> singular values on leftover-axis badges on the grouping comparison strip with +> a distinct name this increment. Stack onto #861. +> 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 10:20 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not > self-approve. #860 leftover-map coordinate ticks on the grouping comparison leftover-map diff --git a/frontend/package.json b/frontend/package.json index 336c33357..304a03d4d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.78.0", + "version": "2.79.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 4f3f774cb..38e065235 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -4177,8 +4177,8 @@ describe("App, authenticated", () => { expect(screen.getByText(/TEST-PU-REPORT/)).toBeInTheDocument(); expect(screen.getAllByText("shared metric").length).toBeGreaterThan(0); expect(screen.getAllByText(/CAT: sales-lead I=0\.70/).length).toBeGreaterThan(0); - expect(screen.getAllByText(/leftover axis 1 82%/).length).toBeGreaterThan(0); - expect(screen.getAllByText(/leftover axis 2 18%/).length).toBeGreaterThan(0); + expect(screen.getAllByText(/leftover axis 1 σ 1\.84 82%/).length).toBeGreaterThan(0); + expect(screen.getAllByText(/leftover axis 2 σ 0\.86 18%/).length).toBeGreaterThan(0); expect(screen.getByLabelText("Leftover-map axis share")).toHaveTextContent( "Open a leftover pair to read the post–criterion cell", ); @@ -4336,6 +4336,12 @@ describe("App, authenticated", () => { expect( within(screen.getByLabelText("Grouping comparison")).queryByText("leftover-map axis 2 (18%)"), ).not.toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).queryByText("leftover axis 1 σ 1.84 82%"), + ).not.toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).queryByText("leftover axis 2 σ 0.86 18%"), + ).not.toBeInTheDocument(); expect( within(screen.getByLabelText("Grouping comparison")).getByRole("button", { name: "Open leftover-map post Public post at ξ (+0.50, +0.10)", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dfa30ed1b..ecd927785 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -137,6 +137,12 @@ import { LEFTOVER_MAP_PLOT_INCOMPLETE_POST, LEFTOVER_MAP_PLOT_ITEM_COVERAGE, } from "./leftoverMapCoverage"; +import { + leftoverMapAxisBadgeShare, + leftoverMapAxisBadgeSingular, + LEFTOVER_MAP_AXIS_BADGE_SHARE, + LEFTOVER_MAP_AXIS_BADGE_SINGULAR, +} from "./leftoverMapAxisBadge"; import { formatLeftoverMapReconstruction, LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL, @@ -3923,18 +3929,25 @@ function ReportsPanel({ {tf(LEFTOVER_MAP_PLOT_INCOMPLETE_ITEM, incompleteItemCount)}

) : null} - {report.leftover_map_axes?.map((axis) => ( - - {tf("leftover axis {axis} {share}%", { - axis: axis.axis_index, - share: (axis.leftover_share * 100).toFixed(0), - })} - - ))} + {report.leftover_map_axes?.map((axis) => { + const singular = leftoverMapAxisBadgeSingular(axis); + const share = leftoverMapAxisBadgeShare(axis.leftover_share); + return ( + + {singular === null + ? tf(LEFTOVER_MAP_AXIS_BADGE_SHARE, { axis: axis.axis_index, share }) + : tf(LEFTOVER_MAP_AXIS_BADGE_SINGULAR, { + axis: axis.axis_index, + value: singular, + share, + })} + + ); + })} {report.leftover_map_axes && report.leftover_map_axes.length > 0 && (

{t( - "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.", + "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Leftover-map singular values are the Gabriel scale of those axes. Open a leftover pair to read the post–criterion cell. The shares and singular values do not invent a leftover score.", )}

)} diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx index a93f6eae8..f9d2a4750 100644 --- a/frontend/src/components/LeftoverMapPlot.tsx +++ b/frontend/src/components/LeftoverMapPlot.tsx @@ -205,7 +205,8 @@ function leftoverMapPlotAxisText( * 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. + * labels. ADR 0322 captions leftover-axis report badges with persisted + * leftover-map singular values, not this graphic. * Never invent a leftover score. */ export function LeftoverMapPlot({ diff --git a/frontend/src/components/LeftoverPairList.tsx b/frontend/src/components/LeftoverPairList.tsx index f546cf6aa..d93ef7ca8 100644 --- a/frontend/src/components/LeftoverPairList.tsx +++ b/frontend/src/components/LeftoverPairList.tsx @@ -130,7 +130,10 @@ export type LeftoverPairListProps = { * captions leftover-map rank on that grouping comparison leftover-map graphic, not this * pair list. ADR 0319 captions leftover-map distance on that grouping comparison leftover-map * graphic, not this pair list. ADR 0320 captions leftover-map coordinate ticks on that grouping - * comparison leftover-map graphic, not this pair list. Every badge still + * comparison leftover-map graphic, not this pair list. ADR 0321 captions leftover-map + * singular values on that grouping comparison leftover-map graphic, not this pair list. + * ADR 0322 captions leftover-map singular values on leftover-axis report badges, not this + * pair list. 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 57b911fb0..6f3be2910 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -43,8 +43,9 @@ describe("i18n", () => { "Page", "Answer", "leftover axis {axis} {share}%", + "leftover axis {axis} σ {value} {share}%", "Leftover-map axis share", - "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.", + "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Leftover-map singular values are the Gabriel scale of those axes. Open a leftover pair to read the post–criterion cell. The shares and singular values do not invent a leftover score.", "Leftover pairs", "Closest leftover", "Farthest leftover", @@ -727,6 +728,18 @@ describe("i18n", () => { }, ); + it.each([ + ["ko", "잔차 축 1 σ 1.84 82%"], + ["zh", "残差轴 1 σ 1.84 82%"], + ["ja", "残差軸 1 σ 1.84 82%"], + ["vi", "trục phần dư 1 σ 1.84 82%"], + ] as const)("formats leftover-axis badge singular value with share in %s", (locale, expected) => { + setLocale(locale); + expect( + tf("leftover 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 203113342..de3e12910 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -606,9 +606,10 @@ const TRANSLATIONS: Partial>> = { "Valid from": "유효 시작", "Valid to": "유효 종료", "leftover axis {axis} {share}%": "잔차 축 {axis} {share}%", + "leftover axis {axis} σ {value} {share}%": "잔차 축 {axis} σ {value} {share}%", "Leftover-map axis share": "잔차 지도 축 비율", - "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.": - "잔차 지도 축 비율은 잔차 SVD 축 1과 2의 Gabriel 관성입니다. 글–기준 셀을 읽으려면 잔차 쌍을 여세요. 이 비율은 잔차 점수를 만들어내지 않습니다.", + "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Leftover-map singular values are the Gabriel scale of those axes. Open a leftover pair to read the post–criterion cell. The shares and singular values do not invent a leftover score.": + "잔차 지도 축 비율은 잔차 SVD 축 1과 2의 Gabriel 관성입니다. 잔차 지도 특이값은 그 축의 Gabriel 척도입니다. 글–기준 셀을 읽으려면 잔차 쌍을 여세요. 이 비율과 특이값은 잔차 점수를 만들어내지 않습니다.", "Leftover pairs": "잔여 쌍", "Closest leftover": "가장 가까운 잔여", "Farthest leftover": "가장 먼 잔여", @@ -1248,9 +1249,10 @@ const TRANSLATIONS: Partial>> = { "Valid from": "有效起始", "Valid to": "有效结束", "leftover axis {axis} {share}%": "残差轴 {axis} {share}%", + "leftover axis {axis} σ {value} {share}%": "残差轴 {axis} σ {value} {share}%", "Leftover-map axis share": "残差图轴占比", - "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.": - "残差图轴占比是残差 SVD 第 1、2 轴的 Gabriel 惯量。打开一个残差配对可查看文章–准则单元格。这些占比不会虚构残差分数。", + "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Leftover-map singular values are the Gabriel scale of those axes. Open a leftover pair to read the post–criterion cell. The shares and singular values do not invent a leftover score.": + "残差图轴占比是残差 SVD 第 1、2 轴的 Gabriel 惯量。残差图奇异值是这些轴的 Gabriel 尺度。打开一个残差配对可查看文章–准则单元格。这些占比和奇异值不会虚构残差分数。", "Leftover pairs": "残余配对", "Closest leftover": "最近残余", "Farthest leftover": "最远残余", @@ -1894,9 +1896,10 @@ const TRANSLATIONS: Partial>> = { "Valid from": "有効開始", "Valid to": "有効終了", "leftover axis {axis} {share}%": "残差軸 {axis} {share}%", + "leftover axis {axis} σ {value} {share}%": "残差軸 {axis} σ {value} {share}%", "Leftover-map axis share": "残差マップ軸の比率", - "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.": - "残差マップ軸の比率は、残差 SVD の第1軸と第2軸の Gabriel 慣性です。投稿–基準セルを読むには残差ペアを開いてください。この比率から残差スコアを作りません。", + "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Leftover-map singular values are the Gabriel scale of those axes. Open a leftover pair to read the post–criterion cell. The shares and singular values do not invent a leftover score.": + "残差マップ軸の比率は、残差 SVD の第1軸と第2軸の Gabriel 慣性です。残差マップ特異値はそれらの軸の Gabriel 尺度です。投稿–基準セルを読むには残差ペアを開いてください。この比率と特異値から残差スコアを作りません。", "Leftover pairs": "残差ペア", "Closest leftover": "最も近い残差", "Farthest leftover": "最も遠い残差", @@ -2541,9 +2544,10 @@ const TRANSLATIONS: Partial>> = { "Valid from": "Hiệu lực từ", "Valid to": "Hiệu lực đến", "leftover axis {axis} {share}%": "trục phần dư {axis} {share}%", + "leftover axis {axis} σ {value} {share}%": "trục phần dư {axis} σ {value} {share}%", "Leftover-map axis share": "Tỷ trọng trục bản đồ phần dư", - "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Open a leftover pair to read the post–criterion cell. The shares do not invent a leftover score.": - "Tỷ trọng trục bản đồ phần dư là quán tính Gabriel của các trục SVD phần dư 1 và 2. Mở một cặp phần dư để đọc ô bài viết–tiêu chí. Các tỷ trọng này không tạo ra điểm phần dư.", + "Leftover-map axis share is Gabriel inertia of residual SVD axes 1 and 2. Leftover-map singular values are the Gabriel scale of those axes. Open a leftover pair to read the post–criterion cell. The shares and singular values do not invent a leftover score.": + "Tỷ trọng trục bản đồ phần dư là quán tính Gabriel của các trục SVD phần dư 1 và 2. Giá trị kỳ dị bản đồ phần dư là thang Gabriel của các trục đó. Mở một cặp phần dư để đọc ô bài viết–tiêu chí. Các tỷ trọng và giá trị kỳ dị này không tạo ra điểm phần dư.", "Leftover pairs": "Cặp phần dư", "Closest leftover": "Phần dư gần nhất", "Farthest leftover": "Phần dư xa nhất", diff --git a/frontend/src/leftoverMapAxisBadge.test.ts b/frontend/src/leftoverMapAxisBadge.test.ts new file mode 100644 index 000000000..84ec57a39 --- /dev/null +++ b/frontend/src/leftoverMapAxisBadge.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import { + leftoverMapAxisBadgeShare, + leftoverMapAxisBadgeSingular, + LEFTOVER_MAP_AXIS_BADGE_SHARE, + LEFTOVER_MAP_AXIS_BADGE_SINGULAR, +} from "./leftoverMapAxisBadge"; +import { + 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"; + +describe("leftoverMapAxisBadgeShare", () => { + it("formats leftover-axis share percent without inventing a leftover score", () => { + expect(leftoverMapAxisBadgeShare(0.82)).toBe("82"); + expect(leftoverMapAxisBadgeShare(0.18)).toBe("18"); + expect(leftoverMapAxisBadgeShare(0)).toBe("0"); + }); +}); + +describe("leftoverMapAxisBadgeSingular", () => { + it("reads persisted leftover-map singular values without inventing a leftover score", () => { + expect( + leftoverMapAxisBadgeSingular({ axis_index: 1, leftover_singular_value: 1.84 }), + ).toBe("1.84"); + expect( + leftoverMapAxisBadgeSingular({ axis_index: 2, leftover_singular_value: 0.86 }), + ).toBe("0.86"); + }); + + it("names a rank-0 zero leftover-map singular value", () => { + expect(leftoverMapAxisBadgeSingular({ axis_index: 1, leftover_singular_value: 0 })).toBe("0.00"); + }); + + it("omits leftover-map singular values that are missing, non-finite, or negative", () => { + expect(leftoverMapAxisBadgeSingular({ axis_index: 1 })).toBeNull(); + expect( + leftoverMapAxisBadgeSingular({ axis_index: 1, leftover_singular_value: Number.NaN }), + ).toBeNull(); + expect( + leftoverMapAxisBadgeSingular({ axis_index: 1, leftover_singular_value: Number.POSITIVE_INFINITY }), + ).toBeNull(); + expect( + leftoverMapAxisBadgeSingular({ axis_index: 1, leftover_singular_value: -0.01 }), + ).toBeNull(); + }); + + it("does not invent leftover-map singular values from leftover-map axis share", () => { + expect(leftoverMapAxisBadgeSingular({ axis_index: 1 })).toBeNull(); + }); + + it("stays distinct from leftover-map graphic and comparison graphic leftover-map axis σ copy", () => { + expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).toBe("leftover axis {axis} σ {value} {share}%"); + expect(LEFTOVER_MAP_AXIS_BADGE_SHARE).toBe("leftover axis {axis} {share}%"); + expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe(LEFTOVER_MAP_PLOT_AXIS_SINGULAR); + expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe(LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE); + expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR); + expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe( + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, + ); + expect(LEFTOVER_MAP_AXIS_BADGE_SINGULAR).not.toBe("leftover axis {axis} σ {value}"); + }); +}); diff --git a/frontend/src/leftoverMapAxisBadge.ts b/frontend/src/leftoverMapAxisBadge.ts new file mode 100644 index 000000000..7d2b377df --- /dev/null +++ b/frontend/src/leftoverMapAxisBadge.ts @@ -0,0 +1,27 @@ +/** Caption leftover-axis report badges with persisted Gabriel singular values. */ + +import type { LeftoverMapAxis } from "./api"; +import { + formatLeftoverMapPlotAxisSingular, + leftoverSingularForAxis, +} from "./leftoverMapPlotAxisSingular"; + +export const LEFTOVER_MAP_AXIS_BADGE_SHARE = "leftover axis {axis} {share}%"; + +export const LEFTOVER_MAP_AXIS_BADGE_SINGULAR = "leftover axis {axis} σ {value} {share}%"; + +export function leftoverMapAxisBadgeShare( + leftoverShare: LeftoverMapAxis["leftover_share"] | null | undefined, +): string { + return ((leftoverShare ?? Number.NaN) * 100).toFixed(0); +} + +export function leftoverMapAxisBadgeSingular( + axis: Pick & { + leftover_singular_value?: LeftoverMapAxis["leftover_singular_value"] | null; + }, +): string | null { + return formatLeftoverMapPlotAxisSingular( + leftoverSingularForAxis([axis], axis.axis_index), + ); +} diff --git a/frontend/src/leftoverMapPlotAxisSingular.test.ts b/frontend/src/leftoverMapPlotAxisSingular.test.ts index 77cb55e73..ce08b36a0 100644 --- a/frontend/src/leftoverMapPlotAxisSingular.test.ts +++ b/frontend/src/leftoverMapPlotAxisSingular.test.ts @@ -89,5 +89,8 @@ describe("leftover map comparison graphic leftover-map axis singular labels", () ); 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}"); + expect(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE).not.toBe( + "leftover axis {axis} σ {value} {share}%", + ); }); }); diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts index c0dac3c23..a6d4787f8 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -81,6 +81,8 @@ * already-named leftover-map coordinates. * ADR 0321 captions leftover-map singular values on that comparison graphic from * already-named leftover-map axes. + * ADR 0322 captions leftover-axis report badges with persisted leftover-map + * singular values, not this graphic. */ import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates"; diff --git a/pyproject.toml b/pyproject.toml index 703556def..da173ad89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.78.0" +version = "2.79.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" }