From 99e3f9f30128da0ce132e3d6df0ca27cc55b0b8c Mon Sep 17 00:00:00 2001 From: taherd <183945978+taherdhanera@users.noreply.github.com> Date: Fri, 22 May 2026 22:56:56 +0530 Subject: [PATCH] Add reviewer workload equity guard --- reviewer-workload-equity-guard/README.md | 36 ++ reviewer-workload-equity-guard/demo-video.js | 173 ++++++++ reviewer-workload-equity-guard/demo.js | 18 + reviewer-workload-equity-guard/index.js | 368 ++++++++++++++++++ reviewer-workload-equity-guard/package.json | 14 + .../reports/demo.webm | Bin 0 -> 52548 bytes .../reports/reviewer-packet.md | 65 ++++ .../reports/summary.json | 191 +++++++++ .../reports/summary.svg | 16 + .../requirements-map.md | 18 + reviewer-workload-equity-guard/sample-data.js | 98 +++++ reviewer-workload-equity-guard/test.js | 106 +++++ 12 files changed, 1103 insertions(+) create mode 100644 reviewer-workload-equity-guard/README.md create mode 100644 reviewer-workload-equity-guard/demo-video.js create mode 100644 reviewer-workload-equity-guard/demo.js create mode 100644 reviewer-workload-equity-guard/index.js create mode 100644 reviewer-workload-equity-guard/package.json create mode 100644 reviewer-workload-equity-guard/reports/demo.webm create mode 100644 reviewer-workload-equity-guard/reports/reviewer-packet.md create mode 100644 reviewer-workload-equity-guard/reports/summary.json create mode 100644 reviewer-workload-equity-guard/reports/summary.svg create mode 100644 reviewer-workload-equity-guard/requirements-map.md create mode 100644 reviewer-workload-equity-guard/sample-data.js create mode 100644 reviewer-workload-equity-guard/test.js diff --git a/reviewer-workload-equity-guard/README.md b/reviewer-workload-equity-guard/README.md new file mode 100644 index 00000000..871ade3e --- /dev/null +++ b/reviewer-workload-equity-guard/README.md @@ -0,0 +1,36 @@ +# Reviewer Workload Equity Guard + +Self-contained Community & User Reputation System slice for +`SCIBASE-AI/SCIBASE.AI#15`. + +The guard evaluates pending peer-review assignments before they affect profile +reputation, badges, leaderboards, or project timelines. It checks reviewer +capacity, weekly review hours, opt-outs, leave windows, rest periods, expertise +match, early-career penalty risk, and recent review-credit concentration. + +This is intentionally separate from broad reputation ledgers, endorsement rings, +leaderboard eligibility, review civility, review timeliness scoring, recusal/COI, +calibration benches, edit-history integrity, identity-leak checks, appeals, +mentorship, correction-impact, credit attestation, profile visibility, and +template-rubric validation. Its job is to stop unfair negative reputation deltas +when the reviewer was overloaded, unavailable, or mismatched before scoring. + +## Run + +```bash +npm run check +npm test +npm run demo +npm run demo:video +``` + +## Outputs + +- `reports/summary.json` +- `reports/reviewer-packet.md` +- `reports/summary.svg` +- `reports/demo.webm` + +All data is synthetic. The module does not call identity services, profile +systems, leaderboards, email systems, review assignment systems, or external +APIs. diff --git a/reviewer-workload-equity-guard/demo-video.js b/reviewer-workload-equity-guard/demo-video.js new file mode 100644 index 00000000..0b0e51f7 --- /dev/null +++ b/reviewer-workload-equity-guard/demo-video.js @@ -0,0 +1,173 @@ +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +const reportDir = path.join(__dirname, "reports"); +const outputPath = path.join(reportDir, "demo.webm"); + +const chromeCandidates = [ + process.env.CHROME_PATH, + "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", + "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", + "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe", + "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" +].filter(Boolean); + +function findBrowser() { + const found = chromeCandidates.find((candidate) => fs.existsSync(candidate)); + if (!found) { + throw new Error("Chrome or Edge was not found. Set CHROME_PATH to generate reports/demo.webm."); + } + return found; +} + +function fileUrl(filePath) { + return `file:///${filePath.replace(/\\/g, "/")}`; +} + +const html = String.raw` + + + + Reviewer workload equity guard demo + + + + +
recording
+ + +`; + +fs.mkdirSync(reportDir, { recursive: true }); + +const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "reviewer-workload-demo-")); +const htmlPath = path.join(tempDir, "demo.html"); +const profileDir = path.join(tempDir, "profile"); +fs.writeFileSync(htmlPath, html, "utf8"); + +const stdout = execFileSync( + findBrowser(), + [ + "--headless=new", + "--disable-gpu", + "--disable-dev-shm-usage", + "--autoplay-policy=no-user-gesture-required", + "--run-all-compositor-stages-before-draw", + "--virtual-time-budget=7500", + `--user-data-dir=${profileDir}`, + "--dump-dom", + fileUrl(htmlPath) + ], + { encoding: "utf8", maxBuffer: 30 * 1024 * 1024 } +); + +const match = stdout.match(/data:video\/webm;base64,([A-Za-z0-9+/=]+)/); +if (!match) { + throw new Error(`Demo video generation failed. Browser output ended with: ${stdout.slice(-600)}`); +} + +fs.writeFileSync(outputPath, Buffer.from(match[1], "base64")); +console.log(`Generated ${path.relative(process.cwd(), outputPath)}`); diff --git a/reviewer-workload-equity-guard/demo.js b/reviewer-workload-equity-guard/demo.js new file mode 100644 index 00000000..97c3730c --- /dev/null +++ b/reviewer-workload-equity-guard/demo.js @@ -0,0 +1,18 @@ +const fs = require("fs"); +const path = require("path"); +const { project } = require("./sample-data"); +const { buildReviewPacket, renderMarkdownReport, renderSvgSummary } = require("./index"); + +const reportDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportDir, { recursive: true }); + +const packet = buildReviewPacket(project); + +fs.writeFileSync(path.join(reportDir, "summary.json"), `${JSON.stringify(packet, null, 2)}\n`, "utf8"); +fs.writeFileSync(path.join(reportDir, "reviewer-packet.md"), renderMarkdownReport(packet), "utf8"); +fs.writeFileSync(path.join(reportDir, "summary.svg"), renderSvgSummary(packet), "utf8"); + +console.log(`Generated reports for ${packet.guard}`); +console.log(`Decision: ${packet.decision}`); +console.log(`Score: ${packet.score}`); +console.log(`Findings: ${packet.findings.length}`); diff --git a/reviewer-workload-equity-guard/index.js b/reviewer-workload-equity-guard/index.js new file mode 100644 index 00000000..2225daaa --- /dev/null +++ b/reviewer-workload-equity-guard/index.js @@ -0,0 +1,368 @@ +const SEVERITY_WEIGHTS = { + critical: 34, + high: 20, + medium: 10, + low: 4 +}; + +function daysBetween(a, b) { + const left = new Date(a).getTime(); + const right = new Date(b).getTime(); + return Math.floor((right - left) / (24 * 60 * 60 * 1000)); +} + +function addFinding(findings, severity, rule, message, action, refs = []) { + findings.push({ severity, rule, message, action, refs }); +} + +function isDateWithinWindow(date, window) { + const value = new Date(date).getTime(); + return value >= new Date(window.startsAt).getTime() && value <= new Date(window.endsAt).getTime(); +} + +function findReviewer(project, reviewerId) { + return project.reviewers.find((reviewer) => reviewer.id === reviewerId); +} + +function workloadAfterAssignment(reviewer, assignment) { + return { + openReviews: reviewer.openReviewIds.length + 1, + weeklyReviewHours: reviewer.recentReviewHours + assignment.estimatedHours + }; +} + +function assignmentRiskReasons(project, reviewer, assignment) { + const reasons = []; + const projected = workloadAfterAssignment(reviewer, assignment); + + if (projected.openReviews > project.policy.maxOpenReviews) { + reasons.push("over_capacity"); + } + if (projected.weeklyReviewHours > Math.min(project.policy.maxWeeklyReviewHours, reviewer.weeklyCapacityHours)) { + reasons.push("weekly_hours_exceeded"); + } + if (!project.policy.acceptedAvailabilityStatuses.includes(reviewer.availabilityStatus)) { + reasons.push("unavailable"); + } + if (reviewer.optOutUntil && daysBetween(project.asOfDate, reviewer.optOutUntil) >= 0) { + reasons.push("opted_out"); + } + if (reviewer.lastCompletedReviewAt && daysBetween(reviewer.lastCompletedReviewAt, project.asOfDate) < project.policy.minimumRestDays) { + reasons.push("rest_window"); + } + if (reviewer.unavailableWindows.some((window) => isDateWithinWindow(assignment.dueDate, window))) { + reasons.push("due_during_unavailable_window"); + } + if (!reviewer.expertise.includes(assignment.topic)) { + reasons.push("expertise_mismatch"); + } + + return reasons; +} + +function buildAssignmentDecision(project, reviewer, assignment, reasons) { + const protectedReasons = reasons.filter((reason) => project.policy.protectedPenaltyReasons.includes(reason)); + if (protectedReasons.length > 0) { + return { + assignmentId: assignment.id, + reviewerId: reviewer.id, + decision: "suppress-negative-reputation-delta", + protectedReasons, + allowedCompletionDelta: assignment.reputationDelta.completion, + blockedDeclineDelta: assignment.reputationDelta.decline, + blockedLateDelta: assignment.reputationDelta.late + }; + } + + if (reasons.includes("weekly_hours_exceeded") || reasons.includes("due_during_unavailable_window")) { + return { + assignmentId: assignment.id, + reviewerId: reviewer.id, + decision: "steward-review-before-scoring", + protectedReasons: reasons, + allowedCompletionDelta: 0, + blockedDeclineDelta: assignment.reputationDelta.decline, + blockedLateDelta: assignment.reputationDelta.late + }; + } + + if (reasons.includes("expertise_mismatch")) { + return { + assignmentId: assignment.id, + reviewerId: reviewer.id, + decision: "reassign-or-add-mentor-before-scoring", + protectedReasons: reasons, + allowedCompletionDelta: 0, + blockedDeclineDelta: assignment.reputationDelta.decline, + blockedLateDelta: assignment.reputationDelta.late + }; + } + + return { + assignmentId: assignment.id, + reviewerId: reviewer.id, + decision: "score-normally", + protectedReasons: [], + allowedCompletionDelta: assignment.reputationDelta.completion, + blockedDeclineDelta: 0, + blockedLateDelta: 0 + }; +} + +function concentrationSummary(project) { + const recent = project.recentAssignmentHistory.filter( + (item) => daysBetween(item.assignedAt, project.asOfDate) <= project.policy.concentrationWindowDays + ); + const byReviewer = recent.reduce((summary, item) => { + summary[item.reviewerId] = (summary[item.reviewerId] || 0) + 1; + return summary; + }, {}); + const total = recent.length || 1; + const entries = Object.entries(byReviewer).map(([reviewerId, count]) => ({ + reviewerId, + count, + ratio: count / total + })); + entries.sort((a, b) => b.ratio - a.ratio); + return { total: recent.length, entries }; +} + +function evaluateWorkloadEquity(project) { + const findings = []; + const decisions = []; + + for (const assignment of project.pendingAssignments) { + const reviewer = findReviewer(project, assignment.reviewerId); + if (!reviewer) { + addFinding( + findings, + "critical", + "assignment-reviewer-missing", + `Assignment ${assignment.id} references missing reviewer ${assignment.reviewerId}.`, + "Block reputation scoring until the assignment is repaired or removed.", + [assignment.id, assignment.reviewerId] + ); + continue; + } + + const projected = workloadAfterAssignment(reviewer, assignment); + const reasons = assignmentRiskReasons(project, reviewer, assignment); + decisions.push(buildAssignmentDecision(project, reviewer, assignment, reasons)); + + if (projected.openReviews > project.policy.maxOpenReviews) { + addFinding( + findings, + "high", + "open-review-load-exceeded", + `${reviewer.displayName} would hold ${projected.openReviews} open reviews after ${assignment.id}.`, + "Reassign or queue the request before applying reputation penalties or leaderboard effects.", + [reviewer.id, assignment.id] + ); + } + + if (projected.weeklyReviewHours > Math.min(project.policy.maxWeeklyReviewHours, reviewer.weeklyCapacityHours)) { + addFinding( + findings, + "high", + "weekly-review-hour-budget-exceeded", + `${reviewer.displayName} would reach ${projected.weeklyReviewHours} review hours this week.`, + "Suppress negative reputation deltas and route the request to a reviewer with available capacity.", + [reviewer.id, assignment.id] + ); + } + + if (!project.policy.acceptedAvailabilityStatuses.includes(reviewer.availabilityStatus)) { + addFinding( + findings, + "critical", + "reviewer-unavailable-but-penalized", + `${reviewer.displayName} is ${reviewer.availabilityStatus} but ${assignment.id} still carries decline and late penalties.`, + "Block decline/late reputation penalties while the reviewer is unavailable.", + [reviewer.id, assignment.id] + ); + } + + if (reviewer.optOutUntil && daysBetween(project.asOfDate, reviewer.optOutUntil) >= 0) { + addFinding( + findings, + "critical", + "reviewer-opt-out-active", + `${reviewer.displayName} has opted out until ${reviewer.optOutUntil}.`, + "Do not assign reputation-affecting reviews until the opt-out expires.", + [reviewer.id, assignment.id, reviewer.optOutUntil] + ); + } + + if (reviewer.lastCompletedReviewAt && daysBetween(reviewer.lastCompletedReviewAt, project.asOfDate) < project.policy.minimumRestDays) { + addFinding( + findings, + "medium", + "reviewer-rest-window-too-short", + `${reviewer.displayName} completed a review on ${reviewer.lastCompletedReviewAt}.`, + "Defer the new request or remove negative reputation effects until the rest window is met.", + [reviewer.id, assignment.id] + ); + } + + if (reviewer.unavailableWindows.some((window) => isDateWithinWindow(assignment.dueDate, window))) { + addFinding( + findings, + "high", + "assignment-due-during-unavailable-window", + `${assignment.id} is due while ${reviewer.displayName} is unavailable.`, + "Move the due date or reassign before the review can change reputation points.", + [reviewer.id, assignment.id, assignment.dueDate] + ); + } + + if (!reviewer.expertise.includes(assignment.topic)) { + addFinding( + findings, + reviewer.trustTier === "early-career" ? "high" : "medium", + "reviewer-expertise-mismatch", + `${assignment.id} topic ${assignment.topic} is outside ${reviewer.displayName}'s declared expertise.`, + "Add a mentor, reassign the review, or prevent reputation penalties from mismatched work.", + [reviewer.id, assignment.id, assignment.topic] + ); + } + + if (reviewer.trustTier === "early-career" && (assignment.reputationDelta.decline < -4 || assignment.reputationDelta.late < -6)) { + addFinding( + findings, + "medium", + "early-career-reviewer-high-penalty-risk", + `${assignment.id} applies high negative deltas to an early-career reviewer.`, + "Route the assignment through mentor review before applying profile or leaderboard penalties.", + [reviewer.id, assignment.id] + ); + } + } + + const concentration = concentrationSummary(project); + const top = concentration.entries[0]; + if (top && top.ratio > project.policy.maxReviewerConcentrationRatio) { + addFinding( + findings, + "medium", + "review-credit-concentration-too-high", + `${top.reviewerId} received ${Math.round(top.ratio * 100)}% of recent review credit.`, + "Spread review opportunities before awarding additional leaderboard-affecting points.", + [top.reviewerId] + ); + } + + const severitySummary = findings.reduce( + (summary, finding) => { + summary[finding.severity] += 1; + return summary; + }, + { critical: 0, high: 0, medium: 0, low: 0 } + ); + const score = Math.max(0, 100 - findings.reduce((sum, finding) => sum + SEVERITY_WEIGHTS[finding.severity], 0)); + + return { findings, decisions, concentration, severitySummary, score }; +} + +function decisionFromEvaluation(evaluation) { + if (evaluation.severitySummary.critical > 0) { + return "block-reputation-scoring-until-workload-is-fair"; + } + if (evaluation.severitySummary.high > 0 || evaluation.score < 75) { + return "hold-leaderboard-and-profile-deltas-for-steward-review"; + } + if (evaluation.score < 90) { + return "manual-equity-review-before-scoring"; + } + return "workload-equity-ready"; +} + +function buildReviewPacket(project) { + const evaluation = evaluateWorkloadEquity(project); + return { + guard: "reviewer-workload-equity-guard", + issue: "SCIBASE-AI/SCIBASE.AI#15", + asOfDate: project.asOfDate, + decision: decisionFromEvaluation(evaluation), + score: evaluation.score, + severitySummary: evaluation.severitySummary, + findings: evaluation.findings, + assignmentDecisions: evaluation.decisions, + concentration: evaluation.concentration, + safety: [ + "Synthetic reviewer, assignment, availability, and reputation data only", + "No profile writes, leaderboard writes, identity calls, email calls, or external review system calls", + "No private reviewer identities, credentials, moderation records, or live reputation mutations" + ] + }; +} + +function renderMarkdownReport(packet) { + const lines = [ + "# Reviewer Workload Equity Guard", + "", + `Issue: ${packet.issue}`, + `Decision: ${packet.decision}`, + `Score: ${packet.score}`, + "", + "## Severity Summary", + "", + "| Severity | Count |", + "| --- | ---: |" + ]; + + for (const severity of ["critical", "high", "medium", "low"]) { + lines.push(`| ${severity} | ${packet.severitySummary[severity]} |`); + } + + lines.push("", "## Assignment Decisions", ""); + for (const decision of packet.assignmentDecisions) { + lines.push(`- ${decision.assignmentId}: ${decision.decision}`); + if (decision.protectedReasons.length > 0) { + lines.push(` - Protected reasons: ${decision.protectedReasons.join(", ")}`); + } + } + + lines.push("", "## Findings", ""); + for (const finding of packet.findings) { + lines.push(`- **${finding.severity} / ${finding.rule}**: ${finding.message}`); + lines.push(` - Action: ${finding.action}`); + lines.push(` - Refs: ${finding.refs.join(", ") || "none"}`); + } + + lines.push("", "## Safety", ""); + for (const item of packet.safety) { + lines.push(`- ${item}`); + } + + return `${lines.join("\n")}\n`; +} + +function renderSvgSummary(packet) { + const scoreWidth = Math.max(44, Math.min(760, packet.score * 7.6)); + return ` + + Reviewer Workload Equity Guard + SCIBASE #15 reputation scoring fairness checkpoint + + ${packet.decision} + Critical ${packet.severitySummary.critical} | High ${packet.severitySummary.high} | Findings ${packet.findings.length} + + Equity score + + + ${packet.score}/100 + + Suppress unfair negative deltas + Checks capacity, opt-outs, rest windows, availability, expertise, and credit concentration before scoring. + +`; +} + +module.exports = { + assignmentRiskReasons, + buildReviewPacket, + decisionFromEvaluation, + evaluateWorkloadEquity, + renderMarkdownReport, + renderSvgSummary +}; diff --git a/reviewer-workload-equity-guard/package.json b/reviewer-workload-equity-guard/package.json new file mode 100644 index 00000000..6f587c80 --- /dev/null +++ b/reviewer-workload-equity-guard/package.json @@ -0,0 +1,14 @@ +{ + "name": "reviewer-workload-equity-guard", + "version": "1.0.0", + "description": "Deterministic reputation fairness guard for peer-review workload and availability.", + "main": "index.js", + "private": true, + "type": "commonjs", + "scripts": { + "check": "node --check index.js && node --check sample-data.js && node --check demo.js && node --check demo-video.js && node --check test.js", + "test": "node test.js", + "demo": "node demo.js", + "demo:video": "node demo-video.js" + } +} diff --git a/reviewer-workload-equity-guard/reports/demo.webm b/reviewer-workload-equity-guard/reports/demo.webm new file mode 100644 index 0000000000000000000000000000000000000000..e646fac71bfcd1e4fd5b694f193f5f152a9c0970 GIT binary patch literal 52548 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
Crdh|-G_CdS*~Qr;HDS%c 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

