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
21 changes: 21 additions & 0 deletions src/services/score-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,26 @@ function reviewPenaltyBreakdown(preview: ScorePreviewResult): ScoreMultiplierBre
};
}

// Sibling of reviewPenaltyBreakdown: upstream models review churn twice — reviewPenaltyMultiplier shrinks the
// current preview while reviewCollateralMultiplier raises the open-PR collateral fraction
// (OPEN_PR_COLLATERAL_PERCENT × multiplier) reserved on concurrent PRs after CHANGES_REQUESTED reviews.
function reviewCollateralBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { reviewCollateralMultiplier, collateralFraction } = preview.gates;
const elevated = reviewCollateralMultiplier > 1.01;
const band: ScoreMultiplierBand = elevated ? "reduced" : "neutral";
return {
component: "reviewCollateralMultiplier",
band,
summary: elevated
? `Open-PR review collateral is elevated (effective fraction ${roundBand(collateralFraction)}) because prior CHANGES_REQUESTED reviews on open PRs raised the collateral multiplier above baseline.`
: `Open-PR review collateral is at the baseline fraction (${roundBand(collateralFraction)}); no CHANGES_REQUESTED review churn is inflating concurrent-PR collateral.`,
lever: elevated
? "Resolve outstanding change requests on open PRs before opening more concurrent work, or expect tighter collateral on the open-PR allowance."
: "Keep open PRs review-clean to avoid collateral inflation on concurrent work.",
leverageScore: elevated ? Math.min(55, Math.round((reviewCollateralMultiplier - 1) * 40 + 20)) : 6,
};
}

function labelMultiplierBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { labelMultiplier } = preview.scoreEstimate;
const band: ScoreMultiplierBand = labelMultiplier > 1 ? "full" : labelMultiplier < 1 ? "reduced" : "neutral";
Expand Down Expand Up @@ -269,6 +289,7 @@ export function explainScoreBreakdown(preview: ScorePreviewResult): ScoreBreakdo
issueMultiplierBreakdown(preview),
credibilityBreakdown(preview),
reviewPenaltyBreakdown(preview),
reviewCollateralBreakdown(preview),
openPrBreakdown(preview),
openIssueBreakdown(preview),
mergedHistoryBreakdown(preview),
Expand Down
96 changes: 96 additions & 0 deletions test/unit/score-breakdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe("explainScoreBreakdown", () => {
"issueMultiplier",
"credibilityMultiplier",
"reviewPenaltyMultiplier",
"reviewCollateralMultiplier",
"openPrMultiplier",
"openIssueMultiplier",
"mergedHistoryMultiplier",
Expand Down Expand Up @@ -212,6 +213,101 @@ describe("explainScoreBreakdown", () => {
expect(breakdown.components.find((entry) => entry.component === "openPrMultiplier")).toMatchObject({ band: "full" });
expect(breakdown.components.find((entry) => entry.component === "credibilityMultiplier")).toMatchObject({ band: "full" });
expect(breakdown.components.find((entry) => entry.component === "reviewPenaltyMultiplier")).toMatchObject({ band: "full" });
expect(breakdown.components.find((entry) => entry.component === "reviewCollateralMultiplier")).toMatchObject({ band: "neutral" });
});

it("explains elevated open-PR review collateral as reduced strength and baseline as neutral", () => {
const baseline = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot,
input: {
repoFullName: repo.fullName,
sourceTokenScore: 80,
totalTokenScore: 100,
sourceLines: 50,
openPrCount: 1,
credibility: 1,
changesRequestedCount: 0,
},
}),
);
expect(baseline.components.find((entry) => entry.component === "reviewCollateralMultiplier")).toMatchObject({
band: "neutral",
summary: expect.stringMatching(/baseline fraction/i),
});

const elevated = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot: {
...snapshot,
constants: { ...snapshot.constants, MAX_OPEN_PR_REVIEW_COLLATERAL_MULTIPLIER: 2.0 },
},
input: {
repoFullName: repo.fullName,
sourceTokenScore: 80,
totalTokenScore: 100,
sourceLines: 50,
openPrCount: 1,
credibility: 1,
changesRequestedCount: 4,
},
}),
);
const collateral = elevated.components.find((entry) => entry.component === "reviewCollateralMultiplier");
expect(collateral).toMatchObject({ band: "reduced" });
expect(collateral?.summary).toMatch(/elevated|CHANGES_REQUESTED/i);
expect(collateral?.summary).toMatch(/0\.32/);
expect(collateral?.lever).toMatch(/change requests/i);
expect(JSON.stringify(elevated)).not.toMatch(FORBIDDEN);

const capped = explainScoreBreakdown(
buildScorePreview({
repo,
snapshot: {
...snapshot,
constants: { ...snapshot.constants, MAX_OPEN_PR_REVIEW_COLLATERAL_MULTIPLIER: 2.0 },
},
input: {
repoFullName: repo.fullName,
sourceTokenScore: 80,
totalTokenScore: 100,
sourceLines: 50,
openPrCount: 1,
credibility: 1,
changesRequestedCount: 10,
},
}),
);
expect(capped.components.find((entry) => entry.component === "reviewCollateralMultiplier")).toMatchObject({
band: "reduced",
summary: expect.stringMatching(/0\.4/),
});
});

it("prioritizes open PR blocking above elevated review collateral", () => {
const preview = buildScorePreview({
repo,
snapshot: {
...snapshot,
constants: { ...snapshot.constants, MAX_OPEN_PR_REVIEW_COLLATERAL_MULTIPLIER: 2.0 },
},
input: {
repoFullName: repo.fullName,
sourceTokenScore: 80,
totalTokenScore: 100,
sourceLines: 50,
openPrCount: 8,
existingContributorTokenScore: 50,
credibility: 1,
changesRequestedCount: 4,
},
});

const breakdown = explainScoreBreakdown(preview);
expect(breakdown.components.find((entry) => entry.component === "reviewCollateralMultiplier")).toMatchObject({ band: "reduced" });
expect(breakdown.highestLeverageLever.component).toBe("openPrMultiplier");
});

it("marks penalty label multipliers as reduced strength (#994)", () => {
Expand Down
Loading