diff --git a/AGENTS.md b/AGENTS.md index f7ebeb9d3..dd56245ab 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 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321 / 0322 / 0323) 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 / 0323 / 0324) 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 @@ -656,6 +656,11 @@ leftoverMapCompareAxisBadge returns a usable leftover-axis caption. Distinct acc name `leftover map comparison leftover axis {axis} σ {value}`. Share and singular value omit independently. Rank-0 unused axes still name `σ 0.00`. Do not invent leftover-map singular values from leftover-map axis share. +ADR 0324 captions leftover-map graphic-display axes with persisted leftover-map singular +values `σ_k` when leftoverMapPlotAxisBadge returns a usable leftover-map axis caption. +Distinct accessible name `leftover-map axis {axis} σ {value} ({share}%)`. Share and +singular value omit independently. Rank-0 unused axes still name leftover-map axis +`σ 0.00`. Do not invent leftover-map singular values from leftover-map axis share. 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 @@ -760,6 +765,9 @@ returns a usable finite non-negative value. Grouping comparison leftover-axis report badge leftover-map singular values (ADR 0323) captions leftover-axis report badges on the grouping comparison strip with persisted leftover-map singular values `σ_k` when leftoverMapCompareAxisBadge returns a usable leftover-axis caption. +Leftover-map graphic-display leftover-map singular values (ADR 0324) captions leftover-map +graphic-display axes with persisted leftover-map singular values `σ_k` when leftoverMapPlotAxisBadge +returns a usable leftover-map axis caption. 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 8321c5d25..84746aa4a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -754,6 +754,8 @@ ADR 0322 captions leftover-axis report badges with persisted leftover-map singular values `σ_k`. ADR 0323 captions leftover-axis report badges on the grouping comparison strip with persisted leftover-map singular values `σ_k`. +ADR 0324 captions leftover-map graphic-display axes 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 @@ -806,7 +808,8 @@ graphic (ADR 0319), leftover-map coordinate ticks on the grouping comparison lef graphic (ADR 0320), leftover-map singular values on the grouping comparison leftover-map graphic (ADR 0321), leftover-map singular values on leftover-axis report badges (ADR 0322), leftover-map singular values on leftover-axis report badges on the -grouping comparison strip (ADR 0323); +grouping comparison strip (ADR 0323), leftover-map singular values on leftover-map +graphic-display axes (ADR 0324); `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.81.0-leftover-map-plot-axis-singular.md b/CHANGELOG.d/2.81.0-leftover-map-plot-axis-singular.md new file mode 100644 index 000000000..a15f4858e --- /dev/null +++ b/CHANGELOG.d/2.81.0-leftover-map-plot-axis-singular.md @@ -0,0 +1,18 @@ +## 2.81.0 — Leftover-map singular values on leftover-map graphic-display axes + +- Caption leftover-map graphic-display axes with persisted leftover-map singular + values `σ_k` (ADR 0324). After `make seed`, leftover-map graphic-display axes + 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 `σ` caption and keeps + `leftover-map axis {k} ({share}%)`. Rank-0 unused axes still name `σ 0.00`. + Leftover-map graphic-display axes stay `leftover-map axis {k} σ {value} ({share}%)` + so they stay distinct from leftover-axis `leftover axis {k} σ {value} {share}%`, + from comparison leftover-axis `leftover map comparison leftover axis {k} σ {value} {share}%`, + and from comparison graphic + `leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`. + Do not invent `σ_k` from leftover-map axis share. This increment does not + change leftover-map singular values on leftover-axis report badges or on + leftover-axis report badges on the grouping comparison strip. 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 9a0d1cec3..f0ab37f05 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-map graphic-display axes with persisted + leftover-map singular values `σ_k` (ADR 0324 / v2.81.0). After `make seed`, leftover-map + graphic-display axes 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 `σ` caption and keeps + `leftover-map axis {k} ({share}%)`. Rank-0 unused axes still name `σ 0.00`. Leftover-map + graphic-display axes stay `leftover-map axis {k} σ {value} ({share}%)` so they stay + distinct from leftover-axis `leftover axis {k} σ {value} {share}%`, from comparison + leftover-axis `leftover map comparison leftover axis {k} σ {value} {share}%`, and from + comparison graphic `leftover map comparison graphic leftover-map axis {k} σ {value} ({share}%)`. + Do not invent `σ_k` from leftover-map axis share. This increment does not change + leftover-map singular values on leftover-axis report badges or on leftover-axis + report badges on the grouping comparison strip. A finite negative leftover is shown, + never clamped. Never invent a leftover score or a theta. No new columns. + - Period leftover pairs now caption leftover-axis report badges on the grouping comparison strip with persisted leftover-map singular values `σ_k` (ADR 0323 / v2.80.0). After `make seed`, grouping comparison leftover-axis diff --git a/CLAUDE.md b/CLAUDE.md index 4546003e0..f29361dc8 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 / 0322 / 0323), 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 / 0323 / 0324), 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 74728c994..7b5f55843 100644 --- a/docs/adr/0148-leftover-map-axis-share.md +++ b/docs/adr/0148-leftover-map-axis-share.md @@ -7,7 +7,9 @@ [ADR 0322](0322-leftover-map-axis-singular.md) (leftover-map singular values on leftover-axis report badges); [ADR 0323](0323-leftover-map-compare-axis-singular.md) -(leftover-map singular values on leftover-axis report badges on the grouping comparison strip) +(leftover-map singular values on leftover-axis report badges on the grouping comparison strip); +[ADR 0324](0324-leftover-map-plot-axis-singular.md) +(leftover-map singular values on leftover-map graphic-display axes) ## Context diff --git a/docs/adr/0322-leftover-map-axis-singular.md b/docs/adr/0322-leftover-map-axis-singular.md index bf1183f72..e098a7567 100644 --- a/docs/adr/0322-leftover-map-axis-singular.md +++ b/docs/adr/0322-leftover-map-axis-singular.md @@ -83,7 +83,9 @@ 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 -([ADR 0323](0323-leftover-map-compare-axis-singular.md)). +([ADR 0323](0323-leftover-map-compare-axis-singular.md)). This increment does +not caption leftover-map singular values on leftover-map graphic-display axes +([ADR 0324](0324-leftover-map-plot-axis-singular.md)). ## References diff --git a/docs/adr/0323-leftover-map-compare-axis-singular.md b/docs/adr/0323-leftover-map-compare-axis-singular.md index 6d294a869..8c2c28d56 100644 --- a/docs/adr/0323-leftover-map-compare-axis-singular.md +++ b/docs/adr/0323-leftover-map-compare-axis-singular.md @@ -103,6 +103,8 @@ leftover-map axis share are finite, leftover-map axis share is Independent of leftover-map singular values on leftover-axis report badges in the period-report panel ([ADR 0322](0322-leftover-map-axis-singular.md)). +Independent of leftover-map singular values on leftover-map graphic-display +axes ([ADR 0324](0324-leftover-map-plot-axis-singular.md)). ## References diff --git a/docs/adr/0324-leftover-map-plot-axis-singular.md b/docs/adr/0324-leftover-map-plot-axis-singular.md new file mode 100644 index 000000000..02616597e --- /dev/null +++ b/docs/adr/0324-leftover-map-plot-axis-singular.md @@ -0,0 +1,129 @@ +# ADR 0324 — Name leftover-map singular values on leftover-map graphic-display axes + +**Decision status:** Accepted +**Date:** 2026-08-31 + +Amends leftover-map axis share on the graphic display +([ADR 0269](0269-leftover-map-axis-share-plot.md)), leftover-map axis share +persistence ([ADR 0148](0148-leftover-map-axis-share.md)). Independent of leftover-map +singular values on leftover-axis report badges +([ADR 0322](0322-leftover-map-axis-singular.md)), leftover-map singular +values on leftover-axis report badges on the grouping comparison strip +([ADR 0323](0323-leftover-map-compare-axis-singular.md)), 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 coordinate ticks on the graphic display +([ADR 0270](0270-leftover-map-coordinate-ticks.md)). + +## Context + +ADR 0148 already persists leftover-map singular values `σ_k` and leftover-map +axis share `σ_k² / Σ_j σ_j²`. ADR 0269 captions leftover-map graphic-display +axes with share only as `leftover-map axis {k} ({share}%)`. ADR 0321 already +captions the grouping comparison leftover-map graphic with persisted `σ_k`. +ADR 0322 already captions leftover-axis report badges with persisted `σ_k`. +ADR 0323 already captions grouping comparison leftover-axis report badges +with persisted `σ_k`. Period leftover-map graphic axes still hide `σ_k`, so +Gabriel (1971) singular values appear on leftover-axis badges and on the +comparison graphic and never on the leftover-map graphic-display axes that +name the same axes. Hiding `σ_k` on leftover-map graphic-display axes 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-map graphic-display axes with already persisted +leftover-map singular values. Leftover-map graphic-display axes stay +`leftover-map axis {k} σ {value} ({share}%)` so they stay distinct from +leftover-axis badges `leftover axis {k} σ {value} {share}%`, from comparison +leftover-axis `leftover map comparison leftover axis {k} σ {value} {share}%`, +and from comparison graphic +`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 **0324** so it does not collide with leftover-map +singular values on leftover-axis report badges on the grouping comparison +strip (0323), leftover-map singular values on leftover-axis report badges +(0322), 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), leftover-map +axis share on the graphic display (0269), or the dashboard stacks. + +## Decision + +On leftover-map graphic-display axes, 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. Share and +singular value omit independently: no share and no `σ_k` keeps +`leftover-map axis {k}`; share only is `leftover-map axis {k} ({share}%)`; +`σ_k` only is `leftover-map axis {k} σ {value}`; both is +`leftover-map axis {k} σ {value} ({share}%)`. A missing, non-finite, or +negative singular value omits that `σ` caption and keeps the existing +`leftover-map 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. + +Leftover-map graphic-display singular values omit independently of leftover-map +comparison graphic leftover-map axis singular values and leftover-axis report +badges. A missing singular value omits that leftover-map graphic-display `σ` +caption and keeps a usable leftover-map axis share caption, leftover-map +comparison graphic leftover-map axis `σ` caption, leftover-axis report badge +`σ` caption, leftover-map comparison leftover-axis `σ` caption, leftover-map +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 graphic coverage notes. + +This increment does not change leftover-map singular values on leftover-axis +report badges. Those leftover-axis badges stay +`leftover axis {axis} σ {value} {share}%` (ADR 0322). This increment does +not change leftover-map singular values on leftover-axis report badges on the +grouping comparison strip (ADR 0323). This increment does not persist leftover-map +inner product, cosine, or length. + +Do not add SQL. Do not edit shipped migrations. Do not invent a leftover +score. Do not invent a theta. + +## Consequences + +After `make seed`, leftover-map graphic-display axes 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 leftover-map axis `σ 0.00`. Grouping comparison leftover-map +graphic leftover-map singular values (ADR 0321), leftover-axis report badge +leftover-map singular values (ADR 0322), and grouping comparison leftover-axis +report badge leftover-map singular values (ADR 0323) remain. + +## Related + +Independent of leftover-map singular values on leftover-axis report badges +([ADR 0322](0322-leftover-map-axis-singular.md)). Independent of leftover-map +singular values on leftover-axis report badges on the grouping comparison +strip ([ADR 0323](0323-leftover-map-compare-axis-singular.md)). Independent of +leftover-map singular values on the grouping comparison leftover-map graphic +([ADR 0321](0321-leftover-map-compare-plot-singular.md)). + +## 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 19515f547..3c8aa6d5d 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 11:05 KST. Protected `main` is +> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not +> self-approve. #863 leftover-map singular values on leftover-axis report badges +> on the grouping comparison strip (v2.80.0 / ADR 0323) is `52b3597`, stacked on +> #862 @ `f24b664`. Independent APPROVE is missing. Next buyer increment on this +> cycle: leftover-map singular values on leftover-map graphic-display axes +> (ADR 0324 / v2.81.0) delivered locally on +> `feat/leftover-map-plot-axis-singular-v2810`. Caption persisted leftover-map +> singular values `σ_k` on leftover-map graphic-display axes from already-named +> leftover-map axes. Distinct accessible name `leftover-map axis {axis} σ {value} ({share}%)`. +> 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 change +> leftover-map singular values on leftover-axis report badges or on leftover-axis +> report badges on the grouping comparison strip this increment. Stack onto #863. +> 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:40 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not > self-approve. #862 leftover-map singular values on leftover-axis report badges diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index 6811f24df..d4c5f9fae 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -5,8 +5,8 @@ operator-facing control you can click before changing product CSS. | Story | Operator next action | Token / module | |---|---|---| -| `Reports/LeftoverMapPlot` | Read the leftover-map graphic display of persisted `ξ` (posts) and `ζ` (criteria), match axis ticks to those coordinates, pair-segment `d` to leftover-map distance, pair-segment `R̂` to leftover-map reconstruction, pair-segment `R̂²/R²` to leftover-map explained leftover share, pair-segment `U²/R²` to leftover-map unexplained leftover share, pair-segment `2R̂U/R²` to leftover-map cross share, pair-segment `U` to leftover-map unexplained leftover, pair-segment `R` to leftover residual, pair-segment `Y` to leftover observed, pair-segment `E` to leftover expected, and pair-segment leftover-map rank, then click a post marker to open that post. Leftover-map axes name persisted leftover-map axis share when finite. The plot names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. The comparison variant names leftover-map complete-case coverage under `Leftover map comparison graphic coverage` and leftover-map item complete-case coverage under `Leftover map comparison graphic item coverage` and leftover-map incomplete post coverage under `Leftover map comparison graphic incomplete posts` and leftover-map incomplete item coverage under `Leftover map comparison graphic incomplete items`. `ClosestAndFarthest`, `RankZeroOrigin`, `MissingCoordinates`, `MissingAxisShare`, `Comparison`, `ComparisonMissingCoverage`, `ComparisonMissingItemCoverage`, `ComparisonMissingIncompletePosts`, and `ComparisonMissingIncompleteItems` cover two-pair maps, rank-0 origin with 0% share, a `0` tick, `d 0.00`, `R̂ 0.00`, `R̂²/R² 0.00`, `U²/R² 0.00`, `2R̂U/R² 0.00`, `U 0.00`, `R 0.00`, `Y 0.00`, `E 0.00`, and `rank 0`, omitted plots, missing share that keeps existing leftover-map axis text, grouping comparison leftover-map graphic coverage that matches leftover map used N of M scored posts, omitted comparison graphic coverage when leftoverMapCoverageCounts fail-closes, grouping comparison leftover-map graphic item coverage that matches leftover map used N of M scored criteria, omitted comparison graphic item coverage when leftoverMapItemCoverageCounts fail-closes, grouping comparison leftover-map graphic incomplete posts that matches leftover map dropped N incomplete posts, omitted comparison graphic incomplete posts when leftoverMapIncompletePostCount fail-closes, grouping comparison leftover-map graphic incomplete items that matches leftover map dropped N incomplete criteria, and omitted comparison graphic incomplete items when leftoverMapIncompleteItemCount fail-closes. The plot does not invent a leftover score. | `LeftoverMapPlot`, `leftoverMapPlotLayout`, `leftoverMapPlotAxisShare`, `--color-primary`, `--color-palette-blue-mid` | -| `Reports/LeftoverPairList` | Read closest/farthest leftover pairs with named `R`, `Y`/`E`, rank, `U`, `s`, `e`, `x`, `R̂`, `ξ`/`ζ`, and `d`, then open that post. The leftover-map graphic display sits above the pair buttons when coordinates are finite, leftover-map axes name persisted leftover-map axis share, leftover-map axis ticks name persisted coordinates, pair segments name persisted leftover-map distance, pair segments name persisted leftover-map reconstruction, pair segments name persisted leftover-map explained leftover share, pair segments name persisted leftover-map unexplained leftover share, pair segments name persisted leftover-map cross share, pair segments name persisted leftover-map unexplained leftover, pair segments name persisted leftover residual, pair segments name persisted leftover observed, pair segments name persisted leftover expected, and pair segments name persisted leftover-map rank. The pair-list note names leftover-map complete-case coverage only when leftoverMapCoverageCounts returns usable complete-case integers, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. The grouping comparison strip names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, leftover-map incomplete item coverage, and leftover-axis report badges with persisted leftover-map singular values under `leftover map comparison leftover axis {axis} σ {value}` with distinct comparison labels when leftoverMapCoverageCounts / leftoverMapItemCoverageCounts / leftoverMapIncompletePostCount / leftoverMapIncompleteItemCount / leftoverMapCompareAxisBadge return usable values. | `LeftoverPairList`, `LeftoverMapPlot`, `ticket-list`, `post-badge` | +| `Reports/LeftoverMapPlot` | Read the leftover-map graphic display of persisted `ξ` (posts) and `ζ` (criteria), match axis ticks to those coordinates, pair-segment `d` to leftover-map distance, pair-segment `R̂` to leftover-map reconstruction, pair-segment `R̂²/R²` to leftover-map explained leftover share, pair-segment `U²/R²` to leftover-map unexplained leftover share, pair-segment `2R̂U/R²` to leftover-map cross share, pair-segment `U` to leftover-map unexplained leftover, pair-segment `R` to leftover residual, pair-segment `Y` to leftover observed, pair-segment `E` to leftover expected, and pair-segment leftover-map rank, then click a post marker to open that post. Leftover-map axes name persisted leftover-map singular values `σ_k` and leftover-map axis share when finite. The plot names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. The comparison variant names leftover-map complete-case coverage under `Leftover map comparison graphic coverage` and leftover-map item complete-case coverage under `Leftover map comparison graphic item coverage` and leftover-map incomplete post coverage under `Leftover map comparison graphic incomplete posts` and leftover-map incomplete item coverage under `Leftover map comparison graphic incomplete items`. `ClosestAndFarthest`, `RankZeroOrigin`, `MissingCoordinates`, `MissingAxisShare`, `Comparison`, `ComparisonMissingCoverage`, `ComparisonMissingItemCoverage`, `ComparisonMissingIncompletePosts`, and `ComparisonMissingIncompleteItems` cover two-pair maps, rank-0 origin with 0% share, a `0` tick, `d 0.00`, `R̂ 0.00`, `R̂²/R² 0.00`, `U²/R² 0.00`, `2R̂U/R² 0.00`, `U 0.00`, `R 0.00`, `Y 0.00`, `E 0.00`, and `rank 0`, omitted plots, missing share that keeps leftover-map axis `σ` without share, grouping comparison leftover-map graphic coverage that matches leftover map used N of M scored posts, omitted comparison graphic coverage when leftoverMapCoverageCounts fail-closes, grouping comparison leftover-map graphic item coverage that matches leftover map used N of M scored criteria, omitted comparison graphic item coverage when leftoverMapItemCoverageCounts fail-closes, grouping comparison leftover-map graphic incomplete posts that matches leftover map dropped N incomplete posts, omitted comparison graphic incomplete posts when leftoverMapIncompletePostCount fail-closes, grouping comparison leftover-map graphic incomplete items that matches leftover map dropped N incomplete criteria, and omitted comparison graphic incomplete items when leftoverMapIncompleteItemCount fail-closes. The plot does not invent a leftover score. | `LeftoverMapPlot`, `leftoverMapPlotLayout`, `leftoverMapPlotAxisShare`, `--color-primary`, `--color-palette-blue-mid` | +| `Reports/LeftoverPairList` | Read closest/farthest leftover pairs with named `R`, `Y`/`E`, rank, `U`, `s`, `e`, `x`, `R̂`, `ξ`/`ζ`, and `d`, then open that post. The leftover-map graphic display sits above the pair buttons when coordinates are finite, leftover-map axes name persisted leftover-map singular values and leftover-map axis share, leftover-map axis ticks name persisted coordinates, pair segments name persisted leftover-map distance, pair segments name persisted leftover-map reconstruction, pair segments name persisted leftover-map explained leftover share, pair segments name persisted leftover-map unexplained leftover share, pair segments name persisted leftover-map cross share, pair segments name persisted leftover-map unexplained leftover, pair segments name persisted leftover residual, pair segments name persisted leftover observed, pair segments name persisted leftover expected, and pair segments name persisted leftover-map rank. The pair-list note names leftover-map complete-case coverage only when leftoverMapCoverageCounts returns usable complete-case integers, leftover-map item complete-case coverage, leftover-map incomplete post coverage, and leftover-map incomplete item coverage when persisted. The grouping comparison strip names leftover-map complete-case coverage, leftover-map item complete-case coverage, leftover-map incomplete post coverage, leftover-map incomplete item coverage, and leftover-axis report badges with persisted leftover-map singular values under `leftover map comparison leftover axis {axis} σ {value}` with distinct comparison labels when leftoverMapCoverageCounts / leftoverMapItemCoverageCounts / leftoverMapIncompletePostCount / leftoverMapIncompleteItemCount / leftoverMapCompareAxisBadge return usable values. | `LeftoverPairList`, `LeftoverMapPlot`, `ticket-list`, `post-badge` | | `Workspace/OperationsDashboard` | Compare Event and post counts, inspect external-information coverage, then open the cited source behind a claim, handover, or repeat-issue fact. `EvidenceReady`, `NarrowViewport`, `AnalysisPendingAndMissingEvidence`, `AnalysisFailed`, and `LoadError` cover populated, mobile, unavailable-evidence, analysis-pending, retryable failure, and transport-error states. | `--color-dashboard-*`, `OperationsDashboard` | | `Post/SimilarVocPanel` | Compare ontology/semantic similar VOC and prior action evidence, then open the source; unavailable states show no fabricated TEPP theta or weight. | `SimilarVocPanel.css`, `SimilarVocPanel` | | `Post/Recorded perspectives` | Read the imported primary and every evidence-connected additional Voice with its recorded truth state instead of flattening them into one compound category. `CombinedEvidence`, `RejectedEvidence`, and `NarrowViewport` cover desktop, rejected-evidence, and narrow layouts. | `VoicePerspectiveList`, `ticket-list`, `post-meta` | diff --git a/frontend/package.json b/frontend/package.json index 9b99eca3c..9750a7d2e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.80.0", + "version": "2.81.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 085626a09..bbc95eac7 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -4200,8 +4200,8 @@ describe("App, authenticated", () => { expect(await screen.findByLabelText("Leftover pairs")).toBeInTheDocument(); expect(screen.getByLabelText("Leftover-map graphic display")).toBeInTheDocument(); 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 axis 1 σ 1.84 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 2 σ 0.86 (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( @@ -4335,6 +4335,12 @@ describe("App, authenticated", () => { "leftover map comparison graphic leftover-map axis 2 σ 0.86 (18%)", ), ).toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).queryByText("leftover-map axis 1 σ 1.84 (82%)"), + ).not.toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).queryByText("leftover-map axis 2 σ 0.86 (18%)"), + ).not.toBeInTheDocument(); expect( within(screen.getByLabelText("Grouping comparison")).queryByText("leftover-map axis 1 (82%)"), ).not.toBeInTheDocument(); diff --git a/frontend/src/components/LeftoverMapPlot.test.tsx b/frontend/src/components/LeftoverMapPlot.test.tsx index ed9b89639..5adcea2ac 100644 --- a/frontend/src/components/LeftoverMapPlot.test.tsx +++ b/frontend/src/components/LeftoverMapPlot.test.tsx @@ -164,8 +164,8 @@ describe("LeftoverMapPlot", () => { expect( screen.getByRole("button", { name: "Open leftover-map post Public post at ξ (0.00, 0.00)" }), ).toBeInTheDocument(); - expect(screen.getByText("leftover-map axis 1 (0%)")).toBeInTheDocument(); - expect(screen.getByText("leftover-map axis 2 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 1 σ 0.00 (0%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 2 σ 0.00 (0%)")).toBeInTheDocument(); expect(screen.getAllByLabelText("leftover-map axis 1 tick 0.00").length).toBeGreaterThan(0); expect(screen.queryByLabelText("leftover-map axis 1 tick +1.00")).not.toBeInTheDocument(); expect(screen.getByLabelText("leftover-map distance d 0.00")).toBeInTheDocument(); @@ -180,7 +180,7 @@ describe("LeftoverMapPlot", () => { expect(screen.getByLabelText("leftover-map rank rank 0")).toBeInTheDocument(); }); - it("captions leftover-map axes with persisted leftover-map axis share", () => { + it("captions leftover-map axes with persisted leftover-map singular values and leftover-map axis share", () => { render( { onSelectPost={vi.fn()} />, ); - expect(screen.getByText("leftover-map axis 1 (82%)")).toBeInTheDocument(); - expect(screen.getByText("leftover-map axis 2 (18%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 2 σ 0.86 (18%)")).toBeInTheDocument(); }); - it("keeps existing leftover-map axis text when share is missing or non-finite", () => { + it("captions leftover-map graphic-display singular values when share is missing or non-finite", () => { render( { onSelectPost={vi.fn()} />, ); - expect(screen.getByText("leftover-map axis 1")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 1 σ 1.84")).toBeInTheDocument(); expect(screen.getByText("leftover-map axis 2")).toBeInTheDocument(); expect(screen.queryByText(/leftover-map axis 1 \(/)).not.toBeInTheDocument(); expect(screen.queryByText(/leftover-map axis 2 \(/)).not.toBeInTheDocument(); }); + it("omits leftover-map graphic-display singular values independently of leftover-map axis share", () => { + render( + , + ); + expect(screen.getByText("leftover-map axis 1 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 2 (18%)")).toBeInTheDocument(); + expect(screen.queryByText("leftover-map axis 1 σ 1.84 (82%)")).not.toBeInTheDocument(); + expect(screen.queryByText("leftover-map axis 2 σ 0.86 (18%)")).not.toBeInTheDocument(); + }); + it("omits leftover-map distance on a pair segment when d is missing", () => { render( { ).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 σ 1.84 (82%)")).not.toBeInTheDocument(); + expect(screen.queryByText("leftover-map axis 2 σ 0.86 (18%)")).not.toBeInTheDocument(); expect(screen.queryByText("leftover-map axis 1 (82%)")).not.toBeInTheDocument(); expect(screen.queryByText("leftover-map axis 2 (18%)")).not.toBeInTheDocument(); }); diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx index a888a56e1..cad1f961d 100644 --- a/frontend/src/components/LeftoverMapPlot.tsx +++ b/frontend/src/components/LeftoverMapPlot.tsx @@ -25,10 +25,10 @@ import { LEFTOVER_MAP_COMPARE_PLOT_AXIS_1, LEFTOVER_MAP_COMPARE_PLOT_AXIS_2, LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE, - LEFTOVER_MAP_PLOT_AXIS_SHARE, } from "../leftoverMapPlotAxisShare"; import { formatLeftoverMapPlotAxisSingular, + leftoverMapPlotAxisBadge, leftoverSingularForAxis, LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR, LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, @@ -86,13 +86,11 @@ function leftoverMapPlotAxisText( leftoverMapAxes: LeftoverMapAxis[] | undefined, variant: LeftoverMapPlotVariant, ): string { - const percent = formatLeftoverMapPlotAxisShare( - leftoverShareForAxis(leftoverMapAxes, axisIndex), - ); + const leftoverShare = leftoverShareForAxis(leftoverMapAxes, axisIndex); + const leftoverSingular = leftoverSingularForAxis(leftoverMapAxes, axisIndex); if (variant === "comparison") { - const singular = formatLeftoverMapPlotAxisSingular( - leftoverSingularForAxis(leftoverMapAxes, axisIndex), - ); + const percent = formatLeftoverMapPlotAxisShare(leftoverShare); + const singular = formatLeftoverMapPlotAxisSingular(leftoverSingular); if (singular === null && percent === null) { return t(axisIndex === 1 ? LEFTOVER_MAP_COMPARE_PLOT_AXIS_1 : LEFTOVER_MAP_COMPARE_PLOT_AXIS_2); } @@ -108,10 +106,11 @@ function leftoverMapPlotAxisText( share: percent as string, }); } - if (percent === null) { + const badge = leftoverMapPlotAxisBadge(axisIndex, leftoverSingular, leftoverShare); + if (badge === null) { return t(axisIndex === 1 ? "leftover-map axis 1" : "leftover-map axis 2"); } - return tf(LEFTOVER_MAP_PLOT_AXIS_SHARE, { axis: axisIndex, share: percent }); + return tf(badge.key, badge.values); } /** @@ -119,7 +118,8 @@ function leftoverMapPlotAxisText( * * Person markers are posts; item markers are leftover criteria. Click a * post marker to open that post. Caption leftover-map axes with persisted - * Gabriel inertia share when finite, including rank-0 zero-share axes. + * leftover-map singular values ``σ_k`` and Gabriel inertia share when finite, + * including rank-0 zero-share axes. * Axis ticks name persisted leftover-map coordinates so ξ / ζ on the * pair row match the plot. Pair segments name persisted leftover-map * distance ``d``, leftover-map reconstruction ``R̂``, leftover-map @@ -208,7 +208,8 @@ function leftoverMapPlotAxisText( * labels. ADR 0322 captions leftover-axis report badges with persisted * leftover-map singular values, not this graphic. ADR 0323 captions leftover-axis * report badges on the grouping comparison strip with persisted leftover-map - * singular values, not this graphic. + * singular values, not this graphic. ADR 0324 captions leftover-map + * graphic-display axes with persisted leftover-map singular values. * Never invent a leftover score. */ export function LeftoverMapPlot({ diff --git a/frontend/src/components/LeftoverPairList.test.tsx b/frontend/src/components/LeftoverPairList.test.tsx index 230f10de2..6972dd29b 100644 --- a/frontend/src/components/LeftoverPairList.test.tsx +++ b/frontend/src/components/LeftoverPairList.test.tsx @@ -204,8 +204,8 @@ describe("LeftoverPairList", () => { name: "Open leftover-map post Public post at ξ (+0.50, +0.10)", }), ); - expect(screen.getByText("leftover-map axis 1 (82%)")).toBeInTheDocument(); - expect(screen.getByText("leftover-map axis 2 (18%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 1 σ 1.84 (82%)")).toBeInTheDocument(); + expect(screen.getByText("leftover-map axis 2 σ 0.86 (18%)")).toBeInTheDocument(); expect(screen.getByLabelText("Leftover-map graphic coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index ecfc52183..c67c890cd 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -66,6 +66,8 @@ describe("i18n", () => { "leftover-map axis 1", "leftover-map axis 2", "leftover-map axis {axis} ({share}%)", + "leftover-map axis {axis} σ {value}", + "leftover-map axis {axis} σ {value} ({share}%)", "leftover-map axis {axis} tick {value}", "leftover-map distance {label}", "leftover-map reconstruction {label}", @@ -1111,6 +1113,28 @@ describe("i18n", () => { expect(tf("leftover-map axis {axis} ({share}%)", { axis: 1, share: "82" })).toBe(expected); }); + it.each([ + ["ko", "잔여 지도 축 1 σ 1.84"], + ["zh", "残差图轴 1 σ 1.84"], + ["ja", "残差マップ軸 1 σ 1.84"], + ["vi", "trục bản đồ phần dư 1 σ 1.84"], + ] as const)("formats leftover-map plot axis singular values in %s", (locale, expected) => { + setLocale(locale); + expect(tf("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 bản đồ phần dư 1 σ 1.84 (82%)"], + ] as const)("formats leftover-map plot axis singular share in %s", (locale, expected) => { + setLocale(locale); + expect( + tf("leftover-map axis {axis} σ {value} ({share}%)", { axis: 1, value: "1.84", share: "82" }), + ).toBe(expected); + }); + it.each([ ["ko", "IRT 주효과 이후 관측 Y 2.40와 기대 E 2.00를 읽은 다음, 이 글을 여세요."], ["zh", "阅读 IRT 主效应后的观测 Y 2.40 与期望 E 2.00,然后打开这篇帖子。"], diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 52cf6a35e..ec4b31d06 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -647,6 +647,8 @@ const TRANSLATIONS: Partial>> = { "leftover-map axis 1": "잔여 지도 축 1", "leftover-map axis 2": "잔여 지도 축 2", "leftover-map axis {axis} ({share}%)": "잔여 지도 축 {axis} ({share}%)", + "leftover-map axis {axis} σ {value}": "잔여 지도 축 {axis} σ {value}", + "leftover-map axis {axis} σ {value} ({share}%)": "잔여 지도 축 {axis} σ {value} ({share}%)", "leftover-map axis {axis} tick {value}": "잔여 지도 축 {axis} 눈금 {value}", "leftover-map distance {label}": "잔여 지도 거리 {label}", "leftover-map reconstruction {label}": "잔여 지도 재구성 {label}", @@ -1297,6 +1299,8 @@ const TRANSLATIONS: Partial>> = { "leftover-map axis 1": "残差图轴 1", "leftover-map axis 2": "残差图轴 2", "leftover-map axis {axis} ({share}%)": "残差图轴 {axis} ({share}%)", + "leftover-map axis {axis} σ {value}": "残差图轴 {axis} σ {value}", + "leftover-map axis {axis} σ {value} ({share}%)": "残差图轴 {axis} σ {value} ({share}%)", "leftover-map axis {axis} tick {value}": "残差图轴 {axis} 刻度 {value}", "leftover-map distance {label}": "残差图距离 {label}", "leftover-map reconstruction {label}": "残差图重建 {label}", @@ -1951,6 +1955,8 @@ const TRANSLATIONS: Partial>> = { "leftover-map axis 1": "残差マップ軸 1", "leftover-map axis 2": "残差マップ軸 2", "leftover-map axis {axis} ({share}%)": "残差マップ軸 {axis} ({share}%)", + "leftover-map axis {axis} σ {value}": "残差マップ軸 {axis} σ {value}", + "leftover-map axis {axis} σ {value} ({share}%)": "残差マップ軸 {axis} σ {value} ({share}%)", "leftover-map axis {axis} tick {value}": "残差マップ軸 {axis} 目盛 {value}", "leftover-map distance {label}": "残差マップ距離 {label}", "leftover-map reconstruction {label}": "残差マップ再構成 {label}", @@ -2608,6 +2614,8 @@ const TRANSLATIONS: Partial>> = { "leftover-map axis 1": "trục bản đồ phần dư 1", "leftover-map axis 2": "trục bản đồ phần dư 2", "leftover-map axis {axis} ({share}%)": "trục bản đồ phần dư {axis} ({share}%)", + "leftover-map axis {axis} σ {value}": "trục bản đồ phần dư {axis} σ {value}", + "leftover-map axis {axis} σ {value} ({share}%)": "trục bản đồ phần dư {axis} σ {value} ({share}%)", "leftover-map axis {axis} tick {value}": "vạch trục bản đồ phần dư {axis} {value}", "leftover-map distance {label}": "khoảng cách bản đồ phần dư {label}", "leftover-map reconstruction {label}": "tái dựng bản đồ phần dư {label}", diff --git a/frontend/src/leftoverMapPlotAxisSingular.test.ts b/frontend/src/leftoverMapPlotAxisSingular.test.ts index bd36ce16a..4e47cfb21 100644 --- a/frontend/src/leftoverMapPlotAxisSingular.test.ts +++ b/frontend/src/leftoverMapPlotAxisSingular.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { formatLeftoverMapPlotAxisSingular, leftoverMapCompareAxisBadge, + leftoverMapPlotAxisBadge, leftoverSingularForAxis, LEFTOVER_MAP_COMPARE_AXIS_CAPTION, LEFTOVER_MAP_COMPARE_AXIS_LABEL, @@ -13,7 +14,10 @@ import { 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_AXIS_SHARE, + LEFTOVER_MAP_PLOT_AXIS_SHARE, +} from "./leftoverMapPlotAxisShare"; import { LEFTOVER_MAP_COMPARE_PLOT_TICK } from "./leftoverMapPlotLayout"; import { LEFTOVER_MAP_AXIS_BADGE_SINGULAR } from "./leftoverMapAxisBadge"; @@ -168,3 +172,58 @@ describe("leftover map comparison leftover-axis report badges", () => { expect(LEFTOVER_MAP_COMPARE_AXIS_CAPTION).toContain("Open a leftover pair"); }); }); + +describe("leftover-map graphic-display axis singular badges", () => { + it("names persisted leftover-map singular values on leftover-map graphic-display axes", () => { + expect(leftoverMapPlotAxisBadge(1, 1.84, 0.82)).toEqual({ + key: LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE, + values: { axis: 1, value: "1.84", share: "82" }, + }); + expect(leftoverMapPlotAxisBadge(2, 0.86, 0.18)).toEqual({ + key: LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE, + values: { axis: 2, value: "0.86", share: "18" }, + }); + }); + + it("names rank-0 zero leftover-map singular values on leftover-map graphic-display axes", () => { + expect(leftoverMapPlotAxisBadge(1, 0, 0)).toEqual({ + key: LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE, + values: { axis: 1, value: "0.00", share: "0" }, + }); + }); + + it("omits leftover-map singular values independently of leftover-map axis share", () => { + expect(leftoverMapPlotAxisBadge(1, Number.NaN, 0.82)).toEqual({ + key: LEFTOVER_MAP_PLOT_AXIS_SHARE, + values: { axis: 1, share: "82" }, + }); + expect(leftoverMapPlotAxisBadge(2, -0.01, 0.18)).toEqual({ + key: LEFTOVER_MAP_PLOT_AXIS_SHARE, + values: { axis: 2, share: "18" }, + }); + expect(leftoverMapPlotAxisBadge(1, 1.84, Number.NaN)).toEqual({ + key: LEFTOVER_MAP_PLOT_AXIS_SINGULAR, + values: { axis: 1, value: "1.84" }, + }); + expect(leftoverMapPlotAxisBadge(1, null, null)).toBeNull(); + }); + + it("does not invent leftover-map singular values from leftover-map axis share", () => { + expect(leftoverMapPlotAxisBadge(1, undefined, 0.82)?.key).toBe(LEFTOVER_MAP_PLOT_AXIS_SHARE); + expect(leftoverMapPlotAxisBadge(1, undefined, 0.82)?.values.value).toBeUndefined(); + }); + + it("stays distinct from leftover-axis, comparison leftover-axis, and comparison graphic copy", () => { + expect(LEFTOVER_MAP_PLOT_AXIS_SINGULAR).toBe("leftover-map axis {axis} σ {value}"); + expect(LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE).toBe( + "leftover-map axis {axis} σ {value} ({share}%)", + ); + expect(LEFTOVER_MAP_PLOT_AXIS_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR); + expect(LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE).not.toBe( + LEFTOVER_MAP_COMPARE_PLOT_AXIS_SINGULAR_SHARE, + ); + expect(LEFTOVER_MAP_PLOT_AXIS_SINGULAR).not.toBe(LEFTOVER_MAP_COMPARE_AXIS_SINGULAR); + expect(LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE).not.toBe(LEFTOVER_MAP_AXIS_BADGE_SINGULAR); + expect(LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE).not.toBe(LEFTOVER_MAP_PLOT_AXIS_SHARE); + }); +}); diff --git a/frontend/src/leftoverMapPlotAxisSingular.ts b/frontend/src/leftoverMapPlotAxisSingular.ts index 70130898e..8d0ab36d9 100644 --- a/frontend/src/leftoverMapPlotAxisSingular.ts +++ b/frontend/src/leftoverMapPlotAxisSingular.ts @@ -1,7 +1,10 @@ -/** Caption leftover-map plot axes with persisted Gabriel singular values. */ +/** Caption leftover-map plot axes with persisted Gabriel singular values (ADR 0324). */ import type { LeftoverMapAxis } from "./api"; -import { formatLeftoverMapPlotAxisShare } from "./leftoverMapPlotAxisShare"; +import { + formatLeftoverMapPlotAxisShare, + LEFTOVER_MAP_PLOT_AXIS_SHARE, +} from "./leftoverMapPlotAxisShare"; export const LEFTOVER_MAP_PLOT_AXIS_SINGULAR = "leftover-map axis {axis} σ {value}"; @@ -63,6 +66,28 @@ export function formatLeftoverMapPlotAxisSingular( return leftoverSingular.toFixed(2); } +export function leftoverMapPlotAxisBadge( + axisIndex: number, + leftoverSingular: number | null | undefined, + leftoverShare: number | null | undefined, +): LeftoverMapCompareAxisBadge | null { + const singular = formatLeftoverMapPlotAxisSingular(leftoverSingular); + const percent = formatLeftoverMapPlotAxisShare(leftoverShare); + if (singular === null && percent === null) { + return null; + } + if (singular === null && percent !== null) { + return { key: LEFTOVER_MAP_PLOT_AXIS_SHARE, values: { axis: axisIndex, share: percent } }; + } + if (singular !== null && percent === null) { + return { key: LEFTOVER_MAP_PLOT_AXIS_SINGULAR, values: { axis: axisIndex, value: singular } }; + } + return { + key: LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE, + values: { axis: axisIndex, value: singular as string, share: percent as string }, + }; +} + export function leftoverMapCompareAxisBadge( axisIndex: number, leftoverSingular: number | null | undefined, diff --git a/frontend/src/leftoverMapPlotLayout.test.ts b/frontend/src/leftoverMapPlotLayout.test.ts index 88d118208..23a3ab3cc 100644 --- a/frontend/src/leftoverMapPlotLayout.test.ts +++ b/frontend/src/leftoverMapPlotLayout.test.ts @@ -1369,6 +1369,7 @@ describe("leftover map comparison graphic labels", () => { 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 axis {axis} σ {value}"); expect(LEFTOVER_MAP_COMPARE_PLOT_TICK).not.toBe( "leftover map comparison leftover axis {axis} σ {value}", ); diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts index 217a56bae..df6cb0caa 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -1,5 +1,7 @@ /** Gabriel leftover-map graphic display of persisted ``ξ_{1:2}`` / ``ζ_{1:2}``. * Leftover-map axis share captions axes 1 and 2 when finite (ADR 0269). + * Leftover-map singular values caption leftover-map graphic-display axes when + * finite (ADR 0324). * Axis ticks name persisted leftover-map coordinates (ADR 0270). * Pair segments name persisted leftover-map distance ``d`` (ADR 0271), * persisted leftover-map reconstruction ``R̂`` (ADR 0272), persisted @@ -85,6 +87,8 @@ * singular values, not this graphic. * ADR 0323 captions leftover-axis report badges on the grouping comparison strip * with persisted leftover-map singular values, not this graphic. + * ADR 0324 captions leftover-map graphic-display axes with persisted leftover-map + * singular values `σ_k`. */ import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates"; diff --git a/pyproject.toml b/pyproject.toml index 95202a36c..d46ab31c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.80.0" +version = "2.81.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" }