From cbf994462ec787d4d3e7a2749f913f62fd9c96d0 Mon Sep 17 00:00:00 2001 From: seonghobae Date: Sun, 30 Aug 2026 17:27:38 +0000 Subject: [PATCH] feat(reports): leftover-map coordinates on grouping comparison payload (v2.60.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return persisted leftover-map coordinates ξ / ζ on GET /api/reports/compare/{period} leftover pairs so grouping comparison leftover-pair buttons can match the pair-row coordinate badge on live responses (ADR 0303). --- AGENTS.md | 10 +- ARCHITECTURE.md | 7 +- ...eftover-map-compare-coordinates-payload.md | 19 ++ CHANGELOG.md | 59 +++++ CLAUDE.md | 2 +- backend/app/report_ingestion.py | 24 +- backend/tests/test_api.py | 30 +++ ...eftover-map-compare-coordinates-payload.md | 223 ++++++++++++++++++ docs/product-technical-gap-baseline.md | 19 ++ frontend/package.json | 2 +- frontend/src/components/LeftoverPairList.tsx | 3 +- frontend/src/leftoverMapPlotLayout.ts | 5 +- pyproject.toml | 2 +- 13 files changed, 395 insertions(+), 10 deletions(-) create mode 100644 CHANGELOG.d/2.60.0-leftover-map-compare-coordinates-payload.md create mode 100644 docs/adr/0303-leftover-map-compare-coordinates-payload.md diff --git a/AGENTS.md b/AGENTS.md index a05699f09..d7b5f781e 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) 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) 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 @@ -476,6 +476,12 @@ compares leftover pairs can match the pair-row coordinate badge; a missing or non-finite axis omits that leftover-map comparison coordinates badge 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. +ADR 0303 returns persisted leftover-map coordinates `ξ` / `ζ` on +`GET /api/reports/compare/{period}` leftover pairs so a buyer who +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. 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 @@ -518,6 +524,8 @@ Grouping comparison leftover-map rank (ADR 0301) captions grouping comparison le buttons with persisted leftover-map rank. Grouping comparison leftover-map coordinates (ADR 0302) captions grouping comparison leftover-pair 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. 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 59caa9e1c..0b32d177f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -695,6 +695,8 @@ ADR 0301 captions grouping comparison leftover-pair buttons with persisted lefto rank through formatLeftoverMapRank. ADR 0302 captions grouping comparison leftover-pair buttons with persisted leftover-map coordinates `ξ` / `ζ` through formatLeftoverMapCoordinates. +ADR 0303 returns persisted leftover-map coordinates `ξ` / `ζ` on +`GET /api/reports/compare/{period}` leftover pairs. 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 @@ -724,8 +726,9 @@ incomplete item coverage (ADR 0292), leftover-map reconstruction `R̂` (ADR 0298), leftover observed `Y` (ADR 0299), leftover expected `E` (ADR 0300), leftover-map rank -(ADR 0301), and leftover-map coordinates `ξ` / `ζ` -(ADR 0302); +(ADR 0301), leftover-map coordinates `ξ` / `ζ` +(ADR 0302), and leftover-map coordinates on the compare leftover-pair payload +(ADR 0303); `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.60.0-leftover-map-compare-coordinates-payload.md b/CHANGELOG.d/2.60.0-leftover-map-compare-coordinates-payload.md new file mode 100644 index 000000000..5154800dc --- /dev/null +++ b/CHANGELOG.d/2.60.0-leftover-map-compare-coordinates-payload.md @@ -0,0 +1,19 @@ +## 2.60.0 — Leftover-map coordinates on grouping comparison leftover-pair payload + +- Return persisted leftover-map coordinates `ξ` / `ζ` on + `GET /api/reports/compare/{period}` leftover pairs (ADR 0303). After + `make seed`, grouping comparison leftover-pair buttons that already + caption formatLeftoverMapCoordinates can match leftover-map coordinates + on the pair row from the live compare payload; click a post marker or a + pair button opens that post. A missing axis stays null and omits that + leftover-map comparison coordinates badge independently of 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`, and + the strip coverage notes. Rank-0 origin cells still return `0.0` on each + axis 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. The strip does not gain + the leftover-map graphic. Never invent a leftover score. Never invent a + theta. No new columns. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3394eb039..274d6b28c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,65 @@ All notable changes to this project are documented here. Format follows ### Added +- 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 + 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, and + a live compare payload that carries those persisted leftover-map + coordinates; 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 return `0.0` on each axis 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. The strip does + not gain the leftover-map graphic. Never invent a leftover score or a + theta. No new columns. + - Period leftover pairs now caption grouping comparison leftover-pair buttons with persisted leftover-map coordinates `ξ` / `ζ` through formatLeftoverMapCoordinates (ADR 0302 / v2.59.0). After `make seed`, diff --git a/CLAUDE.md b/CLAUDE.md index 122bf0b15..799da2d5b 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), 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), 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/backend/app/report_ingestion.py b/backend/app/report_ingestion.py index d675fe3ab..a4912fecd 100644 --- a/backend/app/report_ingestion.py +++ b/backend/app/report_ingestion.py @@ -1052,7 +1052,9 @@ async def fetch_period_comparison( f""" select lp.grouping_kind, lp.grouping_key, lp.pair_kind, lp.post_id, lp.criterion_code, lp.leftover_distance, lp.leftover_residual, - lp.leftover_map_reconstruction, + lp.leftover_map_reconstruction, lp.leftover_map_person_axis_1, + lp.leftover_map_person_axis_2, lp.leftover_map_item_axis_1, + lp.leftover_map_item_axis_2, p.post_title, p.visibility_code, p.corporate_entity_id, ({_SOURCE_CONTEXT_PRESENT_SQL}) as has_real_source_context from report_leftover_pair lp @@ -1123,6 +1125,26 @@ async def fetch_period_comparison( if pair["leftover_map_reconstruction"] is None else float(pair["leftover_map_reconstruction"]) ), + "leftover_map_person_axis_1": ( + None + if pair["leftover_map_person_axis_1"] is None + else float(pair["leftover_map_person_axis_1"]) + ), + "leftover_map_person_axis_2": ( + None + if pair["leftover_map_person_axis_2"] is None + else float(pair["leftover_map_person_axis_2"]) + ), + "leftover_map_item_axis_1": ( + None + if pair["leftover_map_item_axis_1"] is None + else float(pair["leftover_map_item_axis_1"]) + ), + "leftover_map_item_axis_2": ( + None + if pair["leftover_map_item_axis_2"] is None + else float(pair["leftover_map_item_axis_2"]) + ), "visibility_code": pair["visibility_code"], "corporate_entity_id": str(pair["corporate_entity_id"]), "has_real_source_context": bool(pair["has_real_source_context"]), diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 273a7ff94..9e581e27a 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -5851,6 +5851,36 @@ def test_seed_period_report_surfaces_on_get_reports(client, demo_analyst_token, or isinstance(pair["leftover_map_reconstruction"], (int, float)) for pair in leftover_thread.get("leftover_pairs", []) ) + assert all( + { + "leftover_map_person_axis_1", + "leftover_map_person_axis_2", + "leftover_map_item_axis_1", + "leftover_map_item_axis_2", + }.issubset(pair) + for pair in leftover_thread.get("leftover_pairs", []) + ) + for pair in leftover_thread.get("leftover_pairs", []): + person_axis_1 = pair["leftover_map_person_axis_1"] + person_axis_2 = pair["leftover_map_person_axis_2"] + item_axis_1 = pair["leftover_map_item_axis_1"] + item_axis_2 = pair["leftover_map_item_axis_2"] + axes = (person_axis_1, person_axis_2, item_axis_1, item_axis_2) + if any(axis is None for axis in axes): + assert axes == (None, None, None, None) + continue + for axis in axes: + assert isinstance(axis, (int, float)) + assert not math.isnan(axis) + assert not math.isinf(axis) + reconstruction = pair.get("leftover_map_reconstruction") + if reconstruction is not None: + assert reconstruction == pytest.approx( + person_axis_1 * item_axis_1 + person_axis_2 * item_axis_2 + ) + assert pair["leftover_distance"] == pytest.approx( + math.hypot(person_axis_1 - item_axis_1, person_axis_2 - item_axis_2) + ) def test_seed_period_report_includes_fixture_event_lineage_posts( diff --git a/docs/adr/0303-leftover-map-compare-coordinates-payload.md b/docs/adr/0303-leftover-map-compare-coordinates-payload.md new file mode 100644 index 000000000..31fa985f8 --- /dev/null +++ b/docs/adr/0303-leftover-map-compare-coordinates-payload.md @@ -0,0 +1,223 @@ +# ADR 0303 — Return leftover-map coordinates on grouping comparison leftover pairs + +**Decision status:** Accepted +**Date:** 2026-08-31 + +Amends leftover pairs on the grouping comparison strip +([ADR 0149](0149-leftover-pairs-on-comparison-strip.md)), leftover-map +coordinates ([ADR 0267](0267-leftover-map-coordinates.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 graphic display +([ADR 0268](0268-leftover-map-graphic-display.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 0267 already persists two-axis Gabriel person coordinates +`ξ_{1:2}` and item coordinates `ζ_{1:2}` on leftover pair rows so +`R̂ = ξ_{1:2} · ζ_{1:2}` and `d = ‖ξ_{1:2} − ζ_{1:2}‖` stay +auditable. ADR 0302 already captions those persisted coordinates on grouping +comparison leftover-pair buttons through formatLeftoverMapCoordinates. The +period-report detail payload already returns +`leftover_map_person_axis_1`, `leftover_map_person_axis_2`, +`leftover_map_item_axis_1`, and `leftover_map_item_axis_2`. +`GET /api/reports/compare/{period}` still omits those four columns, so +every live grouping comparison leftover-pair button fail-closes the +leftover-map comparison coordinates badge even after the pair list and +graphic already name `ξ (x, y) ζ (x, y)`. Hiding coordinates on the live +strip lets 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̂`, or leftover-map +distance `d` be read as leftover-map location without a next action. + +This increment returns the four persisted leftover-map coordinate columns on +each grouping comparison leftover pair, matching the detail-report payload. +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, 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 **0303** so it does not collide with +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), or the dashboard stacks. + +## Decision + +On `GET /api/reports/compare/{period}`, include persisted +`leftover_map_person_axis_1`, `leftover_map_person_axis_2`, +`leftover_map_item_axis_1`, and `leftover_map_item_axis_2` on each leftover +pair, converting finite database numerics the same way as +`GET /api/reports/{grouping}/{period}` and preserving nulls. A missing axis +stays null and does not invent a coordinate 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. Rank-0 origin cells still return +`0.0, 0.0, 0.0, 0.0` when those persisted coordinates are stored. A finite +negative leftover on neighbouring fields is shown, never clamped. Do not add +the leftover-map graphic to the strip. Click a leftover pair on the strip to +open that post. + +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`, `GET /api/reports/compare/{period}` leftover pairs include +persisted leftover-map coordinates `ξ` / `ζ` when those axes were stored, so +grouping comparison leftover-pair buttons that already caption +formatLeftoverMapCoordinates can match the pair-row coordinate badge on live +responses. Closest and farthest leftover pairs still sit above the member +list with the leftover-map graphic display; 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 inner +product, leftover-map cosine, leftover-map length, leftover-map graphic +display, 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, and +leftover-map coordinate ticks. + +## 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 coordinates return +those persisted positions on `GET /api/reports/compare/{period}` leftover +pairs only when the stored axes are present. Rank-0 origin cells still +return `0.0` on each axis 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 62676381d..98d95bb2a 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,24 @@ # Product & Technical Gap Baseline +> Exact-head loop overlay: 2026-08-31 02:30 KST. Protected `main` is +> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not +> self-approve. #839 leftover-map coordinates `ξ` / `ζ` on grouping +> comparison leftover-pair buttons (v2.59.0 / ADR 0302) is `51bf33a`, +> stacked on #838 @ `6c7c66f`. Next buyer increment on this cycle: +> leftover-map coordinates `ξ` / `ζ` on `GET /api/reports/compare/{period}` +> leftover pairs (ADR 0303 / v2.60.0) delivered locally on +> `feat/leftover-map-compare-coordinates-payload-v2600`. Return the four +> persisted leftover-map axis columns on each compare leftover pair, +> preserving nulls and converting finite numerics the same way as the +> detail report. Rank-0 origin cells still return `0.0` on each axis when +> stored. Missing axis stays null independently of rank, `E`, `Y`, `R`, +> `U`, `x`, `s`, `e`, `R̂`, `d`, and coverage notes. Do not invent +> coordinates from leftover-map rank, leftover-map distance, or leftover-map +> reconstruction. Stack onto #839. 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:20 KST. Protected `main` is > `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not > self-approve. #838 leftover-map rank on grouping comparison leftover-pair diff --git a/frontend/package.json b/frontend/package.json index 3dca0ef53..1dcee04fd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.59.0", + "version": "2.60.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/components/LeftoverPairList.tsx b/frontend/src/components/LeftoverPairList.tsx index d99242a50..0db0667fd 100644 --- a/frontend/src/components/LeftoverPairList.tsx +++ b/frontend/src/components/LeftoverPairList.tsx @@ -103,7 +103,8 @@ export type LeftoverPairListProps = { * persisted leftover-map rank on grouping comparison leftover-pair * buttons, not this pair list. ADR 0302 names persisted leftover-map * coordinates on grouping comparison leftover-pair buttons, not this - * pair list. Every badge still + * pair list. ADR 0303 returns persisted leftover-map coordinates on + * grouping comparison leftover pairs, 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 92cac92eb..d801bcc97 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -29,8 +29,9 @@ * (ADR 0298), grouping comparison leftover observed * (ADR 0299), grouping comparison leftover expected * (ADR 0300), grouping comparison leftover-map rank - * (ADR 0301), and grouping comparison leftover-map coordinates - * (ADR 0302) caption the pair list or the grouping comparison strip, + * (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. */ diff --git a/pyproject.toml b/pyproject.toml index 2c7de2211..d9e27eb25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.59.0" +version = "2.60.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" }