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

@@ -0,0 +1,44 @@
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import {
DEFAULT_PROJECT_SETTINGS,
ProjectInsightsReadModel,
} from '@task-stream/api/tracker/domain';
import { computeInsights } from '../analytics/insights';
import { FindIssuesRepository } from '../ports/find-issues.repository';
import { FindProjectsRepository } from '../ports/find-projects.repository';
import { FindSprintsRepository } from '../ports/find-sprints.repository';
import { FindTimelineRepository } from '../ports/find-timeline.repository';
import { GetProjectInsightsQuery } from './get-project-insights.query';
import { ReadAccess } from './read-access';
@QueryHandler(GetProjectInsightsQuery)
export class GetProjectInsightsQueryHandler
implements IQueryHandler<GetProjectInsightsQuery, ProjectInsightsReadModel>
{
constructor(
private readonly access: ReadAccess,
private readonly findProjects: FindProjectsRepository,
private readonly findIssues: FindIssuesRepository,
private readonly findTimeline: FindTimelineRepository,
private readonly findSprints: FindSprintsRepository,
) {}
async execute(
query: GetProjectInsightsQuery,
): Promise<ProjectInsightsReadModel> {
await this.access.assertCanView(query.projectId, query.viewerId);
const [project, issues, timeline, activeSprint] = await Promise.all([
this.findProjects.findById(query.projectId),
this.findIssues.findByProject(query.projectId),
this.findTimeline.findByProject(query.projectId),
this.findSprints.findActive(query.projectId),
]);
return computeInsights({
issues,
timeline,
settings: project?.settings ?? DEFAULT_PROJECT_SETTINGS,
activeSprint,
now: new Date(),
});
}
}

View File

@@ -0,0 +1,13 @@
import { Query } from '@nestjs/cqrs';
import { ProjectInsightsReadModel } from '@task-stream/api/tracker/domain';
/** The dashboard's reading of a project: sprint, risks, aging, bottlenecks. */
export class GetProjectInsightsQuery extends Query<ProjectInsightsReadModel> {
constructor(
/** Null: anonymous (public projects only). */
public readonly viewerId: string | null,
public readonly projectId: string,
) {
super();
}
}

View File

@@ -6,6 +6,7 @@ export * from './get-issue.query';
export * from './get-issues-by-ids.query';
export * from './get-my-projects.query';
export * from './get-project-analytics.query';
export * from './get-project-insights.query';
export * from './get-project-stats.query';
export * from './get-release-burnup.query';
export * from './get-project.query';

View File

@@ -9,6 +9,7 @@ import { GetIssuesByIdsQueryHandler } from './get-issues-by-ids.query-handler';
import { GetMyProjectsQueryHandler } from './get-my-projects.query-handler';
import { GetProjectQueryHandler } from './get-project.query-handler';
import { GetProjectAnalyticsQueryHandler } from './get-project-analytics.query-handler';
import { GetProjectInsightsQueryHandler } from './get-project-insights.query-handler';
import { GetProjectStatsQueryHandler } from './get-project-stats.query-handler';
import { GetReleaseBurnupQueryHandler } from './get-release-burnup.query-handler';
import { GetReleaseQueryHandler } from './get-release.query-handler';
@@ -34,6 +35,7 @@ export const QUERY_HANDLERS = [
GetMyProjectsQueryHandler,
GetProjectQueryHandler,
GetProjectAnalyticsQueryHandler,
GetProjectInsightsQueryHandler,
GetProjectStatsQueryHandler,
GetReleaseBurnupQueryHandler,
GetReleaseQueryHandler,