From 585316d1cf5e0da4cc3d840f6680e6f1a1a857cf Mon Sep 17 00:00:00 2001 From: Script Generator Date: Mon, 29 Jun 2026 17:49:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=B0=8F=E5=AE=9D=E9=A2=84=E8=AD=A6):=20?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E9=9D=99=E9=BB=98=E9=A3=8E=E9=99=A9=E8=AF=81?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/lib/xiaobao-risk.test.ts | 52 +++++++++++++++++++++++++++++++ apps/web/lib/xiaobao-risk.ts | 17 ++++++++-- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/apps/web/lib/xiaobao-risk.test.ts b/apps/web/lib/xiaobao-risk.test.ts index 78d9dfe..51f4e38 100644 --- a/apps/web/lib/xiaobao-risk.test.ts +++ b/apps/web/lib/xiaobao-risk.test.ts @@ -128,3 +128,55 @@ test('calcXiaobaoVersionRisk preserves daily evidence on the returned risk objec assert.equal(risk.dailyEvidence, dailyEvidence); }); + +test('calcXiaobaoVersionRisk uses silent risks from daily evidence', () => { + const silentRisk = { + key: 'no_progress_today', + title: '今日无进展', + detail: '进行中的任务今天没有动态。', + itemId: 'dev-1', + itemType: 'dev_task' as const, + }; + const dailyEvidence: VersionDailyEvidence = { + todayDeliveries: [], + todayProgress: [], + todayRisks: [], + progressNotes: [], + needsProgressItems: [], + recentActivityCount: 0, + silentRisks: [silentRisk], + }; + + const risk = calcXiaobaoVersionRisk({ + version: version(), + devTasks: [], + testCases: [], + bugs: [], + dailyEvidence, + now: new Date('2026-07-02T01:00:00.000Z'), + }); + + assert.deepEqual(risk.silentRisks, [silentRisk]); + assert.equal(risk.signals.silentRiskCount, 1); + assert.ok(risk.riskScore > 0); + assert.ok(risk.reasons.some((reason: { key: string }) => reason.key === 'silent_risk')); +}); + +test('calcXiaobaoVersionRisk preserves null expected release date as a compatible output value', () => { + const versionWithNullRelease = { + id: 'ver-null-release', + name: 'V1.1', + expectedReleaseDate: null, + members: [], + }; + + const risk = calcXiaobaoVersionRisk({ + version: versionWithNullRelease, + devTasks: [], + testCases: [], + bugs: [], + now: new Date('2026-07-02T01:00:00.000Z'), + }); + + assert.equal(risk.expectedReleaseDate, null); +}); diff --git a/apps/web/lib/xiaobao-risk.ts b/apps/web/lib/xiaobao-risk.ts index 0b151e6..b676c64 100644 --- a/apps/web/lib/xiaobao-risk.ts +++ b/apps/web/lib/xiaobao-risk.ts @@ -15,7 +15,7 @@ export interface XiaobaoVersionRef { status?: string; productName?: string; projectName?: string; - expectedReleaseDate?: string; + expectedReleaseDate?: string | null; members?: Array<{ id?: string; name: string; role?: string }>; } @@ -49,6 +49,7 @@ export interface XiaobaoVersionRisk { versionId: string; riskScore: number; riskLevel: XiaobaoRiskLevel; + expectedReleaseDate: string | null; confidence: number; confidenceLevel: XiaobaoConfidenceLevel; forecastReleaseDate?: string; @@ -86,7 +87,7 @@ export function calcXiaobaoVersionRisk(input: CalcXiaobaoVersionRiskInput): Xiao const devTasks = input.devTasks ?? []; const testCases = input.testCases ?? []; const bugs = input.bugs ?? []; - const silentRisks = input.silentRisks ?? []; + const silentRisks = input.dailyEvidence?.silentRisks ?? []; const remainingDevHours = devTasks.reduce((sum, task) => { const estimate = getEstimateHours(task); @@ -164,6 +165,15 @@ export function calcXiaobaoVersionRisk(input: CalcXiaobaoVersionRiskInput): Xiao count: failedTestCount, }); } + if (silentRisks.length > 0) { + reasons.push({ + key: 'silent_risk', + title: '静默风险', + detail: `发现 ${silentRisks.length} 个今日缺少进展或证据的风险项。`, + severity: 'warning', + count: silentRisks.length, + }); + } const signals: XiaobaoRiskSignals = { unfinishedCount, @@ -193,6 +203,7 @@ export function calcXiaobaoVersionRisk(input: CalcXiaobaoVersionRiskInput): Xiao versionId: input.version.id, riskScore, riskLevel, + expectedReleaseDate: input.version.expectedReleaseDate ?? null, confidence, confidenceLevel: getConfidenceLevel(confidence), forecastReleaseDate, @@ -297,7 +308,7 @@ function getConfidenceLevel(confidence: number): XiaobaoConfidenceLevel { return 'low'; } -function parseDate(iso?: string): Date | undefined { +function parseDate(iso?: string | null): Date | undefined { if (!iso) return undefined; const date = new Date(iso); return Number.isNaN(date.getTime()) ? undefined : date;