Skip to content

Commit 4a48493

Browse files
committed
feat: 完成阶段六结果处理层(deduper + report_generator)
1 parent 19da435 commit 4a48493

2 files changed

Lines changed: 536 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# tRPC-Agent-Python is licensed under Apache-2.0.
6+
"""Finding deduplication and noise reduction for the code review agent.
7+
8+
Provides:
9+
- Deduplicator: Removes duplicate findings (same file + same line + same category).
10+
- ConfidenceGrader: Classifies findings by confidence level.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
from typing import Optional
16+
17+
from .models import Confidence, Finding, Severity
18+
19+
20+
# ──────────────────────────────────────────────
21+
# Confidence scoring helpers
22+
# ──────────────────────────────────────────────
23+
24+
# Severity → numeric weight for tie-breaking
25+
_SEVERITY_WEIGHT: dict[str, int] = {
26+
"critical": 5,
27+
"high": 4,
28+
"medium": 3,
29+
"low": 2,
30+
"warning": 1,
31+
"info": 0,
32+
}
33+
34+
_CONFIDENCE_ORDER: dict[str, int] = {
35+
"high": 3,
36+
"medium": 2,
37+
"low": 1,
38+
}
39+
40+
41+
def _finding_score(finding: Finding) -> int:
42+
"""Compute a numeric score for a finding (higher = more important)."""
43+
sev = _SEVERITY_WEIGHT.get(finding.severity.value, 0)
44+
conf = _CONFIDENCE_ORDER.get(finding.confidence.value, 0)
45+
return sev * 10 + conf
46+
47+
48+
# ──────────────────────────────────────────────
49+
# 6.1 + 6.2: Deduplicator
50+
# ──────────────────────────────────────────────
51+
52+
53+
class Deduplicator:
54+
"""Deduplicates findings and classifies them by confidence.
55+
56+
Dedup rule: same file + same line + same category → keep only one (highest confidence).
57+
If confidence ties, keep the one with highest severity.
58+
"""
59+
60+
def deduplicate(self, findings: list[Finding]) -> list[Finding]:
61+
"""Remove duplicate findings.
62+
63+
Two findings are considered duplicates if they share the same
64+
``file``, ``line``, and ``category``. Only the highest-scoring
65+
finding is retained.
66+
67+
Args:
68+
findings: List of findings to deduplicate.
69+
70+
Returns:
71+
Deduplicated list of findings.
72+
"""
73+
if not findings:
74+
return []
75+
76+
# Group by dedup key
77+
groups: dict[str, list[Finding]] = {}
78+
for f in findings:
79+
key = f"{f.file}:{f.line}:{f.category.value}"
80+
groups.setdefault(key, []).append(f)
81+
82+
# Keep the best finding per group
83+
result: list[Finding] = []
84+
for key, group in groups.items():
85+
best = max(group, key=_finding_score)
86+
best.dedup_key = key
87+
result.append(best)
88+
89+
return result
90+
91+
def classify(
92+
self,
93+
findings: list[Finding],
94+
high_threshold: float = 0.7,
95+
warning_threshold: float = 0.5,
96+
review_threshold: float = 0.3,
97+
) -> tuple[list[Finding], list[Finding], list[Finding]]:
98+
"""Classify findings into three confidence tiers.
99+
100+
The classification is based on the finding's ``confidence`` field:
101+
- ``high`` → ``findings`` (high-confidence findings)
102+
- ``medium`` → ``warnings`` (medium-confidence, needs attention)
103+
- ``low`` → ``needs_human_review`` (low-confidence, human must verify)
104+
105+
Args:
106+
findings: Deduplicated findings.
107+
high_threshold: Unused (reserved for future numeric scoring).
108+
warning_threshold: Unused (reserved for future numeric scoring).
109+
review_threshold: Unused (reserved for future numeric scoring).
110+
111+
Returns:
112+
Tuple of (high_confidence_findings, warnings, needs_human_review).
113+
"""
114+
high_conf: list[Finding] = []
115+
warnings: list[Finding] = []
116+
needs_review: list[Finding] = []
117+
118+
for f in findings:
119+
if f.confidence == Confidence.HIGH:
120+
high_conf.append(f)
121+
elif f.confidence == Confidence.MEDIUM:
122+
warnings.append(f)
123+
else:
124+
needs_review.append(f)
125+
126+
return high_conf, warnings, needs_review
127+
128+
def process(
129+
self,
130+
findings: list[Finding],
131+
) -> tuple[list[Finding], list[Finding], list[Finding]]:
132+
"""Full pipeline: deduplicate then classify.
133+
134+
Args:
135+
findings: Raw findings from analysis.
136+
137+
Returns:
138+
Tuple of (findings, warnings, needs_human_review).
139+
"""
140+
deduped = self.deduplicate(findings)
141+
return self.classify(deduped)
142+
143+
144+
# Convenience function
145+
def process_findings(
146+
findings: list[Finding],
147+
) -> tuple[list[Finding], list[Finding], list[Finding]]:
148+
"""Deduplicate and classify findings in one call."""
149+
return Deduplicator().process(findings)

0 commit comments

Comments
 (0)