Files
gaokao-volunteer-system/scripts/check_crowd_db_consistency.py

156 lines
5.5 KiB
Python
Raw Normal View History

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
#!/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 一致性(放宽:允许多年份共存,部分省份已更新到 2026
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
years = {
(loader.load_metadata(p) or {}).get("data_year")
for p in loader.list_supported_provinces()
}
# 只有当出现异常年份(非 2025/2026才报错
invalid_years = {y for y in years if y not in {2025, 2026}}
if invalid_years:
issues.append(f"[data_year 异常] 发现非 2025/2026 年份: {invalid_years}")
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
elif years == {2026}:
issues.append("[data_year 注意] 已全部切到 2026确认 2026 录取数据已正式公布")
# 多年份共存2025+2026是正常的过渡期状态不报错
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
# 输出
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())