fix(小宝预警): 增加快照节流和 AI cooldown
This commit is contained in:
@@ -5,9 +5,12 @@ import type { XiaobaoRiskSnapshot } from './xiaobao-risk-trend';
|
||||
import {
|
||||
buildRiskInsightSignature,
|
||||
buildRiskInterpretRequest,
|
||||
findLatestRiskInsightForVersion,
|
||||
findPreviousRiskSnapshot,
|
||||
shouldRequestRiskInsight,
|
||||
shouldRequestRiskInsightWithCooldown,
|
||||
} from './xiaobao-risk-ai';
|
||||
import type { XiaobaoRiskInsightCacheItem } from './xiaobao-risk-cache';
|
||||
|
||||
function snapshot(patch: Partial<XiaobaoRiskSnapshot> = {}): XiaobaoRiskSnapshot {
|
||||
return {
|
||||
@@ -69,6 +72,23 @@ function risk(patch: Partial<XiaobaoVersionRisk> = {}): XiaobaoVersionRisk {
|
||||
} as unknown as XiaobaoVersionRisk;
|
||||
}
|
||||
|
||||
function insight(patch: Partial<XiaobaoRiskInsightCacheItem> = {}): XiaobaoRiskInsightCacheItem {
|
||||
return {
|
||||
versionId: 'ver-1',
|
||||
riskSignature: buildRiskInsightSignature(risk({ riskLevel: 'attention', riskScore: 52 })),
|
||||
insight: {
|
||||
summary: 'Risk needs attention.',
|
||||
why: ['Risk rose.'],
|
||||
forecast: 'May slip.',
|
||||
suggestedActions: ['Confirm scope.'],
|
||||
ownerHints: ['PM'],
|
||||
generatedAt: '2026-06-29T08:00:00.000Z',
|
||||
},
|
||||
generatedAt: '2026-06-29T08:00:00.000Z',
|
||||
...patch,
|
||||
};
|
||||
}
|
||||
|
||||
test('shouldRequestRiskInsight skips on_track', () => {
|
||||
assert.equal(shouldRequestRiskInsight(risk({ riskLevel: 'on_track', riskScore: 10 }), undefined), false);
|
||||
});
|
||||
@@ -184,6 +204,45 @@ test('shouldRequestRiskInsight triggers high risk levels', () => {
|
||||
assert.equal(shouldRequestRiskInsight(risk({ riskLevel: 'blocked' }), undefined), true);
|
||||
});
|
||||
|
||||
test('findLatestRiskInsightForVersion returns latest generated insight for a version', () => {
|
||||
const latest = findLatestRiskInsightForVersion([
|
||||
insight({ versionId: 'ver-1', generatedAt: '2026-06-29T08:00:00.000Z' }),
|
||||
insight({ versionId: 'ver-1', generatedAt: '2026-06-29T10:00:00.000Z' }),
|
||||
insight({ versionId: 'ver-2', generatedAt: '2026-06-29T11:00:00.000Z' }),
|
||||
], 'ver-1');
|
||||
|
||||
assert.equal(latest?.generatedAt, '2026-06-29T10:00:00.000Z');
|
||||
});
|
||||
|
||||
test('shouldRequestRiskInsightWithCooldown skips repeated AI requests inside cooldown', () => {
|
||||
const current = risk({ riskLevel: 'at_risk', riskScore: 62 });
|
||||
const previous = snapshot({ riskScore: 40 });
|
||||
const latest = insight({
|
||||
generatedAt: '2026-06-29T10:00:00.000Z',
|
||||
riskSignature: buildRiskInsightSignature(risk({ riskLevel: 'at_risk', riskScore: 60 })),
|
||||
});
|
||||
|
||||
assert.equal(shouldRequestRiskInsightWithCooldown(current, previous, latest, new Date('2026-06-29T11:00:00.000Z')), false);
|
||||
});
|
||||
|
||||
test('shouldRequestRiskInsightWithCooldown allows AI requests after cooldown', () => {
|
||||
const current = risk({ riskLevel: 'at_risk', riskScore: 62 });
|
||||
const previous = snapshot({ riskScore: 40 });
|
||||
const latest = insight({ generatedAt: '2026-06-29T04:00:00.000Z' });
|
||||
|
||||
assert.equal(shouldRequestRiskInsightWithCooldown(current, previous, latest, new Date('2026-06-29T11:00:00.000Z')), true);
|
||||
});
|
||||
|
||||
test('shouldRequestRiskInsightWithCooldown bypasses cooldown when risk level escalates', () => {
|
||||
const previousInsight = insight({
|
||||
generatedAt: '2026-06-29T10:30:00.000Z',
|
||||
riskSignature: buildRiskInsightSignature(risk({ riskLevel: 'attention', riskScore: 52 })),
|
||||
});
|
||||
const current = risk({ riskLevel: 'blocked', riskScore: 90 });
|
||||
|
||||
assert.equal(shouldRequestRiskInsightWithCooldown(current, snapshot({ riskScore: 50 }), previousInsight, new Date('2026-06-29T11:00:00.000Z')), true);
|
||||
});
|
||||
|
||||
test('buildRiskInsightSignature uses all open bugs, not only critical bugs', () => {
|
||||
const base = buildRiskInsightSignature(risk({
|
||||
signals: { ...risk().signals, openBugCount: 1, criticalBugCount: 0 },
|
||||
|
||||
Reference in New Issue
Block a user