Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.d/1.1.0-analysis-run-opened-report-members.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 1.1.0 Land Demo Corp members under the opened-report next action

Open period report 2026-W02 puts Demo Corp mean θ and member posts
immediately under the next action, ahead of Other Corp. Mean θ stays
on the report panel.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ 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).

## [1.1.0] - 2026-08-17

### Added

- Opening **Open period report 2026-W02** now puts the Demo Corp
report (mean θ and member posts) immediately under the named next
action, ahead of Other Corp and the week strip. The Public post
member stays clickable. Mean θ stays on the report panel. No TEPP
theta is invented. No cutoff body is invented (ADR 0016).

## [1.0.0] - 2026-08-17

### Added
Expand Down
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ then **Open period report 2026-W02**. The home week is already
grouping is Corporate entity and Demo Corp is current. The focused
chip name contains `Corporate entity: Demo Corp` and the persisted
mean θ. The period-report panel says Demo Corp is the opened grouping
and to read its mean θ and member posts, then open a post. Changing
the week first still focuses the report period field. Mean θ stays on
the period-report panel.
and to read its mean θ and member posts, then open a post. Those
members land immediately under that next action, ahead of Other Corp
and the week strip. Changing the week first still focuses the report
period field. Mean θ stays on the period-report panel.
7 changes: 4 additions & 3 deletions docs/adr/0024-seed-period-report-analysis-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ current, using the persisted scope grouping key. When the operator is
already on that week, the comparison strip lands on Demo Corp. The
focused chip name contains the visible Corporate entity caption and
the persisted mean θ. The period-report panel names the next action:
read that grouping's mean θ and member posts, then open a post. Mean θ
remains on the period-report panel. Re-seed is idempotent on
`demo-report-seed-2026-w02`.
read that grouping's mean θ and member posts, then open a post. Those
members land immediately under that next action, ahead of other
groupings and the week strip. Mean θ remains on the period-report
panel. Re-seed is idempotent on `demo-report-seed-2026-w02`.

## References — APA 7th

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": "1.0.0",
"version": "1.1.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
30 changes: 29 additions & 1 deletion frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import App from "./App";
Expand Down Expand Up @@ -2191,6 +2191,16 @@ describe("App, authenticated", () => {
);
expect(await screen.findByText(/Demo Corp: mean θ 0\.42/)).toBeInTheDocument();
expect(screen.queryByText(/corp-1: mean θ/)).not.toBeInTheDocument();
const openedReport = screen.getByRole("list", { name: "Opened grouping report" });
expect(openedReport.textContent ?? "").toMatch(/Demo Corp: mean θ 0\.42[\s\S]*Other Corp: mean θ/);
expect(
within(openedReport).getByRole("button", { name: /open report post: public post/i }),
).toBeInTheDocument();
const status = screen.getByRole("status");
const demoMean = screen.getByText(/Demo Corp: mean θ 0\.42/);
const weekChip = screen.getByRole("button", { name: /open report period 2026-W03/i });
expect(status.compareDocumentPosition(demoMean) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0);
expect(demoMean.compareDocumentPosition(weekChip) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0);
});

it("lands the comparison strip on Demo Corp when already on that week", async () => {
Expand Down Expand Up @@ -2227,6 +2237,24 @@ describe("App, authenticated", () => {
"Demo Corp is the opened grouping. Read its mean θ and member posts below, then open a post.",
);
expect(await screen.findByText(/Demo Corp: mean θ 0\.42/)).toBeInTheDocument();
const openedReport = screen.getByRole("list", { name: "Opened grouping report" });
expect(within(openedReport).getByText(/Demo Corp: mean θ 0\.42/).closest("li")).toHaveAttribute(
"aria-current",
"true",
);
expect(openedReport.textContent ?? "").toMatch(/Demo Corp: mean θ 0\.42[\s\S]*Other Corp: mean θ/);
expect(openedReport.textContent ?? "").not.toMatch(/Other Corp: mean θ[\s\S]*Demo Corp: mean θ 0\.42/);
const member = within(openedReport).getByRole("button", {
name: /open report post: public post/i,
});
expect(member).toHaveTextContent("θ 0.91");
const status = screen.getByRole("status");
const demoMean = screen.getByText(/Demo Corp: mean θ 0\.42/);
const weekChip = screen.getByRole("button", { name: /open report period 2026-W03/i });
expect(status.compareDocumentPosition(demoMean) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0);
expect(demoMean.compareDocumentPosition(weekChip) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0);
await userEvent.click(member);
await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument());
} finally {
HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
}
Expand Down
162 changes: 93 additions & 69 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,18 @@ function openedReportNextAction(groupingLabel: string): string {
);
}

function openedReportsFirst<T extends { grouping_key: string; grouping_label?: string }>(
reports: T[],
isOpened: (groupingKey: string, groupingLabel?: string) => boolean,
): T[] {
const opened = reports.filter((report) => isOpened(report.grouping_key, report.grouping_label));
if (opened.length === 0) {
return reports;
}
const rest = reports.filter((report) => !isOpened(report.grouping_key, report.grouping_label));
return [...opened, ...rest];
}

