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

@@ -330,6 +330,17 @@ export class AnalyticsQueryDto {
days = 90;
}
export class InsightsQueryDto {
@ApiPropertyOptional({
description:
'ISO instant (e.g. your last visit): the answer also tells what changed after it',
example: '2026-10-14T08:00:00Z',
})
@IsOptional()
@IsISO8601()
since?: string;
}
export class ChangeMemberRoleDto {
@ApiProperty({ enum: ProjectRole, enumName: 'ProjectRole' })
@IsEnum(ProjectRole)
@@ -609,6 +620,25 @@ export class IssueSearchQueryDto extends PageQueryDto {
@Matches(/^\d{4}-\d{2}-\d{2}$/)
dueTo?: string;
@ApiPropertyOptional({ description: 'Only tickets nobody estimated' })
@IsOptional()
@Transform(toBoolean)
@IsBoolean()
noEstimate?: boolean;
@ApiPropertyOptional({ description: 'Only tickets with no area' })
@IsOptional()
@Transform(toBoolean)
@IsBoolean()
noArea?: boolean;
@ApiPropertyOptional({
description: 'Only tickets not updated since this instant (ISO)',
})
@IsOptional()
@IsISO8601()
updatedBefore?: string;
@ApiPropertyOptional({
type: [String],
example: ['BROWSER:Safari'],