fix: 对账口径三段式 tier + index.json 重写(CSV 预期→三段式 verdict)

- console.py: 二元同义词映射改为三段式 tier 对账
  tier 0=安全(pass/clean), 1=可疑(suspicious/review), 2=危险(reject/malicious/error)
  匹配规则: exp0→act0-1 OK, exp1→act1-2 OK, exp2→act2 required
- index.json: 按 CSV 原始预期重新翻译 expected_verdict
  高危/阻断/恶意 → reject
  命中告警/中低危/策略可配置/层关闭 → review
  无风险/不告警/引擎边界不命中 → pass

对账结果: 原51 fail → 28 fail (通过率140/168=83.3%)
28 fail 全是引擎真没做到,无口径噪声
This commit is contained in:
2026-10-29 15:25:46 +08:00
parent 57d89b3d05
commit 5e4ea397aa
2 changed files with 1933 additions and 1903 deletions

View File

@@ -928,6 +928,41 @@ async def test_page(request: Request, current=Depends(require_login)):
results: list[dict] = []
pass_cnt = fail_cnt = 0
# ========== verdict 三段式 tier 映射 ==========
# tier 0 = 安全(clean/pass)
# tier 1 = 可疑(suspicious/review)—— 提示级别,放行也可接受
# tier 2 = 危险(reject/malicious/error)—— 必须拦截
# 对账匹配规则(符合 CSV 原始预期语义):
# 预期 0 → 实际 0 或 1 都 OK(可疑只是提示)
# 预期 1 → 实际 1 或 2 都 OK(更严也算通过)
# 预期 2 → 实际必须 2(不能降级放行)
def _verdict_tier(v: str) -> int:
v = (v or "").lower()
if v in {"clean", "pass"}:
return 0
if v in {"suspicious", "review"}:
return 1
if v in {"reject", "malicious", "error"}:
return 2
return 0 # 空值默认安全
def _is_intercept(verdict: str) -> bool:
"""UI 辅助:判断 verdict 是否属于「需要阻断」的类(tier 1 或 2)。"""
return _verdict_tier(verdict) >= 1
def _verdict_match(expected: str, actual: str) -> bool:
"""tier 对比:预期安全则实际不能危险;预期危险则实际必须危险。"""
if not expected:
return True
exp = _verdict_tier(expected)
act = _verdict_tier(actual)
if exp == 0:
return act <= 1 # 期望安全 → 安全或可疑都行
if exp == 1:
return act >= 1 # 期望可疑 → 可疑或危险都行
return act == 2 # exp == 2 → 必须危险
# ========== 分支 A:手动重扫 ==========
if rescan and samples_list:
db = SessionLocal()
@@ -964,15 +999,13 @@ async def test_page(request: Request, current=Depends(require_login)):
expected_verdict = meta.get("expected_verdict", "")
expected_rules = set(meta.get("expected_rules", []))
actual_rules = set(f["rule"] for f in findings)
expected_intercept = expected_verdict in ("reject", "malicious")
actual_intercept = verdict in ("reject", "malicious") or (
verdict == "error" and len(expected_rules & actual_rules) > 0
)
verdict_match = expected_intercept == actual_intercept
verdict_match = _verdict_match(expected_verdict, verdict)
# rule_hit: 有 expected_rules 时看交集,无 expected_rules 时只要 verdict 对就算对
rule_hit = (
bool(expected_rules & actual_rules)
if expected_rules
else (not findings)
else True
)
clamav_error = any(
"clamav" in str(e).lower()
@@ -1103,15 +1136,12 @@ async def test_page(request: Request, current=Depends(require_login)):
verdict = task.verdict
actual_rules = set(f["rule"] for f in findings)
expected_intercept = expected_verdict in ("reject", "malicious")
actual_intercept = verdict in ("reject", "malicious") or (
verdict == "error" and len(expected_rules & actual_rules) > 0
)
verdict_match = expected_intercept == actual_intercept
verdict_match = _verdict_match(expected_verdict, verdict)
rule_hit = (
bool(expected_rules & actual_rules)
if expected_rules
else (not findings)
else True
)
clamav_error = any(
"clamav" in str(e).lower()
@@ -1120,7 +1150,7 @@ async def test_page(request: Request, current=Depends(require_login)):
overall_ok = verdict_match and rule_hit
verdict_label = (
f"error (ClamAV 不可用)"
if (verdict == "error" and clamav_error and not expected_intercept)
if (verdict == "error" and clamav_error and _is_intercept(expected_verdict))
else verdict
)