Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,3 +544,5 @@ ADR 0295 keeps grouping-comparison reconstruction in the LineageWeave read-model
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.

ADR 0367 exposes persisted grouping-comparison leftover-map axis share only for a fully caller-visible persisted grouping. Partial visibility returns no axis aggregate; never recompute it from visible members. Missing/non-finite share omits only the badge; persisted zero and finite negative values remain explicit.

ADR 0369 adds persisted grouping-comparison `U²/R²` only as a presentation/read-model consumer on exact #828. Never derive or clamp it, never recompute it from a visible subset, and include every finite buyer-visible comparison metric in the actionable pair button accessible name.
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1097,3 +1097,5 @@ ADR 0295 adds persisted `R̂` to grouping-comparison pair buttons. The explicit
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.

ADR 0367 adds persisted `leftover_map_axes` to the grouping-comparison read model under the same whole-population authorization rule as other psychometric aggregates. The UI consumes persisted `leftover_share` through `leftoverMapCompareAxisShare`; no subset recomputation or singular-value derivation is permitted.

ADR 0369 composes persisted finite grouping-comparison `leftover_map_unexplained_share` into pair actions. The parent button accessible name and visual badge share one formatter result; the visual duplicate is `aria-hidden`. Authorization and psychometric ownership remain upstream/current-parent contracts.
5 changes: 5 additions & 0 deletions CHANGELOG.d/2.55.0-leftover-map-compare-unexplained-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 2.55.0 — Grouping comparison unexplained leftover share

- Show persisted finite `U²/R²` on grouping-comparison leftover-pair actions through `formatLeftoverMapUnexplainedShare` (ADR 0369). Zero and values above 1 remain explicit; missing/non-finite values are omitted.
- Include the localized persisted metric in the actionable button accessible name; hide the duplicate visual badge from the accessibility tree.
- Never derive, clamp, or recompute psychometric truth from caller-visible subsets. Historical ADR0295/v2.52.0 is evidence only.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to this project are documented here. Format follows

### Added

- Grouping comparison pair actions now surface persisted finite unexplained leftover share `U²/R²` under ADR 0369 / v2.55.0, including the metric in the button accessible name and never deriving it from other psychometric values.

- 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.
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,5 @@ ADR 0294 is the Proposed grouping-comparison incomplete-item presentation contra
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.

Grouping-comparison unexplained-share presentation is ADR 0369: consume persisted finite `leftover_map_unexplained_share`, never derive it, and keep its value in the actionable button accessible name.
26 changes: 26 additions & 0 deletions docs/adr/0369-leftover-map-compare-unexplained-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ADR 0369 — Persisted unexplained leftover share in grouping comparison pair actions

**Decision status:** Proposed

## Problem
The grouping-comparison read model already carries persisted `leftover_map_unexplained_share` (`s = U² / R²`) for authorized leftover pairs, but exact #828 does not surface it on comparison pair actions. Historical #829 proved the buyer-visible delta but used stale ancestry/identity and put the metric only in a child badge while the parent button supplied an explicit accessible name.

## Constraints
- Consume persisted psychometric truth; never derive `s` from `U`, `R`, reconstruction, distance, geometry, rank, coverage, or other shares.
- Missing/non-finite values are omitted. Zero and finite values above 1 remain explicit and unclamped.
- Whole-population authorization remains current-parent authority; no visible-subset recomputation.
- The actionable pair button and visible badge communicate the same finite persisted value.
- Inline KO/ZH/JA/VI copy is compatibility presentation only; #922/#929/#932 remain canonical eight-locale translation-ledger authority.

## Alternatives and decision
Hiding `s` loses persisted buyer evidence. Recomputing it duplicates psychometric authority. A child-only badge fails the actionable accessible-name contract. Therefore consume persisted finite `s`, append its localized label/value to the button accessible name, and mark the duplicate visible badge `aria-hidden`.

This is the serialized successor of exact #828 `7b6dbbb99a09d0523bce43c6872bbd9fa382a43a`, adopts the distinct historical #829 delta, and allocates v2.55.0. ADR0295/v2.52.0 is historical evidence only.

## Verification
The focused current-parent test must first RED while the parent name omits `U²/R² 0.02`, then GREEN after only the accessible-name/duplicate-badge fix. Full backend, frontend lint/test/build/Storybook, version parity, clean-tree authority, and non-force promotion are required. Hosted exact-product-head checks, browser/keyboard/focus/a11y evidence, and qualifying independent approval remain separate merge gates.

## Evidence
Gabriel, K. R. (1971). The biplot graphic display of matrices with application to principal component analysis. *Biometrika, 58*(3), 453–467.

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.
3 changes: 3 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Product & Technical Gap Baseline

