diff --git a/src/signals/engine.ts b/src/signals/engine.ts index f96580bbc1..c4a63865bb 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -2952,7 +2952,7 @@ export function buildBurdenForecast( const stalePrs = openPrs.filter((pr) => daysSince(pr.updatedAt ?? pr.createdAt) > 30).length; const projectedReviewLoad = clamp(openPrs.length * 3 + updatedRecently * 2 + collisions.summary.highRiskCount * 4 + stalePrs, 0, 100); const queueGrowthRisk = clamp((openPrs.length - queueHealth.signals.likelyReviewablePullRequests) * 5 + collisions.summary.clusterCount * 7, 0, 100); - const level = projectedReviewLoad >= 80 || queueGrowthRisk >= 80 ? "critical" : projectedReviewLoad >= 55 || queueGrowthRisk >= 55 ? "high" : projectedReviewLoad >= 25 ? "medium" : "low"; + const level = projectedReviewLoad >= 80 || queueGrowthRisk >= 80 ? "critical" : projectedReviewLoad >= 55 || queueGrowthRisk >= 55 ? "high" : projectedReviewLoad >= 25 || queueGrowthRisk >= 25 ? "medium" : "low"; const findings: SignalFinding[] = [ ...(queueGrowthRisk >= 55 ? [ diff --git a/test/unit/burden-forecast.test.ts b/test/unit/burden-forecast.test.ts index 0e0d0517ec..34f09b9690 100644 --- a/test/unit/burden-forecast.test.ts +++ b/test/unit/burden-forecast.test.ts @@ -35,6 +35,19 @@ describe("burden forecast builder", () => { expect(forecast.forecast.duplicateTrend).toBe(4); }); + it("classifies a small unreviewable queue as medium burden via queue-growth risk", () => { + const repo = repoFixture("owner/growth"); + // 5 open PRs with no linked issues, updated 10 days ago: not recent (> horizon 7) and not stale (< 30). + // Low projectedReviewLoad, but queueGrowthRisk is driven up by the unreviewable count. + const open = Array.from({ length: 5 }, (_, index) => pr(repo.fullName, index + 1, `open ${index}`, { linkedIssues: [], updatedAt: daysAgo(10) })); + const forecast = buildBurdenForecast(repo, [], open, buildCollisionReport(repo.fullName, [], open), 7); + expect(forecast.forecast.projectedReviewLoad).toBeLessThan(25); + expect(forecast.forecast.queueGrowthRisk).toBeGreaterThanOrEqual(25); + expect(forecast.forecast.queueGrowthRisk).toBeLessThan(55); + // queueGrowthRisk must drive the medium tier even when projectedReviewLoad is low. + expect(forecast.level).toBe("medium"); + }); + it("surfaces a stale PR trend in the forecast findings", () => { const repo = repoFixture("owner/stale"); const stalePrs = Array.from({ length: 4 }, (_, index) => pr(repo.fullName, index + 1, `stale ${index}`, { updatedAt: daysAgo(31), linkedIssues: [] }));