feat: dashboard forecasts releases, tells what changed and weighs the load

The insights reading grows (GraphQL insights(since:), REST ?since=, MCP):
- planned releases with a likely (p50) and safe (p85) finish date from a
  Monte Carlo over the last six weeks of finished work, one after another,
  and a verdict against their target;
- with `since` (the viewer's last visit, kept in the browser): tickets
  created, finished and sent back from QA, and their mentions;
- each person's open points against what they closed per sprint lately;
- open tickets without estimate, area or assignee, or untouched for 90
  days, each count opening the list filtered (new noEstimate, noArea and
  updatedBefore filters).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 12:50:24 +02:00
parent fe03c5bda8
commit 0d158a2ed7
41 changed files with 2050 additions and 118 deletions

View File

@@ -2179,12 +2179,67 @@ export interface components {
wipLimit: number | null;
overLimit: boolean;
};
ReleaseForecastDto: {
/** Format: uuid */
releaseId: string;
name: string;
/** Format: date */
targetDate: string | null;
total: number;
finished: number;
/**
* Format: date
* @description Half of the simulated futures finish by then
*/
likelyDate: string | null;
/**
* Format: date
* @description 85% finish by then
*/
safeDate: string | null;
/** @enum {string} */
verdict: "DONE" | "NO_DATE" | "UNKNOWN" | "ON_TRACK" | "AT_RISK" | "LATE";
};
ChangesSinceDto: {
since: string;
created: components["schemas"]["InsightIssueDto"][];
createdCount: number;
finished: components["schemas"]["InsightIssueDto"][];
finishedCount: number;
/** @description Sent back from QA */
returned: components["schemas"]["InsightIssueDto"][];
returnedCount: number;
/** @description Times you were mentioned here */
mentions: number;
};
TeamLoadDto: {
user: components["schemas"]["UserRefDto"];
openTickets: number;
openPoints: number;
blocked: number;
/** @description Points closed per sprint, last three closed sprints */
pointsPerSprint: number | null;
overloaded: boolean;
};
BacklogHealthDto: {
open: number;
unestimated: number;
noArea: number;
unassigned: number;
stale: number;
staleDays: number;
};
ProjectInsightsDto: {
generatedAt: string;
sprint: components["schemas"]["SprintPulseDto"] | null;
risks: components["schemas"]["RisksDto"];
aging: components["schemas"]["AgingDto"];
bottlenecks: components["schemas"]["StatusTimeDto"][];
releases: components["schemas"]["ReleaseForecastDto"][];
/** @description Only when asked with `since` */
changes: components["schemas"]["ChangesSinceDto"] | null;
team: components["schemas"]["TeamLoadDto"][];
backlog: components["schemas"]["BacklogHealthDto"];
};
ActivityDto: {
id: string;
@@ -3970,7 +4025,10 @@ export interface operations {
};
ProjectsController_insights: {
parameters: {
query?: never;
query?: {
/** @description ISO instant (e.g. your last visit): the answer also tells what changed after it */
since?: string;
};
header?: never;
path: {
/** @description Project id or key */
@@ -4579,6 +4637,12 @@ export interface operations {
dueFrom?: string;
/** @description Due on or before this day */
dueTo?: string;
/** @description Only tickets nobody estimated */
noEstimate?: boolean;
/** @description Only tickets with no area */
noArea?: boolean;
/** @description Only tickets not updated since this instant (ISO) */
updatedBefore?: string;
/** @description Custom values the tickets must hold, as FIELD:value. Repeat the parameter for several */
field?: string[];
/** @description RECENT (default): last updated first. RANK: backlog order. */