Skip to content

Commit 13353d4

Browse files
committed
Require substantive work and meaningful reviewer dialogue in score calibration
1 parent 607a895 commit 13353d4

1 file changed

Lines changed: 39 additions & 4 deletions

File tree

analyzer.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,20 @@ async def fetch_one(pr: dict) -> dict:
521521
1 for c in (comments if isinstance(comments, list) else [])
522522
if (c.get("user") or {}).get("login", "") != author_login
523523
)
524+
review_list = reviews if isinstance(reviews, list) else []
525+
meaningful_states = {"CHANGES_REQUESTED", "COMMENTED"}
526+
nontrivial_review_bodies = 0
527+
for r in review_list:
528+
body = (r.get("body") or "").strip().lower()
529+
if len(body) < 20:
530+
continue
531+
if any(tok in body for tok in ("lgtm", "looks good", "nice work", "thanks")):
532+
continue
533+
nontrivial_review_bodies += 1
534+
meaningful_interaction = reviewer_comments > 0 and (
535+
any((r.get("state") or "") in meaningful_states for r in review_list)
536+
or nontrivial_review_bodies > 0
537+
)
524538

525539
result = {
526540
"repo": repo,
@@ -535,6 +549,8 @@ async def fetch_one(pr: dict) -> dict:
535549
"total_lines": pr_data.get("additions", 0) + pr_data.get("deletions", 0),
536550
"reviewer_comments": reviewer_comments,
537551
"review_states": [r.get("state") for r in (reviews if isinstance(reviews, list) else [])],
552+
"meaningful_interaction": meaningful_interaction,
553+
"nontrivial_review_bodies": nontrivial_review_bodies,
538554
"thread": "\n".join(thread_parts)[:2000],
539555
}
540556
await db.cache_set(cache_key, result, ttl=GH_CACHE_TTL)
@@ -617,6 +633,13 @@ def _is_release_maintenance(s: dict) -> bool:
617633

618634
rev_comments = [s.get("reviewer_comments", 0) for s in valid_samples]
619635
avg_reviewer_comments = sum(rev_comments) / len(rev_comments) if rev_comments else 0
636+
substantive_count = sum(
637+
1 for s in valid_samples
638+
if s.get("total_lines", 0) >= 30 or s.get("changed_files", 0) >= 3
639+
)
640+
meaningful_interaction_count = sum(1 for s in valid_samples if s.get("meaningful_interaction"))
641+
substantive_rate = substantive_count / len(valid_samples) if valid_samples else 0
642+
meaningful_interaction_rate = meaningful_interaction_count / len(valid_samples) if valid_samples else 0
620643

621644
return {
622645
"total_prs": total_prs,
@@ -635,6 +658,8 @@ def _is_release_maintenance(s: dict) -> bool:
635658
"trivial_pr_rate_pct": round(trivial_rate * 100, 1),
636659
"maintenance_small_pr_count": maintenance_small,
637660
"avg_reviewer_comments": round(avg_reviewer_comments, 1),
661+
"substantive_sample_rate_pct": round(substantive_rate * 100, 1),
662+
"meaningful_interaction_rate_pct": round(meaningful_interaction_rate * 100, 1),
638663
}
639664

640665

@@ -710,18 +735,22 @@ def calibrate_signal_consistency(overall: dict, activity_signals: dict) -> dict:
710735
merge = float(activity_signals.get("merge_rate_pct", 0) or 0)
711736
trivial = float(activity_signals.get("trivial_pr_rate_pct", 0) or 0)
712737
reviewer = float(activity_signals.get("avg_reviewer_comments", 0) or 0)
738+
meaningful = float(activity_signals.get("meaningful_interaction_rate_pct", 0) or 0)
739+
substantive = float(activity_signals.get("substantive_sample_rate_pct", 0) or 0)
713740
orgs = int(activity_signals.get("unique_orgs", 0) or 0)
714741
merged_n = int(activity_signals.get("merged_prs", 0) or 0)
715742
total = int(activity_signals.get("total_prs", 0) or 0)
716743

