海报确认、文案生成、海报生成增加服务端失败关闭门禁,绑定文件哈希、解析快照哈希和确认数据哈希,并返回 422 业务错误。[validators.py (line 65)](D:/work/code/python/coding/baodanagent/api/insurance/plan_data/validators.py:65) 缺失金额不再转换为 0;删除错误字段兜底和“年缴×年期=合同总保费”事实推导;里程碑冲突会阻断确认。[normalizer.py (line 14)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/normalizer.py:14) PPT 渲染器支持可空金额和实际币种,缺失值显示“待确认”,避免 float(None)、空值除法等异常。 模板必须覆盖全部输入保司和产品;自动选择排序确定化,同优先级歧义时阻断。[template_selection.py (line 4)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/template_selection.py:4) 场景判定写入 scenarioOverrideTrace,记录请求、模板、服务端及 Worker 最终判定。[routes.py (line 555)](D:/work/code/python/coding/baodanagent/api/insurance/ppt/routes.py:555) 前端增加哈希提交、人工调整原因、模板歧义提示及真实能力说明。 冻结三份核心 Schema,并建立 Goldens manifest、说明和评估脚本。 验证结果: 后端目标回归:111 passed, 1 skipped PPT 运行时回归:81 passed 前端生产构建和 vue-tsc:通过 Python compileall:通过 三份 Schema JSON:解析通过 git diff --check:通过,仅有换行符提示
43 KiB
PPT 与海报生成全链路深度审计报告
审计日期:2026-08-02
审计范围:PDF 解析、结构化数据、人工复核、海报生成、PPT 生成、PPT 模板与场景管理、异步任务、质量校验、测试与数据留存
报告性质:代码级静态审计 + 现有自动化测试验证,不等同于基于生产 PDF 样本的准确率测评
1. 执行摘要
当前问题不是某一个提示词、某一个正则或某一个 PPT 模板配置失效,而是整条链路缺少一个统一、可追溯、可版本化的“计划书事实层”。
目前系统实际上存在三套相互分离的解析路径:
- PPT 计划书使用完整解析流程;
- 海报计划书优先使用面向里程碑年份的紧凑解析流程;
- 产品手册使用另一套页面筛选和大模型解析流程。
三条路径的数据结构、字段兜底、准确性判定和证据保存方式均不一致。因此,同一份 PDF 在海报和 PPT 中可能得到不同的数据;修复其中一条路径,也不能保证另外两条同步正确。
本次审计确认了四个根因级问题:
- PDF 解析当前主要判断“有没有可读文字”和“字段齐不齐”,没有判断“值是否来自正确的页、表、列和场景”;
- 海报复核界面没有真正展示原 PDF 和完整利益表,后端又允许空数据或不完整数据被确认;
- 后台保存的 PPT 页面配置与场景逻辑,在生成阶段被硬编码的 scenarioSlides 和场景检测结果覆盖;
- 生成任务没有冻结完整的数据、模板和规则版本,排队后再读取可变数据库状态,存在“提交时看到的数据”和“最终生成的数据”不一致的问题。
因此,不建议继续在现有紧凑解析、字段兜底和模板覆盖链路上逐点加补丁。正确方向是:
- 先建立通用 PDF 文档中间层和字段级证据链;
- 再形成唯一的、经过校验或人工确认的 PlanData 快照;
- 海报和 PPT 都只消费该快照;
- 场景定义负责业务内容,模板负责视觉承载,两者使用显式、可验证的合并规则;
- 最终导出前进行数值对账,关键字段无法追溯或存在冲突时禁止导出。
1.1 总体风险判断
| 领域 | 当前判断 | 主要风险 |
|---|---|---|
| PDF 文字读取 | 可用但不可靠 | 能读出字符,不代表表格列和场景正确 |
| 利益表解析 | 高风险 | 多页表、同年份多场景、列错位可能被静默合并 |
| 海报数据确认 | 高风险 | 无原文对照、无完整表格、后端可确认空数据 |
| PPT 数据标准化 | 高风险 | 缺失值变 0、不同保险含义字段互相替代 |
| PPT 场景应用 | 已确认失效 | 后台场景与模板页配置被运行时硬编码覆盖 |
| 模板资产应用 | 部分生效 | 主要作为背景或画框,不是可执行业务模板 |
| 异步生成一致性 | 高风险 | 未冻结完整输入,队列执行期间数据可漂移 |
| 导出质量门禁 | 不足 | 质量检查偏存在性检查,且失败通常不阻断导出 |
| 自动化测试 | 代码回归较好、业务准确率证据不足 | 100 个相关测试通过,但没有真实 PDF 金标准集 |
| 隐私与留存 | 高风险 | 客户源 PDF 和确认数据缺少完整的到期清理闭环 |
2. 审计方法、判定边界与成功标准
本报告遵循“先还原事实,再提出方案”的原则。
2.1 审计方法
- 追踪前端上传、解析、确认、模板选择、生成和下载的调用链;
- 检查后端解析器、标准化器、比较器、渲染器和异步任务之间的数据契约;
- 对比后台管理界面承诺的配置能力与运行时真正读取的字段;
- 检查错误处理、降级、缓存、版本、并发、日志和数据清理;
- 运行与 PPT、海报、模板、任务生命周期相关的现有自动化测试。
2.2 结论标签
- 已确认:能够从当前代码路径直接证明,或已有测试/探针验证;
- 高风险设计:代码结构表明很容易发生,但发生比例需真实 PDF 样本测量;
- 运行待核验:依赖生产模型、生产数据、部署参数或用户行为,当前仓库无法独立证明。
2.3 本次测试结果
执行以下相关测试集合:
tests/ppt_poster_optimization_test.py
tests/ppt_template_asset_test.py
tests/ppt_task_lifecycle_test.py
tests/ppt_parse_route_test.py
tests/poster_render_document_test.py
tests/poster_render_validation_test.py
tests/user_product_material_test.py
结果为:100 passed,1 skipped。
这说明当前实现的既有行为具有一定代码回归保护,但不能证明解析准确。仓库内没有可用于端到端验证的真实计划书 PDF 金标准集;现有测试主要使用合成文本、矩阵、临时文件和 mock。被跳过的测试与当前本地 Python 环境缺少 python-pptx 有关,而部署镜像中已有对应依赖配置。
3. 当前真实业务链路
3.1 PPT 链路
PDF 上传
→ ExtractionOrchestrator.extract_plan
→ PDF 文本/OCR候选
→ 正则提取 + 大模型分段提取
→ 字段合并、标准化、完整性评估
→ session.extractions_json
→ 前端 PptDataReview 人工编辑
→ Normalizer 转 DeckContract
→ detect_generation_scenario 硬编码场景识别
→ build_scenario_slides 硬编码页面内容
→ Renderer + fast_pptx_renderer
→ 生成 PPTX
→ 非阻断质量检查
3.2 海报链路
PDF 上传并绑定后台产品
→ extract_for_poster 紧凑解析
→ 少量目标年份与标量字段
→ 若少数字段满足要求则不执行完整解析
→ 映射为海报扁平字段
→ PosterSourcePanel 人工编辑部分字段
→ confirm_case_upload
→ ContentBuilder 再次推导利益数据和文案
→ 海报渲染
3.3 产品手册链路
产品手册 PDF
→ manual_parser 页面关键词筛选
→ 大模型提取产品规则
→ 人工确认
→ 产品资料解析结果
→ 海报文案/规则来源
3.4 核心断点
PPT 和海报没有消费同一份确认后的事实数据。海报紧凑解析不是完整计划书数据的一个只读视图,而是一条独立解析路径;产品手册也没有共享统一的页、文本块、表格和证据表示。这是数据不一致的首要根因。
4. 已确认问题清单
4.1 P0:可能直接造成错误金额、错误比较或配置完全不生效
| 编号 | 问题 | 证据位置 | 影响 |
|---|---|---|---|
| P0-01 | 海报与 PPT 使用不同计划书解析路径 | api/insurance/ppt/extraction.py、api/insurance/poster/tasks.py | 同一 PDF 可产生两套不一致数据 |
| P0-02 | PDF 解析只验证字符可读性,不验证表格列、行和场景归属 | extraction.py 的 _extract_pdf_text、_looks_corrupted、_score_page_quality | 列错位仍可能被判为高质量 |
| P0-03 | 利益表按 policy_year 单键去重 | extraction.py 的 _merge_rows_by_policy_year | 同年份不同场景或不同表格可能互相覆盖 |
| P0-04 | 多页利益表筛页依赖关键词命中,续页可能被漏掉 | extraction.py 的 _llm_extract_split | 首页面表头保留,后续数据页丢失 |
| P0-05 | 海报紧凑解析只要少量标量字段不失败,就不回退完整解析 | poster/tasks.py 的 _execute_case_parse | 利益表错误或缺失仍进入确认 |
| P0-06 | 海报确认接口不校验必填字段、解析状态和业务一致性 | poster/service.py 的 confirm_case_upload | 空对象也可被确认并进入生成 |
| P0-07 | 海报复核没有挂载原 PDF 预览,也没有完整利益表编辑 | PosterSourcePanel.vue;PdfPagePreview.vue 未被引用 | 用户无法发现列错、页错、场景错 |
| P0-08 | 后台 slidesConfig 被 scenarioSlides 优先覆盖 | fast_pptx_renderer.py 的 _resolve_page_types、render_deck | 后台设置页序、页面类型和内容提示不生效 |
| P0-09 | 后台场景被文件类型硬编码识别结果覆盖 | generation/celery_tasks.py、comparison.py | 管理员配置的同类/异类比较逻辑无法控制生成 |
| P0-10 | 场景数据模型没有真正的选择规则、比较指标和计算配置 | models/ppt_config.py | 后台只能保存标签,无法表达所需业务逻辑 |
| P0-11 | 缺失或解析失败的数值大量被标准化为 0 | normalizer.py、comparison.py | “未知”被当成真实 0 参与图表和比较 |
| P0-12 | 重疾计划把缺失的 totalSurrenderValue 直接设为 deathBenefit | normalizer.py | 退保价值与身故保障被错误等同 |
| P0-13 | 多处储蓄险页面硬编码 US$ | fast_pptx_renderer.py | HKD、CNY、SGD 等保单展示错误币种 |
| P0-14 | 生成任务不冻结结构化数据和完整模板配置 | generation/celery_tasks.py | 排队期间编辑可能改变最终输出 |
| P0-15 | 任务完成时用当前 draft_revision 标记 generated_revision | generation/celery_tasks.py | 旧数据生成物可能被错误标记为最新版本 |
| P0-16 | 导出后质量检查不做完整数值对账,失败通常不阻断成功状态 | renderer.py、质量检查相关代码 | 明知存在警告或缺失仍可交付 |
| P0-17 | 没有真实 PDF 金标准回归集和端到端数值一致性门禁 | tests、api/insurance/ppt/golden_test.py | 无法度量“精准”或阻止准确率回退 |
4.2 P1:高概率造成错误、漂移、不可维护或合规风险
| 编号 | 问题 | 证据位置 | 影响 |
|---|---|---|---|
| P1-01 | 文本最大长度 120000 字符,可能从中间截断后续页面 | extraction.py | 长计划书后段条款或表格丢失 |
| P1-02 | OCR 最多处理 40 页 | extraction.py 的 _extract_pdf_text_ocr | 长扫描件后续页面无法解析 |
| P1-03 | OCR 触发主要依赖乱码/低分,版面错序但字符可读时不触发 | extraction.py | 多栏和复杂表格错误不能被补救 |
| P1-04 | 大模型利益表 schema 约束较弱,行字段和类型不严格 | extraction.py 的 _llm_extract_split | 输出结构和列含义不稳定 |
| P1-05 | source_page 由模型给出,未与真实坐标证据绑定 | extraction.py | 页面号本身可能不可信 |
| P1-06 | 正则结果因“有标签”就可覆盖大模型结果 | extraction.py 的 _merge_extraction_data | 错误正则值可能静默取得优先权 |
| P1-07 | 文件名提示可覆盖产品、年龄、币种、缴费期等字段 | extraction.py 的 _apply_filename_hints | 重命名文件或命名不规范会污染事实数据 |
| P1-08 | 后台所选产品名称优先于 PDF 提取名称 | extraction.py、poster/tasks.py | 上传错 PDF 时错误被掩盖 |
| P1-09 | 准确性评估侧重字段完整度,不校验字段正确性 | extraction.py 的 assess_extraction_payload | 内容错误也可能返回 success |
| P1-10 | provenance 是按解析方法估算置信度,不是字段级原文证据 | extraction.py 的 _build_provenance | 无法审计每个金额从哪里来 |
| P1-11 | 缓存键没有完整纳入模型、OCR、规则和产品提示版本 | extraction.py | 配置变更后可能复用旧结果 |
| P1-12 | 海报映射使用 Python or 选择数值,合法的 0 会被当成缺失 | poster/tasks.py、content_builder.py | 零值或特殊产品数据被替换 |
| P1-13 | 年缴保费乘缴费年期推导总保费 | poster/tasks.py、normalizer.py | 折扣、附加险、缴费频率等情况下错误 |
| P1-14 | 海报非保证价值字段与完整解析字段命名不一致 | poster/content_builder.py | 红利和终期红利可能显示为 0 |
| P1-15 | 显式里程碑字段可覆盖利益表值,未做一致性检查 | poster/content_builder.py | 同一海报内部事实来源冲突 |
| P1-16 | 海报产品匹配只比较用户选择的产品 ID,不比较 PDF 内容 | poster/service.py 等 | 错误计划书可能套用另一产品模板和名称 |
| P1-17 | 海报 case 对外返回服务器绝对源文件路径 | models/poster_case_upload.py | 暴露内部目录结构 |
| P1-18 | PPT 模板适用产品使用“有任一交集”判断 | PPT 后端模板路由、PptGenerate.vue | 多产品比较时不兼容模板仍可通过 |
| P1-19 | 前端重复实现硬编码场景检测 | PptGenerate.vue、comparison.py | 前后端规则漂移 |
| P1-20 | 模板自动选择未显式排序 | generation/celery_tasks.py | 多模板匹配时结果可能不稳定 |
| P1-21 | 历史记录可能只保存请求 template_id,而非实际解析出的模板 ID | generation/celery_tasks.py | 无法准确追溯实际使用模板 |
| P1-22 | 源 PPT 中的文本、图表、表格和多数形状被清除 | fast_pptx_renderer.py 的 _sanitize_template_shape | 所谓模板主要退化为背景画框 |
| P1-23 | 页面匹配失败时静默使用第一张未用页面 | fast_pptx_renderer.py 的 _auto_frame_map | 版式与业务页类型可能错配 |
| P1-24 | 模板只要能解析就被标记 clone_ready | ppt_admin_service.py | 没有槽位、页类型和场景契约校验 |
| P1-25 | 比较年份固定为 5、10、20、30 年 | comparison.py | 管理后台不能配置重点年份 |
| P1-26 | 跨计划兼容性主要只阻断币种,其他条件多为警告 | comparison.py | 不同年龄、性别、缴费条件可能被直接横向比较 |
| P1-27 | PptDataReview 只能编辑 source_page 数字,不能并排看 PDF | PptDataReview.vue | 人工复核缺乏可信证据 |
| P1-28 | extraction 更新按 pdfName 匹配 | PPT session 更新逻辑 | 同名 PDF 可能碰撞或更新错对象 |
| P1-29 | 前端 modificationLog 没有被后端持久化 | PptDataReview.vue、更新接口 | 看似有审计记录,实际刷新后丢失 |
| P1-30 | 客户源 PDF 和确认数据缺少完整留存到期清理 | api/insurance/utils/cleanup.py | 敏感保险资料可能长期保留 |
4.3 P2:影响易用性、可观察性和长期演进
| 编号 | 问题 | 影响 |
|---|---|---|
| P2-01 | 海报 parsed 状态资料可能被列为可用,但实际解析结果解析器要求 confirmed | 用户会看到不可用材料 |
| P2-02 | 场景管理前端可新增/删除/启停,但缺少完整编辑能力 | 配置维护困难 |
| P2-03 | 前端文案暗示可手动选择场景,生成页实际没有手动选择入口 | 管理员预期与用户行为不一致 |
| P2-04 | 多个模板风格映射到同一 renderer theme | 后台风格选项视觉差异有限 |
| P2-05 | 旧 parse_worker.py 与当前 Celery 路径并存 | 容易误维护非生效代码 |
| P2-06 | 长任务恢复主要依赖创建时间,不是心跳 | 合法长任务可能被误判为超时 |
| P2-07 | 缺少按保险公司、产品、版式、扫描类型的解析质量看板 | 无法定位准确率下降来源 |
| P2-08 | 缺少生成后视觉回归和溢出检测的稳定门禁 | 模板变化可能造成版式回退 |
5. PDF 解析为何“不精准”
5.1 当前判断标准错把“可读”当成“正确”
系统会尝试 PyMuPDF、PyPDF2、pypdf、pdfplumber 等读取方式,但一般采用首个看起来字符正常的结果。现有质量评分关注:
- 可读字符比例;
- 保险关键词命中;
- 数字密度;
- 是否存在 CID 等乱码。
这些指标只能发现“读不出来”,发现不了以下更危险的问题:
- 左右两列被串成一行;
- 表头与数值错位;
- 页脚和正文混入表格;
- 基本情景、红利情景、退保情景的同年份行被混在一起;
- 跨页表格第二页没有表头,因而被当作普通数字文本;
- 一个金额被正确识别,但归到了错误字段。
因此,当前 success 更接近“数据结构填出来了”,不是“数据已与 PDF 对账正确”。
5.2 表格被降维为文本后,几何信息永久丢失
保险计划书的核心不是连续段落,而是复杂表格。当前流程早期就把 PDF 页面降成线性文本,大部分字段后续只能依靠关键词、正则和大模型猜测列关系。
一旦丢失以下信息,后续再强的大模型也难以稳定还原:
- 文本块坐标;
- 行列边界;
- 合并单元格;
- 表头层级;
- 表格跨页关系;
- 同一页多个表格的归属;
- 颜色、线条和版面分区。
通用 PDF 解析层必须先保存这些信息,再生成便于模型理解的语义视图,而不是只保存纯文本。
5.3 按年份去重会破坏保险场景
当前利益行合并以 policy_year 为核心键。同一个年份可能同时存在:
- 保证现金价值;
- 非保证红利;
- 基本/悲观/乐观情景;
- 提取前和提取后;
- 身故赔偿;
- 退保价值;
- 不同币种或不同单位。
仅按年份合并会导致正确的多场景数据被当成重复行。当前实现倾向选择“非空字段更多”的一行,而不是保留每张表、每个场景的身份,也不会可靠合并互补字段。这是数据与原 PDF 对不上最关键的代码级原因之一。
5.4 紧凑解析优化了速度,却提前丢弃了事实
海报紧凑解析最多选少量页面和少量目标年份。对只展示 10、20、30 年价值的海报来说,这一思路看似合理,但它把“展示需求”错误地提前放到了“事实提取阶段”。
正确顺序应为:
- 完整、准确地解析计划书;
- 形成唯一事实快照;
- 海报视图再从快照中选择 10、20、30 年。
当前顺序是先按海报需要删减页面,再尝试从残缺上下文恢复事实。这样无法保证页表连续性,也无法在后续 PPT 中复用。
5.5 文件名和后台选择不应覆盖 PDF 事实
文件名、后台所选产品和用户输入可以作为提示或冲突检测条件,但不能静默覆盖 PDF 中提取出的事实。
建议将来源拆为:
- document_value:PDF 中有证据的值;
- context_hint:文件名、所选产品或上游业务上下文;
- confirmed_value:用户确认后的值;
- conflict:document_value 与 context_hint 不一致。
只有 confirmed_value 才能成为最终生成快照。任何覆盖都必须留痕。
6. 海报生成专项问题
6.1 复核流程实际上无法完成“核对”
PosterSourcePanel 当前主要展示文件名、若干基础字段和里程碑值。虽然项目已有 PdfPagePreview 组件,但它没有在实际复核页中挂载。用户看不到:
- 当前字段对应的原 PDF 页面;
- 原文片段;
- 表格所在区域;
- 完整逐年利益表;
- 保证与非保证列;
- 多个情景之间的差异;
- 哪些值来自自动提取、推导、后台产品或人工输入。
所以“确认”按钮表达的是业务确认,但界面只提供了有限编辑,无法支撑真正的事实确认。
6.2 后端没有承担最终数据门禁
确认接口对 confirmedData 缺少严格 schema 和业务校验。即使前端后续补了校验,也不能替代后端,因为请求可以绕过前端。
确认时至少必须验证:
- 解析任务状态成功;
- 文件哈希与解析快照一致;
- 关键身份字段非空;
- 年龄、缴费期、保费和保额在合理范围;
- 币种明确;
- 海报展示的每个金额有证据或明确人工录入原因;
- 完整利益表和里程碑字段互相一致;
- 所选产品与 PDF 产品一致,或已有带原因的人工覆盖;
- 未解决的冲突为零。
6.3 海报字段二次推导制造了新的差异
海报在解析后又通过映射和 ContentBuilder 重组数据。期间存在:
- 使用 or 导致 0 值被当成缺失;
- 年缴保费乘年期推导总保费;
- 非保证字段命名与完整解析不一致;
- 显式里程碑值覆盖利益表值;
- 后台所选产品名优先于 PDF 产品名。
因此,即使上游解析正确,海报最终展示也可能再次偏离。
7. PPT 模板与场景配置为何没有应用
7.1 已确认的覆盖链
后台模板编辑器会保存 slidesConfig,场景管理会保存 scenarioTag、baseScenario 和 generationMode 等字段。但实际生成时:
- comparison.detect_generation_scenario 根据上传文件类型重新推断场景;
- generation_mode_for_scenario 根据硬编码映射重新计算 generationMode;
- build_scenario_slides 根据硬编码场景生成页面列表和内容;
- fast_pptx_renderer 优先使用 scenarioSlides;
- 只有 scenarioSlides 不存在时,才会使用模板 slidesConfig 或 requiredPageTypes。
而正常生成总会构造 scenarioSlides,所以后台 slidesConfig 在页面业务逻辑上的优先级实际上永远排在后面。
这不是偶发现象,而是确定的代码优先级。
7.2 当前后台配置“看起来能配”,数据模型却无法执行
用户所说的“同类型计划书对比”和“不同计划书比较”至少需要以下配置:
- 选择条件:计划类型、产品、公司、数量、币种、客户条件;
- 兼容规则:哪些字段必须一致,哪些允许差异;
- 分组方式:按险种、产品、公司或计划分组;
- 指标定义:保费、现金价值、身故利益、IRR 等;
- 比较年份;
- 缺失值策略;
- 排名和结论规则;
- 页面构成;
- 图表类型;
- 不满足条件时的降级或阻断方式。
当前 PptScenario 模型只有名称、编码、基础场景、生成模式、描述等元数据,无法保存以上可执行规则。因此,即使运行时不覆盖,它也不足以表达完整业务逻辑。
7.3 模板当前更像“视觉皮肤”
上传源 PPT 后,渲染器会清除其中的文本、图表、表格和大量形状,再使用固定坐标的页面构造器绘制内容。模板主要提供:
- 背景;
- 部分保留形状;
- 页面尺寸与视觉框架;
- 自动匹配的源页面。
它没有稳定的语义槽位契约,所以不能可靠表达“把某个比较表放在这里、把某个结论放在那里”。页面匹配失败时还会静默选取第一张未用页面,进一步增加错配风险。
7.4 建议重新定义三个概念
| 概念 | 应负责什么 | 不应负责什么 |
|---|---|---|
| ScenarioDefinition | 业务选择规则、页面语义、比较指标、年份、数据要求 | 具体品牌颜色和坐标 |
| TemplateDefinition | 视觉主题、页面母版、语义槽位、字体与版式 | 判断用哪种保险比较逻辑 |
| GenerationPolicy | 兼容性、缺失值、排名、阻断和降级规则 | 任意修改源数据 |
运行时应先得到确定的 ScenarioDefinition,再把其页面语义映射到 TemplateDefinition 的槽位。合并规则必须唯一、显式、可测试,不应存在隐藏覆盖。
8. PPT 数据标准化和比较逻辑问题
8.1 缺失值不能等于零
当前多个 _safe_number 或 _number 辅助方法把空值、非法值转换成 0。保险计划书中以下三种状态必须分开:
- 明确为 0;
- PDF 没有该字段;
- 解析失败或存在冲突。
如果合并,图表会把未知当成零,排名和 IRR 也会被污染。目标数据模型应使用 value、status、reason 和 evidence 表示,而不是只传一个 number。
8.2 不同保险概念不能互相兜底
本次发现的高危示例包括:
- deathBenefit 代替 totalSurrenderValue;
- accountValue、cashValue、nonGuaranteedCashValue 互相兜底;
- sumAssured 代替缺失的 deathBenefit;
- annualPremium 乘年期代替合同总保费。
这些字段在业务上可能相关,但不是同义词。可以将其作为“待人工确认的推断候选”,不能作为无提示的最终值。
8.3 比较条件不完整
当前主要强制阻断混合币种,年龄、性别等差异多以警告处理。至少还需考虑:
- 年龄与性别;
- 吸烟状态;
- 保费与缴费期;
- 保额;
- 币种与汇率时点;
- 保单年度与受保人年龄;
- 保证/非保证情景;
- 提取计划;
- 红利实现率或演示利率;
- 是否包含附加险。
不满足同口径时,不应生成“谁更优”的结论;最多只能并列展示,并清楚标记不可直接比较。
9. 异步任务、版本和可复现性
生成任务虽然保存了部分 input_snapshot,但工作进程仍会读取 session.extractions_json 和当前模板数据库记录。由此产生以下竞态:
- 用户提交生成任务;
- 任务在队列中等待;
- 用户修改解析数据,或管理员修改模板/场景;
- Worker 启动并读取新状态;
- 输出与提交时预览不一致;
- Worker 又用当前 draft_revision 标记 generated_revision。
目标做法是生成前冻结完整快照:
- PlanData 快照 ID、版本和内容哈希;
- 每个源 PDF 哈希;
- ScenarioDefinition 版本和哈希;
- GenerationPolicy 版本和哈希;
- TemplateDefinition 版本、配置哈希和源资产哈希;
- renderer 版本;
- 用户选择和覆盖记录;
- 提交时 draft_revision。
Worker 只能读取该不可变快照。完成时若任务 revision 与当前 session revision 不一致,应标记“已生成旧版本”,不能冒充最新输出。
10. 目标架构:统一 PDF 事实层
10.1 分层原则
PDF 原文件
→ Document IR:页、坐标、文本块、表格、图片、OCR候选
→ Extraction Candidates:多个解析器的字段候选
→ Reconciliation:规则校验、冲突检测、场景归属
→ Review:原 PDF 与字段/表格并排确认
→ Confirmed PlanData Snapshot:唯一事实快照
→ Poster Projection / PPT DeckContract
→ 模板和场景组合
→ 渲染
→ 输出数值对账与视觉检查
10.2 建议的核心数据结构
Document:
- document_id、sha256、page_count;
- parser_version、ocr_version;
- pages;
- document_type;
- extraction_profile;
- 处理日志与耗时。
Page:
- page_number、width、height;
- text_blocks,每个包含 bbox、文本、字体信息和读取顺序;
- tables,每个包含 bbox、行列、合并单元格和跨页关系;
- images;
- native_text_quality、ocr_quality;
- 页面分类。
EvidenceRef:
- document_id、page_number、bbox;
- raw_text、text_hash;
- extractor、extractor_version;
- confidence;
- table_id、row_id、column_id;
- evidence_status。
FieldValue:
- raw_value、normalized_value;
- unit、currency;
- status:auto、confirmed、conflict、missing、derived;
- evidence 列表;
- derived_formula;
- confirmed_by、confirmed_at、override_reason。
ScenarioTable:
- table_id、scenario_type;
- currency、unit;
- headers;
- rows;
- source_pages;
- continuation_of;
- table_conflicts。
PlanData:
- identity;
- policy;
- insured;
- benefits_by_scenario;
- withdrawals_by_scenario;
- riders;
- assumptions;
- validation_results;
- snapshot_version、snapshot_hash。
10.3 解析策略
建议采用“确定性表格提取优先,大模型做语义识别”的组合:
- 判断原生文本、扫描、混合型 PDF;
- 同时保存原生文本块与页面坐标;
- 使用线条、坐标聚类和表头关系恢复表格;
- 对扫描页或低可信区域执行 OCR,并保留 OCR 坐标;
- 通过保险公司/产品/版式 profile 识别列语义;
- 通用 profile 作为未知版式回退;
- 大模型用于页面分类、表头语义、同义词映射和段落摘要;
- 大模型不作为密集金额表逐格抄录的唯一来源;
- 多解析候选产生冲突时保留冲突,不静默选一个;
- 通过业务约束和人工复核形成确认快照。
10.4 关键校验规则
- 币种必须在文档内一致,或每张表明确币种;
- 保单年度与受保人年龄关系必须一致;
- 保证、非保证、总额之间满足明确公式时必须对账;
- 同年份不同场景不得合并;
- 身故利益与退保价值不得互相替代;
- 提取前后、不同红利实现率必须分场景保存;
- 每个导出金额必须有 EvidenceRef 或带理由的人工录入记录;
- 任何冲突不得被 completeness 分数掩盖;
- 不把缺失值变成 0;
- 文件名和后台选择只作为提示或冲突源,不覆盖文档事实。
11. 目标架构:模板和场景真正可配置
11.1 场景定义
ScenarioDefinition 建议至少包含:
- code、name、version、status;
- selector:文件数量、险种组合、产品组合和客户条件;
- compatibilityRules;
- pageSpecs;
- metricSpecs;
- comparisonYears;
- calculationPolicy;
- missingValuePolicy;
- conclusionPolicy;
- fallbackScenario;
- sampleFixtureIds;
- publishedAt。
每个 pageSpec 使用稳定 page_type 和 content_contract,例如:
- 输入字段;
- 表格列;
- 图表序列;
- 标题模板;
- 数据为空时的行为;
- 不允许比较时的说明。
11.2 模板定义
TemplateDefinition 建议包含:
- 支持的 page_type;
- 每种 page_type 对应的母版页;
- semantic slots;
- 字体、颜色、间距和图表主题;
- 每个槽位的容量与溢出规则;
- 支持的 ScenarioDefinition 版本范围;
- 预览样本;
- draft、validated、published 状态。
上传 PPT 后不能只验证“文件可打开”。必须验证每个必需 page_type 都有可用槽位,且样本渲染通过。
11.3 明确解析优先级
建议运行时优先级:
- 用户明确选择的已发布场景,且输入满足其 selector;
- 规则精确匹配到唯一已发布场景;
- 使用明确配置的 fallback;
- 多个同优先级匹配或无匹配时阻断,并展示 matchTrace。
禁止运行时静默把管理员或用户选择替换为硬编码值。
matchTrace 应记录:
- 输入事实;
- 命中的规则;
- 未命中的规则与原因;
- 最终场景和版本;
- 模板和版本;
- 页面合并结果;
- 所有降级。
12. 分阶段整改计划
阶段 0:先止损和建立基线
目标:在大重构前阻止明显错误继续流入输出。
- 海报确认接口增加后端 schema、必填、冲突和状态校验;
- 海报复核页接入 PDF 预览和完整利益表;
- 禁止未解析、解析失败和空 confirmedData 进入生成;
- 禁止缺失值自动变 0;
- 删除 deathBenefit 代替退保价值等错误兜底;
- 统一所有页面币种显示;
- 修复多产品模板适用条件,要求全部产品满足;
- 记录当前场景覆盖链并临时在后台明确标注“仅视觉配置”,避免误导;
- 建立第一批真实 PDF 金标准集和人工双人复核基线。
验收:
- 空数据和未解决冲突无法确认;
- 海报所有展示金额可从界面跳转到 PDF 证据;
- 同一快照的海报与 PPT 基础字段完全一致;
- 不再出现未知值显示为 0;
- 不再出现非美元保单显示 US$。
阶段 1:建立通用 Document IR 与字段证据链
目标:解决“读到字符但对应不上原 PDF”。
- 新建统一 PDF 文档层;
- 保存页面坐标、原生文本块、OCR 文本块和表格结构;
- 引入 PDF 类型识别和 parser profile;
- 设计 FieldValue、EvidenceRef、ScenarioTable;
- 取消按年份单键去重;
- 引入跨页表格识别;
- 文件名和产品选择改为冲突提示;
- 复核界面支持字段、表格、原 PDF 双向定位。
验收:
- 每个关键金额具有页码和 bbox;
- 所有表格行保留 table_id、scenario_type 和 source_page;
- 同年份多场景数据零污染;
- 真实样本集达到第 15 节的准确率门槛。
阶段 2:统一 PlanData 快照,海报/PPT 共用
目标:消除两套计划书事实。
- 完整解析只执行一次;
- 海报紧凑数据改为 PlanData 的 projection,不再独立解析;
- 海报和 PPT 均引用 confirmed snapshot ID;
- 所有派生字段记录公式和输入证据;
- 重构字段命名,统一保证、非保证、红利、退保和身故利益语义;
- 为旧数据提供显式迁移或重新解析策略。
验收:
- 同一 PDF 在海报和 PPT 中字段、币种和里程碑值 100% 一致;
- 任何修改都会产生新 snapshot version;
- 已生成物可追溯到唯一 snapshot hash。
阶段 3:重构 PPT 场景与模板系统
目标:后台配置真正控制业务生成。
- 扩展 ScenarioDefinition 和 GenerationPolicy;
- 取消硬编码场景对后台选择的静默覆盖;
- 统一前后端场景解析,由服务端返回结果和 matchTrace;
- 明确 scenario pageSpecs 与 template slots 的合并契约;
- 模板建立 draft、validated、published 生命周期;
- 管理后台增加样本预览和规则测试;
- 比较年份、指标和兼容条件可配置;
- 历史记录保存实际场景、模板和规则版本。
验收:
- 修改已发布场景的比较年份后,样本 PPT 页面和数据随版本变化;
- 修改模板页面映射后,输出页面顺序按配置变化;
- 不兼容输入被阻断并给出命中轨迹;
- 同一输入和同一版本重复生成得到相同结构结果。
阶段 4:冻结任务输入并建立导出门禁
目标:保证生成可复现,交付物与确认数据完全一致。
- 任务保存完整不可变输入快照;
- Worker 禁止读取可变 session 当前值;
- 使用提交时 revision 标记生成物;
- 生成后解析 PPTX XML,对文本、表格和图表序列与 DeckContract 对账;
- P0 数值不一致直接失败;
- 增加页面溢出、遮挡、空图、空表和字体检查;
- 建立任务心跳和幂等策略。
验收:
- 队列等待期间修改数据不会改变已提交任务;
- 所有导出金额与 PlanData 快照对账 100%;
- 不一致时不产生可下载“成功”文件;
- 任务、输出和审计日志可完整重放。
阶段 5:质量运营、隐私与持续回归
目标:让准确率可持续,而不是一次性修复。
- 按保险公司、产品、版式和 PDF 类型统计字段准确率;
- 记录人工修改率、冲突率、OCR 率和失败原因;
- 建立解析 profile 灰度发布;
- 建立真实样本回归、视觉回归和性能基线;
- 完善客户 PDF、确认快照、生成物和日志的留存策略;
- 对源文件下载使用受控接口,不暴露服务器路径;
- 敏感字段脱敏并限制日志内容。
13. 建议的代码边界
遵循现有项目原则,全部自研改造继续放在 api/insurance 下,不侵入 BaoDan 其他目录。建议结构:
api/insurance/document/
ingest.py
pdf_classifier.py
native_text.py
ocr.py
layout.py
tables.py
evidence.py
schemas.py
api/insurance/plan_data/
profiles/
extractors/
reconcile.py
validators.py
snapshots.py
projections.py
api/insurance/generation/
scenarios.py
policies.py
resolver.py
snapshots.py
reconcile_output.py
现有 api/insurance/ppt 和 api/insurance/poster 保留各自的展示与渲染责任,但不再各自定义 PDF 事实。
14. 测试矩阵
14.1 PDF 样本维度
- 原生文本、全扫描、混合 PDF;
- 单栏、多栏、横向页面;
- 有线框表、无线框表、合并表头;
- 单页表、跨页表、续页无表头;
- 中文、英文、中英混合;
- 不同保险公司和产品系列;
- 同一年存在多个利益情景;
- 文件名正确、错误、无业务含义;
- 加密、损坏、超长和大文件;
- OCR 模糊、倾斜、低对比度。
14.2 数据正确性测试
- 字段精确值;
- 表格单元格精确值;
- 行召回率;
- 场景分类;
- 页码和 bbox;
- 币种和单位;
- 年龄/年度关系;
- 保证、非保证和总额公式;
- 冲突触发;
- 未知值保持未知;
- 人工覆盖和审计记录。
14.3 海报测试
- PDF 与复核字段双向定位;
- 完整利益表编辑;
- 不允许空确认;
- 产品冲突阻断;
- 同一 PlanData projection 的值一致;
- 文案与数值来源分离;
- 海报渲染文本与快照对账;
- 中文溢出、金额长度和多币种。
14.4 PPT 测试
- 后台场景 selector 命中;
- 同类比较、跨类型比较和不兼容输入;
- 管理员修改比较年份后输出变化;
- 管理员修改 pageSpecs 后输出变化;
- 模板槽位缺失时发布失败;
- 场景与模板版本追溯;
- 表格、图表和结论文案与 DeckContract 对账;
- 队列中修改 session 不影响已提交任务;
- 同输入重复生成的确定性;
- 多产品模板必须全部适用。
14.5 视觉与稳定性测试
- 每页渲染为图片做基线对比;
- 文本越界、重叠、裁切;
- 图表空序列和异常比例;
- 不同 Office/WPS 打开兼容性;
- 超长名称、长数字和英文断行;
- 并发生成和重复提交;
- Worker 重试、超时和幂等;
- 源 PDF、快照和输出的到期清理。
15. 可量化验收标准
“感觉更准”不能作为上线标准。建议至少建立以下门槛:
| 指标 | 建议门槛 |
|---|---|
| 关键标量精确率:年龄、性别、币种、保费、缴费期、保额 | ≥ 99.5% |
| 金额表格单元格精确率 | ≥ 99.0% |
| 利益表目标行召回率 | ≥ 99.0% |
| 导出金额具有正确页码与区域证据 | 100% |
| 同年份不同场景污染 | 0 |
| 未解决冲突被自动确认 | 0 |
| 未知值被静默变为 0 | 0 |
| 海报与 PPT 同快照字段一致率 | 100% |
| PPT 文本、表格、图表与 DeckContract 数值一致率 | 100% |
| 后台已发布场景和模板配置应用测试 | 100% |
| 生成物可追溯到数据、场景、模板和渲染器版本 | 100% |
建议首批金标准集不少于 30 份,覆盖至少 3 家保险公司、多个产品版式和原生/扫描/混合 PDF;在进入稳定运营前扩展到 50 至 100 份。关键样本必须由两名业务人员独立标注并处理分歧。
16. 监控与审计指标
每次解析建议记录:
- 文档哈希、类型、页数和 profile;
- 原生文本页数、OCR 页数和耗时;
- 每个字段的 extractor 和 confidence;
- 字段冲突数;
- 表格数、跨页表数、未识别列数;
- 自动通过、人工修改和阻断数量;
- 按字段统计的人工修改率;
- 最终快照版本。
每次生成建议记录:
- 输入 snapshot hash;
- scenario、policy、template 和 renderer 版本;
- matchTrace;
- 页面类型列表;
- 降级和警告;
- 输出数值对账结果;
- 视觉检查结果;
- 生成耗时和重试次数。
这些指标应能按保险公司、产品、版式和解析版本聚合。人工修改率突然上升通常比任务失败率更早暴露解析回退。
17. 隐私与数据留存
计划书可能包含姓名、年龄、保费、保额和资产安排,属于高度敏感资料。当前清理逻辑没有覆盖所有源 PDF、海报 case 数据和确认快照,且海报 case 返回值包含服务器源文件绝对路径。
建议:
- 所有源 PDF 通过带鉴权、短时效的下载/预览接口访问;
- API 不返回服务器绝对路径;
- 为源 PDF、解析中间产物、确认快照、海报、PPT 和日志分别定义留存期;
- 到期清理同时删除数据库记录和文件对象,并留下不含敏感内容的审计记录;
- 日志不得记录原始全文、完整身份证明或大段客户数据;
- 非生产环境样本必须脱敏;
- 金标准集使用获批且脱敏的样本。
18. 推荐实施顺序与文件影响
第一批应优先修改的模块:
- api/insurance/poster/service.py:确认与生成门禁;
- frontend/src 下海报复核页:接入原 PDF 和完整利益表;
- api/insurance/ppt/normalizer.py:取消错误兜底和缺失转 0;
- api/insurance/ppt/scripts/fast_pptx_renderer.py:修复币种和配置覆盖;
- api/insurance/generation/celery_tasks.py:冻结任务输入和 revision;
- api/insurance/ppt/comparison.py:把硬编码场景逐步迁移为版本化定义;
- 新建 api/insurance/document 与 api/insurance/plan_data:统一文档和事实层;
- tests 下建立脱敏 PDF 金标准、字段标注和输出对账测试。
不建议首先大改视觉模板或继续扩充 LLM 提示词。没有统一事实层和证据链时,视觉层越丰富,错误数据传播的范围越大。
19. 仍需运行环境核验的事项
以下问题无法仅凭仓库静态代码得出发生比例,应在实施阶段采集真实证据:
- 各保险公司 PDF 的实际解析准确率和最常见错列模式;
- 生产环境所用模型和提示词版本;
- OCR 在中英混合、低清扫描件上的质量;
- 当前后台已保存的模板、场景和 slidesConfig 数据;
- 是否已有用户因错误数据手工修改或放弃生成;
- 生产队列等待时间是否足以触发输入漂移;
- 客户 PDF 实际留存规模和访问权限;
- Office、WPS 与不同字体环境下的渲染差异。
这些待核验项不会改变本报告确认的架构问题,只影响优先级细分和工作量估算。
20. 最终结论
要实现“通用 PDF 精准解析,并基于精准数据生成海报和 PPT”,系统必须从“每个功能自己解析、自己兜底、自己生成”转为“一个事实层、多个受控输出”。
最重要的六条决策是:
- PDF 先形成包含坐标和表格的 Document IR,不能只保留纯文本;
- 所有关键字段必须有字段级证据,不能用完整度代替正确性;
- 海报和 PPT 只能消费同一个确认后的 PlanData 快照;
- 场景负责业务逻辑,模板负责视觉槽位,运行时不得静默覆盖后台配置;
- 异步任务必须冻结数据、场景、模板和渲染器版本;
- 最终导出必须进行数值对账,任何关键金额无法追溯或存在冲突时都应失败关闭。
本次发现的问题中,PPT 后台配置不生效、海报无法有效核对、缺失值转 0、错误字段兜底、任务快照漂移和缺少真实金标准,均应在继续扩展新模板或新海报功能之前处理。否则新增功能会继续放大同一类数据可信度问题。
附录 A:关键证据文件
- api/insurance/ppt/extraction.py
- api/insurance/ppt/regex_extractor.py
- api/insurance/ppt/normalizer.py
- api/insurance/ppt/validator.py
- api/insurance/ppt/comparison.py
- api/insurance/ppt/renderer.py
- api/insurance/ppt/scripts/fast_pptx_renderer.py
- api/insurance/generation/celery_tasks.py
- api/insurance/admin/ppt_admin_service.py
- api/insurance/models/ppt_config.py
- api/insurance/poster/tasks.py
- api/insurance/poster/service.py
- api/insurance/poster/content_builder.py
- api/insurance/poster/manual_parser.py
- api/insurance/models/poster_case_upload.py
- frontend/src/pages/components/ppt/PptDataReview.vue
- frontend/src/pages/components/ppt/PptGenerate.vue
- frontend/src/pages/admin/PptTemplatesAdmin.vue
- frontend/src/pages/components/ppt/PdfPagePreview.vue
- frontend/src/components/poster/workspace/PosterSourcePanel.vue
附录 B:与现有文档的关系
仓库中已有 PDF 解析、PPT 优化、模板重构和后续演进相关文档。本报告不替代具体实施计划,而是提供一次横跨 PDF、海报、PPT、模板、异步任务、质量门禁和隐私留存的统一根因审计。
后续拆解开发任务时,应以本报告的 P0/P1 优先级、统一 PlanData 快照和可量化验收标准为约束,避免把工作重新拆成互不相通的海报解析优化与 PPT 解析优化。