Files
gaokao-volunteer-system/scripts/check_crowd_db_consistency.py
Hermes Agent 4c732eb836
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled
fix(crowd_db): 高信任数据系统性修复 — 契约硬化+真相单一化+防漂移
review 发现数据本身真实达标(7 high / 20 usable / 0 skeleton),
但代码/文档/测试/元数据 4 层出现严重脱节,存在静默升级风险与
合规假象回归漏洞。

Phase 1: 契约硬化(P0)
- risk_report.py 新增 _classify_score_bands + _compute_quality_level
- 质量等级判定从"仅看 confidence"升级为综合门槛
  (conf + sr + recs + alts + 三层分数带),对齐 plan §4
- 新增 low 等级区分"已脱离骨架但未达可用"
- _load_provenance_metadata 改为优先 load_province 取完整数据
- finding_to_risk_dict 不再二次规范化已规范化数据
- quality_summary.py 增加 low 等级统计
- SCHEMA.md §6 同步完整门槛定义

Phase 2: 测试加固(P0)
- 新增 test_high_trust_thresholds.py 锁死 high/usable 完整门槛
  (plan §9.2 要求的"防静默升级"测试)
- 修复 test_crowd_db_data_quality.py 等级枚举支持 low
- 修复 test_risk_report.py 硬编码日期脆弱性

Phase 3: 真相源单一化(P0)
- CURRENT_STATE.md §0.5 清除 6/20 旧文案"4 high + 3 usable + 20 skeleton"
- 改为引用顶部状态词 + 历史轨迹仅供审计
- NATIONALIZATION §4 清除"当前 high 已扩展为 5 省"矛盾文案
- 顶部状态词从"Phase 0 收口中"升级为"已完成"

Phase 4: 元数据状态对齐(P1)
- hunan.json / sichuan.json trusted_sources.kind
  province_official_pending_review -> province_official
- 同步更新 quality_note 说明已完成 2025 年度复核
- 消除"状态 high/usable 但 kind=pending_review"的矛盾

Phase 5: 防漂移机制(P1)
- 新增 scripts/check_crowd_db_consistency.py
  跨文档+数据+测试白名单一致性检查(5 项检查)
- dev-verify.sh 接入 crowd_db quality summary 打印

验证:
- ruff: All checks passed
- mypy: Success, no issues in 16 source files
- pytest crowd_db/: 148 passed, 2 skipped
- pytest 全量: 1283 passed, 2 skipped (无回归)
- consistency check: high=7 usable=20 low=0 skeleton=0

Refs: docs/plans/2026-06-23-national-high-trust-crowd-db-plan.md §4/§9
2026-06-25 07:41:11 +08:00