717-
if merge >= 70 and trivial <= 25 and reviewer >= 1:
744+
if merge >= 70 and trivial <= 25 and reviewer >= 1 and meaningful >= 35:
718745
score += 0.4
719746
if orgs >= 3 and merged_n >= 10:
720747
score += 0.3
721748
if total >= 10 and merge < 20:
722749
score -= 0.8
723750
if total >= 10 and merge < 40 and trivial > 55:
724751
score -= 0.5
752+
if meaningful < 25 and substantive < 40:
753+
score -= 0.4
725754

726755
score = max(0.0, min(10.0, score))
727756
adjusted["overall_score"] = round(score, 1)
@@ -735,9 +764,9 @@ def calibrate_signal_consistency(overall: dict, activity_signals: dict) -> dict:
735764
adjusted["contribution_quality"] = round(max(0.0, min(10.0, cq)), 1)
736765

737766
rec = str(adjusted.get("recommendation", "maybe"))
738-
if score >= 8.5:
767+
if score >= 8.5 and meaningful >= 40 and substantive >= 45:
739768
adjusted["recommendation"] = "strong yes"
740-
elif score >= 6.8 and rec in {"maybe", "no"}:
769+
elif score >= 6.8 and meaningful >= 30 and substantive >= 35 and rec in {"maybe", "no"}:
741770
adjusted["recommendation"] = "yes"
742771
elif score >= 5.5 and rec == "no":
743772
adjusted["recommendation"] = "maybe"
@@ -993,6 +1022,10 @@ def _date10(value: Any) -> str:
9931022
farming_warnings.append(
9941023
f"WARNING: {sig['prs_per_week']:.1f} PRs/week with weak merge outcomes can indicate low-signal volume."
9951024
)
1025+
if sig.get("meaningful_interaction_rate_pct", 100) < 30 and sig.get("sample_size", 0) >= 6:
1026+
farming_warnings.append(
1027+
f"WARNING: Only {sig['meaningful_interaction_rate_pct']}% of sampled PRs show meaningful reviewer dialogue."
1028+
)
9961029
if sig.get("avg_reviewer_comments", 10) < 1 and sig.get("total_prs", 0) >= 5:
9971030
farming_warnings.append(
9981031
f"WARNING: avg {sig['avg_reviewer_comments']:.1f} reviewer comments per PR — maintainers not engaging."
@@ -1090,6 +1123,8 @@ def _date10(value: Any) -> str:
10901123
Tiny non-maintenance PRs in sample (<10 lines, merged release chores excluded): {sig.get('trivial_pr_rate_pct', 0)}%
10911124
Small merged maintenance PRs (version/release/deps/changelog): {sig.get('maintenance_small_pr_count', 0)}
10921125
Avg reviewer engagement: {sig.get('avg_reviewer_comments', 0)} reviewer comments per PR
1126+
Substantive sampled PRs: {sig.get('substantive_sample_rate_pct', 0)}%
1127+
Meaningful reviewer interaction in sample: {sig.get('meaningful_interaction_rate_pct', 0)}%
10931128
10941129
Automated warnings:
10951130
{farming_warnings_str}
@@ -1148,7 +1183,7 @@ def _date10(value: Any) -> str:
11481183
Produce a thorough, evidence-based assessment. Use the actual PR discussion threads above.
11491184
11501185
Key things to evaluate:
1151-
1. FARMING vs genuine: Low merge rate + tiny non-maintenance diffs + no reviewer engagement = PR farming. Weight this heavily.
1186+
1. FARMING vs genuine: Low merge rate + tiny non-maintenance diffs + lack of meaningful reviewer dialogue = PR farming. Weight this heavily.
11521187
Do NOT over-penalize merged release/maintenance chores (version bumps, changelog, dependency updates) when they are accepted by maintainers.
11531188
2. Discussion quality: In the sampled PRs, does the author engage with maintainer feedback? Do maintainers respond at all?
11541189
3. Commit message quality: Are they descriptive and thoughtful, or generic "fix", "update", "Initial commit"?

0 commit comments

Comments
 (0)