> Current serialized repair (2026-09-07): #829 consumes exact promoted #828 `7b6dbbb99a09d0523bce43c6872bbd9fa382a43a` and preserves persisted `U²/R²` comparison presentation as ADR 0369 / v2.55.0. Finite values are present in the actionable button name and visible badge; missing/non-finite values are absent; values are never derived/clamped; current-parent whole-population authorization remains intact. Historical ADR0295/v2.52.0 is evidence only. Inline five-locale copy is compatibility presentation while #922/#929/#932 remain the eight-locale translation-ledger owner path.


> Current serialized repair (2026-09-07): #828 is reconstructed from exact #827 `0f9e9c8db37948041f39b4e68e4bbd808fa83752` as ADR 0367 / v2.54.0. The valid delta carries persisted grouping-comparison `leftover_map_axes` but exposes them only when the caller can see the entire persisted grouping population; partial visibility returns no aggregate and never recomputes psychometrics from the visible subset. Presentation consumes persisted `leftover_share` only, with zero and finite negative values explicit and missing/non-finite values omitted. Historical v2.50.0/ADR0293 identity is evidence only. Existing five-locale compatibility copy is preserved while the database-backed eight-locale translation-ledger authority remains #922/#929/#932; this Draft does not create a competing translation source.


Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "2.54.0",
"version": "2.55.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,7 @@ describe("App, authenticated", () => {
leftover_residual: 0.4,
leftover_map_reconstruction: 0.248,
leftover_map_explained_share: 0.76,
leftover_map_unexplained_share: 0.02,
},
],
leftover_map_coverage: {
Expand Down Expand Up @@ -4383,6 +4384,10 @@ describe("App, authenticated", () => {
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");
const unexplainedSharePair = screen.getByRole("button", {
name: /open leftover closest pair from comparison: public post.*leftover map comparison unexplained leftover share U²\/R² 0\.02/i,
});
expect(unexplainedSharePair).toHaveTextContent("U²/R² 0.02");
await waitFor(() =>
expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining("/api/reports/thread_group/2026-W02"),
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ import {
formatLeftoverMapExplainedShare,
LEFTOVER_MAP_COMPARE_EXPLAINED_SHARE_LABEL,
} from "./leftoverMapExplainedShare";
import {
formatLeftoverMapUnexplainedShare,
LEFTOVER_MAP_COMPARE_UNEXPLAINED_SHARE_LABEL,
} from "./leftoverMapUnexplainedShare";
import {
leftoverMapCompareAxisShare,
LEFTOVER_MAP_COMPARE_AXIS_SHARE,
Expand Down Expand Up @@ -4100,6 +4104,9 @@ function ReportsPanel({
const explainedShare = formatLeftoverMapExplainedShare(
pair.leftover_map_explained_share,
);
const unexplainedShare = formatLeftoverMapUnexplainedShare(
pair.leftover_map_unexplained_share,
);
Comment on lines +4107 to +4109

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Unexplained share badge never renders

Live payloads from fetch_period_comparison omit leftover_map_unexplained_share, so formatLeftoverMapUnexplainedShare always receives undefined. The new badge only appears with mocked data.

Prompt for agents
The grouping comparison endpoint must include the persisted unexplained share consumed by ReportsPanel. Update backend/app/report_ingestion.py fetch_period_comparison so its leftover-pair query selects report_leftover_pair.leftover_map_unexplained_share and its payload serializes a finite database value as a float while preserving null. Add or extend backend comparison tests to verify the field reaches GET /api/reports/compare/{period_code}; keep the existing authorization filtering unchanged.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against exact #829 0e4fd5815686120ec66203cb6848834b56bdf289: this remains a valid product finding. fetch_period_comparison selects/serializes reconstruction but omits leftover_map_unexplained_share, while the frontend mock already supplies it. I have returned dependent #830 to Draft and started repair lane 34153392153 from this exact head. The lane requires a realistic RED on the missing SELECT field, then adds only persisted-field SELECT/serialization plus a transport regression, runs the full PostgreSQL/frontend tree, and may advance the product branch only non-force if the live head is unchanged. I will not resolve this thread from predecessor/mock evidence.

const pairAccessibleName = `Open leftover ${pair.pair_kind} pair from comparison: ${pair.post_title} · ${criterion}${
reconstruction
? ` · ${t(LEFTOVER_MAP_COMPARE_RECONSTRUCTION_LABEL)} ${reconstruction}`
Expand All @@ -4108,6 +4115,10 @@ function ReportsPanel({
explainedShare
? ` · ${t(LEFTOVER_MAP_COMPARE_EXPLAINED_SHARE_LABEL)} ${explainedShare}`
: ""
}${
unexplainedShare
? ` · ${t(LEFTOVER_MAP_COMPARE_UNEXPLAINED_SHARE_LABEL)} ${unexplainedShare}`
: ""
}`;
return (
<li
Expand Down Expand Up @@ -4145,6 +4156,11 @@ function ReportsPanel({
{explainedShare}
</span>
) : null}
{unexplainedShare ? (
<span className="post-badge" aria-hidden="true">
{unexplainedShare}
</span>
) : null}
</button>
</li>
);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe("i18n", () => {
"leftover map comparison axis {axis} {share}%",
"Leftover map comparison reconstruction",
"Leftover map comparison explained leftover share",
"Leftover map comparison unexplained leftover share",
"Leftover-map graphic item coverage",
"Leftover map item coverage",
"Leftover map incomplete posts",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"leftover map comparison axis {axis} {share}%": "잔여 지도 비교 축 {axis} {share}%",
"Leftover map comparison reconstruction": "잔여 지도 비교 재구성",
"Leftover map comparison explained leftover share": "잔여 지도 비교 설명 잔여 비율",
"Leftover map comparison unexplained leftover share": "잔여 지도 비교 미설명 잔여 비율",
"Leftover map item coverage": "잔여 지도 기준 포함 범위",
"Leftover map incomplete posts": "잔여 지도 불완전 글",
"Leftover map incomplete items": "잔여 지도 불완전 기준",
Expand Down Expand Up @@ -893,6 +894,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"leftover map comparison axis {axis} {share}%": "残差地图比较轴 {axis} {share}%",
"Leftover map comparison reconstruction": "残差地图比较重建",
"Leftover map comparison explained leftover share": "残差地图比较已解释残差占比",
"Leftover map comparison unexplained leftover share": "残差地图比较未解释残差占比",
"Leftover map item coverage": "残差地图准则覆盖范围",
"Leftover map incomplete posts": "残差地图不完整帖文",
"Leftover map incomplete items": "残差地图不完整准则",
Expand Down Expand Up @@ -1531,6 +1533,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"leftover map comparison axis {axis} {share}%": "残差マップの比較軸 {axis} {share}%",
"Leftover map comparison reconstruction": "残差マップの比較再構成",
"Leftover map comparison explained leftover share": "残差マップの比較説明済み残差比率",
"Leftover map comparison unexplained leftover share": "残差マップの比較未説明残差比率",
"Leftover map item coverage": "残差マップの基準対象範囲",
"Leftover map incomplete posts": "残差マップの不完全投稿",
"Leftover map incomplete items": "残差マップの不完全基準",
Expand Down Expand Up @@ -2148,6 +2151,7 @@ const TRANSLATIONS: Partial<Record<Locale, Record<string, string>>> = {
"leftover map comparison axis {axis} {share}%": "trục so sánh bản đồ phần dư {axis} {share}%",
"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 comparison unexplained leftover share": "Tỷ phần phần dư chưa đượ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ư",
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/leftoverMapUnexplainedShare.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, expect, it } from "vitest";
import { formatLeftoverMapUnexplainedShare } from "./leftoverMapUnexplainedShare";
import {
formatLeftoverMapUnexplainedShare,
LEFTOVER_MAP_COMPARE_UNEXPLAINED_SHARE_LABEL,
} from "./leftoverMapUnexplainedShare";

describe("formatLeftoverMapUnexplainedShare", () => {
it("names leftover-map unexplained leftover share without inventing a leftover score", () => {
Expand All @@ -15,4 +18,10 @@ describe("formatLeftoverMapUnexplainedShare", () => {
expect(formatLeftoverMapUnexplainedShare(Number.POSITIVE_INFINITY)).toBeNull();
expect(formatLeftoverMapUnexplainedShare(Number.NEGATIVE_INFINITY)).toBeNull();
});

it("keeps the grouping comparison unexplained leftover share label distinct", () => {
expect(LEFTOVER_MAP_COMPARE_UNEXPLAINED_SHARE_LABEL).toBe(
"Leftover map comparison unexplained leftover share",
);
});
});
3 changes: 3 additions & 0 deletions frontend/src/leftoverMapUnexplainedShare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
export const LEFTOVER_MAP_UNEXPLAINED_SHARE_ACTION =
"Leftover map leaves unexplained leftover share {value} of raw residual after IRT main effects. Open this post to read {criterion}.";

export const LEFTOVER_MAP_COMPARE_UNEXPLAINED_SHARE_LABEL =
"Leftover map comparison unexplained leftover share";

export function formatLeftoverMapUnexplainedShare(
value: number | null | undefined,
): string | null {
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@
"serialize_lineage_analysis_result",
]

__version__ = "2.54.0"
__version__ = "2.55.0"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
version = "2.54.0"
version = "2.55.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" }
Expand Down