Files
ftb-project-management/apps/web/lib/xiaobao-risk-suggestion.test.ts
Script Generator eef3c8f000 feat(版本): 优化概览与只读状态
关键改动:

- 增加需求排序和版本只读状态规则及测试

- 完善版本概览阶段耗时、项目页和工作台展示

- 优化小宝预警请求节流、建议状态和风险过滤

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-06-30 18:18:18 +08:00

175 lines
5.7 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { buildRiskInsightSignature } from './xiaobao-risk-ai';
import type { XiaobaoRiskInsightCacheItem } from './xiaobao-risk-cache';
import type { XiaobaoVersionRisk } from './xiaobao-risk';
import { attachXiaobaoRiskSuggestion, buildXiaobaoRiskInsightPendingKey } from './xiaobao-risk-suggestion';
function risk(patch: Partial<XiaobaoVersionRisk> = {}): XiaobaoVersionRisk {
return {
versionId: 'ver-1',
versionName: 'V1.0',
productId: 'prod-1',
productName: 'Product A',
projectId: 'proj-1',
projectName: 'Project A',
riskScore: 68,
riskLevel: 'attention',
expectedReleaseDate: '2026-07-05',
confidence: 80,
confidenceLevel: 'high',
forecastReleaseDate: '2026-07-05T10:00:00.000Z',
delayDays: 0,
remainingWorkHours: 8,
reasons: [
{
key: 'remaining_work',
title: '剩余工作量',
detail: '预计还剩 8h 工作量。',
severity: 'warning',
},
],
silentRisks: [],
signals: {
unfinishedCount: 1,
openBugCount: 0,
criticalBugCount: 0,
failedTestCount: 0,
blockedCount: 0,
silentRiskCount: 0,
daysToExpectedRelease: 2,
},
currentSnapshot: {
versionId: 'ver-1',
date: '2026-06-30',
riskScore: 68,
riskLevel: 'attention',
forecastReleaseDate: '2026-07-05T10:00:00.000Z',
openBugCount: 0,
criticalBugCount: 0,
failedTestCount: 0,
blockedCount: 0,
silentRiskCount: 0,
confidence: 80,
createdAt: '2026-06-30T10:00:00.000Z',
},
trend: { direction: 'flat', delta: 0, summary: '风险保持稳定。', pattern: 'stable' },
...patch,
};
}
function insight(itemRisk: XiaobaoVersionRisk, patch: Partial<XiaobaoRiskInsightCacheItem> = {}): XiaobaoRiskInsightCacheItem {
return {
versionId: itemRisk.versionId,
riskSignature: buildRiskInsightSignature(itemRisk),
insight: {
summary: '旧的小宝建议',
why: ['仍有工作未完成'],
forecast: '当前建议继续观察。',
suggestedActions: ['确认剩余任务负责人和完成时间。'],
ownerHints: [],
generatedAt: '2026-06-30T09:00:00.000Z',
},
generatedAt: '2026-06-30T09:00:00.000Z',
...patch,
};
}
test('attachXiaobaoRiskSuggestion provides a rule suggestion when AI cache is empty', () => {
const result = attachXiaobaoRiskSuggestion(risk(), {
insights: [],
pendingInsightKeys: [],
});
assert.equal(result.aiInsightSource, 'rule');
assert.equal(result.aiInsightUpdating, false);
assert.ok(result.aiInsight?.summary);
assert.ok(result.aiInsight?.forecast);
assert.ok((result.aiInsight?.suggestedActions.length ?? 0) > 0);
});
test('attachXiaobaoRiskSuggestion keeps previous AI suggestion while a new one is updating', () => {
const previous = risk({ riskScore: 52, riskLevel: 'attention' });
const current = risk({ riskScore: 82, riskLevel: 'at_risk', delayDays: 1 });
const pendingKey = buildXiaobaoRiskInsightPendingKey(current);
const result = attachXiaobaoRiskSuggestion(current, {
insights: [insight(previous)],
pendingInsightKeys: [pendingKey],
});
assert.equal(result.aiInsightSource, 'previous_ai');
assert.equal(result.aiInsightUpdating, true);
assert.equal(result.aiInsight?.summary, '旧的小宝建议');
});
test('attachXiaobaoRiskSuggestion uses the current AI suggestion when the signature matches', () => {
const current = risk({ riskScore: 82, riskLevel: 'at_risk', delayDays: 1 });
const result = attachXiaobaoRiskSuggestion(current, {
insights: [insight(current, {
insight: {
summary: '新的小宝建议',
why: ['风险已升高'],
forecast: '预计延期 1 天。',
suggestedActions: ['先处理阻塞和失败用例。'],
ownerHints: [],
generatedAt: '2026-06-30T10:00:00.000Z',
},
generatedAt: '2026-06-30T10:00:00.000Z',
})],
pendingInsightKeys: [],
});
assert.equal(result.aiInsightSource, 'current_ai');
assert.equal(result.aiInsightUpdating, false);
assert.equal(result.aiInsight?.summary, '新的小宝建议');
});
test('buildXiaobaoRiskInsightPendingKey stays stable for refresh-only risk drift', () => {
const before = risk({
riskLevel: 'likely_delayed',
riskScore: 100,
confidence: 65,
forecastReleaseDate: '2026-07-14T03:37:15.903Z',
signals: { ...risk().signals, failedTestCount: 14, silentRiskCount: 58, daysToExpectedRelease: 1 },
dailyEvidence: {
todayDeliveries: [],
todayProgress: [
{ id: 'ev-2', title: 'Progress', summary: 'Fixed login issue.', occurredAt: '2026-06-29T03:37:15.903Z' },
],
todayCreations: [],
todayRisks: [],
progressNotes: [],
needsProgressItems: [],
recentActivityCount: 12,
totalActivityCount: 6,
todayActualHours: 1.5,
lastActivityAt: '2026-06-29T03:37:15.903Z',
},
});
const after = risk({
riskLevel: 'likely_delayed',
riskScore: 100,
confidence: 65,
forecastReleaseDate: '2026-07-15T06:21:38.597Z',
signals: { ...risk().signals, failedTestCount: 14, silentRiskCount: 58, daysToExpectedRelease: 0 },
dailyEvidence: {
todayDeliveries: [],
todayProgress: [
{ id: 'ev-2', title: 'Progress', summary: 'Fixed login issue.', occurredAt: '2026-06-29T06:21:38.597Z' },
],
todayCreations: [],
todayRisks: [],
progressNotes: [],
needsProgressItems: [],
recentActivityCount: 13,
totalActivityCount: 6,
todayActualHours: 3,
lastActivityAt: '2026-06-29T06:21:38.597Z',
},
});
assert.equal(buildXiaobaoRiskInsightPendingKey(before), buildXiaobaoRiskInsightPendingKey(after));
});