755 lines
30 KiB
Markdown
755 lines
30 KiB
Markdown
# PPT PDF 解析与数据核对页面问题修复报告
|
||
|
||
> 日期:2026-07-30
|
||
> 范围:`api/insurance/ppt/`、`api/insurance/generation/celery_tasks.py`、`frontend/src/pages/PptPage.vue`、`frontend/src/pages/components/ppt/PptDataReview.vue`
|
||
> 结论性质:源码、测试与静态界面审查结论;本次不修改业务代码
|
||
|
||
## 1. 执行结论
|
||
|
||
当前问题不是单纯的“模型不够聪明”或“页面样式不好看”,而是两条链路都存在结构性错误。
|
||
|
||
### 1.1 PDF 解析的核心问题
|
||
|
||
系统当前用“正则识别出至少 3 行利益表数据”作为整份计划书识别成功的主要分流条件。只要表格行数达到阈值,即使产品名称仍为 `unknown`、被保人年龄仍为空,也不会进入完整 LLM 结构化提取;后续 LLM 仅生成销售分析,不回填关键字段。
|
||
|
||
这直接解释了“利益表似乎有数据,但年龄和保险名称反而识别不出来”的现象。
|
||
|
||
同时还存在以下放大因素:
|
||
|
||
- 上传时已经选择的保司、产品及产品别名没有传入解析器作为先验信息;
|
||
- 文本提取采用“第一个可读结果即返回”,没有比较各解析器的字段和表格质量;
|
||
- 混合型 PDF 只按整份文档判断是否 OCR,文本页正常但局部扫描页可能完全漏掉;
|
||
- Docker OCR 只安装简体中文与英文,香港保险常见繁体中文未覆盖;
|
||
- 关键页筛选最多取 10 页、28,000 字符,却要求模型提取“所有页面、所有年度”,目标与输入不一致;
|
||
- 正则表格解析依赖理想化换行和列顺序,无法稳定处理多层表头、多页表格和视觉布局复杂的计划书;
|
||
- 某些数值会在解析后被代码静默改写,可能把列错位伪装成“合理结果”。
|
||
|
||
### 1.2 数据核对页面的核心问题
|
||
|
||
用户的真实任务不是“编辑一份 JSON”,而是:
|
||
|
||
> 用最少时间确认 AI 提取值是否与 PDF 原文一致,并安全地修正少量异常。
|
||
|
||
现有页面没有 PDF 原文、页图、高亮区域、字段来源或置信度。用户看到的是全部字段和整张可编辑表格,却看不到判断正确与否所需的证据。因此页面虽然叫“核对数据”,实际交互是“人工重新录入数据”。
|
||
|
||
此外,外层页面已经有步骤栏、上下文栏和底部操作栏,核对组件内部又增加产品栏、问题栏和第二套操作栏,形成“外层三栏套内层三栏”。错误状态还分散在四个位置,并且部分计数永远不同步。用户会持续产生三种不确定感:
|
||
|
||
1. 我现在到底应该先看哪里?
|
||
2. 我刚才改的值是否已经保存、是否已经通过?
|
||
3. 系统说的错误到底对应哪个产品、哪一页、哪一个输入框?
|
||
|
||
## 2. 调用链与根因链
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["上传 PDF,并选择险种/保司/产品"] --> B["保存 companyId / productId"]
|
||
B --> C["解析任务只传 pdf_path + plan_type"]
|
||
C --> D["首个整体可读的文本解析器直接返回"]
|
||
D --> E["正则提取产品、年龄、利益表"]
|
||
E --> F{"利益表行数 ≥ 3?"}
|
||
F -->|是| G["直接采用正则结果"]
|
||
G --> H["LLM 只做销售分析,不回填名称/年龄"]
|
||
F -->|否| I["选最多 10 个关键页交给 LLM"]
|
||
I --> J["校验并可选二次纠错"]
|
||
H --> K["partial 数据进入核对页"]
|
||
J --> K
|
||
K --> L["用户看不到 PDF 证据,只能编辑全量表格"]
|
||
```
|
||
|
||
关键断点是:
|
||
|
||
- 上传阶段已有可靠先验,但解析阶段丢弃;
|
||
- 分流只看“表格行数”,不看“关键字段完整性”;
|
||
- 核对阶段只给结果,不给证据。
|
||
|
||
## 3. PDF 解析问题明细
|
||
|
||
### PARSE-P0-01:解析分流条件错误
|
||
|
||
**证据**
|
||
|
||
- `ExtractionOrchestrator.REGEX_ROW_THRESHOLD = 3`:`api/insurance/ppt/extraction.py:391`
|
||
- 分支只判断 `regex_rows >= 3 and not used_ocr`:`api/insurance/ppt/extraction.py:468`
|
||
- 进入该分支后直接使用 `regex_data`:`api/insurance/ppt/extraction.py:470`
|
||
- LLM 输出只合并到 `sales_insights`:`api/insurance/ppt/extraction.py:480-491`
|
||
- `ANALYSIS_SYSTEM_PROMPT` 明确要求输出 `keyPoints/gaps/suggestedQuestions`,不输出修复后的结构化字段:`api/insurance/ppt/prompts.py:64-92`
|
||
|
||
**影响**
|
||
|
||
一份 PDF 只要利益表被识别出 3 行,即使:
|
||
|
||
- `product_name = "unknown"`;
|
||
- `insured.age = null`;
|
||
- 保额、缴费期、指数账户缺失;
|
||
|
||
仍会绕过完整 LLM 提取。最终只在末尾被标记为 `partial`,把解析失败的成本转移给用户人工修正。
|
||
|
||
**修复**
|
||
|
||
将分流条件改成“结构化质量门”,至少同时判断:
|
||
|
||
- 产品名称可确认;
|
||
- 被保人年龄可确认;
|
||
- 对应险种的必填保单字段完整;
|
||
- 表格字段覆盖率和年度覆盖率达标;
|
||
- 表格列关系通过基本一致性校验。
|
||
|
||
只要关键字段缺失,即使已经识别出很多利益表行,也必须进入“字段补全/冲突校验”流程,而不是销售分析流程。
|
||
|
||
### PARSE-P0-02:上传阶段的产品先验被丢弃
|
||
|
||
**证据**
|
||
|
||
- 上传记录保存 `companyId` 和 `productId`:`api/insurance/ppt/routes.py:140-145`、`169-175`
|
||
- Celery 任务也读取了这两个字段:`api/insurance/generation/celery_tasks.py:142-147`
|
||
- 实际调用解析器时只传 `filepath` 和 `plan_type`:`api/insurance/generation/celery_tasks.py:167-172`
|
||
- 产品表已经维护 `display_name` 和 `aliases_json`:`api/insurance/models/ppt_config.py:64-65`
|
||
|
||
**影响**
|
||
|
||
用户如果已经明确选择了某个产品,解析器仍然从零猜产品名称。封面文字是图片、艺术字、缩写或 OCR 错字时,系统无法利用产品目录做标准化与纠错。
|
||
|
||
**修复**
|
||
|
||
解析器接收以下上下文:
|
||
|
||
- `company_id`;
|
||
- `product_id`;
|
||
- 产品标准名称;
|
||
- 产品别名、英文名、产品代码;
|
||
- 险种;
|
||
- 可选的产品小册子规则。
|
||
|
||
解析结果与用户选择冲突时,不应静默覆盖任何一方,而应产生结构化冲突问题供用户确认。
|
||
|
||
### PARSE-P1-01:文本提取只判断“可读”,不判断“适合提取”
|
||
|
||
**证据**
|
||
|
||
- PyMuPDF 第一个通过整体乱码检测就直接返回:`api/insurance/ppt/extraction.py:119-127`
|
||
- 后续 PyPDF2、pypdf、pdfplumber 只有前一个失败才会尝试:`api/insurance/ppt/extraction.py:135-180`
|
||
- 乱码检测只按整份文本的可见字符比例判断:`api/insurance/ppt/extraction.py:201-225`
|
||
- 文本超过 120,000 字符时从尾部直接截断:`api/insurance/ppt/extraction.py:105`、`127`
|
||
|
||
**影响**
|
||
|
||
“能读出文字”不等于“字段和表格顺序正确”。PyMuPDF 可能读出大量正常文本,但:
|
||
|
||
- 表单标签和值被拆散;
|
||
- 表格按绘制顺序而非视觉列顺序输出;
|
||
- 封面产品名是图片,没有任何文本;
|
||
- 后半部分利益表在 120,000 字符截断后消失。
|
||
|
||
由于整体文本仍然“可读”,系统不会尝试更适合表格的解析方式或局部 OCR。
|
||
|
||
**修复**
|
||
|
||
改为逐页质量评估,并为每页记录:
|
||
|
||
- 字符数;
|
||
- 可读字符比例;
|
||
- 标签命中数;
|
||
- 数字密度;
|
||
- 表格结构得分;
|
||
- 是否需要 OCR;
|
||
- 使用的解析器。
|
||
|
||
不同页允许使用不同策略,不能用整份 PDF 的单一结果决定全部页面。
|
||
|
||
### PARSE-P1-02:关键页筛选与“提取全部数据”的目标冲突
|
||
|
||
**证据**
|
||
|
||
- 完整 LLM 提取最多取 10 页、28,000 字符:`api/insurance/ppt/extraction.py:523-530`
|
||
- 页面评分只使用少量通用关键词和数字密度:`api/insurance/ppt/prompts.py:147-176`
|
||
- 字符预算耗尽时直接截断当前页并停止:`api/insurance/ppt/prompts.py:178-187`
|
||
- Prompt 却要求“扫描所有页面,提取所有保单年度数据”:`api/insurance/ppt/prompts.py:19-23`
|
||
|
||
**影响**
|
||
|
||
当计划书包含:
|
||
|
||
- 多页利益演示;
|
||
- 单独的客户资料页;
|
||
- 附加险页;
|
||
- 繁体字段名;
|
||
- 产品名只出现在图形化封面;
|
||
|
||
模型根本没有看到对应页面,却被要求输出完整结果。模型只能遗漏或猜测。
|
||
|
||
**修复**
|
||
|
||
先识别页面角色,再强制选入:
|
||
|
||
1. 封面/产品身份页;
|
||
2. 客户或受保人资料页;
|
||
3. 保单摘要页;
|
||
4. 所有利益演示表页;
|
||
5. 所有退保/提取表页;
|
||
6. 对应险种的专属页面。
|
||
|
||
利益表不应通过“一次大 JSON”从 100 个年度一起生成;应按页或表块解析,再在后端合并和校验。
|
||
|
||
### PARSE-P1-03:正则规则只覆盖理想化标签和表格
|
||
|
||
**证据**
|
||
|
||
- 产品名称必须依赖 `产品名称:`、`Product Name:`、`Plan Name:` 等带标签格式:`api/insurance/ppt/regex_extractor.py:38-53`
|
||
- 年龄规则不覆盖繁体 `年齡`、`Issue Age`、`Age at Entry`、出生日期等常见表达:`api/insurance/ppt/regex_extractor.py:94-110`
|
||
- `Age:` 采用整份文档第一次匹配,可能误取其他人的年龄或利益表年龄;
|
||
- 表格列按换行和数字出现顺序分配,而不是按 PDF 几何坐标:`api/insurance/ppt/regex_extractor.py:266-289`、`350-397`
|
||
- 进入第一张表后,连续 5 行非数据就 `break` 整个扫描:`api/insurance/ppt/regex_extractor.py:292-347`
|
||
- 金额解析把合法的 `0` 转为 `None`:`api/insurance/ppt/regex_extractor.py:14-27`
|
||
- 未识别币种时默认 `USD`:`api/insurance/ppt/regex_extractor.py:56-76`
|
||
|
||
**影响**
|
||
|
||
- 香港繁体计划书年龄漏识别;
|
||
- 产品名以大标题或图片出现时漏识别;
|
||
- 多页表格、双层表头、保证/非保证子列容易错位;
|
||
- 合法 0 值被当成缺失;
|
||
- 未知币种被伪装成已确认的 USD。
|
||
|
||
**修复**
|
||
|
||
- 扩展繁体、英文、缩写和无冒号标签;
|
||
- 对年龄引入“受保人上下文窗口”,区分投保人、受保人、保单年度年龄;
|
||
- 支持出生日期加投保日期推算,并保留推算标记;
|
||
- 基于 PDF 词块坐标和表格边界重建列;
|
||
- 支持多页重复表头与表格续页;
|
||
- `0`、`null`、`未识别` 必须保持不同语义;
|
||
- 未知币种必须返回 `null` 并进入待确认队列。
|
||
|
||
### PARSE-P1-04:OCR 对香港保险 PDF 的适配不足
|
||
|
||
**证据**
|
||
|
||
- OCR 仅在整份文本为空或整体疑似乱码后执行:`api/insurance/ppt/extraction.py:125-186`
|
||
- 最多 OCR 40 页:`api/insurance/ppt/extraction.py:228-247`
|
||
- 使用固定 `chi_sim+eng`:`api/insurance/ppt/extraction.py:257-265`
|
||
- Docker 只安装 `tesseract-ocr-chi-sim` 与英文包:`Dockerfile.dify-custom:9-10`
|
||
- 所有页面固定使用 `--psm 6`,假设整页为单一文本块。
|
||
|
||
**影响**
|
||
|
||
繁体中文、图形化封面、复杂多栏表单和“部分页面扫描、部分页面可选文字”的混合 PDF 都容易漏识别。
|
||
|
||
**修复**
|
||
|
||
- 安装并启用 `chi_tra+chi_sim+eng`;
|
||
- 按页判断是否 OCR,只 OCR 低质量页;
|
||
- 表单页、封面页、表格页使用不同版面模式;
|
||
- 加入旋转检测、去噪、二值化和 300 DPI 基线;
|
||
- OCR 结果必须保留页码、词块坐标和置信度。
|
||
|
||
### PARSE-P1-05:LLM 输出约束不足
|
||
|
||
**证据**
|
||
|
||
- 最大输出固定为 8192 tokens:`api/insurance/ppt/llm_client.py:21-22`
|
||
- Prompt 可能要求输出 20 至 100 个年度的多列 JSON;
|
||
- `structured_output` 没有传入正式 JSON Schema,只依赖 Prompt 示例:`api/insurance/ppt/extraction.py:527-530`
|
||
- 只在 JSON 无法解析时重试;JSON 合法但字段遗漏时,主要依赖后置完整性检查。
|
||
|
||
**影响**
|
||
|
||
大表格容易被截断、压缩为里程碑年度,或生成合法但不完整的 JSON。模型供应商不同,对同一 Prompt 的字段一致性也会不同。
|
||
|
||
**修复**
|
||
|
||
- 身份字段、保单字段、各类表格分开调用;
|
||
- 表格按页/块输出小 JSON,再后端合并;
|
||
- 使用正式 JSON Schema;
|
||
- 结构化提取温度设为 0;
|
||
- 对字段类型、必填项、枚举和数组行建立程序化校验;
|
||
- 只对缺失块重试,不重复整份文档。
|
||
|
||
### PARSE-P1-06:静默“修复”可能掩盖解析错位
|
||
|
||
**证据**
|
||
|
||
储蓄险中若 `total_surrender_value < guaranteed_cash_value`,代码直接把总退保价值改成保证价值、红利和终期红利之和:`api/insurance/ppt/extraction.py:572-582`。
|
||
|
||
**影响**
|
||
|
||
如果真实原因是列错位或提取错误,系统会修改原始数字,使结果看起来符合业务常识,但不再忠实于 PDF。保险数据场景中,这比显式报错风险更高。
|
||
|
||
**修复**
|
||
|
||
禁止静默覆盖来源数据。应产生:
|
||
|
||
- 原始值;
|
||
- 规则计算值;
|
||
- 差异;
|
||
- 来源页;
|
||
- `blocking_conflict` 问题。
|
||
|
||
只有用户确认后才能采用修正值。
|
||
|
||
### PARSE-P1-07:缺少字段级可观测性和真实回归样本
|
||
|
||
**证据**
|
||
|
||
- `extraction_stats` 只写日志,没有保存到 extraction 结果:`api/insurance/ppt/extraction.py:459-463`、`593-602`
|
||
- 解析结果没有字段置信度、来源文本或坐标;
|
||
- 当前相关测试能通过 70 项,但主要覆盖理想化文本、少量页面筛选和流程状态;
|
||
- 没有发现真实保司 PDF 的金标回归语料。
|
||
|
||
**影响**
|
||
|
||
无法回答以下关键问题:
|
||
|
||
- 哪个解析器最常失败?
|
||
- 哪家保司、哪种模板、哪个字段准确率最低?
|
||
- 产品名是正则、产品目录、OCR 还是 LLM 得出的?
|
||
- 用户修改最多的字段是什么?
|
||
|
||
**修复**
|
||
|
||
建立脱敏金标 PDF 语料和字段级评估:
|
||
|
||
- 数字版简体、繁体、英文、双语;
|
||
- 扫描版与混合版;
|
||
- 多页利益表、多层表头、图形化封面;
|
||
- 储蓄险、重疾险、IUL;
|
||
- 每次发布输出字段准确率、表格行召回率、数字单元格准确率。
|
||
|
||
## 4. 校验接口与前端契约问题
|
||
|
||
### CONTRACT-P0-01:解析失败的产品可能不进入错误列表
|
||
|
||
**证据**
|
||
|
||
校验接口直接跳过 `status` 不是 `success/partial` 或没有 `data` 的 extraction:`api/insurance/ppt/routes.py:581-583`。
|
||
|
||
**影响**
|
||
|
||
某个产品解析失败时,校验问题列表可能没有对应阻断项。页面的总体错误数可能为 0,“确认生成”可能被错误启用;后续生成接口又只要求至少存在一个有效 extraction:`api/insurance/ppt/routes.py:463-469`。
|
||
|
||
**修复**
|
||
|
||
每个上传文件必须产生校验结果。解析失败必须返回带产品/文件身份的 blocking issue,除非用户明确移除该文件。
|
||
|
||
### CONTRACT-P1-02:问题没有产品和字段定位信息
|
||
|
||
**证据**
|
||
|
||
- 后端把所有产品的问题汇总成 `{field: code, severity, message}`:`api/insurance/ppt/routes.py:575-599`
|
||
- 没有 `extractionId/productIndex/pdfName/path/rowKey/sourcePage`;
|
||
- 前端只能用产品名称或 `[idx]` 字符串猜测归属:`frontend/src/pages/components/ppt/PptDataReview.vue:374-384`
|
||
- 问题代码是大写枚举,前端却用小写字符串判断标签页:`frontend/src/pages/components/ppt/PptDataReview.vue:392-410`
|
||
|
||
**影响**
|
||
|
||
多产品场景下错误徽标可能全部为 0;点击 `BENEFIT_ROWS_INCOMPLETE`、`ANNUAL_PREMIUM_INVALID` 等问题时,无法可靠切换到正确产品和字段,也不会聚焦具体输入框。
|
||
|
||
**修复**
|
||
|
||
统一 issue 契约:
|
||
|
||
```json
|
||
{
|
||
"id": "stable-id",
|
||
"extractionId": "pdf-or-product-id",
|
||
"pdfName": "plan.pdf",
|
||
"productIndex": 0,
|
||
"path": "insured.age",
|
||
"section": "fields",
|
||
"rowKey": null,
|
||
"sourcePage": 2,
|
||
"severity": "error",
|
||
"state": "unresolved",
|
||
"message": "被保险人年龄缺失",
|
||
"suggestedAction": "fill_or_confirm"
|
||
}
|
||
```
|
||
|
||
### CONTRACT-P1-03:字段数据类型前后端不一致
|
||
|
||
**证据**
|
||
|
||
- 正则和 LLM 将缴费年期输出为 `"5年"`:`api/insurance/ppt/regex_extractor.py:131-150`、`api/insurance/ppt/prompts.py:14`
|
||
- 前端用 `el-input-number` 绑定该字段:`frontend/src/pages/components/ppt/PptDataReview.vue:102-104`
|
||
- 前端性别选项值是 `male/female`:`frontend/src/pages/components/ppt/PptDataReview.vue:93-97`
|
||
- 后端正则输出 `男/女`:`api/insurance/ppt/regex_extractor.py:112-128`
|
||
- `data.product_type`、`ext.planType` 同时存在并可能不一致;
|
||
- 正则结果固定写 `product_type: savings`:`api/insurance/ppt/regex_extractor.py:659-679`
|
||
|
||
**影响**
|
||
|
||
- 缴费年期控件可能无法正常显示字符串;
|
||
- 指标可能显示“5年年”或计算失败;
|
||
- 已识别性别在下拉框中没有匹配项;
|
||
- 页面显示 IUL 标签,但“产品类型”下拉可能显示储蓄险;
|
||
- 保存后重新推断险种可能改变原始上传选择。
|
||
|
||
**修复**
|
||
|
||
建立唯一 DTO:
|
||
|
||
- `premium_payment_years: number | null`;
|
||
- `gender: "male" | "female" | "unknown"`;
|
||
- `plan_type` 只保留一个权威字段;
|
||
- 展示层自行追加“年”“岁”等单位;
|
||
- API 入参出参都做 schema 校验和迁移兼容。
|
||
|
||
## 5. 核对页面 UX 问题明细
|
||
|
||
### UX-P1-01:没有原始证据层
|
||
|
||
**证据**
|
||
|
||
- `PptDataReview` 只接收 `sessionId`:`frontend/src/pages/components/ppt/PptDataReview.vue:305-307`
|
||
- 页面没有 PDF 预览或来源文本;
|
||
- “来源页”被设计为可编辑数字:`frontend/src/pages/components/ppt/PptDataReview.vue:183-186`、`227-230`
|
||
- PPT 模块没有面向当前用户的源 PDF 预览接口。
|
||
|
||
**第一性原理**
|
||
|
||
核对必须同时看到“系统答案”和“原始证据”。没有证据,用户只能猜、回忆或另开窗口,认知负担和出错率都会上升。
|
||
|
||
**修复**
|
||
|
||
点击任一待确认字段时:
|
||
|
||
- 展示对应 PDF 页;
|
||
- 高亮来源区域;
|
||
- 同时显示提取值、原文、置信度、识别方式;
|
||
- 支持“接受”“修改”“标记无法确认”;
|
||
- 来源页由系统记录,不作为普通业务值自由编辑。
|
||
|
||
### UX-P1-02:外层三栏套内层三栏
|
||
|
||
**证据**
|
||
|
||
- 外层:步骤导航、中央内容、240px 上下文栏:`frontend/src/pages/PptPage.vue:39-132`
|
||
- 内层:200px 产品栏、编辑器、240px 问题栏:`frontend/src/pages/components/ppt/PptDataReview.vue:25-275`
|
||
- 外层和内层各有一套底部操作栏:`frontend/src/pages/PptPage.vue:76-83`、`frontend/src/pages/components/ppt/PptDataReview.vue:277-291`
|
||
|
||
**影响**
|
||
|
||
固定区域在进入编辑器前已经占用大量宽度,大表格只能依赖横向滚动。状态、导航和操作同时竞争注意力,用户无法形成稳定视觉路径。
|
||
|
||
**修复**
|
||
|
||
核对步骤进入专注模式:
|
||
|
||
- 隐藏冗余外层上下文栏;
|
||
- 产品切换合并到顶部;
|
||
- 主体只保留“PDF 证据 + 当前核对任务”两栏;
|
||
- 只保留一套粘底操作栏。
|
||
|
||
### UX-P1-03:校验状态重复且互相矛盾
|
||
|
||
**证据**
|
||
|
||
状态同时出现在:
|
||
|
||
1. 核对组件顶部;
|
||
2. 核对组件右栏;
|
||
3. 外层步骤导航;
|
||
4. 外层上下文栏。
|
||
|
||
父级 `stepErrors/stepWarnings` 初始化后没有接收子组件更新:`frontend/src/pages/PptPage.vue:197-198`。因此外层可能始终显示 0,内层却显示真实错误。
|
||
|
||
右侧栏关闭按钮也没有真正控制 aside 的 `v-if`:`frontend/src/pages/PptPage.vue:87-90`。
|
||
|
||
**修复**
|
||
|
||
- 校验状态只保留一个权威 store;
|
||
- 父子组件共享同一状态;
|
||
- 顶部只显示总进度;
|
||
- 详细问题只出现在问题队列;
|
||
- 修复关闭按钮条件或移除冗余栏。
|
||
|
||
### UX-P1-04:问题点击不是真正的“定位”
|
||
|
||
**证据**
|
||
|
||
`jumpToIssue` 只尝试:
|
||
|
||
- 用产品名猜产品;
|
||
- 用字符串包含关系猜标签页。
|
||
|
||
它不会:
|
||
|
||
- 滚动到目标字段;
|
||
- 聚焦输入框;
|
||
- 高亮表格行或单元格;
|
||
- 打开 PDF 来源页;
|
||
- 将焦点移到下一条未解决问题。
|
||
|
||
**影响**
|
||
|
||
用户点击问题后仍需自己搜索。产品名本身缺失时,恰好无法靠产品名定位。
|
||
|
||
**修复**
|
||
|
||
使用结构化 `path + rowKey + sourcePage`,实现:
|
||
|
||
> 点击问题 → 切正确产品 → 打开正确分区 → 滚动并高亮字段 → PDF 跳页并高亮 → 修改后自动进入下一问题。
|
||
|
||
### UX-P1-05:把“核对”设计成“全表编辑”
|
||
|
||
**证据**
|
||
|
||
利益表的每一个单元格始终显示 `el-input-number`:`frontend/src/pages/components/ppt/PptDataReview.vue:117-193`。IUL 最多同时展示十余列。
|
||
|
||
**影响**
|
||
|
||
- 数值与控件外观混在一起,扫描速度低;
|
||
- 用户不知道哪些值真的有问题;
|
||
- 20 至 100 行时出现数百个输入控件;
|
||
- 缺少键盘移动、批量粘贴、只看异常和批量确认。
|
||
|
||
**修复**
|
||
|
||
- 默认只读格式化表格;
|
||
- 只把缺失、冲突、低置信单元格高亮;
|
||
- 单击或 Enter 进入编辑;
|
||
- 支持“仅看异常”“接受当前页”“从 Excel 粘贴”;
|
||
- 支持上下一个问题和键盘导航;
|
||
- 全量表格放在二级入口,默认先完成异常队列。
|
||
|
||
### UX-P1-06:保存机制给出错误安全感
|
||
|
||
**证据**
|
||
|
||
- 数据变化时所谓自动保存只提交 `{workflow_step: "review", hasEdits: true}`:`frontend/src/pages/components/ppt/PptDataReview.vue:322-331`
|
||
- 实际 extraction 数据只有点击“保存修改”才调用 `updateExtractions`:`frontend/src/pages/components/ppt/PptDataReview.vue:588-603`
|
||
- 页面离开前也只保存步骤和时间戳:`frontend/src/pages/PptPage.vue:169-176`
|
||
- 编辑后校验问题不会实时刷新,只有显式保存后重新校验。
|
||
|
||
**影响**
|
||
|
||
顶部草稿指示器可能显示“已保存”,但用户修改的真实保险数据并未保存。返回步骤、点击导航或刷新都可能丢失编辑。
|
||
|
||
**修复**
|
||
|
||
- 自动保存必须保存真实 extraction patch;
|
||
- 保存状态明确区分“正在保存数据”“已保存并已校验”;
|
||
- 离开前强制 flush;
|
||
- 冲突时展示字段级差异;
|
||
- 编辑后本地即时校验,防抖服务端校验;
|
||
- 提供撤销与恢复。
|
||
|
||
### UX-P1-07:缺失字段反而没有输入入口
|
||
|
||
**证据**
|
||
|
||
保额字段使用 `v-if="currentExt.data.policy.sum_insured"`:`frontend/src/pages/components/ppt/PptDataReview.vue:105-107`。
|
||
|
||
**影响**
|
||
|
||
AI 漏识别保额时,该字段被隐藏,用户无法补录;而 CI/IUL 校验又要求保额大于 0。
|
||
|
||
**修复**
|
||
|
||
字段是否显示应由险种 schema 决定,而不是由当前值是否存在决定。缺失的必填字段必须最醒目。
|
||
|
||
### UX-P2-08:状态与编辑值不同步
|
||
|
||
**证据**
|
||
|
||
- 产品列表和标题优先显示 `ext.productName`;
|
||
- 输入框修改的是 `currentExt.data.product_name`;
|
||
- 保存响应中的重算状态没有合并回本地 extraction。
|
||
|
||
相关代码:`frontend/src/pages/components/ppt/PptDataReview.vue:43`、`61`、`80-82`、`599-603`。
|
||
|
||
**影响**
|
||
|
||
用户修改产品名称后,标题可能仍显示旧名称;修复 partial 数据后,标签也可能继续显示“解析不完整”。
|
||
|
||
### UX-P2-09:返回、删除与辅助功能问题
|
||
|
||
- 内层按钮写“返回上传”,实际只回到解析步骤:`frontend/src/pages/components/ppt/PptDataReview.vue:279`
|
||
- 外层还有一个“返回上一步”,形成重复;
|
||
- 删除表格行无确认、无撤销;
|
||
- 顶部返回和右栏关闭是可点击图标,不是语义按钮:`frontend/src/pages/PptPage.vue:6`、`90`
|
||
- 产品状态圆点主要依靠颜色表达;
|
||
- 多个独立滚动区使键盘和 200% 缩放用户难以定位。
|
||
|
||
## 6. 设计健康度
|
||
|
||
基于 Nielsen 10 项可用性原则:
|
||
|
||
| # | 原则 | 分数 | 主要问题 |
|
||
|---|---|---:|---|
|
||
| 1 | 系统状态可见 | 2/4 | 有加载和错误数,但校验陈旧、父子计数矛盾 |
|
||
| 2 | 符合现实世界 | 2/4 | 保险术语正确,但没有真实 PDF 证据 |
|
||
| 3 | 用户控制与自由 | 1/4 | 无撤销,删除立即执行,关闭按钮失效 |
|
||
| 4 | 一致性与标准 | 2/4 | 基础组件一致,状态、返回、底栏和字段类型不一致 |
|
||
| 5 | 错误预防 | 1/4 | 缺失字段隐藏,来源页可误改,0 与缺失混淆 |
|
||
| 6 | 识别而非记忆 | 1/4 | 必须另开 PDF 或凭记忆比对 |
|
||
| 7 | 灵活与高效 | 1/4 | 无异常队列、批量、粘贴、快捷键 |
|
||
| 8 | 美观与极简 | 1/4 | 外层三栏叠内层三栏,所有单元格长期编辑态 |
|
||
| 9 | 错误识别与恢复 | 1/4 | 问题不能定位、聚焦、高亮或提供恢复路径 |
|
||
| 10 | 帮助与文档 | 0/4 | 无置信度、字段口径、警告处理指导 |
|
||
| **总分** | | **12/40** | **Poor,需要重做核对任务架构** |
|
||
|
||
认知负荷检查有 6/8 项失败,属于高负荷:
|
||
|
||
- 不能保持单一焦点;
|
||
- 视觉层级不清;
|
||
- 不能一次只处理一个决策;
|
||
- 同屏选择过多;
|
||
- 依赖工作记忆;
|
||
- 没有渐进披露。
|
||
|
||
## 7. 推荐目标交互
|
||
|
||
### 7.1 页面结构
|
||
|
||
```text
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 产品/文件切换 · 已解决 4/7 · 2错误 1待确认 · 已保存并校验 │
|
||
├──────────────────────────────┬──────────────────────────────────┤
|
||
│ PDF 原文 / 页图 │ 当前待核对问题 │
|
||
│ │ 被保人年龄 │
|
||
│ 自动跳到第 2 页 │ AI 值:— │
|
||
│ 高亮“受保人年齡 38” │ 原文:受保人年齡 38 │
|
||
│ │ 置信度:低 · OCR │
|
||
│ │ [接受 38] [修改] [无法确认] │
|
||
│ │ │
|
||
│ │ 下一个问题:产品名称 │
|
||
├──────────────────────────────┴──────────────────────────────────┤
|
||
│ 撤销 · 上一个问题 保存状态 · 下一个问题/确认生成 │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 7.2 信息优先级
|
||
|
||
1. 阻断错误;
|
||
2. 字段冲突;
|
||
3. 低置信字段;
|
||
4. 缺来源字段;
|
||
5. 普通警告;
|
||
6. 已确认的正常数据;
|
||
7. 全量原始表格。
|
||
|
||
高置信且通过交叉校验的数据默认折叠,不要求用户逐项确认。
|
||
|
||
### 7.3 操作原则
|
||
|
||
- 一个页面只保留一个主要任务:清空待核对问题;
|
||
- 一个时刻只编辑一个异常;
|
||
- 所有判断依据与当前异常同屏;
|
||
- 修改后立即反馈是否解决;
|
||
- 自动保存真实数据;
|
||
- 用户可随时撤销、返回,且不会丢失工作;
|
||
- 全量编辑是高级入口,不是默认入口。
|
||
|
||
## 8. 修复顺序
|
||
|
||
### 阶段 1:先修数据安全与分流
|
||
|
||
1. 以关键字段完整度替换 `regex_rows >= 3` 单一门槛;
|
||
2. 解析失败的每个文件都生成 blocking issue;
|
||
3. 移除数值静默覆盖;
|
||
4. 将 `companyId/productId/aliases` 传入解析器;
|
||
5. 统一 `plan_type/gender/premium_payment_years` DTO;
|
||
6. 增加针对当前错误分支的回归测试。
|
||
|
||
### 阶段 2:改造 PDF 解析
|
||
|
||
1. 逐页文本质量评估;
|
||
2. 混合页 OCR;
|
||
3. 支持繁体 OCR;
|
||
4. 页面角色识别与必选页策略;
|
||
5. 基于坐标的表格重建;
|
||
6. 身份、保单、表格分块结构化提取;
|
||
7. 字段级 provenance/confidence。
|
||
|
||
### 阶段 3:重做校验契约
|
||
|
||
1. issue 带产品、路径、行、页码和状态;
|
||
2. 本地即时校验与服务端权威校验统一;
|
||
3. 多产品问题不再使用字符串猜归属;
|
||
4. 记录用户接受、修改和无法确认状态。
|
||
|
||
### 阶段 4:重构核对页面
|
||
|
||
1. 核对步骤专注模式;
|
||
2. PDF 与当前问题双栏;
|
||
3. 待核对队列;
|
||
4. 默认只读、异常才编辑;
|
||
5. 真正的数据自动保存;
|
||
6. 撤销、批量、粘贴和键盘操作;
|
||
7. 去掉重复状态栏、上下文栏和底栏。
|
||
|
||
### 阶段 5:真实数据回归与灰度
|
||
|
||
1. 建立脱敏金标 PDF 集;
|
||
2. 按保司、险种、语言、扫描类型统计准确率;
|
||
3. 记录用户修改率与问题处理耗时;
|
||
4. 新旧解析器双跑对比;
|
||
5. 达标后切换默认流程。
|
||
|
||
## 9. 验收标准
|
||
|
||
### 9.1 解析准确性
|
||
|
||
- 产品名称标准化准确率 ≥ 98%;
|
||
- 被保人年龄精确准确率 ≥ 99%;
|
||
- 关键保单字段准确率 ≥ 97%;
|
||
- 可读数字版 PDF 的利益表行召回率 ≥ 95%;
|
||
- 已识别数字单元格精确准确率 ≥ 99%;
|
||
- 关键字段缺失时 100% 进入待核对,不得显示整体通过;
|
||
- 所有自动修正都可追溯,不允许静默改变 PDF 来源值。
|
||
|
||
### 9.2 核对体验
|
||
|
||
- 用户进入页面 5 秒内能看懂还有多少问题、先处理哪一个;
|
||
- 点击问题后一次操作内看到正确产品、字段和 PDF 来源页;
|
||
- 修正后 1 秒内本地状态更新,服务端校验完成后明确确认;
|
||
- 高置信数据无需逐项进入编辑;
|
||
- 正常离开、刷新、返回步骤均不丢失修改;
|
||
- 只有一套错误计数、一套主操作栏;
|
||
- 1280px 宽度下不出现“多层侧栏挤压核心编辑区”;
|
||
- 键盘可完成上一个问题、修改、接受、下一个问题和确认。
|
||
|
||
### 9.3 测试覆盖
|
||
|
||
必须新增:
|
||
|
||
- “利益表 ≥3 行但产品名/年龄缺失时仍进入字段补全”的测试;
|
||
- 繁体 `年齡`、`受保人年齡`、`Issue Age`、出生日期测试;
|
||
- 产品名无标签、别名、产品代码和 OCR 错字测试;
|
||
- 多页利益表、重复表头、多层表头测试;
|
||
- 混合型 PDF 局部 OCR 测试;
|
||
- 超过 40 页、超过 120,000 字符测试;
|
||
- 多产品 issue 精确定位测试;
|
||
- 解析失败产品阻断生成测试;
|
||
- 自动保存真实编辑数据和离开恢复测试;
|
||
- 核对页键盘、响应式和 200% 缩放测试。
|
||
|
||
## 10. 本次诊断的证据边界
|
||
|
||
- 已运行相关后端测试:70 项全部通过;
|
||
- 测试通过说明现有理想化样例没有回归,不代表真实计划书准确率达标;
|
||
- 当前工作区未发现可用于复现的真实失败 PDF,因此无法对具体保司模板给出字段级误差统计;
|
||
- 自动界面检测器扫描 `PptDataReview.vue` 返回 0 条规则问题,但该检测器不理解“核对必须有原始证据”等业务交互问题;
|
||
- 当前浏览器运行环境不可用,未进行实页截图和交互录屏验证;
|
||
- 上述高优先级问题均可由源码调用链直接证实,真实 PDF 与实页验证主要用于量化影响,而不是决定问题是否存在。
|
||
|
||
## 11. 最终判断
|
||
|
||
修复顺序必须先保证“机器不把不完整结果当成功”,再优化 OCR 和模型,最后重做核对交互。
|
||
|
||
如果只换模型:
|
||
|
||
- 正则成功分支仍不会让模型回填名称和年龄;
|
||
- 上传产品先验仍然浪费;
|
||
- 关键页仍可能不包含所需页面;
|
||
- 用户仍然看不到来源证据。
|
||
|
||
如果只美化页面:
|
||
|
||
- 外层和内层状态仍会矛盾;
|
||
- 问题仍无法定位;
|
||
- 自动保存仍不保存真实修改;
|
||
- 用户仍然需要人工重录整张表。
|
||
|
||
因此,这次应按“解析质量门 → 来源与置信度 → 校验契约 → 核对任务架构”的顺序进行,而不是做局部样式微调。
|