"""方案导出服务:生成 PDF/Word 文件。""" import io import json from insurance.db.compat import db from insurance.models.recommendation import RecommendationRecord DISCLAIMER = ( "\n\n免责声明:以上方案仅供参考,具体保障内容以保险合同条款为准。" "投保前请仔细阅读产品条款,了解保险责任、责任免除、等待期等重要内容。" ) def export_word(proposal_id: str): """导出方案为 Word 文档。""" from docx import Document from docx.shared import Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH record = db.session.query(RecommendationRecord).filter_by(id=int(proposal_id)).first() if not record: return None, "方案不存在" plans = json.loads(record.plan_variants) if record.plan_variants else [] # 如果结构化解析为空但有 Markdown 原文,直接渲染 Markdown if not plans and record.generated_plan: return _export_word_from_markdown(record) doc = Document() # 标题 title = doc.add_heading("保险产品推荐方案", level=0) title.alignment = WD_ALIGN_PARAGRAPH.CENTER # 客户信息 doc.add_heading("客户信息", level=1) info_table = doc.add_table(rows=4, cols=4, style="Table Grid") info_data = [ ("姓名", record.customer_name or "-", "年龄", f"{record.customer_age}岁"), ("性别", "男" if record.customer_gender == "male" else "女", "职业", record.occupation or "-"), ("健康状况", record.health_status or "-", "年收入", f"{record.annual_income}万"), ("月预算", f"{record.monthly_budget}元", "保障期限", record.coverage_period or "-"), ] for i, (k1, v1, k2, v2) in enumerate(info_data): info_table.rows[i].cells[0].text = k1 info_table.rows[i].cells[1].text = v1 info_table.rows[i].cells[2].text = k2 info_table.rows[i].cells[3].text = v2 # 各方案 for plan in plans: doc.add_heading(plan.get("name", "方案"), level=1) premium = plan.get("total_premium", "") doc.add_paragraph(f"年保费合计:{premium}元" if premium else "年保费合计:-") items = plan.get("items", []) if items: table = doc.add_table(rows=1 + len(items), cols=4, style="Table Grid") headers = ["产品名称", "保额", "年保费", "推荐理由"] for j, h in enumerate(headers): table.rows[0].cells[j].text = h for run in table.rows[0].cells[j].paragraphs[0].runs: run.bold = True for k, item in enumerate(items): table.rows[k + 1].cells[0].text = item.get("product_name", "") table.rows[k + 1].cells[1].text = item.get("coverage", "") table.rows[k + 1].cells[2].text = item.get("premium", "") table.rows[k + 1].cells[3].text = item.get("reason", "") summary = plan.get("summary", "") if summary: doc.add_paragraph(summary) # 免责声明 doc.add_paragraph(DISCLAIMER) buf = io.BytesIO() doc.save(buf) buf.seek(0) return buf, None def export_pdf(proposal_id: str): """导出方案为 PDF(通过 HTML 转换)。""" record = db.session.query(RecommendationRecord).filter_by(id=int(proposal_id)).first() if not record: return None, "方案不存在" plans = json.loads(record.plan_variants) if record.plan_variants else [] # 如果结构化解析为空但有 Markdown 原文,直接渲染 Markdown if not plans and record.generated_plan: return _export_pdf_from_markdown(record) # 构建 HTML html_parts = [ "
", "", "姓名:{record.customer_name or '-'} | 年龄:{record.customer_age}岁 | " f"性别:{'男' if record.customer_gender == 'male' else '女'} | " f"职业:{record.occupation or '-'}
", f"健康状况:{record.health_status or '-'} | 年收入:{record.annual_income}万 | " f"月预算:{record.monthly_budget}元 | 保障期限:{record.coverage_period or '-'}
", ] for plan in plans: html_parts.append(f"年保费合计:{premium}元
" if premium else "年保费合计:-
") items = plan.get("items", []) if items: html_parts.append("| 产品 | 保额 | 年保费 | 推荐理由 |
|---|---|---|---|
| {item.get('product_name', '')} | " f"{item.get('coverage', '')} | " f"{item.get('premium', '')} | " f"{item.get('reason', '')} |
{summary}
") html_parts.append(f"{DISCLAIMER}
") html_parts.append("") html_content = "\n".join(html_parts) # 尝试用 weasyprint 生成 PDF try: from weasyprint import HTML buf = io.BytesIO() HTML(string=html_content).write_pdf(buf) buf.seek(0) return buf, None except ImportError: pass # 回退:返回 HTML 文件 buf = io.BytesIO(html_content.encode("utf-8")) return buf, None def _export_word_from_markdown(record) -> tuple: """当结构化解析为空时,直接将 Markdown 渲染为 Word。""" from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH import re doc = Document() title = doc.add_heading("保险产品推荐方案", level=0) title.alignment = WD_ALIGN_PARAGRAPH.CENTER # 客户信息 doc.add_heading("客户信息", level=1) doc.add_paragraph( f"姓名:{record.customer_name or '-'} | 年龄:{record.customer_age}岁 | " f"性别:{'男' if record.customer_gender == 'male' else '女'} | " f"职业:{record.occupation or '-'}" ) # 将 Markdown 逐段写入 Word md_text = record.generated_plan or "" for line in md_text.split("\n"): line = line.strip() if not line: continue if line.startswith("### "): doc.add_heading(line[4:], level=2) elif line.startswith("## "): doc.add_heading(line[3:], level=1) elif line.startswith("# "): doc.add_heading(line[2:], level=0) elif line.startswith("|") and "---" not in line: # 表格行 → 用段落模拟 cells = [c.strip() for c in line.split("|") if c.strip()] doc.add_paragraph(" | ".join(cells)) else: doc.add_paragraph(line) doc.add_paragraph(DISCLAIMER) buf = io.BytesIO() doc.save(buf) buf.seek(0) return buf, None def _export_pdf_from_markdown(record) -> tuple: """当结构化解析为空时,直接将 Markdown 渲染为 PDF/HTML。""" import re md_text = record.generated_plan or "" # 简单 Markdown → HTML 转换 html_body = md_text html_body = re.sub(r'^### (.+)$', r'', html_body) html_body = f"
{html_body}
" html_content = ( "" "" f"姓名:{record.customer_name or '-'} | 年龄:{record.customer_age}岁 | " f"性别:{'男' if record.customer_gender == 'male' else '女'} | " f"职业:{record.occupation or '-'}
" f"{html_body}" f"{DISCLAIMER}
" "" ) try: from weasyprint import HTML buf = io.BytesIO() HTML(string=html_content).write_pdf(buf) buf.seek(0) return buf, None except ImportError: pass buf = io.BytesIO(html_content.encode("utf-8")) return buf, None