From dc9a988b82e30b88a4cfb3ea6271f71c66f26bf4 Mon Sep 17 00:00:00 2001 From: GildardoDev <267998055+GildardoDev@users.noreply.github.com> Date: Wed, 1 Jul 2026 04:54:29 -0500 Subject: [PATCH] feat(signals): add contributor intake score breakdown with weighted deductions and top lever --- src/services/contributor-intake-breakdown.ts | 190 ++++++++++++++++++ .../unit/contributor-intake-breakdown.test.ts | 143 +++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 src/services/contributor-intake-breakdown.ts create mode 100644 test/unit/contributor-intake-breakdown.test.ts diff --git a/src/services/contributor-intake-breakdown.ts b/src/services/contributor-intake-breakdown.ts new file mode 100644 index 0000000000..3ef755112c --- /dev/null +++ b/src/services/contributor-intake-breakdown.ts @@ -0,0 +1,190 @@ +import { sanitizePublicComment } from "../github/commands"; +import type { ContributorIntakeHealth } from "../signals/engine"; + +// ─── Contributor intake breakdown (explanation family) ─────────────────────────────────────────── +// A pure projection over a computed {@link ContributorIntakeHealth} that decomposes its otherwise-opaque +// intake `score` into the weighted deductions that lowered it from a perfect 100, and names the single +// highest-leverage lever a maintainer can pull to make the repo more attractive to contributors. Sibling of +// `queue-burden-breakdown.ts` and `score-breakdown.ts`: deterministic, no I/O, no GitHub fetch. Public-safe by +// construction — it reports the same observable drivers the intake summary already prints (queue burden out of +// 100, duplicate-cluster count, config band) and routes every rendered string through `sanitizePublicComment`. + +export type IntakeDeductionBand = "none" | "low" | "moderate" | "high"; + +export type IntakeDeduction = { + /** The intake factor this deduction comes from. */ + component: "queueBurden" | "duplicateClusters" | "configQuality"; + /** Observable driver behind the deduction (a queue-burden reading, a cluster count, or a config band). */ + driver: string; + /** Points removed from the base 100 by this factor (always >= 0). */ + deduction: number; + /** Share of the total deduction (0–100). */ + sharePercent: number; + band: IntakeDeductionBand; + summary: string; + lever: string; + /** 0–100 ranking weight used to pick the single highest-leverage improvement lever. */ + leverageScore: number; +}; + +export type ContributorIntakeBreakdown = { + repoFullName: string; + generatedAt: string; + /** The authoritative (already clamped) intake score carried on the ContributorIntakeHealth. */ + score: number; + level: ContributorIntakeHealth["level"]; + /** The perfect score every repo starts from before deductions. */ + baseScore: number; + /** Sum of every factor's deduction (may exceed 100 before the engine floors the score at 0). */ + totalDeduction: number; + /** True when the deductions exceeded the base 100 and the engine floored the score at 0. */ + clamped: boolean; + components: IntakeDeduction[]; + highestLeverageLever: { component: string; lever: string; reason: string }; + summary: string; +}; + +const BASE_SCORE = 100; + +// These weights MIRROR buildContributorIntakeHealth() in src/signals/engine.ts: +// score = clamp(100 - burdenScore*0.55 - duplicateClusters*8 - configPenalty, 0, 100) +// A drift-guard test rebuilds a ContributorIntakeHealth through that function and asserts this module recomposes +// the same score, so an engine weight change fails the suite instead of silently producing a wrong breakdown. +const QUEUE_BURDEN_WEIGHT = 0.55; +const DUPLICATE_CLUSTER_WEIGHT = 8; + +// The config-quality band maps to a fixed penalty (an excellent/unknown band costs nothing). +function configPenaltyFor(level: ContributorIntakeHealth["configLevel"]): number { + if (level === "fragile") return 30; + if (level === "needs_attention") return 18; + if (level === "good") return 6; + return 0; +} + +function bandFor(deduction: number, sharePercent: number): IntakeDeductionBand { + if (deduction <= 0) return "none"; + if (sharePercent >= 40) return "high"; + if (sharePercent >= 15) return "moderate"; + return "low"; +} + +function shareOf(deduction: number, totalDeduction: number): number { + if (totalDeduction <= 0) return 0; + return Math.round((deduction / totalDeduction) * 100); +} + +function pickHighestLeverage(components: IntakeDeduction[]): ContributorIntakeBreakdown["highestLeverageLever"] { + // Rank by share of the total deduction; break ties toward the larger raw deduction, then by name for + // determinism. When nothing is deducted (a perfect intake), return an explicit no-op lever rather than an + // arbitrary component, so the breakdown stays honest for a healthy repo. + const ranked = [...components].sort( + (left, right) => + right.leverageScore - left.leverageScore || + right.deduction - left.deduction || + left.component.localeCompare(right.component), + ); + const top = ranked[0]!; + if (top.leverageScore <= 0) { + return { + component: "none", + lever: sanitizePublicComment("No intake lever needs attention; nothing is currently lowering the intake score."), + reason: sanitizePublicComment("Contributor intake is at full strength, so there is no pressing lever to pull."), + }; + } + const reason = + top.band === "high" + ? `${top.component} is the dominant drag on contributor intake right now.` + : `${top.component} is the largest remaining drag on contributor intake.`; + return { + component: top.component, + lever: top.lever, + reason: sanitizePublicComment(reason), + }; +} + +/** + * Pure projection over a {@link ContributorIntakeHealth} that explains how the intake `score` breaks down into + * the weighted deductions that lowered it and names the single highest-leverage lever to improve intake. + */ +export function explainContributorIntake(health: ContributorIntakeHealth): ContributorIntakeBreakdown { + const burdenDeduction = health.queueHealth.burdenScore * QUEUE_BURDEN_WEIGHT; + const clusterDeduction = health.duplicateClusters * DUPLICATE_CLUSTER_WEIGHT; + const configDeduction = configPenaltyFor(health.configLevel); + const totalDeduction = burdenDeduction + clusterDeduction + configDeduction; + + const raw: Array> = [ + { + component: "queueBurden", + driver: `queue burden ${health.queueHealth.burdenScore}/100`, + deduction: burdenDeduction, + summary: + burdenDeduction > 0 + ? `Queue burden of ${health.queueHealth.burdenScore}/100 is the review-load drag on intake.` + : "Queue burden is zero, so it is not dragging on intake.", + lever: + burdenDeduction > 0 + ? "Bring the queue burden down (land or link open PRs, clear stale and aged work) to lift intake." + : "Keep the queue clear so review load never drags on intake.", + }, + { + component: "duplicateClusters", + driver: `${health.duplicateClusters} duplicate cluster(s)`, + deduction: clusterDeduction, + summary: + clusterDeduction > 0 + ? `${health.duplicateClusters} duplicate or overlapping work cluster(s) are discouraging clean contributions.` + : "No duplicate or overlapping work clusters are dragging on intake.", + lever: + clusterDeduction > 0 + ? "Resolve overlapping submissions so contributors are not competing on the same work." + : "Keep deduplicating incoming work so collisions never drag on intake.", + }, + { + component: "configQuality", + driver: `config ${health.configLevel}`, + deduction: configDeduction, + summary: + configDeduction > 0 + ? `Repository config quality is ${health.configLevel}, which lowers how confidently contributors can engage.` + : `Repository config quality is ${health.configLevel}, so it is not dragging on intake.`, + lever: + configDeduction > 0 + ? "Fix the flagged registry and label config issues to raise config quality and intake." + : "Keep the registry and label config healthy so config quality never drags on intake.", + }, + ]; + + const components: IntakeDeduction[] = raw.map((entry) => { + const sharePercent = shareOf(entry.deduction, totalDeduction); + return { + component: entry.component, + driver: entry.driver, + deduction: entry.deduction, + sharePercent, + band: bandFor(entry.deduction, sharePercent), + summary: sanitizePublicComment(entry.summary), + lever: sanitizePublicComment(entry.lever), + leverageScore: sharePercent, + }; + }); + + const clamped = totalDeduction > BASE_SCORE; + const highestLeverageLever = pickHighestLeverage(components); + const summary = + highestLeverageLever.component === "none" + ? `Contributor intake is ${health.level} with nothing lowering the score.` + : `Contributor intake is ${health.level}; ${highestLeverageLever.component} is the leading drag to address.`; + + return { + repoFullName: health.repoFullName, + generatedAt: health.generatedAt, + score: health.score, + level: health.level, + baseScore: BASE_SCORE, + totalDeduction, + clamped, + components, + highestLeverageLever, + summary: sanitizePublicComment(summary), + }; +} diff --git a/test/unit/contributor-intake-breakdown.test.ts b/test/unit/contributor-intake-breakdown.test.ts new file mode 100644 index 0000000000..a75a281ab9 --- /dev/null +++ b/test/unit/contributor-intake-breakdown.test.ts @@ -0,0 +1,143 @@ +import { describe, expect, it } from "vitest"; +import { explainContributorIntake } from "../../src/services/contributor-intake-breakdown"; +import { buildContributorIntakeHealth, type CollisionReport, type ContributorIntakeHealth } from "../../src/signals/engine"; +import type { IssueRecord, PullRequestRecord, RepositoryRecord } from "../../src/types"; + +const FORBIDDEN = /\b(wallet|hotkey|coldkey|mnemonic|payout|reward|raw[-_\s]?trust|credibility|farming)\b/i; + +const clamp = (value: number): number => Math.max(0, Math.min(100, value)); + +function makeIntake(input: { + burdenScore?: number; + duplicateClusters?: number; + configLevel?: ContributorIntakeHealth["configLevel"]; + score?: number; + level?: ContributorIntakeHealth["level"]; + repoFullName?: string; + generatedAt?: string; +}): ContributorIntakeHealth { + return { + repoFullName: input.repoFullName ?? "owner/repo", + generatedAt: input.generatedAt ?? "2026-07-01T00:00:00.000Z", + level: input.level ?? "healthy", + score: input.score ?? 100, + queueHealth: { + burdenScore: input.burdenScore ?? 0, + level: "low", + signals: { + openIssues: 0, + openPullRequests: 0, + unlinkedPullRequests: 0, + stalePullRequests: 0, + draftPullRequests: 0, + maintainerAuthoredPullRequests: 0, + collisionClusters: 0, + ageBuckets: { under7Days: 0, days7To30: 0, over30Days: 0 }, + likelyReviewablePullRequests: 0, + }, + }, + configLevel: input.configLevel ?? "excellent", + duplicateClusters: input.duplicateClusters ?? 0, + reviewablePullRequests: 0, + summary: "fixture", + findings: [], + }; +} + +const componentByName = (breakdown: ReturnType, name: string) => + breakdown.components.find((entry) => entry.component === name)!; + +describe("contributor intake breakdown", () => { + it("reports a perfect intake with no deductions and an honest no-op lever", () => { + const breakdown = explainContributorIntake(makeIntake({ score: 100, level: "healthy" })); + expect(breakdown.totalDeduction).toBe(0); + expect(breakdown.clamped).toBe(false); + expect(breakdown.components).toHaveLength(3); + for (const entry of breakdown.components) { + expect(entry.band).toBe("none"); + expect(entry.sharePercent).toBe(0); + expect(entry.leverageScore).toBe(0); + } + expect(breakdown.highestLeverageLever.component).toBe("none"); + expect(breakdown.highestLeverageLever.reason).toMatch(/full strength/i); + expect(breakdown.summary).toMatch(/nothing lowering/i); + }); + + it("flags a dominant queue-burden drag as high band and the top lever, with a small low-band factor", () => { + // burden 100×0.55 = 55, config good = 6, clusters 0 → total 61. burden ≈90% (high), config ≈10% (low). + const breakdown = explainContributorIntake(makeIntake({ burdenScore: 100, configLevel: "good", score: 39, level: "strained" })); + const burden = componentByName(breakdown, "queueBurden"); + expect(burden.deduction).toBeCloseTo(55); + expect(burden.sharePercent).toBe(90); + expect(burden.band).toBe("high"); + expect(componentByName(breakdown, "duplicateClusters").band).toBe("none"); + expect(componentByName(breakdown, "configQuality").band).toBe("low"); + expect(breakdown.clamped).toBe(false); + expect(breakdown.highestLeverageLever.component).toBe("queueBurden"); + expect(breakdown.highestLeverageLever.reason).toMatch(/dominant/i); + }); + + it("classifies a moderate top drag and names the largest-remaining lever", () => { + // burden 15×0.55 = 8.25, clusters 1×8 = 8, config good = 6 → total 22.25; all shares in the moderate band. + const breakdown = explainContributorIntake(makeIntake({ burdenScore: 15, duplicateClusters: 1, configLevel: "good", score: 78, level: "healthy" })); + const burden = componentByName(breakdown, "queueBurden"); + expect(burden.band).toBe("moderate"); + expect(breakdown.highestLeverageLever.component).toBe("queueBurden"); + expect(breakdown.highestLeverageLever.reason).toMatch(/largest remaining/i); + }); + + it("decomposes each config band into its fixed penalty", () => { + expect(componentByName(explainContributorIntake(makeIntake({ configLevel: "needs_attention" })), "configQuality").deduction).toBe(18); + expect(componentByName(explainContributorIntake(makeIntake({ configLevel: "good" })), "configQuality").deduction).toBe(6); + expect(componentByName(explainContributorIntake(makeIntake({ configLevel: "excellent" })), "configQuality").deduction).toBe(0); + }); + + it("marks the breakdown clamped when deductions exceed the base 100", () => { + // burden 100×0.55 = 55, clusters 3×8 = 24, config fragile = 30 → 109 > 100, engine floors the score at 0. + const breakdown = explainContributorIntake(makeIntake({ burdenScore: 100, duplicateClusters: 3, configLevel: "fragile", score: 0, level: "blocked" })); + expect(breakdown.totalDeduction).toBeCloseTo(109); + expect(breakdown.clamped).toBe(true); + expect(componentByName(breakdown, "configQuality").deduction).toBe(30); + expect(breakdown.score).toBe(0); + }); + + it("passes through repo identity, level, and generatedAt", () => { + const breakdown = explainContributorIntake( + makeIntake({ repoFullName: "acme/widgets", generatedAt: "2026-02-03T04:05:06.000Z", level: "watch", score: 60, burdenScore: 40 }), + ); + expect(breakdown.repoFullName).toBe("acme/widgets"); + expect(breakdown.generatedAt).toBe("2026-02-03T04:05:06.000Z"); + expect(breakdown.level).toBe("watch"); + expect(breakdown.baseScore).toBe(100); + expect(breakdown.summary).toMatch(/contributor intake is watch/i); + }); + + it("never leaks private or reward terminology in any rendered string", () => { + const breakdown = explainContributorIntake(makeIntake({ burdenScore: 80, duplicateClusters: 2, configLevel: "needs_attention" })); + for (const entry of breakdown.components) { + expect(entry.summary).not.toMatch(FORBIDDEN); + expect(entry.lever).not.toMatch(FORBIDDEN); + expect(entry.driver).not.toMatch(FORBIDDEN); + } + expect(breakdown.highestLeverageLever.reason).not.toMatch(FORBIDDEN); + expect(breakdown.summary).not.toMatch(FORBIDDEN); + }); + + it("recomposes the exact intake score the engine computes (weight drift guard)", () => { + const repo = { fullName: "owner/repo", isRegistered: true } as unknown as RepositoryRecord; + const pullRequests: PullRequestRecord[] = [ + { repoFullName: "owner/repo", number: 1, title: "aged unlinked", state: "open", labels: [], linkedIssues: [], updatedAt: "2020-01-01T00:00:00.000Z" }, + { repoFullName: "owner/repo", number: 2, title: "aged unlinked two", state: "open", labels: [], linkedIssues: [], updatedAt: "2020-01-01T00:00:00.000Z" }, + ]; + const issues = [{ repoFullName: "owner/repo", number: 10, title: "open issue", state: "open", labels: [], linkedPrs: [], body: null }]; + const collisions = { repoFullName: "owner/repo", summary: { clusterCount: 2, highRiskCount: 0 } } as unknown as CollisionReport; + + const built = buildContributorIntakeHealth(repo, issues, pullRequests, "owner/repo", collisions); + const breakdown = explainContributorIntake(built); + + // Reconstructing 100 minus the decomposed deductions (using this module's weights) must equal the engine score. + expect(clamp(breakdown.baseScore - breakdown.totalDeduction)).toBeCloseTo(built.score, 6); + expect(componentByName(breakdown, "duplicateClusters").deduction).toBe(16); + expect(componentByName(breakdown, "queueBurden").deduction).toBeCloseTo(built.queueHealth.burdenScore * 0.55, 6); + }); +});