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

@@ -1449,6 +1449,16 @@
"example": "WEB",
"type": "string"
}
},
{
"name": "since",
"required": false,
"in": "query",
"description": "ISO instant (e.g. your last visit): the answer also tells what changed after it",
"schema": {
"example": "2026-10-14T08:00:00Z",
"type": "string"
}
}
],
"responses": {
@@ -2507,6 +2517,33 @@
"type": "string"
}
},
{
"name": "noEstimate",
"required": false,
"in": "query",
"description": "Only tickets nobody estimated",
"schema": {
"type": "boolean"
}
},
{
"name": "noArea",
"required": false,
"in": "query",
"description": "Only tickets with no area",
"schema": {
"type": "boolean"
}
},
{
"name": "updatedBefore",
"required": false,
"in": "query",
"description": "Only tickets not updated since this instant (ISO)",
"schema": {
"type": "string"
}
},
{
"name": "field",
"required": false,
@@ -12109,6 +12146,176 @@
"overLimit"
]
},
"ReleaseForecastDto": {
"type": "object",
"properties": {
"releaseId": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"targetDate": {
"type": "string",
"nullable": true,
"format": "date"
},
"total": {
"type": "number"
},
"finished": {
"type": "number"
},
"likelyDate": {
"type": "string",
"nullable": true,
"format": "date",
"description": "Half of the simulated futures finish by then"
},
"safeDate": {
"type": "string",
"nullable": true,
"format": "date",
"description": "85% finish by then"
},
"verdict": {
"type": "string",
"enum": [
"DONE",
"NO_DATE",
"UNKNOWN",
"ON_TRACK",
"AT_RISK",
"LATE"
]
}
},
"required": [
"releaseId",
"name",
"targetDate",
"total",
"finished",
"likelyDate",
"safeDate",
"verdict"
]
},
"ChangesSinceDto": {
"type": "object",
"properties": {
"since": {
"type": "string"
},
"created": {
"type": "array",
"items": {
"$ref": "#/components/schemas/InsightIssueDto"
}
},
"createdCount": {
"type": "number"
},
"finished": {
"type": "array",
"items": {
"$ref": "#/components/schemas/InsightIssueDto"
}
},
"finishedCount": {
"type": "number"
},
"returned": {
"description": "Sent back from QA",
"type": "array",
"items": {
"$ref": "#/components/schemas/InsightIssueDto"
}
},
"returnedCount": {
"type": "number"
},
"mentions": {
"type": "number",
"description": "Times you were mentioned here"
}
},
"required": [
"since",
"created",
"createdCount",
"finished",
"finishedCount",
"returned",
"returnedCount",
"mentions"
]
},
"TeamLoadDto": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/UserRefDto"
},
"openTickets": {
"type": "number"
},
"openPoints": {
"type": "number"
},
"blocked": {
"type": "number"
},
"pointsPerSprint": {
"type": "number",
"nullable": true,
"description": "Points closed per sprint, last three closed sprints"
},
"overloaded": {
"type": "boolean"
}
},
"required": [
"user",
"openTickets",
"openPoints",
"blocked",
"pointsPerSprint",
"overloaded"
]
},
"BacklogHealthDto": {
"type": "object",
"properties": {
"open": {
"type": "number"
},
"unestimated": {
"type": "number"
},
"noArea": {
"type": "number"
},
"unassigned": {
"type": "number"
},
"stale": {
"type": "number"
},
"staleDays": {
"type": "number"
}
},
"required": [
"open",
"unestimated",
"noArea",
"unassigned",
"stale",
"staleDays"
]
},
"ProjectInsightsDto": {
"type": "object",
"properties": {
@@ -12135,6 +12342,31 @@
"items": {
"$ref": "#/components/schemas/StatusTimeDto"
}
},
"releases": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ReleaseForecastDto"
}
},
"changes": {
"nullable": true,
"description": "Only when asked with `since`",
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ChangesSinceDto"
}
]
},
"team": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TeamLoadDto"
}
},
"backlog": {
"$ref": "#/components/schemas/BacklogHealthDto"
}
},
"required": [
@@ -12142,7 +12374,11 @@
"sprint",
"risks",
"aging",
"bottlenecks"
"bottlenecks",
"releases",
"changes",
"team",
"backlog"
]
},
"ActivityDto": {