票据图片变成JSON,只完成录入的一部分。界面显示total为1200时,用户还需要知道它是哪一种币种,模型有没有把“1.200,00”按另一种格式解释。比“全自动记账”更值得做好的是,让人快速发现不该自动入账的字段。
2026年10月19日,Cloudflare发布Clairic提取模型预览,介绍从非结构化输入中提取数值的能力。发布说明 本文没有调用该模型,也不复现厂商准确率。即使换成专门模型,字段进入财务系统之前的应用校验仍然存在。
保存值,也保存出处
我会把字段拆为原文、规范值、币种、页码和复核状态。原文“1.200,00 €”可规范为“1200.00”,但转换规则必须来自明确地区或人工选择,不能简单删除全部逗号。
金额关系也需要语义。税额可能已经包含在单价里;折扣、运费可能单列。没有文档含义,拿两个字段相加,也可能是在验证错误公式。
用户点击金额,应该回到原文区域,并看到币种及转换方式。置信度可以帮助排序,不能代替原文;缺少出处时直接显示“缺证据”。
拒绝猜测地区
下面只支持事先指定的两种格式,拒绝不符合分组约定的输入,不处理货币符号,也不自动猜测地区。
from decimal import Decimal import re def amount(raw, style): patterns = { 'en': r'(?:\d+|\d{1,3}(?:,\d{3})+)\.\d{2}', 'de': r'(?:\d+|\d{1,3}(?:\.\d{3})+),\d{2}' } if style not in patterns or not isinstance(raw, str): raise ValueError('unsupported format') if not re.fullmatch(patterns[style], raw): raise ValueError('needs human review') value = raw.replace(',', '') if style == 'en' else raw.replace('.', '').replace(',', '.') return Decimal(value) assert amount('1,200.00', 'en') == Decimal('1200.00') assert amount('1.200,00', 'de') == Decimal('1200.00') try: amount('12,34.56', 'en') raise AssertionError('invalid grouping accepted') except ValueError: pass print('amount: 3 cases passed')保存为amount.py,运行python3 amount.py。从字符串构造Decimal可以避免先经过二进制浮点数再带入误差。Python文档解释了其十进制计算和舍入机制。负数、零位小数币种、贷项通知和舍入政策仍须另行设计。
结构正确也可能认错数字
Structured Outputs文档区分JSON模式与受Schema约束的输出。票据上的“8”被认成“3”,仍可能满足数字字段。UI不能把结构校验的绿勾解释成财务正确。
更稳妥的路径是先检查结构,再检查格式与字段关系,需要复核的内容进入人工队列。只有确认后的结果才能导出;修改保留前后值与原因。
本地例子只验证金额解析规则,没有测试OCR、模型、真实票据集合、财务软件集成或地区税务适用性。让每项检查的范围可见,才能知道这段代码适合放在系统的哪一层。