@@ -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
10941129Automated warnings:
10951130{ farming_warnings_str }
@@ -1148,7 +1183,7 @@ def _date10(value: Any) -> str:
11481183Produce a thorough, evidence-based assessment. Use the actual PR discussion threads above.
11491184
11501185Key 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.
115311882. Discussion quality: In the sampled PRs, does the author engage with maintainer feedback? Do maintainers respond at all?
115411893. Commit message quality: Are they descriptive and thoughtful, or generic "fix", "update", "Initial commit"?
0 commit comments