function ReportsPanel({
accessToken,
canRebuild,
Expand Down Expand Up @@ -2205,6 +2217,85 @@ function ReportsPanel({
}
}

const orderedReports = payload
? openedReportsFirst(payload.reports, (groupingKey, groupingLabel) =>
groupingIsOpened(grouping, groupingKey, groupingLabel),
)
: [];
const reportList =
payload === null && !error ? (
<p>Loading reports...</p>
) : payload && payload.reports.length === 0 ? (
<p className="popup-placeholder">
No calibrated report for this grouping and period. Evaluate posts, then rebuild.
</p>
) : payload && payload.reports.length > 0 ? (
<ul
className="ticket-list"
aria-label={openedGroupingLabel ? "Opened grouping report" : "Period report groups"}
>
{orderedReports.map((report) => (
<li
key={report.grouping_key}
className="ticket-list-item"
aria-current={
groupingIsOpened(grouping, report.grouping_key, report.grouping_label)
? "true"
: undefined
}
>
<span className="ticket-title">
{report.grouping_label ?? report.grouping_key}: mean θ {report.mean_theta.toFixed(2)} ({report.selected_model}
{report.fit_converged ? ", converged" : ", not converged"})
</span>
<span className="post-badge">{report.post_count} posts</span>
{report.link_method === "fipc" && report.anchor_period_code && report.delta_mean_theta != null && (
<span className="post-badge">
vs {report.anchor_period_code}: {report.delta_mean_theta >= 0 ? "+" : ""}
{report.delta_mean_theta.toFixed(2)}
</span>
)}
{report.link_method === "fipc" && report.delta_mean_theta == null && (
<span className="post-badge">shared metric</span>
)}
{report.selected_items?.[0] && (
<span className="post-badge">
CAT: {criterionShortLabel(report.selected_items[0].item_code)} I=
{report.selected_items[0].information.toFixed(2)}
</span>
)}
{report.members.length > 0 && (
<ul className="ticket-list">
{report.members.map((member) => (
<li key={member.post_id} className="ticket-list-item">
<button
className="post-list-item"
aria-label={`Open report post: ${member.post_title}`}
onClick={() => onSelectPost(member.post_id)}
>
<span className="ticket-title">{member.post_title}</span>
<span className="post-badge">θ {member.theta_eap.toFixed(2)}</span>
{member.ticket_title && (
<span className="post-badge">{member.ticket_title}</span>
)}
{(member.ticket_status_label ?? member.ticket_status_code) && (
<span className="post-badge">
{member.ticket_status_label ?? member.ticket_status_code}
</span>
)}
{member.ticket_due_date && (
<span className="post-badge">due {member.ticket_due_date}</span>
)}
</button>
</li>
))}
</ul>
)}
</li>
))}
</ul>
) : null;

return (
<section className="popup-section lineage-home">
<div className="lineage-home-header">
Expand Down Expand Up @@ -2275,6 +2366,7 @@ function ReportsPanel({
{openedReportNextAction(openedGroupingLabel)}
</p>
)}
{openedGroupingLabel && reportList}
{index && index.periods.length > 0 && (
<ul className="ticket-list">
{index.periods.map((row) => (
Expand Down Expand Up @@ -2306,75 +2398,7 @@ function ReportsPanel({
</ul>
)}
{error && <p className="error">{error}</p>}
{payload === null && !error && <p>Loading reports...</p>}
{payload && payload.reports.length === 0 && (
<p className="popup-placeholder">
No calibrated report for this grouping and period. Evaluate posts, then rebuild.
</p>
)}
{payload && payload.reports.length > 0 && (
<ul className="ticket-list">
{payload.reports.map((report) => (
<li
key={report.grouping_key}
className="ticket-list-item"
aria-current={
groupingIsOpened(grouping, report.grouping_key, report.grouping_label)
? "true"
: undefined
}
>
<span className="ticket-title">
{report.grouping_label ?? report.grouping_key}: mean θ {report.mean_theta.toFixed(2)} ({report.selected_model}
{report.fit_converged ? ", converged" : ", not converged"})
</span>
<span className="post-badge">{report.post_count} posts</span>
{report.link_method === "fipc" && report.anchor_period_code && report.delta_mean_theta != null && (
<span className="post-badge">
vs {report.anchor_period_code}: {report.delta_mean_theta >= 0 ? "+" : ""}
{report.delta_mean_theta.toFixed(2)}
</span>
)}
{report.link_method === "fipc" && report.delta_mean_theta == null && (
<span className="post-badge">shared metric</span>
)}
{report.selected_items?.[0] && (
<span className="post-badge">
CAT: {criterionShortLabel(report.selected_items[0].item_code)} I=
{report.selected_items[0].information.toFixed(2)}
</span>
)}
{report.members.length > 0 && (
<ul className="ticket-list">
{report.members.map((member) => (
<li key={member.post_id} className="ticket-list-item">
<button
className="post-list-item"
aria-label={`Open report post: ${member.post_title}`}
onClick={() => onSelectPost(member.post_id)}
>
<span className="ticket-title">{member.post_title}</span>
<span className="post-badge">θ {member.theta_eap.toFixed(2)}</span>
{member.ticket_title && (
<span className="post-badge">{member.ticket_title}</span>
)}
{(member.ticket_status_label ?? member.ticket_status_code) && (
<span className="post-badge">
{member.ticket_status_label ?? member.ticket_status_code}
</span>
)}
{member.ticket_due_date && (
<span className="post-badge">due {member.ticket_due_date}</span>
)}
</button>
</li>
))}
</ul>
)}
</li>
))}
</ul>
)}
{!openedGroupingLabel && reportList}
</section>
);
}
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@
"sentence_excerpts",
]

__version__ = "1.0.0"
__version__ = "1.1.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 = "1.0.0"
version = "1.1.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
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.