diff --git a/AGENTS.md b/AGENTS.md index 805c548ed..230ef3aad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -540,3 +540,5 @@ published beside the ontology. ADR 0294 keeps grouping-comparison incomplete-item coverage in the LineageWeave read-model boundary: consume only authorization-filtered persisted coverage for a full grouping, omit partial visibility, and never recompute psychometrics in the client. ADR 0295 keeps grouping-comparison reconstruction in the LineageWeave read-model/UI boundary: format only persisted `R̂`, expose the value in the pair button accessible name, and never derive psychometric reconstruction from UI-visible proxies. + +ADR 0296 adds persisted grouping-comparison explained-leftover share `e` in the LineageWeave read-model/UI boundary: expose it in the pair button accessible name, keep the duplicate visible badge presentation-only, and never derive or clamp psychometric values. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 98c0c7669..f16d44bed 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1093,3 +1093,5 @@ the rare creation branch serializes. ADR 0294 adds persisted incomplete-item coverage to grouping comparison presentation while the API remains the full-group visibility authority; the frontend formats only an admitted persisted count. ADR 0295 adds persisted `R̂` to grouping-comparison pair buttons. The explicit button accessible name carries the reconstruction label/value; the visible duplicate badge is presentation-only. Psychometric computation remains owned by fast-mlsirm. + +ADR 0296 adds persisted explained-leftover share `e = R̂²/R²` to grouping-comparison pair actions; the button accessible name is authoritative for assistive technology and fast-mlsirm remains psychometric owner. diff --git a/CHANGELOG.d/2.53.0-leftover-map-compare-explained-share.md b/CHANGELOG.d/2.53.0-leftover-map-compare-explained-share.md new file mode 100644 index 000000000..11013ecb4 --- /dev/null +++ b/CHANGELOG.d/2.53.0-leftover-map-compare-explained-share.md @@ -0,0 +1,5 @@ +## 2.53.0 — Grouping comparison explained-leftover share + +- Show persisted `e = R̂²/R²` on grouping-comparison pair buttons (ADR 0296). +- Put the localized metric/value in the button accessible name; the duplicate visible badge is `aria-hidden`. +- Missing/non-finite values omit only this metric; zero and values above 1 are preserved and no proxy is derived. diff --git a/CHANGELOG.md b/CHANGELOG.md index f34327ac3..5cffe6075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to this project are documented here. Format follows ### Added +- Grouping comparison pair buttons now expose persisted explained-leftover share `e = R̂²/R²` (ADR 0296 / v2.53.0). The visible duplicate badge is accessibility-hidden while the actionable button name includes the localized label and formatted persisted value. Missing/non-finite `e` omits only the suffix; finite zero and values above 1 remain explicit without clamping or client-side derivation. + - Grouping comparison leftover-pair buttons now expose persisted leftover-map reconstruction `R̂` (ADR 0295 / v2.52.0). The visible badge is decorative to accessibility APIs; the button accessible name carries the reconstruction label and value, so the explicit button `aria-label` cannot suppress the metric. Missing/non-finite `R̂` omits only that value and no client-side proxy is invented. - Grouping comparison rows now show persisted leftover-map incomplete-item count through `leftoverMapIncompleteItemCount` (ADR 0294 / v2.51.0) only for fully caller-visible persisted groupings. Partial visibility remains omitted by the API; valid zero stays visible, and the UI never derives dropped criteria from scored-minus-used. diff --git a/CLAUDE.md b/CLAUDE.md index 00cfe1087..a4d4ece4e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,3 +61,5 @@ here. ADR 0294 is the Proposed grouping-comparison incomplete-item presentation contract; preserve server-side full-group visibility gating and do not infer dropped criteria in the frontend. ADR 0295 is the Proposed grouping-comparison reconstruction contract: persisted `R̂` only, no client inference, accessible-name exposure required despite the button's explicit aria-label. + +ADR 0296 is the Proposed grouping-comparison explained-share contract: persisted `e` only, accessible-name exposure required, no client derivation or clamping. diff --git a/docs/adr/0296-leftover-map-compare-explained-share.md b/docs/adr/0296-leftover-map-compare-explained-share.md new file mode 100644 index 000000000..a99daad79 --- /dev/null +++ b/docs/adr/0296-leftover-map-compare-explained-share.md @@ -0,0 +1,11 @@ +# ADR 0296 — Grouping comparison explained-leftover share + +**Decision status:** Proposed + +## Problem +The serialized comparison surface omits persisted explained-leftover share `e = R̂²/R²`. The historical implementation rendered a descendant badge inside a button with an explicit accessible name, so the visible metric was not reliably announced as part of the action. + +## Decision +Format only persisted `leftover_map_explained_share` through `formatLeftoverMapExplainedShare`. Append the localized label and formatted value to the pair button accessible name, and mark the duplicate visible badge `aria-hidden`. Missing/non-finite values omit only this suffix; finite zero and values above 1 remain explicit. Never derive or clamp `e` from `R̂`, `R`, distance, coordinates, residuals, rank, coverage, or visible-subset proxies. fast-mlsirm remains psychometric owner. + +This ADR remains Proposed while Draft. Acceptance requires current-head hosted tests, rendered keyboard/screen-reader/a11y evidence, canonical eight-locale ledger convergence, independent approval, and normal protected-branch merge. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 76c6db089..9b2d3acea 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1397,3 +1397,7 @@ Exact parent `#824@499d653ed6e9206249e3f3a07518ad3fc01f14bd` lacks the grouping- ### 2026-09-07 — #826 exact-parent reconstruction candidate Exact parent `#825@b73b10e3079e77f3e62235b2b709dc8a3f450292` omits persisted `R̂` from grouping-comparison pair rows. ADR 0295 / v2.52.0 restores that valid read-model delta and repairs the historical accessibility defect by carrying the label/value in the button accessible name. Candidate evidence is not protected-main/release evidence; current-head browser/screen-reader/a11y, canonical eight-locale ledger consumption, independent review, and normal protected merge remain outstanding. + +### 2026-09-07 — #827 exact-parent reconstruction candidate + +Exact parent `#826@66a7750a10ad0e9526716d382707f99528a75d90` omits persisted grouping-comparison explained-leftover share. ADR 0296 / v2.53.0 restores that valid delta and fixes its historical accessible-name defect. Candidate evidence is not protected-main/release evidence; current-head browser/screen-reader/a11y, canonical eight-locale ledger consumption, independent review, and normal protected merge remain outstanding. diff --git a/frontend/package.json b/frontend/package.json index 10d0e822c..ff2d6a69f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.52.0", + "version": "2.53.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 49dedd06e..ff0154e28 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -923,6 +923,7 @@ describe("App, authenticated", () => { leftover_distance: 0.12, leftover_residual: 0.4, leftover_map_reconstruction: 0.248, + leftover_map_explained_share: 0.76, }, ], leftover_map_coverage: { @@ -4342,6 +4343,10 @@ describe("App, authenticated", () => { name: /open leftover closest pair from comparison: public post.*leftover map comparison reconstruction R̂ \+0\.25/i, }); expect(reconstructionPair).toHaveTextContent("R̂ +0.25"); + const explainedSharePair = screen.getByRole("button", { + name: /open leftover closest pair from comparison: public post.*leftover map comparison reconstruction R̂ \+0\.25.*leftover map comparison explained leftover share R̂²\/R² 0\.76/i, + }); + expect(explainedSharePair).toHaveTextContent("R̂²/R² 0.76"); await waitFor(() => expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining("/api/reports/thread_group/2026-W02"), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2d4724e72..4d249f852 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -147,6 +147,10 @@ import { formatLeftoverMapReconstruction, LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL, } from "./leftoverMapReconstruction"; +import { + formatLeftoverMapExplainedShare, + LEFTOVER_MAP_COMPARE_EXPLAINED_SHARE_LABEL, +} from "./leftoverMapExplainedShare"; import "./App.css"; const AdminPanel = lazy(() => import("./components/AdminPanel").then((module) => ({ default: module.AdminPanel }))); @@ -4073,10 +4077,17 @@ function ReportsPanel({ const reconstruction = formatLeftoverMapReconstruction( pair.leftover_map_reconstruction, ); + const explainedShare = formatLeftoverMapExplainedShare( + pair.leftover_map_explained_share, + ); const pairAccessibleName = `Open leftover ${pair.pair_kind} pair from comparison: ${pair.post_title} · ${criterion}${ reconstruction ? ` · ${t(LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL)} ${reconstruction}` : "" + }${ + explainedShare + ? ` · ${t(LEFTOVER_MAP_COMPARE_EXPLAINED_SHARE_LABEL)} ${explainedShare}` + : "" }`; return (
  • ) : null} + {explainedShare ? ( + + ) : null}
  • ); diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index 53568b6cc..d75abb6c9 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -85,6 +85,7 @@ describe("i18n", () => { "Leftover map comparison incomplete posts", "Leftover map comparison incomplete items", "Leftover map comparison reconstruction", + "Leftover map comparison explained leftover share", "Leftover-map graphic item coverage", "Leftover map item coverage", "Leftover map incomplete posts", diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 41cf88f39..3a1931720 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -273,6 +273,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison incomplete posts": "잔여 지도 비교 불완전 글", "Leftover map comparison incomplete items": "잔여 지도 비교 불완전 기준", "Leftover map comparison reconstruction": "잔여 지도 비교 재구성", + "Leftover map comparison explained leftover share": "잔여 지도 비교 설명 잔여 비율", "Leftover map item coverage": "잔여 지도 기준 포함 범위", "Leftover map incomplete posts": "잔여 지도 불완전 글", "Leftover map incomplete items": "잔여 지도 불완전 기준", @@ -887,6 +888,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison incomplete posts": "残差地图比较不完整帖文", "Leftover map comparison incomplete items": "残差地图比较不完整准则", "Leftover map comparison reconstruction": "残差地图比较重建", + "Leftover map comparison explained leftover share": "残差地图比较已解释残差占比", "Leftover map item coverage": "残差地图准则覆盖范围", "Leftover map incomplete posts": "残差地图不完整帖文", "Leftover map incomplete items": "残差地图不完整准则", @@ -1522,6 +1524,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison incomplete posts": "残差マップの比較不完全投稿", "Leftover map comparison incomplete items": "残差マップの比較不完全基準", "Leftover map comparison reconstruction": "残差マップの比較再構成", + "Leftover map comparison explained leftover share": "残差マップの比較説明済み残差比率", "Leftover map item coverage": "残差マップの基準対象範囲", "Leftover map incomplete posts": "残差マップの不完全投稿", "Leftover map incomplete items": "残差マップの不完全基準", @@ -2136,6 +2139,7 @@ const TRANSLATIONS: Partial>> = { "Leftover map comparison incomplete posts": "Bài không đầy đủ so sánh trên bản đồ phần dư", "Leftover map comparison incomplete items": "Tiêu chí không đầy đủ so sánh trên bản đồ phần dư", "Leftover map comparison reconstruction": "Tái dựng so sánh bản đồ phần dư", + "Leftover map comparison explained leftover share": "Tỷ phần phần dư được giải thích khi so sánh bản đồ phần dư", "Leftover map item coverage": "Phạm vi tiêu chí bản đồ phần dư", "Leftover map incomplete posts": "Bài không đầy đủ trên bản đồ phần dư", "Leftover map incomplete items": "Tiêu chí không đầy đủ trên bản đồ phần dư", diff --git a/frontend/src/leftoverMapExplainedShare.ts b/frontend/src/leftoverMapExplainedShare.ts index 9f2c2215e..a1eba63cb 100644 --- a/frontend/src/leftoverMapExplainedShare.ts +++ b/frontend/src/leftoverMapExplainedShare.ts @@ -3,6 +3,9 @@ export const LEFTOVER_MAP_EXPLAINED_SHARE_ACTION = "Leftover map leaves explained leftover share {value} of raw residual after IRT main effects. Open this post to read {criterion}."; +export const LEFTOVER_MAP_COMPARE_EXPLAINED_SHARE_LABEL = + "Leftover map comparison explained leftover share"; + export function formatLeftoverMapExplainedShare( value: number | null | undefined, ): string | null { diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py index 8bd815557..82376c516 100644 --- a/lineageweave/__init__.py +++ b/lineageweave/__init__.py @@ -130,4 +130,4 @@ "serialize_lineage_analysis_result", ] -__version__ = "2.52.0" +__version__ = "2.53.0" diff --git a/pyproject.toml b/pyproject.toml index 8fc35c845..38d8858ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.52.0" +version = "2.53.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_adr_0296_governance.py b/tests/test_adr_0296_governance.py new file mode 100644 index 000000000..8e22bf9b5 --- /dev/null +++ b/tests/test_adr_0296_governance.py @@ -0,0 +1,8 @@ +from pathlib import Path +ROOT=Path(__file__).resolve().parents[1] +def test_explained_share_comparison_uses_distinct_proposed_adr_0296() -> None: + assert (ROOT/'docs/adr/0295-leftover-map-compare-reconstruction.md').exists() + text=(ROOT/'docs/adr/0296-leftover-map-compare-explained-share.md').read_text(encoding='utf-8') + assert '**Decision status:** Proposed' in text + assert '**Decision status:** Accepted' not in text + assert 'accessible name' in text and 'fast-mlsirm' in text