结构化提取

字段名一改,下游就坏:给提取结果加版本

上周的 `amount` 表示“分”,这周改成“元”,字段名却没变。类型校验仍然通过,业务结果却可能差两个数量级。提取接口最危险的升级,往往不是显式报错,而是悄悄改变含义。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 上周的 amount 表示“分”,这周改成“元”,字段名却没变。类型校验仍然通过,业务结果却可能差两个数量级。提取接口最危险的升级,往往不是显式报错,而是悄悄改变含义。 版本号要跟语义一起走 增加字段与改变单位不是一回事。可以给每个输出带上 schema_version ,并按版本选择验证器和迁移函数。版本号本身不会保护系统:如果所有版本都走同一条未经区分的解析逻辑,它只是一个装饰字段。 下面演示从整数分转换成十进制金额字符串。迁移是明确的数据转换,不是把旧数据直接贴上新版本标签。 一个可审计的迁移函数 from decimal import Decimal def migrate(record): if record.get("schema_version") != "v1": raise ValueError("unexpected input version") cents = record.get("amount_cents") if type(cents) is not int: raise ValueError("integer cents required") return {"schema_version": "v2", "amount": format(Decimal(cents) / Decimal(100), ".2f"), "currency": record["currency"]} old = {"schema_version": "v1", "amount_cents": 12345, "currency": "USD"} new = migrate(old) assert new["amount"] == "123.45" assert old["amount_cents"] == 12345 assert migrate({**old, "amount_cents": -25})["amount"] == "-0.25" try: migrate({**old, "amount_cents": True}) except ValueError: pass else: raise AssertionError("bool is not a monetary integer here") 此例假设输入币种使用百分单位关系,且输出保留两位小数。它不是覆盖所有货币单位的通用转换器。 type(cents) is int 刻意排除了 Python 中作为整数子类的布尔值,避免把 True 接受成一分钱。 迁移记录应该留下什么 信息 用途 缺失的风险 输入与输出版本 选择正确解释规则 同名字段含义混乱 迁移程序版本 重现转换 更新后结果无法解释 原始记录身份 找回来源 新旧数据无法对应 失败原因 分流人工处理 不合格数据被补默认值 不要无限兼容历史错误 若旧记录的单位本身不确定,迁移函数不应猜测。可以返回明确失败,保留旧数据并要求补证据。对已经消费旧接口的下游,也应安排版本切换,而不是期望它自动发现数值语义改变。 译后核对也需要版本意识 把提取字段用于检查 OmniDoc(产品入口) 译稿时,应说明字段由哪个协议生成。源文件、提取规则或目标语言变化,都可能影响比对方式。保留版本信息,能让“这次为什么和上次不同”成为可排查的问题,而不是一句模型不稳定。 参考: JSON Schema 通用关键字 。 - 1 -
更新于 2026-10-08