""" 高考志愿填报可视化报告生成器 V2.0 支持:雷达图、热力图、对比表、风险检测 新增:HTML导出、PDF导出、精美样式 """ from datetime import datetime # 尝试导入可选依赖 try: from jinja2 import Template HAS_JINJA = True except ImportError: HAS_JINJA = False print("警告: jinja2 未安装,HTML模板功能将受限") try: from weasyprint import HTML # type: ignore[import-untyped] HAS_WEASYPRINT = True except ImportError: HAS_WEASYPRINT = False print("警告: weasyprint 未安装,PDF导出功能不可用") def generate_student_radar(student_profile): """ 生成考生画像雷达图数据 """ scores = { "兴趣匹配度": student_profile.get("interest_match", 0), "能力匹配度": student_profile.get("ability_match", 0), "就业匹配度": student_profile.get("employment_match", 0), "家庭适配度": student_profile.get("family_match", 0), } # 综合得分计算 weighted_score = ( scores["兴趣匹配度"] * 0.3 + scores["能力匹配度"] * 0.3 + scores["就业匹配度"] * 0.25 + scores["家庭适配度"] * 0.15 ) # 生成ASCII雷达图 radar_chart = f""" ┌─────────────────────────────────────────────────────────────────┐ │ 🎯 {student_profile.get("name", "考生")} 画像雷达图 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 兴趣匹配度 │ │ {scores["兴趣匹配度"]:>3}/10 │ │ │ │ │ {min(scores["能力匹配度"], 10):>2}/10 ─────────┼───────── {min(scores["就业匹配度"], 10):>2}/10 │ │ 能力匹配度 │ 就业匹配度 │ │ │ │ │ 【{weighted_score:.1f}/10】 │ │ 综合得分 │ │ │ │ │ {scores["家庭适配度"]:>3}/10 │ │ 家庭适配度 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ 评分说明: │ │ • 兴趣匹配:基于霍兰德职业兴趣测试结果 │ │ • 能力匹配:学科成绩与专业要求的匹配度 │ │ • 就业匹配:专业就业前景评估 │ │ • 家庭适配:经济条件、地域要求的符合度 │ └─────────────────────────────────────────────────────────────────┘ """ return radar_chart, weighted_score, scores def generate_school_comparison(volunteer_list): """ 生成院校对比决策表 """ table = """ ┌──────────────────────────────────────────────────────────────────────┐ │ 🎯 志愿方案可视化对比 │ ├────────────┬────────────┬──────────┬─────────┬─────────┬───────────┤ │ 志愿类型 │ 院校 │ 专业 │ 录取概率│ 匹配指数│ 推荐指数 │ ├────────────┼────────────┼──────────┼─────────┼─────────┼───────────┤ """ for idx, vol in enumerate(volunteer_list, 1): v_type = vol.get("type", "稳") emoji = {"冲": "🔴", "稳": "🟡", "保": "🟢"}.get(v_type, "⚪") prob_bar = "█" * int(vol.get("probability", 0) / 10) + "░" * ( 10 - int(vol.get("probability", 0) / 10) ) match_score = vol.get("match_score", 0) stars = "⭐" * int(match_score / 20) + "☆" * (5 - int(match_score / 20)) table += f"""│ {emoji} {v_type:>2} {idx:>2} │ {vol.get("school", "待定"):<10} │ {vol.get("major", "待定"):<8} │ {prob_bar} │ {match_score:>3} │ {stars} │ ├────────────┼────────────┼──────────┼─────────┼─────────┼───────────┤ """ table += """│ │ │ 图例: │ │ 录取概率 █████ 90%+ ████░ 80%+ ███░░ 60%+ ██░░░ 40%+ █░░░░ <20%│ │ 匹配指数 90-100 完美 80-90 高度 70-80 中度 <70 需谨慎 │ └──────────────────────────────────────────────────────────────────┘ """ return table def generate_major_heatmap(majors): """ 生成专业匹配度热力图 """ heatmap = """ ┌──────────────────────────────────────────────────────────────────────┐ │ 🔥 兴趣-专业匹配度热力图 │ ├──────────────────────────────────────────────────────────────────────┤ │ │ """ for major in majors: name = major.get("name", "") score = major.get("match_score", 0) # 生成热力条 if score >= 90: bar = "█" * 20 status = "强烈推荐" elif score >= 80: bar = "█" * 17 + "░" * 3 status = "推荐选择" elif score >= 70: bar = "█" * 14 + "░" * 6 status = "可以考虑" elif score >= 60: bar = "█" * 11 + "░" * 9 status = "谨慎考虑" elif score >= 40: bar = "█" * 8 + "░" * 12 status = "不太建议" else: bar = "█" * 5 + "░" * 15 status = "不推荐" heatmap += f"│ {name:<12} {bar} {score:>3}% {status:<12} │\n" heatmap += """│ │ │ 热力指数:████ 90-100% ████ 80-90% ███░ 70-80% ██░░ 60-70% │ │ █░░░ 30-60% ░░░░ <30% (不推荐) │ └──────────────────────────────────────────────────────────────────────┘ """ return heatmap def detect_risks(student_profile, volunteer_list): """ 智能风险检测 """ risks = [] # 检查位次差距 for vol in volunteer_list: if vol.get("type") == "冲": if vol.get("probability", 0) < 30: risks.append({ "level": "warning", "item": f"{vol.get('school')}录取概率过低", "desc": f"录取概率仅{vol.get('probability')}%,需要增加相近备选", }) # 检查学科匹配 weak_subjects = student_profile.get("weak_subjects", []) for vol in volunteer_list: required = vol.get("required_subjects", []) for subj in required: if subj in weak_subjects: risks.append({ "level": "danger", "item": f"{vol.get('school')}-{vol.get('major')}学科不匹配", "desc": f"该专业需要{subj},但考生{subj}为弱项", }) # 检查梯度合理性 types_count = {"冲": 0, "稳": 0, "保": 0} for vol in volunteer_list: v_type = vol.get("type", "稳") types_count[v_type] = types_count.get(v_type, 0) + 1 total = len(volunteer_list) if total > 0: if types_count["保"] / total < 0.2: risks.append({ "level": "danger", "item": "保底志愿不足", "desc": f"保底志愿仅占{types_count['保'] / total * 100:.0f}%,建议至少30%", }) # 生成风险报告 risk_report = """ ┌──────────────────────────────────────────────────────────────────────┐ │ 🚦 志愿填报风险检测报告 │ ├──────────────────────────────────────────────────────────────────────┤ │ │ """ danger_list = [r for r in risks if r["level"] == "danger"] warning_list = [r for r in risks if r["level"] == "warning"] if danger_list: risk_report += ( "│ 🔴 高风险项目(必须修改): │\n" ) for risk in danger_list: risk_report += f"│ ✗ {risk['item']:<30} │\n" risk_report += f"│ → {risk['desc']:<50} │\n" risk_report += "│ │\n" if warning_list: risk_report += ( "│ 🟡 中风险项目(建议调整): │\n" ) for risk in warning_list: risk_report += f"│ ⚠ {risk['item']:<30} │\n" risk_report += f"│ → {risk['desc']:<50} │\n" risk_report += "│ │\n" if not risks: risk_report += ( "│ 🟢 恭喜!未检测到高风险项目,当前方案可以安全填报 │\n" ) risk_report += """│ │ └──────────────────────────────────────────────────────────────────────┘ """ return risk_report, risks def generate_html_report(student_data, volunteer_list, output_file=None): """ 生成HTML可视化报告 """ if not HAS_JINJA: print("错误: 需要安装 jinja2 才能生成HTML报告") print("运行: pip3 install --user jinja2") return None # 计算雷达图数据 radar_data = { "interest": student_data.get("interest_match", 0), "ability": student_data.get("ability_match", 0), "employment": student_data.get("employment_match", 0), "family": student_data.get("family_match", 0), } radar_data["average"] = sum(radar_data.values()) / 4 # HTML模板 html_template = """
{{ name }} | {{ province }} | {{ score }}分 | 位次: {{ rank }}
生成时间: {{ generated_at }}
| 类型 | 院校 | 专业 | 录取概率 | 匹配度 |
|---|---|---|---|---|
| {% if vol.type == '冲' %} 🔴 冲 {% elif vol.type == '稳' %} 🟡 稳 {% else %} 🟢 保 {% endif %} | {{ vol.school }} | {{ vol.major }} | {{ vol.match_score }}分 |
{{ risk.desc }}