"""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→school,score 为 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())