Files
ftb-project-management/apps/web/lib/xiaobao-v22-summary.test.ts
2e595c7e72
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
refactor(data): 收口关系表运行时数据源
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读
- 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移
- 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试

Co-Authored-By: Codex GPT-5 <codex@openai.com>
2026-07-09 14:59:49 +08:00

262 lines
8.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import type { VersionWithContext } from './derive';
import type { V22XiaobaoWarningSummary } from './v22-api';
import {
buildXiaobaoRisksFromV22Summaries,
collectV22XiaobaoLatestInsights,
filterV22XiaobaoSummariesForVisibleVersions,
mergeV22XiaobaoLatestInsights,
shouldLoadXiaobaoStoreFallback,
} from './xiaobao-v22-summary';
test('buildXiaobaoRisksFromV22Summaries preserves precomputed risk details with version context', () => {
const risks = buildXiaobaoRisksFromV22Summaries({
summaries: [{
versionId: 'version-1',
riskLevel: 'likely_delayed',
riskScore: 86,
confidence: 72,
forecastReleaseDate: '2026-07-09T00:00:00.000Z',
riskSignature: 'signature-1',
recomputedAt: '2026-07-03T09:00:00.000Z',
updatedAt: '2026-07-03T09:05:00.000Z',
summary: {
delayDays: 2,
remainingWorkHours: 30,
reasons: [{
key: 'forecast_delay',
title: 'Forecast delay',
detail: 'Forecast release date moved after the expected release date.',
severity: 'danger',
count: 1,
}],
silentRisks: [{
key: 'no_activity',
title: 'No activity',
detail: 'No work activity for four days.',
itemId: 'dev-1',
itemType: 'dev_task',
}],
signals: {
unfinishedCount: 5,
openBugCount: 2,
criticalBugCount: 1,
failedTestCount: 1,
blockedCount: 0,
silentRiskCount: 1,
daysToExpectedRelease: 1,
},
dailyEvidence: {
todayDeliveries: [{ id: 'ev-1', title: 'Delivery', summary: 'Submitted core task.', occurredAt: '2026-07-03T08:00:00.000Z' }],
todayProgress: [],
todayCreations: [],
todayRisks: [],
progressNotes: [],
needsProgressItems: [],
recentActivityCount: 3,
totalActivityCount: 1,
todayActualHours: 2,
lastActivityAt: '2026-07-03T08:00:00.000Z',
},
trend: {
direction: 'up',
delta: 16,
summary: 'Risk rose by 16 points.',
pattern: 'score_delta',
},
currentSnapshot: {
date: '2026-07-03',
openBugCount: 2,
criticalBugCount: 1,
failedTestCount: 1,
blockedCount: 0,
silentRiskCount: 1,
createdAt: '2026-07-03T09:00:00.000Z',
},
},
}],
versions: [version()],
snapshots: [],
now: new Date('2026-07-03T10:00:00.000Z'),
});
assert.equal(risks.length, 1);
assert.equal(risks[0].versionName, 'Payments V1.0');
assert.equal(risks[0].productName, 'Payments');
assert.equal(risks[0].projectName, 'Wallet');
assert.equal(risks[0].expectedReleaseDate, '2026-07-07');
assert.equal(risks[0].riskLevel, 'likely_delayed');
assert.equal(risks[0].riskScore, 86);
assert.equal(risks[0].confidence, 72);
assert.equal(risks[0].confidenceLevel, 'medium');
assert.equal(risks[0].forecastReleaseDate, '2026-07-09T00:00:00.000Z');
assert.equal(risks[0].delayDays, 2);
assert.equal(risks[0].remainingWorkHours, 30);
assert.equal(risks[0].reasons[0].key, 'forecast_delay');
assert.equal(risks[0].silentRisks[0].itemType, 'dev_task');
assert.equal(risks[0].signals.openBugCount, 2);
assert.equal(risks[0].dailyEvidence?.todayDeliveries[0].summary, 'Submitted core task.');
assert.equal(risks[0].currentSnapshot.date, '2026-07-03');
assert.equal(risks[0].currentSnapshot.riskScore, 86);
assert.equal(risks[0].currentSnapshot.riskLevel, 'likely_delayed');
assert.equal(risks[0].trend.summary, 'Risk rose by 16 points.');
});
test('buildXiaobaoRisksFromV22Summaries builds compatible defaults from sparse summaries', () => {
const risks = buildXiaobaoRisksFromV22Summaries({
summaries: [{
versionId: 'version-1',
riskLevel: 'at_risk',
riskScore: 66,
confidence: 48,
forecastReleaseDate: '2026-07-09T00:00:00.000Z',
riskSignature: 'signature-1',
updatedAt: '2026-07-03T09:05:00.000Z',
summary: { reasons: 'not-an-array' },
}],
versions: [version({ expectedReleaseDate: '2026-07-07' })],
snapshots: [{
versionId: 'version-1',
date: '2026-07-01',
riskScore: 40,
riskLevel: 'attention',
openBugCount: 1,
criticalBugCount: 0,
failedTestCount: 0,
blockedCount: 0,
silentRiskCount: 0,
confidence: 80,
createdAt: '2026-07-01T09:00:00.000Z',
}],
now: new Date('2026-07-03T10:00:00.000Z'),
});
assert.equal(risks[0].delayDays, 2);
assert.equal(risks[0].remainingWorkHours, 0);
assert.equal(risks[0].confidenceLevel, 'low');
assert.equal(risks[0].reasons.length, 1);
assert.equal(risks[0].reasons[0].key, 'precomputed_risk');
assert.equal(risks[0].signals.unfinishedCount, 0);
assert.equal(risks[0].currentSnapshot.openBugCount, 0);
assert.equal(risks[0].currentSnapshot.createdAt, '2026-07-03T09:05:00.000Z');
assert.equal(risks[0].trend.direction, 'up');
});
test('shouldLoadXiaobaoStoreFallback waits for V2.2 summary before loading relation-backed stores', () => {
assert.equal(shouldLoadXiaobaoStoreFallback('idle'), false);
assert.equal(shouldLoadXiaobaoStoreFallback('loading'), false);
assert.equal(shouldLoadXiaobaoStoreFallback('ready'), false);
assert.equal(shouldLoadXiaobaoStoreFallback('empty'), true);
assert.equal(shouldLoadXiaobaoStoreFallback('failed'), true);
});
test('filterV22XiaobaoSummariesForVisibleVersions keeps only visible version summaries', () => {
const summaries = [
summary({ versionId: 'visible-1', riskScore: 80 }),
summary({ versionId: 'hidden-1', riskScore: 95 }),
summary({ versionId: 'visible-2', riskScore: 70 }),
];
const result = filterV22XiaobaoSummariesForVisibleVersions(summaries, [
version({ id: 'visible-1' }),
version({ id: 'visible-2' }),
]);
assert.deepEqual(result.map((item) => item.versionId), ['visible-1', 'visible-2']);
});
test('collectV22XiaobaoLatestInsights exposes relation-backed AI insights', () => {
const result = collectV22XiaobaoLatestInsights([
summary({
versionId: 'version-1',
latestInsight: {
versionId: 'version-1',
riskSignature: 'signature-1',
generatedAt: '2026-07-08T10:00:00.000Z',
providerInfo: { model: 'claude-test' },
insight: {
summary: '高风险版本需要处理阻塞。',
why: ['存在阻塞项'],
forecast: '预计延期 1 天。',
suggestedActions: ['先解除阻塞'],
ownerHints: ['确认阻塞负责人'],
generatedAt: '2026-07-08T10:00:00.000Z',
},
},
}),
summary({ versionId: 'version-2' }),
]);
assert.equal(result.length, 1);
assert.equal(result[0].versionId, 'version-1');
assert.equal(result[0].riskSignature, 'signature-1');
assert.equal(result[0].insight.summary, '高风险版本需要处理阻塞。');
assert.equal(result[0].providerInfo?.model, 'claude-test');
});
test('mergeV22XiaobaoLatestInsights prefers relation-backed insights over legacy cache duplicates', () => {
const relationInsight = {
versionId: 'version-1',
riskSignature: 'signature-1',
generatedAt: '2026-07-08T10:00:00.000Z',
insight: {
summary: '关系表新解读。',
why: ['new'],
forecast: 'new forecast',
suggestedActions: ['new action'],
ownerHints: ['new owner'],
generatedAt: '2026-07-08T10:00:00.000Z',
},
};
const legacyInsight = {
versionId: 'version-1',
riskSignature: 'signature-1',
generatedAt: '2026-07-08T09:00:00.000Z',
insight: {
summary: '旧缓存解读。',
why: ['old'],
forecast: 'old forecast',
suggestedActions: ['old action'],
ownerHints: ['old owner'],
generatedAt: '2026-07-08T09:00:00.000Z',
},
};
const result = mergeV22XiaobaoLatestInsights([relationInsight], [legacyInsight]);
assert.equal(result.length, 1);
assert.equal(result[0].insight.summary, '关系表新解读。');
});
function summary(patch: Partial<V22XiaobaoWarningSummary> = {}): V22XiaobaoWarningSummary {
return {
versionId: 'version-1',
riskLevel: 'at_risk',
riskScore: 60,
confidence: 80,
riskSignature: 'signature',
summary: {},
updatedAt: '2026-07-03T09:00:00.000Z',
...patch,
};
}
function version(patch: Partial<VersionWithContext> = {}): VersionWithContext {
return {
id: 'version-1',
name: 'Payments V1.0',
status: 'developing',
releaseDate: null,
createdAt: '2026-07-01T00:00:00.000Z',
productId: 'product-1',
productName: 'Payments',
projectId: 'project-1',
projectName: 'Wallet',
expectedReleaseDate: '2026-07-07',
members: [{ name: 'Alice', role: 'frontend' }],
...patch,
} as VersionWithContext;
}