r$O^|^GhNCub2fNETDCn(kOAyA9B$n+0 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 literal 0 HcmV?d00001 diff --git a/reviewer-workload-equity-guard/reports/reviewer-packet.md b/reviewer-workload-equity-guard/reports/reviewer-packet.md new file mode 100644 index 00000000..38c49072 --- /dev/null +++ b/reviewer-workload-equity-guard/reports/reviewer-packet.md @@ -0,0 +1,65 @@ +# Reviewer Workload Equity Guard + +Issue: SCIBASE-AI/SCIBASE.AI#15 +Decision: block-reputation-scoring-until-workload-is-fair +Score: 0 + +## Severity Summary + +| Severity | Count | +| --- | ---: | +| critical | 2 | +| high | 6 | +| medium | 3 | +| low | 0 | + +## Assignment Decisions + +- asg-105: suppress-negative-reputation-delta + - Protected reasons: over_capacity, rest_window +- asg-106: steward-review-before-scoring + - Protected reasons: weekly_hours_exceeded, expertise_mismatch +- asg-107: suppress-negative-reputation-delta + - Protected reasons: unavailable, opted_out + +## Findings + +- **high / open-review-load-exceeded**: A. Rowan would hold 4 open reviews after asg-105. + - Action: Reassign or queue the request before applying reputation penalties or leaderboard effects. + - Refs: rev-001, asg-105 +- **high / weekly-review-hour-budget-exceeded**: A. Rowan would reach 13 review hours this week. + - Action: Suppress negative reputation deltas and route the request to a reviewer with available capacity. + - Refs: rev-001, asg-105 +- **medium / reviewer-rest-window-too-short**: A. Rowan completed a review on 2026-05-21. + - Action: Defer the new request or remove negative reputation effects until the rest window is met. + - Refs: rev-001, asg-105 +- **high / assignment-due-during-unavailable-window**: asg-105 is due while A. Rowan is unavailable. + - Action: Move the due date or reassign before the review can change reputation points. + - Refs: rev-001, asg-105, 2026-05-24 +- **high / weekly-review-hour-budget-exceeded**: M. Quinn would reach 10 review hours this week. + - Action: Suppress negative reputation deltas and route the request to a reviewer with available capacity. + - Refs: rev-002, asg-106 +- **high / reviewer-expertise-mismatch**: asg-106 topic quantum-simulation is outside M. Quinn's declared expertise. + - Action: Add a mentor, reassign the review, or prevent reputation penalties from mismatched work. + - Refs: rev-002, asg-106, quantum-simulation +- **medium / early-career-reviewer-high-penalty-risk**: asg-106 applies high negative deltas to an early-career reviewer. + - Action: Route the assignment through mentor review before applying profile or leaderboard penalties. + - Refs: rev-002, asg-106 +- **critical / reviewer-unavailable-but-penalized**: S. Imani is unavailable but asg-107 still carries decline and late penalties. + - Action: Block decline/late reputation penalties while the reviewer is unavailable. + - Refs: rev-003, asg-107 +- **critical / reviewer-opt-out-active**: S. Imani has opted out until 2026-05-30. + - Action: Do not assign reputation-affecting reviews until the opt-out expires. + - Refs: rev-003, asg-107, 2026-05-30 +- **high / assignment-due-during-unavailable-window**: asg-107 is due while S. Imani is unavailable. + - Action: Move the due date or reassign before the review can change reputation points. + - Refs: rev-003, asg-107, 2026-05-26 +- **medium / review-credit-concentration-too-high**: rev-001 received 67% of recent review credit. + - Action: Spread review opportunities before awarding additional leaderboard-affecting points. + - Refs: rev-001 + +## Safety + +- Synthetic reviewer, assignment, availability, and reputation data only +- No profile writes, leaderboard writes, identity calls, email calls, or external review system calls +- No private reviewer identities, credentials, moderation records, or live reputation mutations diff --git a/reviewer-workload-equity-guard/reports/summary.json b/reviewer-workload-equity-guard/reports/summary.json new file mode 100644 index 00000000..7be59258 --- /dev/null +++ b/reviewer-workload-equity-guard/reports/summary.json @@ -0,0 +1,191 @@ +{ + "guard": "reviewer-workload-equity-guard", + "issue": "SCIBASE-AI/SCIBASE.AI#15", + "asOfDate": "2026-05-22", + "decision": "block-reputation-scoring-until-workload-is-fair", + "score": 0, + "severitySummary": { + "critical": 2, + "high": 6, + "medium": 3, + "low": 0 + }, + "findings": [ + { + "severity": "high", + "rule": "open-review-load-exceeded", + "message": "A. Rowan would hold 4 open reviews after asg-105.", + "action": "Reassign or queue the request before applying reputation penalties or leaderboard effects.", + "refs": [ + "rev-001", + "asg-105" + ] + }, + { + "severity": "high", + "rule": "weekly-review-hour-budget-exceeded", + "message": "A. Rowan would reach 13 review hours this week.", + "action": "Suppress negative reputation deltas and route the request to a reviewer with available capacity.", + "refs": [ + "rev-001", + "asg-105" + ] + }, + { + "severity": "medium", + "rule": "reviewer-rest-window-too-short", + "message": "A. Rowan completed a review on 2026-05-21.", + "action": "Defer the new request or remove negative reputation effects until the rest window is met.", + "refs": [ + "rev-001", + "asg-105" + ] + }, + { + "severity": "high", + "rule": "assignment-due-during-unavailable-window", + "message": "asg-105 is due while A. Rowan is unavailable.", + "action": "Move the due date or reassign before the review can change reputation points.", + "refs": [ + "rev-001", + "asg-105", + "2026-05-24" + ] + }, + { + "severity": "high", + "rule": "weekly-review-hour-budget-exceeded", + "message": "M. Quinn would reach 10 review hours this week.", + "action": "Suppress negative reputation deltas and route the request to a reviewer with available capacity.", + "refs": [ + "rev-002", + "asg-106" + ] + }, + { + "severity": "high", + "rule": "reviewer-expertise-mismatch", + "message": "asg-106 topic quantum-simulation is outside M. Quinn's declared expertise.", + "action": "Add a mentor, reassign the review, or prevent reputation penalties from mismatched work.", + "refs": [ + "rev-002", + "asg-106", + "quantum-simulation" + ] + }, + { + "severity": "medium", + "rule": "early-career-reviewer-high-penalty-risk", + "message": "asg-106 applies high negative deltas to an early-career reviewer.", + "action": "Route the assignment through mentor review before applying profile or leaderboard penalties.", + "refs": [ + "rev-002", + "asg-106" + ] + }, + { + "severity": "critical", + "rule": "reviewer-unavailable-but-penalized", + "message": "S. Imani is unavailable but asg-107 still carries decline and late penalties.", + "action": "Block decline/late reputation penalties while the reviewer is unavailable.", + "refs": [ + "rev-003", + "asg-107" + ] + }, + { + "severity": "critical", + "rule": "reviewer-opt-out-active", + "message": "S. Imani has opted out until 2026-05-30.", + "action": "Do not assign reputation-affecting reviews until the opt-out expires.", + "refs": [ + "rev-003", + "asg-107", + "2026-05-30" + ] + }, + { + "severity": "high", + "rule": "assignment-due-during-unavailable-window", + "message": "asg-107 is due while S. Imani is unavailable.", + "action": "Move the due date or reassign before the review can change reputation points.", + "refs": [ + "rev-003", + "asg-107", + "2026-05-26" + ] + }, + { + "severity": "medium", + "rule": "review-credit-concentration-too-high", + "message": "rev-001 received 67% of recent review credit.", + "action": "Spread review opportunities before awarding additional leaderboard-affecting points.", + "refs": [ + "rev-001" + ] + } + ], + "assignmentDecisions": [ + { + "assignmentId": "asg-105", + "reviewerId": "rev-001", + "decision": "suppress-negative-reputation-delta", + "protectedReasons": [ + "over_capacity", + "rest_window" + ], + "allowedCompletionDelta": 8, + "blockedDeclineDelta": -4, + "blockedLateDelta": -6 + }, + { + "assignmentId": "asg-106", + "reviewerId": "rev-002", + "decision": "steward-review-before-scoring", + "protectedReasons": [ + "weekly_hours_exceeded", + "expertise_mismatch" + ], + "allowedCompletionDelta": 0, + "blockedDeclineDelta": -5, + "blockedLateDelta": -8 + }, + { + "assignmentId": "asg-107", + "reviewerId": "rev-003", + "decision": "suppress-negative-reputation-delta", + "protectedReasons": [ + "unavailable", + "opted_out" + ], + "allowedCompletionDelta": 6, + "blockedDeclineDelta": -3, + "blockedLateDelta": -5 + } + ], + "concentration": { + "total": 6, + "entries": [ + { + "reviewerId": "rev-001", + "count": 4, + "ratio": 0.6666666666666666 + }, + { + "reviewerId": "rev-002", + "count": 1, + "ratio": 0.16666666666666666 + }, + { + "reviewerId": "rev-003", + "count": 1, + "ratio": 0.16666666666666666 + } + ] + }, + "safety": [ + "Synthetic reviewer, assignment, availability, and reputation data only", + "No profile writes, leaderboard writes, identity calls, email calls, or external review system calls", + "No private reviewer identities, credentials, moderation records, or live reputation mutations" + ] +} diff --git a/reviewer-workload-equity-guard/reports/summary.svg b/reviewer-workload-equity-guard/reports/summary.svg new file mode 100644 index 00000000..14609bca --- /dev/null +++ b/reviewer-workload-equity-guard/reports/summary.svg @@ -0,0 +1,16 @@ + + + Reviewer Workload Equity Guard + SCIBASE #15 reputation scoring fairness checkpoint + + block-reputation-scoring-until-workload-is-fair + Critical 2 | High 6 | Findings 11 + + Equity score + + + 0/100 + + Suppress unfair negative deltas + Checks capacity, opt-outs, rest windows, availability, expertise, and credit concentration before scoring. + diff --git a/reviewer-workload-equity-guard/requirements-map.md b/reviewer-workload-equity-guard/requirements-map.md new file mode 100644 index 00000000..242ead56 --- /dev/null +++ b/reviewer-workload-equity-guard/requirements-map.md @@ -0,0 +1,18 @@ +# Requirements Map + +Issue: `SCIBASE-AI/SCIBASE.AI#15` + +| Issue requirement | Implementation | +| --- | --- | +| Community and user reputation | Evaluates peer-review assignments before profile reputation, badges, leaderboards, or timeline effects are applied. | +| Fair reputation scoring | Suppresses unfair decline and late penalties when reviewers are overloaded, unavailable, opted out, resting, or assigned outside expertise. | +| Reviewer profile safety | Produces assignment-level decisions that separate allowed completion gains from blocked negative deltas. | +| Community health | Detects concentration of review credit so reputation opportunities are not captured by a small group of reviewers. | +| Non-overlap with existing slices | Focuses on workload equity and unfair penalty suppression, not recusal, timeliness scoring, edit history, profile visibility, civility, or template rubrics. | +| Safe local validation | Includes dependency-free tests and demo generation from synthetic reviewer and assignment metadata only. | + +## Non-goals + +- No live profile, leaderboard, badge, identity, moderation, or email mutations. +- No private reviewer data, credentials, real user records, or external calls. +- No replacement for recusal, calibration, mentorship, appeal, or rubric workflows. diff --git a/reviewer-workload-equity-guard/sample-data.js b/reviewer-workload-equity-guard/sample-data.js new file mode 100644 index 00000000..eaef4078 --- /dev/null +++ b/reviewer-workload-equity-guard/sample-data.js @@ -0,0 +1,98 @@ +const project = { + asOfDate: "2026-05-22", + policy: { + maxOpenReviews: 3, + maxWeeklyReviewHours: 10, + minimumRestDays: 2, + concentrationWindowDays: 14, + maxReviewerConcentrationRatio: 0.35, + acceptedAvailabilityStatuses: ["available", "limited"], + protectedPenaltyReasons: ["over_capacity", "unavailable", "rest_window", "opted_out"] + }, + reviewers: [ + { + id: "rev-001", + displayName: "A. Rowan", + trustTier: "senior", + availabilityStatus: "limited", + weeklyCapacityHours: 8, + recentReviewHours: 9, + openReviewIds: ["asg-101", "asg-102", "asg-103"], + lastCompletedReviewAt: "2026-05-21", + optOutUntil: null, + unavailableWindows: [{ startsAt: "2026-05-23", endsAt: "2026-05-27", reason: "fieldwork" }], + expertise: ["astrobiology", "data-curation"], + profileImpactAllowed: true + }, + { + id: "rev-002", + displayName: "M. Quinn", + trustTier: "early-career", + availabilityStatus: "available", + weeklyCapacityHours: 6, + recentReviewHours: 5, + openReviewIds: ["asg-104"], + lastCompletedReviewAt: "2026-05-16", + optOutUntil: null, + unavailableWindows: [], + expertise: ["statistics", "survey-design"], + profileImpactAllowed: true + }, + { + id: "rev-003", + displayName: "S. Imani", + trustTier: "senior", + availabilityStatus: "unavailable", + weeklyCapacityHours: 10, + recentReviewHours: 1, + openReviewIds: [], + lastCompletedReviewAt: "2026-05-10", + optOutUntil: "2026-05-30", + unavailableWindows: [{ startsAt: "2026-05-20", endsAt: "2026-05-31", reason: "leave" }], + expertise: ["machine-learning", "statistics"], + profileImpactAllowed: true + } + ], + pendingAssignments: [ + { + id: "asg-105", + reviewerId: "rev-001", + topic: "data-curation", + dueDate: "2026-05-24", + estimatedHours: 4, + status: "pending", + reputationDelta: { completion: 8, decline: -4, late: -6 }, + affectsLeaderboard: true + }, + { + id: "asg-106", + reviewerId: "rev-002", + topic: "quantum-simulation", + dueDate: "2026-05-25", + estimatedHours: 5, + status: "pending", + reputationDelta: { completion: 10, decline: -5, late: -8 }, + affectsLeaderboard: true + }, + { + id: "asg-107", + reviewerId: "rev-003", + topic: "machine-learning", + dueDate: "2026-05-26", + estimatedHours: 3, + status: "pending", + reputationDelta: { completion: 6, decline: -3, late: -5 }, + affectsLeaderboard: true + } + ], + recentAssignmentHistory: [ + { id: "hist-001", reviewerId: "rev-001", assignedAt: "2026-05-12", pointsAwarded: 8 }, + { id: "hist-002", reviewerId: "rev-001", assignedAt: "2026-05-13", pointsAwarded: 7 }, + { id: "hist-003", reviewerId: "rev-001", assignedAt: "2026-05-16", pointsAwarded: 8 }, + { id: "hist-004", reviewerId: "rev-001", assignedAt: "2026-05-19", pointsAwarded: 9 }, + { id: "hist-005", reviewerId: "rev-002", assignedAt: "2026-05-17", pointsAwarded: 6 }, + { id: "hist-006", reviewerId: "rev-003", assignedAt: "2026-05-20", pointsAwarded: 5 } + ] +}; + +module.exports = { project }; diff --git a/reviewer-workload-equity-guard/test.js b/reviewer-workload-equity-guard/test.js new file mode 100644 index 00000000..d5d3a090 --- /dev/null +++ b/reviewer-workload-equity-guard/test.js @@ -0,0 +1,106 @@ +const assert = require("assert"); +const { project } = require("./sample-data"); +const { + assignmentRiskReasons, + buildReviewPacket, + evaluateWorkloadEquity, + renderMarkdownReport, + renderSvgSummary +} = require("./index"); + +const evaluation = evaluateWorkloadEquity(project); +const packet = buildReviewPacket(project); + +assert.strictEqual(packet.guard, "reviewer-workload-equity-guard"); +assert.strictEqual(packet.issue, "SCIBASE-AI/SCIBASE.AI#15"); +assert.strictEqual(packet.decision, "block-reputation-scoring-until-workload-is-fair"); + +assert.ok( + evaluation.findings.some((finding) => finding.rule === "open-review-load-exceeded"), + "expected open review load finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "weekly-review-hour-budget-exceeded"), + "expected weekly hour budget finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "reviewer-unavailable-but-penalized"), + "expected unavailable reviewer penalty finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "reviewer-opt-out-active"), + "expected opt-out finding" +); +assert.ok( + evaluation.findings.some((finding) => finding.rule === "review-credit-concentration-too-high"), + "expected concentration finding" +); + +const overloadedReviewer = project.reviewers.find((reviewer) => reviewer.id === "rev-001"); +const overloadedAssignment = project.pendingAssignments.find((assignment) => assignment.id === "asg-105"); +const reasons = assignmentRiskReasons(project, overloadedReviewer, overloadedAssignment); +assert.ok(reasons.includes("over_capacity")); +assert.ok(reasons.includes("weekly_hours_exceeded")); + +const cleanProject = JSON.parse(JSON.stringify(project)); +cleanProject.reviewers.forEach((reviewer) => { + reviewer.availabilityStatus = "available"; + reviewer.weeklyCapacityHours = 14; + reviewer.recentReviewHours = 1; + reviewer.openReviewIds = []; + reviewer.lastCompletedReviewAt = "2026-05-15"; + reviewer.optOutUntil = null; + reviewer.unavailableWindows = []; +}); +cleanProject.pendingAssignments = [ + { + id: "asg-clean-1", + reviewerId: "rev-001", + topic: "data-curation", + dueDate: "2026-05-29", + estimatedHours: 2, + status: "pending", + reputationDelta: { completion: 5, decline: -1, late: -2 }, + affectsLeaderboard: false + }, + { + id: "asg-clean-2", + reviewerId: "rev-002", + topic: "statistics", + dueDate: "2026-05-29", + estimatedHours: 2, + status: "pending", + reputationDelta: { completion: 5, decline: -1, late: -2 }, + affectsLeaderboard: false + }, + { + id: "asg-clean-3", + reviewerId: "rev-003", + topic: "machine-learning", + dueDate: "2026-05-29", + estimatedHours: 2, + status: "pending", + reputationDelta: { completion: 5, decline: -1, late: -2 }, + affectsLeaderboard: false + } +]; +cleanProject.recentAssignmentHistory = [ + { id: "hist-clean-1", reviewerId: "rev-001", assignedAt: "2026-05-18", pointsAwarded: 3 }, + { id: "hist-clean-2", reviewerId: "rev-002", assignedAt: "2026-05-18", pointsAwarded: 3 }, + { id: "hist-clean-3", reviewerId: "rev-003", assignedAt: "2026-05-18", pointsAwarded: 3 } +]; +cleanProject.policy.maxReviewerConcentrationRatio = 0.34; + +const cleanPacket = buildReviewPacket(cleanProject); +assert.strictEqual(cleanPacket.decision, "workload-equity-ready"); +assert.strictEqual(cleanPacket.findings.length, 0); + +const markdown = renderMarkdownReport(packet); +assert.ok(markdown.includes("## Assignment Decisions")); +assert.ok(markdown.includes("suppress-negative-reputation-delta")); + +const svg = renderSvgSummary(packet); +assert.ok(svg.includes("