153 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""crowd_db 跨文档/数据一致性检查(防漂移)。
检查项:
1. 实测质量分布 vs CURRENT_STATE.md 顶部状态词一致
2. 实测 high 白名单 vs test_crowd_db_data_quality.py HIGH_TRUST_PROVINCES 一致
3. trusted_sources.kind != province_official_pending_review 当 quality_level in (high, usable)
4. 所有省份 confidence 在 [0, 1] 范围内
5. 所有 data_year 一致(当前应为 2025
退出码:
- 0: 一致
- 非 0: 发现漂移(细节打印到 stdout
用法:
python scripts/check_crowd_db_consistency.py
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from data.crowd_db.loader import CrowdDBLoader # noqa: E402
from data.crowd_db.quality_summary import build_quality_summary # noqa: E402
def _extract_current_state_distribution() -> dict[str, int]:
"""从 CURRENT_STATE.md 顶部状态词解析质量分布。"""
path = ROOT / "docs" / "CURRENT_STATE.md"
text = path.read_text(encoding="utf-8")
# 匹配 "7 high / 20 usable / 0 skeleton"
match = re.search(
r"(\d+)\s*high\s*/\s*(\d+)\s*usable\s*/\s*(\d+)\s*skeleton",
text,
)
if not match:
return {"high": -1, "usable": -1, "skeleton": -1}
return {
"high": int(match.group(1)),
"usable": int(match.group(2)),
"skeleton": int(match.group(3)),
}
def _extract_test_whitelist() -> set[str]:
"""从 test_crowd_db_data_quality.py 解析 HIGH_TRUST_PROVINCES 白名单。"""
path = ROOT / "data" / "crowd_db" / "tests" / "test_crowd_db_data_quality.py"
text = path.read_text(encoding="utf-8")
match = re.search(
r"HIGH_TRUST_PROVINCES\s*=\s*frozenset\(\s*\{([^}]+)\}",
text,
)
if not match:
return set()
raw = match.group(1)
return set(re.findall(r'"([^"]+)"', raw))
def main() -> int:
issues: list[str] = []
# 实测
loader = CrowdDBLoader(warn_low_confidence=False)
summary = build_quality_summary(loader=loader)
actual_dist = summary["by_quality_level"]
actual_high = {
p["province"] for p in summary["provinces"] if p["quality_level"] == "high"
}
# 检查 1: 状态词一致性
doc_dist = _extract_current_state_distribution()
for level in ("high", "usable", "skeleton"):
if doc_dist[level] != actual_dist.get(level, 0):
issues.append(
f"[状态词漂移] CURRENT_STATE.md 声明 {level}={doc_dist[level]}, "
f"实测 {level}={actual_dist.get(level, 0)}"
)
# 检查 2: 测试白名单一致性
whitelist = _extract_test_whitelist()
if whitelist != actual_high:
missing = actual_high - whitelist
extra = whitelist - actual_high
if missing:
issues.append(f"[白名单漂移] 实测 high 但测试白名单缺失: {sorted(missing)}")
if extra:
issues.append(f"[白名单漂移] 测试白名单有但实测非 high: {sorted(extra)}")
# 检查 3: trusted_sources.kind 与 quality_level 一致性
for province in loader.list_supported_provinces():
full = loader.load_province(province)
if not full:
continue
meta = loader.load_metadata(province) or {}
normalized = _normalize_via_summary(summary, province)
if normalized in ("high", "usable"):
for ts in full.get("trusted_sources", []):
if ts.get("kind") == "province_official_pending_review":
issues.append(
f"[kind 漂移] {province} quality_level={normalized}"
f"{ts.get('name', '?')} kind=province_official_pending_review"
)
# 检查 4: confidence 范围
for province in loader.list_supported_provinces():
meta = loader.load_metadata(province) or {}
conf = meta.get("confidence")
if conf is None or not (0.0 <= conf <= 1.0):
issues.append(f"[confidence 越界] {province} confidence={conf}")
# 检查 5: data_year 一致性(当前应为 2025
years = {
(loader.load_metadata(p) or {}).get("data_year")
for p in loader.list_supported_provinces()
}
if len(years) > 1:
issues.append(f"[data_year 不一致] 多年份共存: {years}")
elif years == {2026}:
issues.append("[data_year 注意] 已切到 2026确认 2026 录取数据已正式公布")
# 输出
if issues:
print("❌ crowd_db 一致性检查发现漂移:")
for i, issue in enumerate(issues, 1):
print(f" {i}. {issue}")
return 1
print(
f"✅ crowd_db 一致性检查通过:"
f"high={actual_dist.get('high', 0)} "
f"usable={actual_dist.get('usable', 0)} "
f"low={actual_dist.get('low', 0)} "
f"skeleton={actual_dist.get('skeleton', 0)}"
)
return 0
def _normalize_via_summary(summary: dict, province: str) -> str:
"""从已构建的 summary 中取 quality_level。"""
for p in summary["provinces"]:
if p["province"] == province:
return p["quality_level"]
return "unknown"
if __name__ == "__main__":
sys.exit(main())