Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/signals/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
? [
Expand Down
13 changes: 13 additions & 0 deletions test/unit/burden-forecast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] }));
Expand Down