diff --git a/AGENTS.md b/AGENTS.md index c790995c1..82f16152e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,12 +75,13 @@ in the same spirit) -- never against real data, per the hard rule above. against a live local stack (`make up`) and self-skip without one -- see [README.md](README.md#local-product-stack-docker-compose). -Period leftover pairs (ADR 0017 / 0018) are computed in +Period leftover pairs (ADR 0017 / 0018 / 0028) are computed in `lineageweave/leftover_pairs.py` from the residual after a real GRM/GPCM score, never invented. Missing cells stay out of the Gabriel factorization. Closest and farthest post–criterion pairs persist to `report_leftover_pair` and sit above the member list so -a click opens that post. +a click opens that post. A leftover member also names the leftover +criterion next to θ. `frontend/` has its own toolchain (Node pinned via `frontend/mise.toml`, pnpm via Corepack -- do not add a second Node package manager or a diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index f8a83ceb1..308fd8616 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -524,7 +524,8 @@ bank as the dummy high/low band rows, so comparison-strip click through opens those DAG posts. Report members include the earliest open ticket title, status lookup label, and due date when one exists. The home page renders the actual mean θ, the FIPC delta, the CAT-selected item, leftover -closest/farthest pairs above the member list, and the +closest/farthest pairs above the member list, leftover criterion +badges on matching members, and the PU / corp / thread comparison -- never a placeholder. TEPP is unchanged. ## Phase 6b: Knowledge Graph as a real Ontology + Semantic Layer diff --git a/CHANGELOG.d/0.87.0-leftover-member-badge.md b/CHANGELOG.d/0.87.0-leftover-member-badge.md new file mode 100644 index 000000000..65301a644 --- /dev/null +++ b/CHANGELOG.d/0.87.0-leftover-member-badge.md @@ -0,0 +1,7 @@ +# 0.87.0 — Leftover badge on the matching report member + +## Added + +- Report members that are leftover pairs name the leftover criterion + (ADR 0028). After `make seed`, the leftover member reads + **Closest leftover · sales-lead** next to θ; click opens that post. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bfcaa28f..f9a64908d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.87.0] - 2026-08-17 + +### Added + +- Report members that are leftover pairs name the leftover criterion + (ADR 0028). After `make seed`, the leftover member reads + **Closest leftover · sales-lead** next to θ; click opens that post. + ## [0.75.0] - 2026-08-17 ### Added diff --git a/docs/adr/0028-leftover-pair-on-report-member.md b/docs/adr/0028-leftover-pair-on-report-member.md new file mode 100644 index 000000000..e121c05cd --- /dev/null +++ b/docs/adr/0028-leftover-pair-on-report-member.md @@ -0,0 +1,43 @@ +# ADR 0028 — Name leftover on the matching report member + +**Decision status:** Accepted +**Date:** 2026-08-17 + +## Context + +ADR 0018 puts leftover pairs above the member list. A buyer who scans +members first still sees only θ and the ticket. The leftover post is +already in that list; it just does not name the leftover criterion. + +Do not invent a second leftover store. Do not invent a theta. + +## Decision + +When an authorized leftover pair names a member post, that member +button shows `Closest leftover · {criterion}` or +`Farthest leftover · {criterion}` and includes the same caption in +its accessible name. + +A member that is not a leftover pair stays unmarked. A leftover pair +for a hidden post never reaches the member list (ADR 0017 ABAC). + +After `make seed`, the leftover member reads **Closest leftover · +sales-lead** next to θ; click still opens that post. + +## Consequences + +Leftover buttons above the list stay. This slice only labels the +already-visible member. Comparison-strip leftover is ADR 0025 / #233. +Opened-post leftover copy is #224. + +## Related + +Depends on [ADR 0017](0017-persist-lsirm-leftover-pairs.md) and +[ADR 0018](0018-leftover-pair-report-ui.md). + +## 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 diff --git a/frontend/package.json b/frontend/package.json index 575b7c586..c93670d28 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.75.0", + "version": "0.87.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index a32a26403..9882bff7a 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1365,7 +1365,14 @@ describe("App, authenticated", () => { ); expect(farthestPair).toHaveTextContent("d 1.84"); const memberButton = screen.getByRole("button", { name: /open report post: public post/i }); + expect(memberButton).toHaveTextContent("Closest leftover · sales-lead"); + expect(memberButton).toHaveAccessibleName( + "Open report post: Public post (Closest leftover · sales-lead)", + ); expect(closestPair.compareDocumentPosition(memberButton) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect( + screen.getByRole("button", { name: /open report post: specification revision requested/i }), + ).toHaveTextContent("Farthest leftover · negative"); expect( screen.getByRole("button", { name: /open report post: specification revision requested/i }), ).toHaveTextContent("Send Westfield Power the revised specification"); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6056e5eb4..817270fbf 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1620,15 +1620,42 @@ function ReportsPanel({ )} {report.members.length > 0 && (