结构化提取

抽取明细表,别让排序改掉行与行的关系

两行明细的金额相同,不能说明它们是重复数据;排序后的表格更整齐,也不能说明它仍与原文逐行对应。提取明细时,最容易丢掉的不是字段,而是行与来源之间的关系。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 两行明细的金额相同,不能说明它们是重复数据;排序后的表格更整齐,也不能说明它仍与原文逐行对应。提取明细时,最容易丢掉的不是字段,而是行与来源之间的关系。 先保存稳定的行身份 为每条明细保留来源页、表格编号和源行号,或一个由这些信息构成的稳定标识。产品名和金额可能重复,不适合单独作为身份。下游需要按金额排序时,可以建立展示视图,但不要把排序后的序号误写回原始来源。 JSON 数组本身保留顺序,不过 schema 里的 uniqueItems 不是“某个字段唯一”的快捷写法。两个对象只要其他字段不同,就可能仍被视为不同项。字段级唯一性要单独检查。 一组同金额、不同来源的明细 from collections import Counter rows = [{"source_id": "p1-t1-r1", "item": "Bolt", "amount": "10.00"}, {"source_id": "p1-t1-r2", "item": "Nut", "amount": "10.00"}] def check_ids(items): counts = Counter(row["source_id"] for row in items) return [key for key, count in counts.items() if count > 1] assert check_ids(rows) == [] assert len(rows) == 2 assert check_ids(rows + [dict(rows[0])]) == ["p1-t1-r1"] display = sorted(rows, key=lambda row: row["item"], reverse=True) assert [r["source_id"] for r in rows] == ["p1-t1-r1", "p1-t1-r2"] assert {r["source_id"] for r in display} == {r["source_id"] for r in rows} 这是合成数据的身份检查,不是表格识别器。代码故意不把相同金额去重,也不把显示顺序当成来源顺序。真实来源 ID 的生成规则需要考虑文件版本,否则两个版本的同一页同一行可能被误当成同一证据。 行数不对时先找丢失环节 现象 应检查 不应立即采取 金额重复 是否是不同项目或不同源行 按金额去重 来源 ID 重复 是否重复导入或映射错误 随机补一个新 ID 行数减少 合并单元格、空行与过滤规则 用上一行补齐 顺序变化 是否仅为展示排序 覆盖原始行号 跨页表头不是普通数据行 重复表头、续表说明和小计行都可能进入明细数组。删除它们需要可解释的规则,不能把“第一行”或“文字较短”当成唯一条件。保留被排除的行及理由,可以帮助审校发现误删,而不是只能在最终总额对不上时追查。 把来源关系带到译后核对 多语言明细表应能按来源 ID 对齐,不能只按译后产品名查找。评估 OmniDoc(产品入口) 的表格文档翻译结果时,行列关系、数量和单位都值得单独核对。本文的来源标识是外层工程约定,不是对平台内部实现的说明。 参考: JSON Schema 数组 。 - 1 -
更新于 2026-10-08