Skip to content

Commit c88112a

Browse files
committed
feat(examples): add finding model with dedup and confidence gating
1 parent 7eda4e1 commit c88112a

2 files changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 model plus dedup and confidence-gating helpers."""
7+
from pydantic import BaseModel
8+
9+
SEVERITY_ORDER = {"critical": 4, "high": 3, "medium": 2, "low": 1, "info": 0}
10+
CONFIDENCE_THRESHOLD = 0.6
11+
12+
13+
class Finding(BaseModel):
14+
"""One structured review finding."""
15+
16+
severity: str
17+
category: str
18+
file: str
19+
line: int
20+
title: str
21+
evidence: str = ""
22+
recommendation: str = ""
23+
confidence: float = 1.0
24+
source: str = "static"
25+
26+
@property
27+
def dedup_key(self) -> str:
28+
return f"{self.file}:{self.line}:{self.category}"
29+
30+
31+
def _merge(winner: Finding, loser: Finding) -> Finding:
32+
update = {}
33+
if SEVERITY_ORDER.get(loser.severity, 0) > SEVERITY_ORDER.get(winner.severity, 0):
34+
update["severity"] = loser.severity
35+
if loser.confidence > winner.confidence:
36+
update["confidence"] = loser.confidence
37+
if loser.source != winner.source:
38+
update["source"] = "static+llm"
39+
return winner.model_copy(update=update) if update else winner
40+
41+
42+
def dedupe(findings):
43+
"""Collapse findings sharing (file, line, category). Returns (kept, dropped)."""
44+
kept: dict[str, Finding] = {}
45+
dropped = []
46+
for f in findings:
47+
cur = kept.get(f.dedup_key)
48+
if cur is None:
49+
kept[f.dedup_key] = f
50+
else:
51+
kept[f.dedup_key] = _merge(cur, f)
52+
dropped.append(f)
53+
return list(kept.values()), dropped
54+
55+
56+
def gate(findings, threshold: float = CONFIDENCE_THRESHOLD):
57+
"""Split into (reported, needs_human_review) by confidence."""
58+
reported = [f for f in findings if f.confidence >= threshold]
59+
needs = [f for f in findings if f.confidence < threshold]
60+
return reported, needs
61+
62+
63+
def severity_distribution(findings):
64+
"""Count findings per severity."""
65+
dist: dict[str, int] = {}
66+
for f in findings:
67+
dist[f.severity] = dist.get(f.severity, 0) + 1
68+
return dist
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
"""Tests for the Finding model, dedup and confidence gating."""
7+
from review.findings import Finding, dedupe, gate, severity_distribution
8+
9+
10+
def make(severity="high", category="security", file="a.py", line=1,
11+
confidence=0.9, source="static", title="t"):
12+
return Finding(severity=severity, category=category, file=file, line=line,
13+
title=title, confidence=confidence, source=source)
14+
15+
16+
def test_dedup_same_file_line_category():
17+
kept, dropped = dedupe([make(title="eval"), make(title="exec", confidence=0.8)])
18+
assert len(kept) == 1 and len(dropped) == 1
19+
assert kept[0].confidence == 0.9
20+
21+
22+
def test_dedup_merges_sources():
23+
kept, _ = dedupe([make(source="static"), make(source="llm", confidence=0.7)])
24+
assert kept[0].source == "static+llm"
25+
26+
27+
def test_dedup_keeps_highest_severity():
28+
kept, _ = dedupe([make(severity="medium"), make(severity="critical", confidence=0.5)])
29+
assert kept[0].severity == "critical"
30+
31+
32+
def test_no_dedup_across_lines():
33+
kept, dropped = dedupe([make(line=1), make(line=2)])
34+
assert len(kept) == 2 and dropped == []
35+
36+
37+
def test_gate_splits_low_confidence():
38+
reported, needs = gate([make(confidence=0.9), make(line=2, confidence=0.5)])
39+
assert len(reported) == 1 and len(needs) == 1
40+
assert needs[0].confidence == 0.5
41+
42+
43+
def test_severity_distribution():
44+
dist = severity_distribution([make(), make(line=2, severity="low")])
45+
assert dist == {"high": 1, "low": 1}

0 commit comments

Comments
 (0)