feat: dashboard reads the sprint, the risks, aging and bottlenecks

A new projectInsights reading (GraphQL Project.insights, REST
/projects/{project}/insights, MCP get_project_insights), computed by pure
functions over the read model and the timeline:
- the sprint under way: days left, points done, pace against what is
  needed and whether it gets there;
- tickets at risk: overdue, due within a week, blocked by an open ticket
  and stuck (7 days or more in an in-progress or QA status);
- aging of the work under way against the p85 cycle time;
- average days in each in-progress and QA status, with WIP limits.
The dashboard shows them in four new panels, reloaded on every change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 11:55:52 +02:00
parent 86e313efaa
commit fe03c5bda8
41 changed files with 2774 additions and 20 deletions

View File

@@ -11,6 +11,7 @@ import {
import {
IssuePriority,
ProjectAnalyticsReadModel,
ProjectInsightsReadModel,
ProjectReadModel,
ProjectRole,
ProjectSettings,
@@ -34,6 +35,7 @@ import { IssueFilter } from './ports/find-issues.repository';
import { BoardColumn, GetBoardQuery } from './queries/get-board.query';
import { GetMyProjectsQuery } from './queries/get-my-projects.query';
import { GetProjectAnalyticsQuery } from './queries/get-project-analytics.query';
import { GetProjectInsightsQuery } from './queries/get-project-insights.query';
import { GetProjectStatsQuery } from './queries/get-project-stats.query';
import { GetPublicProjectsQuery } from './queries/get-public-projects.query';
import { GetProjectQuery } from './queries/get-project.query';
@@ -321,6 +323,16 @@ export class ProjectsService {
}
/** Statistics over the last `days` days (created vs resolved, flow, times, QA). */
/** Sprint under way, risks, aging and bottlenecks, for the dashboard. */
insights(
viewerId: string | null,
projectId: string,
): Promise<ProjectInsightsReadModel> {
return this.queryBus.execute(
new GetProjectInsightsQuery(viewerId, projectId),
);
}
analytics(
viewerId: string | null,
projectId: string,