diff --git a/AGENTS.md b/AGENTS.md index d7b5f781e..b8dc96b39 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) 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) 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 @@ -481,7 +481,13 @@ ADR 0303 returns persisted leftover-map coordinates `ξ` / `ζ` on compares leftover pairs can match the pair-row coordinate badge on live responses; a missing axis stays null and does not invent coordinates from leftover-map rank, leftover-map distance, leftover-map reconstruction, or -leftover residual. The strip does not gain the leftover-map graphic. +leftover residual. ADR 0304 draws the leftover-map graphic display of those +already-named `ξ` / `ζ` coordinates above grouping comparison leftover-pair +buttons when four leftover-map axes are finite; a missing or non-finite axis +omits that pair from the comparison graphic and does not invent coordinates +from leftover-map rank, leftover-map distance, leftover-map reconstruction, or +leftover residual. Omit the comparison graphic when no leftover pair has four +finite leftover-map coordinates. 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 @@ -526,6 +532,9 @@ Grouping comparison leftover-map coordinates (ADR 0302) captions grouping compar buttons with persisted leftover-map coordinates `ξ` / `ζ`. Grouping comparison leftover-map coordinates payload (ADR 0303) returns persisted leftover-map coordinates `ξ` / `ζ` on `GET /api/reports/compare/{period}` leftover pairs. +Grouping comparison leftover-map graphic display (ADR 0304) draws the leftover-map +graphic of those already-named coordinates above grouping comparison leftover-pair +buttons when four leftover-map axes are finite. 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 0b32d177f..21a698856 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -697,6 +697,9 @@ ADR 0302 captions grouping comparison leftover-pair buttons with persisted lefto coordinates `ξ` / `ζ` through formatLeftoverMapCoordinates. ADR 0303 returns persisted leftover-map coordinates `ξ` / `ζ` on `GET /api/reports/compare/{period}` leftover pairs. +ADR 0304 draws the leftover-map graphic display of those already-named +coordinates above grouping comparison leftover-pair buttons when four +leftover-map axes are finite. 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 @@ -727,8 +730,9 @@ incomplete item coverage (ADR 0292), leftover-map reconstruction `R̂` (ADR 0299), leftover expected `E` (ADR 0300), leftover-map rank (ADR 0301), leftover-map coordinates `ξ` / `ζ` -(ADR 0302), and leftover-map coordinates on the compare leftover-pair payload -(ADR 0303); +(ADR 0302), leftover-map coordinates on the compare leftover-pair payload +(ADR 0303), and leftover-map graphic display on the grouping comparison strip +(ADR 0304); `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.61.0-leftover-map-compare-graphic.md b/CHANGELOG.d/2.61.0-leftover-map-compare-graphic.md new file mode 100644 index 000000000..42d6b9ffe --- /dev/null +++ b/CHANGELOG.d/2.61.0-leftover-map-compare-graphic.md @@ -0,0 +1,15 @@ +## 2.61.0 — Leftover-map graphic display on grouping comparison strip + +- Draw the leftover-map graphic display of persisted `ξ` / `ζ` above + grouping comparison leftover-pair buttons (ADR 0304). After `make seed`, + closest and farthest leftover pairs sit above the member list with the + Gabriel biplot of already-named coordinates on the grouping comparison + strip when four leftover-map axes are finite; click a post marker or a + pair button opens that post. Omit the plot when no leftover pair has four + finite leftover-map coordinates. Rank-0 origin cells still plot at + `(0, 0)` when those persisted coordinates are stored. Do not invent + coordinates from leftover-map rank, leftover-map distance, or leftover-map + reconstruction. A finite negative leftover is shown, never clamped. This + increment does not caption leftover-map axis share or leftover-map coverage + on the comparison graphic. Never invent a leftover score. Never invent a + theta. No new columns. diff --git a/CHANGELOG.md b/CHANGELOG.md index 274d6b28c..885a3ce75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,68 @@ All notable changes to this project are documented here. Format follows ### Added +- Period leftover pairs now draw the leftover-map graphic display of + persisted leftover-map coordinates `ξ` / `ζ` above grouping comparison + leftover-pair buttons (ADR 0304 / v2.61.0). After `make seed`, closest + and farthest leftover pairs sit above the member list with the Gabriel + biplot of already-named coordinates, leftover-map axis share when finite, + axis ticks that match `ξ (x, y) ζ (x, y)` on the pair row, pair segments + that match `d` on the pair row, pair segments that match `R̂` on the pair + row, pair segments that match `R̂²/R²` on the pair row, pair segments that + match `U²/R²` on the pair row, pair segments that match `2R̂U/R²` on the + pair row, pair segments that match `U` on the pair row, pair segments that + match `R` on the pair row, pair segments that match `Y` on the pair row, + pair segments that match `E` on the pair row, pair segments that match + leftover-map rank on the pair row, a plot caption that matches leftover-map + complete-case coverage above the pair list, a plot caption that matches + leftover-map item complete-case coverage, a plot caption that matches + leftover-map incomplete post coverage, a plot caption that matches + leftover-map incomplete item coverage, a pair-list note that matches + leftover-map post complete-case coverage, a pair-list note that matches + leftover-map item complete-case coverage, a pair-list note that matches + leftover-map incomplete post coverage, a pair-list note that matches + leftover-map incomplete item coverage, a grouping comparison note that + matches leftover-map post complete-case coverage, a grouping comparison + note that matches leftover-map item complete-case coverage, a grouping + comparison note that matches leftover-map incomplete post coverage, a + grouping comparison note that matches leftover-map incomplete item + coverage, grouping comparison leftover-pair buttons that match leftover-map + reconstruction `R̂` on the pair row, grouping comparison leftover-pair + buttons that match leftover-map explained leftover share `e` on the pair + row, grouping comparison leftover-pair buttons that match leftover-map + unexplained leftover share `s` on the pair row, grouping comparison + leftover-pair buttons that match leftover-map cross share `x` on the pair + row, grouping comparison leftover-pair buttons that match leftover-map + unexplained leftover `U` on the pair row, grouping comparison leftover-pair + buttons that match leftover residual `R` on the pair row, grouping + comparison leftover-pair buttons that match leftover observed `Y` on the + pair row, grouping comparison leftover-pair buttons that match leftover + expected `E` on the pair row, grouping comparison leftover-pair buttons + that match leftover-map rank on the pair row, grouping comparison leftover-pair + buttons that match leftover-map coordinates `ξ` / `ζ` on the pair row, a + live compare payload that carries those persisted leftover-map coordinates, + and the grouping comparison leftover-map graphic of those already-named + coordinates when four leftover-map axes are finite; click a post marker or + a pair button opens that post. A missing axis stays null and omits that + leftover-map comparison coordinates badge and keeps leftover-map rank when + that rank is a non-negative integer, leftover expected `E` when finite, + leftover observed `Y` when finite, leftover residual `R` when finite, + leftover-map unexplained leftover `U` when finite, leftover-map cross share + `x` when finite, leftover-map unexplained leftover share `s` when finite, + leftover-map explained leftover share `e` when finite, leftover-map + reconstruction `R̂` when finite, leftover-map distance `d`, plus the strip + coverage notes. Rank-0 origin cells still plot at `(0, 0)` when those + persisted coordinates are stored. Do not invent coordinates from leftover-map + rank, leftover-map distance, leftover expected, leftover observed, leftover + residual, leftover-map reconstruction, leftover-map unexplained leftover, + leftover-map post coverage, leftover-map item coverage, leftover-map + incomplete post coverage, leftover-map incomplete item coverage, or the + count of unused axes. A finite negative leftover is shown, never clamped. + Omit the comparison graphic when no leftover pair has four finite leftover-map + coordinates. This increment does not caption leftover-map axis share or + leftover-map coverage on the comparison graphic. Never invent a leftover + score or a theta. No new columns. + - Period leftover pairs now return persisted leftover-map coordinates `ξ` / `ζ` on `GET /api/reports/compare/{period}` leftover pairs (ADR 0303 / v2.60.0). After `make seed`, closest and farthest leftover diff --git a/CLAUDE.md b/CLAUDE.md index 799da2d5b..d0da895bd 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), 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), 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/0268-leftover-map-graphic-display.md b/docs/adr/0268-leftover-map-graphic-display.md index c91dc5b9c..f61bf8c40 100644 --- a/docs/adr/0268-leftover-map-graphic-display.md +++ b/docs/adr/0268-leftover-map-graphic-display.md @@ -26,7 +26,9 @@ [ADR 0279](0279-leftover-map-segment-expected.md) (leftover expected E on pair segments); [ADR 0280](0280-leftover-map-segment-rank.md) -(leftover-map rank on pair segments) +(leftover-map rank on pair segments); +[ADR 0304](0304-leftover-map-compare-graphic.md) +(leftover-map graphic display on the grouping comparison strip) Amends [ADR 0049](0049-leftover-pair-report-ui.md) and [ADR 0267](0267-leftover-map-coordinates.md). Independent of leftover-map @@ -87,8 +89,8 @@ that pair from the plot rather than inventing a location. When no pair has four finite coordinates, omit the plot and keep the existing pair-list next action. Duplicate posts share one person marker; duplicate criteria share one item marker. The grouping comparison -strip (ADR 0149) stays on its reduced leftover payload and does not -gain this plot. +strip (ADR 0149) later gains this plot from already-named leftover-map +coordinates (ADR 0304). Do not add SQL. Do not edit shipped migrations. Do not persist inner product, cosine, or length as separate columns. diff --git a/docs/adr/0304-leftover-map-compare-graphic.md b/docs/adr/0304-leftover-map-compare-graphic.md new file mode 100644 index 000000000..783534ca9 --- /dev/null +++ b/docs/adr/0304-leftover-map-compare-graphic.md @@ -0,0 +1,237 @@ +# ADR 0304 — Show leftover-map graphic display on grouping comparison strip + +**Decision status:** Proposed +**Date:** 2026-08-31 + +Amends leftover pairs on the grouping comparison strip +([ADR 0149](0149-leftover-pairs-on-comparison-strip.md)), leftover-map +graphic display ([ADR 0268](0268-leftover-map-graphic-display.md)), leftover-map +coordinates on the compare leftover-pair payload +([ADR 0303](0303-leftover-map-compare-coordinates-payload.md)), leftover-map +coordinates on grouping comparison strip pair rows +([ADR 0302](0302-leftover-map-compare-coordinates.md)), leftover-map +rank on grouping comparison strip pair rows +([ADR 0301](0301-leftover-map-compare-rank.md)), leftover expected on grouping +comparison strip pair rows +([ADR 0300](0300-leftover-map-compare-expected.md)), leftover observed on +grouping comparison strip pair rows +([ADR 0299](0299-leftover-map-compare-observed.md)), leftover residual +on grouping comparison strip pair rows +([ADR 0298](0298-leftover-map-compare-residual.md)), leftover-map unexplained leftover +on grouping comparison strip pair rows +([ADR 0297](0297-leftover-map-compare-unexplained.md)), leftover-map cross share +on grouping comparison strip pair rows +([ADR 0296](0296-leftover-map-compare-cross-share.md)), leftover-map unexplained +leftover share on grouping comparison strip pair rows +([ADR 0295](0295-leftover-map-compare-unexplained-share.md)), leftover-map +explained leftover share on grouping comparison strip pair rows +([ADR 0294](0294-leftover-map-compare-explained-share.md)), leftover-map +reconstruction on grouping comparison strip pair rows +([ADR 0293](0293-leftover-map-compare-reconstruction.md)), leftover-map +incomplete item coverage on the grouping comparison strip +([ADR 0292](0292-leftover-map-compare-incomplete-item.md)), leftover-map +incomplete post coverage on the grouping comparison strip +([ADR 0291](0291-leftover-map-compare-incomplete-post.md)), leftover-map item +complete-case coverage on the grouping comparison strip +([ADR 0290](0290-leftover-map-compare-item-coverage.md)), leftover-map +complete-case coverage on the grouping comparison strip +([ADR 0289](0289-leftover-map-compare-coverage.md)), leftover residual +disclosure ([ADR 0162](0162-leftover-residual-disclosure.md)), leftover +observed `Y` / expected `E` ([ADR 0163](0163-leftover-observed-expected.md)), leftover-map unexplained leftover persistence ([ADR 0182](0182-leftover-map-unexplained.md)), +leftover-map reconstruction persistence +([ADR 0201](0201-leftover-map-reconstruction.md)), leftover-map rank +([ADR 0164](0164-leftover-map-rank.md)), leftover-map rank on pair segments +([ADR 0280](0280-leftover-map-segment-rank.md)), leftover-map coordinate ticks +([ADR 0270](0270-leftover-map-coordinate-ticks.md)), and leftover-map coordinates +([ADR 0267](0267-leftover-map-coordinates.md)). Independent of leftover-map +post complete-case coverage fail-closed on the pair list +([ADR 0288](0288-leftover-map-list-post-coverage-helper.md)), leftover-map +incomplete item coverage on the pair list +([ADR 0287](0287-leftover-map-list-incomplete-item.md)), leftover-map incomplete +post coverage on the pair list +([ADR 0286](0286-leftover-map-list-incomplete-post.md)), leftover-map item +complete-case coverage on the pair list +([ADR 0285](0285-leftover-map-list-item-coverage.md)), leftover-map incomplete +item coverage on the graphic display +([ADR 0284](0284-leftover-map-plot-incomplete-item.md)), leftover-map incomplete +post coverage on the graphic display +([ADR 0283](0283-leftover-map-plot-incomplete.md)), leftover-map item +complete-case coverage on the graphic display +([ADR 0282](0282-leftover-map-plot-item-coverage.md)), leftover-map complete-case +coverage on the graphic display +([ADR 0281](0281-leftover-map-plot-coverage.md)), leftover expected on pair +segments ([ADR 0279](0279-leftover-map-segment-expected.md)), leftover observed +on pair segments ([ADR 0278](0278-leftover-map-segment-observed.md)), leftover +residual on pair segments ([ADR 0277](0277-leftover-map-segment-residual.md)), +leftover-map unexplained leftover on pair segments +([ADR 0276](0276-leftover-map-segment-unexplained-leftover.md)), leftover-map +cross share on pair segments ([ADR 0275](0275-leftover-map-segment-cross-share.md)), +leftover-map unexplained leftover share on pair segments +([ADR 0274](0274-leftover-map-segment-unexplained-share.md)), leftover-map +explained leftover share on pair segments +([ADR 0273](0273-leftover-map-segment-explained-share.md)), leftover-map +reconstruction on pair segments +([ADR 0272](0272-leftover-map-segment-reconstruction.md)), leftover-map +distance on pair segments ([ADR 0271](0271-leftover-map-segment-distance.md)), +leftover-map axis share on the graphic display +([ADR 0269](0269-leftover-map-axis-share-plot.md)), leftover-map +cross share persistence ([ADR 0185](0185-leftover-map-cross-share.md)), leftover-map +unexplained leftover share persistence +([ADR 0233](0233-leftover-map-unexplained-share.md)), leftover-map explained leftover +share persistence ([ADR 0266](0266-leftover-map-explained-share.md)), and leftover-map +axis share persistence ([ADR 0148](0148-leftover-map-axis-share.md)). + +## Context + +ADR 0268 already draws the leftover-map graphic display of persisted +Gabriel person coordinates `ξ_{1:2}` and item coordinates `ζ_{1:2}` +above leftover pair buttons on the period-report list. ADR 0303 already +returns those four persisted leftover-map axis columns on +`GET /api/reports/compare/{period}` leftover pairs, and ADR 0302 already +captions `ξ (x, y) ζ (x, y)` on grouping comparison leftover-pair buttons. +The grouping comparison strip still omits the graphic, so a buyer who +compares leftover pairs can treat leftover-map rank, leftover expected `E`, +leftover observed `Y`, leftover residual `R`, leftover-map unexplained leftover +`U`, leftover-map cross share `x`, leftover-map unexplained leftover share `s`, +leftover-map explained leftover share `e`, leftover-map reconstruction `R̂`, +leftover-map distance `d`, or the coordinate badge as leftover-map location +without seeing the Gabriel biplot that already names those positions on the +pair list. + +This increment reuses LeftoverMapPlot on each grouping comparison row +above leftover-pair buttons when at least one leftover pair has four finite +persisted leftover-map coordinates. It does not add columns. It does not +recompute coordinates from leftover-map rank, leftover-map distance, leftover +expected, leftover observed, leftover residual, leftover-map reconstruction, +leftover-map explained leftover share, leftover-map unexplained leftover share, +leftover-map cross share, leftover-map unexplained leftover, leftover-map +coverage, or the count of unused axes. It does not persist leftover-map inner +product, cosine, or length as separate columns. Do not invent a leftover +score. Do not invent a theta. + +The dashboard stack already used neighbouring leftover facts under other +numbers. This protected increment uses **0304** so it does not collide with +leftover-map coordinates on the compare leftover-pair payload (0303), leftover-map +coordinates on grouping comparison strip pair rows (0302), leftover-map +rank on grouping comparison strip pair rows (0301), leftover +expected on grouping comparison strip pair rows (0300), leftover observed on +grouping comparison strip pair rows (0299), leftover residual on grouping +comparison strip pair rows (0298), leftover-map unexplained leftover on grouping +comparison strip pair rows (0297), leftover-map cross share on grouping +comparison strip pair rows (0296), leftover-map unexplained leftover share on +grouping comparison strip pair rows (0295), leftover-map explained leftover share +on grouping comparison strip pair rows (0294), leftover-map reconstruction on +grouping comparison strip pair rows (0293), leftover-map rank on pair segments +(0280), leftover-map coordinate ticks (0270), leftover-map coordinates (0267), +leftover-map graphic display (0268), leftover expected on pair segments (0279), +leftover observed on pair segments (0278), leftover residual on pair segments +(0277), leftover residual disclosure (0162), leftover observed `Y` / expected `E` +(0163), leftover-map rank persistence (0164), leftover-map reconstruction +persistence (0201), leftover-map coverage on the graphic (0281–0284), leftover-map +axis share on the graphic (0269), or the dashboard stacks. + +## Decision + +On the grouping comparison strip, render the existing leftover-map graphic +display **above** leftover-pair buttons when at least one leftover pair has +four finite persisted leftover-map coordinates. Person markers are posts at +persisted `ξ_{1:2}`; item markers are criteria at persisted `ζ_{1:2}`. A faint +segment joins each closest or farthest pair so leftover-map distance `d` is +the drawn length, not a second score. The origin stays in view because it is +the rank-0 unused-axis location. Scale is isotropic so Euclidean `d` is +visually comparable on both axes. A rank-0 origin cell plots at `(0, 0)` with +a unit display window; that window is drawing scale, not a leftover score. + +Click a post marker to open that post with leftover focus so Post quality +marks the named criterion current (ADR 0158). Criterion markers are not post +buttons. A missing or non-finite coordinate omits that pair from the plot +rather than inventing a location. When no pair has four finite coordinates, +omit the plot and keep the existing leftover-pair buttons, coverage notes, and +coordinate badges. Duplicate posts share one person marker; duplicate criteria +share one item marker. + +This increment does not caption leftover-map axis share or leftover-map +coverage on the comparison graphic. Those notes already sit on the strip +through ADR 0289–0292 and stay independent of this plot. Do not invent +coordinates from leftover-map rank, leftover-map distance, leftover expected, +leftover observed, leftover residual, leftover-map reconstruction, leftover-map +unexplained leftover, leftover-map explained leftover share, leftover-map +unexplained leftover share, leftover-map cross share, leftover-map post +coverage, leftover-map item coverage, leftover-map incomplete post coverage, +leftover-map incomplete item coverage, or the count of unused axes. A finite +negative leftover on neighbouring fields is shown, never clamped. + +Do not add SQL migrations. 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`, grouping comparison leftover pairs that already return +persisted leftover-map coordinates `ξ` / `ζ` also show the leftover-map +graphic display of those already-named positions above leftover-pair buttons +when four finite axes are present. Click a post marker or a pair button opens +that post. Hidden posts stay hidden. When `Y`, `E`, and `R` are finite, +`Y − E = R`. When `R`, `R̂`, and `U` are finite, `U + R̂ = R`. When `R`, +`R̂`, `U`, `x`, `s`, and `e` are finite, `e + s + x = 1`. When coordinates, +reconstruction, and distance are finite, `R̂ = ξ · ζ` and `d = ‖ξ − ζ‖`. + +## Related + +Independent of leftover interaction-map persistence, leftover-criterion +evaluation landing, leftover residual disclosure, leftover-map +complete-case coverage persistence, leftover-map axis share persistence, +leftover pairs on the grouping comparison strip, leftover-map complete-case +coverage on the grouping comparison strip, leftover-map item complete-case +coverage on the grouping comparison strip, leftover-map incomplete post +coverage on the grouping comparison strip, leftover-map incomplete item +coverage on the grouping comparison strip, leftover-map reconstruction on +grouping comparison strip pair rows, leftover-map explained leftover share +on grouping comparison strip pair rows, leftover-map unexplained leftover +share on grouping comparison strip pair rows, leftover-map cross share on +grouping comparison strip pair rows, leftover-map unexplained leftover on +grouping comparison strip pair rows, leftover residual on grouping +comparison strip pair rows, leftover observed on grouping comparison strip +pair rows, leftover expected on grouping comparison strip pair rows, +leftover-map rank on grouping comparison strip pair rows, leftover-map +coordinates on grouping comparison strip pair rows, leftover-map coordinates +on the compare leftover-pair payload, leftover-map inner product, leftover-map +cosine, leftover-map length, leftover residual on pair segments, leftover +observed on pair segments, leftover expected on pair segments, leftover-map +rank on pair segments, leftover-map unexplained leftover on pair segments, +leftover-map unexplained leftover persistence, leftover-map cross share on +pair segments, leftover-map cross share persistence, leftover-map unexplained +leftover share on pair segments, leftover-map unexplained leftover share +persistence, leftover-map explained leftover share on pair segments, +leftover-map explained leftover share persistence, leftover-map reconstruction +on pair segments, leftover-map reconstruction persistence, leftover-map item +complete-case coverage on the graphic display, leftover-map item complete-case +coverage on the pair list, leftover-map incomplete post coverage on the graphic +display, leftover-map incomplete post coverage on the pair list, leftover-map +incomplete item coverage on the graphic display, leftover-map incomplete item +coverage on the pair list, leftover-map post complete-case coverage fail-closed +on the pair list, leftover-map rank persistence, leftover-map coordinate +persistence, leftover-map coordinate ticks, leftover-map axis share on the +graphic display, leftover-map complete-case coverage on the graphic display, +leftover-map item complete-case coverage on the graphic display, leftover-map +incomplete post coverage on the graphic display, and leftover-map incomplete +item coverage on the graphic display. + +## 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 +(LSIRM interaction `−γ‖ξ_j − ζ_i‖` after main effects +`α_j − β_i`; typically `p = 2` for the interaction map. Leftover-map +coordinates are persisted Gabriel person `ξ_{1:2}` and item `ζ_{1:2}` +after IRT main effects. Grouping comparison leftover-map graphic display +plots those persisted positions on the grouping comparison strip only when +four stored axes are finite. Rank-0 origin cells still plot at `(0, 0)` +when those axes are stored. When finite, `R̂ = ξ · ζ` and +`d = ‖ξ − ζ‖`.) + +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 98d95bb2a..653e2f5fb 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 03:25 KST. Protected `main` is +> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not +> self-approve. #840 leftover-map coordinates `ξ` / `ζ` on +> `GET /api/reports/compare/{period}` leftover pairs (v2.60.0 / ADR 0303) +> is `cbf9944`, stacked on #839 @ `51bf33a`. Next buyer increment on this +> cycle: leftover-map graphic display of already-named `ξ` / `ζ` on the +> grouping comparison strip (ADR 0304 / v2.61.0) delivered locally on +> `feat/leftover-map-compare-graphic-v2610`. Reuse LeftoverMapPlot above +> leftover-pair buttons when four leftover-map axes are finite. Omit the +> plot when no leftover pair has four finite leftover-map coordinates. +> Do not caption leftover-map axis share or leftover-map coverage on that +> comparison graphic. Do not invent coordinates from leftover-map rank, +> leftover-map distance, or leftover-map reconstruction. Stack onto #840. +> Leave open for independent review. Do not squash-merge. Issues #79 and +> #87 stay open. #96 is already closed. Do not merge #808 without +> independent APPROVE. + + > Exact-head loop overlay: 2026-08-31 02:30 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not > self-approve. #839 leftover-map coordinates `ξ` / `ζ` on grouping diff --git a/frontend/package.json b/frontend/package.json index 1dcee04fd..2049affe5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.60.0", + "version": "2.61.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 15580f204..7ac02010e 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -4181,14 +4181,14 @@ describe("App, authenticated", () => { expect(screen.getByRole("button", { name: /open report post: public post/i })).toHaveTextContent("Open"); expect(screen.getByRole("button", { name: /open report post: public post/i })).toHaveTextContent("due 2026-01-12"); expect(await screen.findByLabelText("Leftover pairs")).toBeInTheDocument(); - expect(screen.getByLabelText("Leftover-map graphic display")).toBeInTheDocument(); + expect(screen.getAllByLabelText("Leftover-map graphic display")).toHaveLength(2); expect(screen.getByText("leftover-map axis 1 (82%)")).toBeInTheDocument(); expect(screen.getByText("leftover-map axis 2 (18%)")).toBeInTheDocument(); expect( - screen.getByRole("button", { + screen.getAllByRole("button", { name: /open leftover-map post public post at ξ \(\+0\.50, \+0\.10\)/i, }), - ).toBeInTheDocument(); + ).toHaveLength(2); expect(screen.getByLabelText("Leftover map coverage")).toHaveTextContent( "Leftover map used 2 of 3 scored posts (complete-case)", ); @@ -4278,6 +4278,21 @@ describe("App, authenticated", () => { render(); expect(await screen.findByLabelText("Grouping comparison")).toBeInTheDocument(); + expect( + await within(screen.getByLabelText("Grouping comparison")).findByLabelText( + "Leftover-map graphic display", + ), + ).toBeInTheDocument(); + expect( + within(screen.getByLabelText("Grouping comparison")).getAllByLabelText( + "Leftover-map graphic display", + ), + ).toHaveLength(1); + expect( + within(screen.getByLabelText("Grouping comparison")).getByRole("button", { + name: "Open leftover-map post Public post at ξ (+0.50, +0.10)", + }), + ).toBeInTheDocument(); expect( within(screen.getByLabelText("Grouping comparison")).queryByLabelText("Leftover map coverage"), ).not.toBeInTheDocument(); @@ -4349,7 +4364,7 @@ describe("App, authenticated", () => { within(screen.getByLabelText("Grouping comparison")).queryByLabelText( /leftover-map reconstruction/, ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("R̂ +0.25"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison reconstruction", @@ -4364,7 +4379,7 @@ describe("App, authenticated", () => { within(screen.getByLabelText("Grouping comparison")).queryByLabelText( /leftover-map explained leftover share/, ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("R̂²/R² 0.76"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison explained leftover share", @@ -4379,7 +4394,7 @@ describe("App, authenticated", () => { within(screen.getByLabelText("Grouping comparison")).queryByLabelText( /leftover-map unexplained leftover share/, ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("U²/R² 0.02"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison unexplained leftover share", @@ -4394,7 +4409,7 @@ describe("App, authenticated", () => { within(screen.getByLabelText("Grouping comparison")).queryByLabelText( /leftover-map cross share/, ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("2R̂U/R² 0.12"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison cross share", @@ -4406,10 +4421,10 @@ describe("App, authenticated", () => { ), ).toHaveLength(1); expect( - within(screen.getByLabelText("Grouping comparison")).queryByLabelText( - /leftover-map unexplained leftover/, + within(screen.getByLabelText("Grouping comparison")).getByLabelText( + "leftover-map unexplained leftover U +0.05", ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("U +0.05"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison unexplained leftover", @@ -4421,10 +4436,10 @@ describe("App, authenticated", () => { ), ).toHaveLength(1); expect( - within(screen.getByLabelText("Grouping comparison")).queryByLabelText( - /leftover residual/, + within(screen.getByLabelText("Grouping comparison")).getByLabelText( + "leftover residual R +0.40", ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("R +0.40"); expect( within(screen.getByLabelText("Grouping comparison")).getAllByLabelText( "Leftover map comparison residual", @@ -4444,7 +4459,7 @@ describe("App, authenticated", () => { within(screen.getByLabelText("Grouping comparison")).queryByLabelText( /leftover observed/, ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("Y 2.40"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison observed", @@ -4459,7 +4474,7 @@ describe("App, authenticated", () => { within(screen.getByLabelText("Grouping comparison")).queryByLabelText( /leftover expected/, ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("E 2.00"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison expected", @@ -4474,7 +4489,7 @@ describe("App, authenticated", () => { within(screen.getByLabelText("Grouping comparison")).queryByLabelText( /leftover-map rank/, ), - ).not.toBeInTheDocument(); + ).toHaveTextContent("rank 1"); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison rank", @@ -4486,10 +4501,10 @@ describe("App, authenticated", () => { ), ).toHaveLength(1); expect( - within(screen.getByLabelText("Grouping comparison")).queryByLabelText( - /leftover-map axis/, - ), - ).not.toBeInTheDocument(); + within(screen.getByLabelText("Grouping comparison")).getAllByLabelText( + /leftover-map axis \d tick/, + ).length, + ).toBeGreaterThan(0); expect( within(screen.getByLabelText("Grouping comparison")).getByLabelText( "Leftover map comparison coordinates", @@ -4653,6 +4668,19 @@ describe("App, authenticated", () => { await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); }); + it("opens a leftover pair post from the grouping comparison leftover-map graphic", async () => { + stubBackend(); + render(); + + const comparison = await screen.findByLabelText("Grouping comparison"); + await userEvent.click( + await within(comparison).findByRole("button", { + name: "Open leftover-map post Public post at ξ (+0.50, +0.10)", + }), + ); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + }); + it("opens a leftover pair post from the report panel", async () => { stubBackend(); render(); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d3d32cfad..30dfa2d99 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -180,6 +180,7 @@ import "./App.css"; const AdminPanel = lazy(() => import("./components/AdminPanel").then((module) => ({ default: module.AdminPanel }))); const AskEvidenceLayerPopup = lazy(() => import("./components/AskEvidenceLayerPopup").then((module) => ({ default: module.AskEvidenceLayerPopup }))); const LeftoverPairList = lazy(() => import("./components/LeftoverPairList").then((module) => ({ default: module.LeftoverPairList }))); +const LeftoverMapPlot = lazy(() => import("./components/LeftoverMapPlot").then((module) => ({ default: module.LeftoverMapPlot }))); const LineageDag = lazy(() => import("./LineageDag").then((module) => ({ default: module.LineageDag }))); const OntologyExplorer = lazy(() => import("./components/OntologyExplorer").then((module) => ({ default: module.OntologyExplorer }))); const OperationsDashboard = lazy(() => import("./components/OperationsDashboard").then((module) => ({ default: module.OperationsDashboard }))); @@ -4088,6 +4089,21 @@ function ReportsPanel({

) : null} {row.leftover_pairs && row.leftover_pairs.length > 0 && ( + <> + + { + onSelectPost(pair.post_id, { + fromLeftoverPair: { + pairKind: pair.pair_kind === "farthest" ? "farthest" : "closest", + criterionCode: pair.criterion_code, + }, + }); + }} + /> +
    {row.leftover_pairs.map((pair) => { const kindLabel = @@ -4233,6 +4249,7 @@ function ReportsPanel({ ); })}
+ )} ); diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx index 97e2c9bf0..46621aa30 100644 --- a/frontend/src/components/LeftoverMapPlot.tsx +++ b/frontend/src/components/LeftoverMapPlot.tsx @@ -101,6 +101,9 @@ function leftoverMapPlotAxisText( * Omit that axis badge when share is * missing or non-finite and keep the existing leftover-map axis text. * Omit the plot when no pair has four finite leftover-map coordinates. + * ADR 0304 reuses this graphic on the grouping comparison strip from + * already-named leftover-map coordinates and does not caption leftover-map + * axis share or leftover-map coverage on that comparison plot. * Never invent a leftover score. */ export function LeftoverMapPlot({ diff --git a/frontend/src/components/LeftoverPairList.tsx b/frontend/src/components/LeftoverPairList.tsx index 0db0667fd..c1bfa341f 100644 --- a/frontend/src/components/LeftoverPairList.tsx +++ b/frontend/src/components/LeftoverPairList.tsx @@ -104,7 +104,9 @@ export type LeftoverPairListProps = { * buttons, not this pair list. ADR 0302 names persisted leftover-map * coordinates on grouping comparison leftover-pair buttons, not this * pair list. ADR 0303 returns persisted leftover-map coordinates on - * grouping comparison leftover pairs, not this pair list. Every badge still + * grouping comparison leftover pairs, not this pair list. ADR 0304 draws + * the leftover-map graphic display on the grouping comparison strip, not + * this pair list. Every badge still * renders together before opening the named post. */ export function LeftoverPairList({ diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts index d801bcc97..4946c96d8 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -30,9 +30,10 @@ * (ADR 0299), grouping comparison leftover expected * (ADR 0300), grouping comparison leftover-map rank * (ADR 0301), grouping comparison leftover-map coordinates - * (ADR 0302), and grouping comparison leftover-map coordinates payload - * (ADR 0303) caption the pair list or the grouping comparison strip, - * not this graphic layout. + * (ADR 0302), grouping comparison leftover-map coordinates payload + * (ADR 0303), and grouping comparison leftover-map graphic display + * (ADR 0304) caption the pair list or the grouping comparison strip. + * ADR 0304 reuses this graphic layout on the grouping comparison strip. */ import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates"; diff --git a/pyproject.toml b/pyproject.toml index d9e27eb25..355fbe0c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.60.0" +version = "2.61.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" } diff --git a/tests/test_compare_graphic_adr_status.py b/tests/test_compare_graphic_adr_status.py new file mode 100644 index 000000000..641399a49 --- /dev/null +++ b/tests/test_compare_graphic_adr_status.py @@ -0,0 +1,19 @@ +"""Governance contract for the grouping-comparison graphic ADR state.""" + +from pathlib import Path + + +_ADR = ( + Path(__file__).parents[1] + / "docs" + / "adr" + / "0304-leftover-map-compare-graphic.md" +) + + +def test_compare_graphic_adr_remains_proposed_until_independent_acceptance() -> None: + """A Draft candidate cannot claim an accepted architecture decision.""" + text = _ADR.read_text(encoding="utf-8") + assert text.startswith("# ADR 0304") + assert "**Decision status:** Proposed" in text + assert "**Decision status:** Accepted" not in text