# jaspersoft 五轮修正失败问题 — 现象文档 ## 1. 问题概述 **触发场景**:用户上传一张车历卡(维修结算单)图片,系统通过 OCR 识别并生成 JRXML 报表模板。 **现象**:5 次自动修正循环全部失败,系统提示"[内容保真度不足] 得分 0.00/1.0",最终无法生成可用 JRXML。 **测试会话**:`sessions/6d39a91e11c54f02bb70a62d856ea2d4.json`(2026-05-24 15:14) --- ## 2. 完整流程追踪 ### 2.1 用户输入 - 上传文件:`e1113725c20fc4ec39bc9e4ab0caa6b2.jpg`(车历卡,1357×1920 RGB) - 用户输入:空(仅上传文件) ### 2.2 系统处理流程 ``` 上传图片 → process_input 节点(OCR 字段提取 + 布局分析) → layout_analyzer(34 行 × 1 列,A4 纵向) → ocr_extractor(4 策略提取) → classify_intent(= initial_generation) → retrieve → route_after_retrieve(有 layout_schema,走 generate_skeleton) → generate_skeleton(生成 ~34k 字符骨架 JRXML) → refine_layout(Band 级窗口化精调) → map_fields(程序化字段替换 $F{field_N} → 真实字段名) → validate(validate 节点) → XSD 验证:✅ 通过 → OCR 保真度检查:❌ score=0.41 < 0.5 → 降级为 fail → error_msg = "[内容保真度不足] 得分 0.41/1.0。元素覆盖不足:JRXML 仅有 142 个文本元素,OCR 源有 173 个文本元素(覆盖率 82%)。JRXML 中未声明任何字段,但 OCR 提取了结构化字段数据" → 5 次 correct_jrxml 修正循环均失败 → 状态:fail(MAX_RETRY=5 耗尽) ``` ### 2.3 最终状态 ``` status: fail retry_count: 5 error_msg: "[内容保真度不足] 得分 0.00/1.0。JRXML 仅有 0 个文本元素,OCR 源有 173 个文本元素(覆盖率 0%)" ``` ⚠️ **重要矛盾**:`current_jrxml`(24391 字符,142 个 textField)与 `error_msg`("0 个文本元素",score=0.00)存在矛盾。 验证服务审计(`validate-service-audit`)指出:"6d39a91e has 0 text elements causing score=0.00"——说明在**触发 fail 的那个时间点**,JRXML 确实只有 0 个文本元素。 但 session 文件最终保存的 `current_jrxml` 是 24391 字符版本。`jrxml_versions` 最后一条记录的 `jrxml` 才是触发失败的真实版本。 **结论**:`current_jrxml` 在 5 次修正过程中被逐步侵蚀,最终版本是某个接近空壳的状态。`jrxml_versions[-1]` 中的 `jrxml` 才是真正的失败版本。 --- ## 3. 关键数据对比 ### 3.1 JRXML 实际内容 session `6d39a91e` 的 `current_jrxml`: - 长度:24391 字符 - `` 元素:0 个 - `` 声明:63 个 - pageWidth/pageHeight:595×842(A4) - namespace:无 ns0: 前缀(✅ 已消除) ### 3.2 OCR 数据 layout_schema: - total_rows: 34 - total_columns: 1 - 总文本元素:173 个 ocr_extraction_result: - total_elements: 173 - fields 数组:18 个字段,全部是**发票模板字段** - 不含税金额、价税合计、单价、发票代码、发票号码、合计金额、开票日期、总金额、数量、日期、校验码、税率、税额、规格型号、货物名称、购买方名称、金额、销售方名称 - 正确匹配率:3/18(17%) ### 3.3 评分数据 `_check_ocr_fidelity` 实际运行结果: ```python # element_coverage 计算 textField_count = 142 # 完整元素(非标签数) staticText_count = 0 total_jrxml_elements = 142 ocr_text_count = 173 element_coverage = min(142/173, 1.0) = 0.82 # field_coverage 计算 jrxml_fields = {"print_date", "repair_number", ..., "vehicle_plate", ...} # 63 个英文字段 ocr_field_names = {"发票代码", "发票号码", "合计金额", ...} # 18 个中文字段 matched = jrxml_fields ∩ ocr_field_names = ∅ field_coverage = 0/18 = 0.0 # score 计算 score = 0.0 * 0.5 + 0.82 * 0.5 = 0.41 ``` **评分公式**(`nodes.py:1251`): ```python score = round(field_coverage * 0.5 + element_coverage * 0.5, 3) ``` **降级条件**(`nodes.py:1294`): ```python if fidelity["score"] < 0.5: state["status"] = "fail" ``` --- ## 4. 已确认的根因 ### 根因 1 — 评分逻辑设计错误(P0) **问题**:`field_coverage` 将英文字段名和中文字段名做交集比对,在普通生成场景下永远为 0。 **原因**: 1. LLM 生成英文字段名(`print_date`、`vehicle_plate`)是正确的设计选择 2. OCR 提取器硬套发票模板,提取中文字段名(`发票代码`、`合计金额`) 3. 两者来自完全不同的命名体系,不可能匹配 4. `field_coverage=0` 是**预期行为**,而非错误 **修复方向**:评分公式改为只依赖 `element_coverage`,`field_coverage` 作为信息提示而非降级条件。 ### 根因 2 — OCR 字段提取器无文档类型区分(P0) **问题**:`backend/ocr_extractor.py` 对所有单据使用同一套发票字段模板(14 个字段)。 **现象**: - 车历卡被当作发票处理 - 手机号 `13516727312` 被 6 个字段复用(发票代码/校验码/价税合计/单价/税率/不含税金额) - 字段名错配:发票号码→"服务套餐"、总金额→"钣金"、数量→"零件等级" ### 根因 3 — namespace 修复指令是条件触发(P1) **位置**:`prompts/correction.md` 第 11 行 **问题**:namespace 修复指令只在错误消息**包含 "namespace" 关键词**时才激活,是条件触发而非无条件指令。 **现象**: - ecd592 session 所有 5 次修正的实际错误类型是"字段未声明"(`字段 'u53d1_u7968...' 在表达式中使用但未声明`),**不包含 "namespace" 关键词** - 因此 namespace 修复指令从未被激活 - ns0: 前缀在前两次修正中持续存在,直到第 3 次才被 LLM 自发消除 - 最终 `current_jrxml`(ecd592)仍有 `` **修复方向**: - `prompts/correction.md`:改为无条件指令(检查 JRXML 是否包含 `ns0:`,而非依赖错误类型) - `prompts/initial_generation.md` + `skeleton_generation.md`:添加删除 ns0: 前缀的无条件指令 ### 根因 4 — 正则 `\w+` 不支持中文(低优先级) **位置**:`nodes.py:1211` ```python jrxml_fields = set(re.findall(r'