Files
gaokao-volunteer-system/scripts/verify_t2_4_e2e.py
Hermes Agent 6af6e0156b feat(crowd-db): T2.4 扎堆报告生成器 + 端到端渲染验证
- data/crowd_db/risk_report.py: build_crowd_risks 把 RiskFinding 转
  模板所需 crowd_risks 字典;高/中/低三色 emoji(🔴/🟡/🟢);
  group_by_risk / format_risk_summary / render_risk_table 辅助能力;
  LoaderLike Protocol 避免对 CrowdDBLoader 强依赖
- data/crowd_db/tests/test_risk_report.py: 22 用例覆盖三色 emoji、
  模板字段、alternatives 重映射、防御 fallback、空方案、不存在
  省份、自定义 loader 注入
- scripts/verify_t2_4_e2e.py: 用真实湖南 575 分数据 + audit_report.html
  模板渲染一遍,6/6 集成断言通过
- CHANGELOG: T2.4 记录

验证:data/crowd_db/tests/ 105/105、仓库全量 392/392、ruff All checks
passed、validate_template.py PASS
2026-06-12 16:35:20 +08:00

115 lines
4.0 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.
"""T2.4 端到端集成验证脚本
模拟 audit_report.html 模板实际渲染时使用的 crowd_risks 字典,
确认 build_crowd_risks 输出的字段与模板期望完全一致。
跑法python3 scripts/verify_t2_4_e2e.py
"""
import sys
from pathlib import Path
# 让 data/ 与 skills/ 模块可导入
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from jinja2 import Environment, FileSystemLoader, select_autoescape # noqa: E402
from data.crowd_db.risk_report import ( # noqa: E402
build_crowd_risks,
format_risk_summary,
group_by_risk,
)
def main() -> int:
# 1) 真实场景:湖南 575 分,扎堆样本方案
plan = [
{"school": "长沙理工大学", "major": "计算机科学与技术"}, # frequency=4 → high
{
"school": "湖南科技大学",
"major": "机械设计制造及其自动化",
}, # frequency=2 → medium
{"school": "某某野鸡大学", "major": "考古学"}, # 0 → 跳过
]
crowd_risks = build_crowd_risks(plan, user_score=575, province="湖南")
print(f"✅ build_crowd_risks 输出 {len(crowd_risks)} 条风险")
print(f" 汇总:{format_risk_summary(crowd_risks)}")
grouped = group_by_risk(crowd_risks)
print(
f" 分组: high={len(grouped['high'])} medium={len(grouped['medium'])} low={len(grouped['low'])}"
)
# 2) 模板字段完整性逐项断言
required_fields = {
"school",
"major",
"frequency",
"predicted_increase",
"risk_level",
"risk_level_label",
"risk_emoji",
"platforms",
"alternatives",
}
for r in crowd_risks:
missing = required_fields - r.keys()
assert not missing, f"missing fields: {missing} in {r}"
print(f"✅ 模板必需字段全部存在({len(required_fields)} 个)")
# 3) alternatives 字段名重映射检查
for r in crowd_risks:
for alt in r["alternatives"]:
assert "school" in alt, f"alternative missing 'school' key: {alt}"
assert "score" in alt, f"alternative missing 'score' key: {alt}"
assert isinstance(alt["score"], int), f"score not int: {alt}"
print("✅ alternatives 字段名重映射正确name→schoolscore 为 int")
# 4) 三色 emoji 检查
emojis = {r["risk_emoji"] for r in crowd_risks}
assert emojis.issubset({"🔴", "🟡", "🟢"}), f"unexpected emojis: {emojis}"
print(f"✅ 三色 emoji 标识正常:{emojis}")
# 5) 真实模板渲染(不修改模板,使用当前 crowd_risks 数据)
template_path = ROOT / "skills" / "gaokao-audit" / "templates" / "audit_report.html"
env = Environment(
loader=FileSystemLoader(str(template_path.parent)),
autoescape=select_autoescape(["html"]),
)
template = env.get_template("audit_report.html")
mock_payload = {
"source": "端到端测试",
"candidate_info": "湖南 575分 物化生",
"audit_time": "2026-06-12 17:00",
"report_id": "AUDIT-T24-E2E",
"overall_score": 70,
"fatal_count": 0,
"warning_count": len(crowd_risks),
"info_count": 0,
"policy_errors": [],
"crowd_risks": crowd_risks,
"data_issues": [],
"suggestions": [],
}
rendered = template.render(**mock_payload)
# 渲染输出必须包含每个风险院校名 + emoji
for r in crowd_risks:
assert r["school"] in rendered, f"school not in rendered: {r['school']}"
assert r["risk_emoji"] in rendered, f"emoji not in rendered: {r['risk_emoji']}"
print(f"✅ 真实模板渲染成功,{len(rendered)} chars")
# 6) 三色 emoji 在渲染输出中可见性
assert "🔴" in rendered or "🟡" in rendered or "🟢" in rendered, (
"no risk emoji found in rendered output"
)
print("✅ 风险 emoji 已渲染到 HTML 中")
print("\n🎯 T2.4 端到端集成验证全部通过。")
return 0
if __name__ == "__main__":
sys.exit(main())