| Files | Last commit | Last update |
|---|---|---|
| 8 days ago | ||
| 8 days ago | ||
| 8 days ago | ||
| 8 days ago | ||
| 8 days ago | ||
| 10 days ago | ||
| 8 days ago | ||
| 10 days ago | ||
| 8 days ago | ||
| 8 days ago | ||
| 8 days ago | ||
| 8 days ago |
演示样例
这个目录下的五张图是脚本合成的,不是真实票据。它们的作用是给回放模式
(--provider replay)提供一套图片与账本严格对齐的演示数据:JSON 里每个
区域的 bbox 就是图上那段文字的实际墨迹范围,点字段能直接跳到原图那一格。
五张图覆盖五个场景(发票、服务合同、报销单、资产负债表、购销合同),跨文档 对账要拿它们放在一起比。
重新生成
cd <仓库根目录>
python3 scripts/make_samples.py # 发票 / 服务合同 / 报销单
python3 scripts/make_samples_extra.py # 资产负债表 / 设备采购合同
脚本用 PIL + numpy 绘制,所有随机量(手写抖动、翻拍噪点、光照不均、旋转角) 都走固定 seed,每次执行产出完全相同的图片和 JSON。
| 文件 | 内容 |
|---|---|
samples/invoice_001.png |
增值税专用发票,1240×1754 |
samples/contract_001.png |
技术开发与运维服务合同,1240×1754 |
samples/expense_001.png |
员工报销单,手机翻拍模拟:0.9° 旋转 + 噪点 + 亮度不均 |
samples/financial_statement_001.png |
资产负债表,1240×1754 |
samples/purchase_contract_001.png |
设备采购合同,1240×1754 |
samples/sessions/demo.json |
前三张图的回放会话 |
samples/sessions/extra.json |
后两张图的回放会话 |
samples/real_ocr/ 里是 MiniCPM-OCR 1.0 在这三张图上的真实输出,录制命令见
scripts/record_ocr.py。
跑出来是什么
回放模式下跑完整链路(tracecell demo 或 tracecell serve):
| 文档 | 场景 | 区域 | 字段 | 待人工 | 成本 |
|---|---|---|---|---|---|
| invoice-001 增值税专用发票 | invoice | 18 | 9 | 4 | 13.10 rcu |
| contract-001 技术服务合同 | contract | 19 | 7 | 2 | 12.60 rcu |
| expense-001 员工报销单 | expense | 17 | 9 | 2 | 10.70 rcu |
| financial-statement-001 资产负债表 | financial_statement | 14 | 10 | 5 | 8.60 rcu |
| purchase-contract-001 设备采购合同 | purchase_contract | 15 | 17 | 3 | 9.00 rcu |
| 合计 | 83 | 52 | 16 | 54.00 rcu |
发票 9 个字段是 发票代码、发票号码、开票日期、购买方名称、销售方名称、
纳税人识别号、金额、税额、价税合计。报销单多出来的 3 个是明细行
(明细:差旅费 / 明细:办公用品 / 明细:业务招待费),勾稽校验要用。
每份样例埋了什么
invoice-001 增值税专用发票(场景 invoice)
r014 是「价税合计(小写)¥145,318.00」。小写金额比标签小一号而且用浅灰印。
同一个区域的两条候选给出两个数:primary 读成 ¥145,318.00(0.9930),
reparse-16x 把 3 看成 8 读成 ¥145,818.00(0.0000)——纸面上没有这个数。
系统没有硬选,两条都留在账本里,区域标成待人工。
同一区域还有一条 vlm-review 候选给出正确值 ¥145,318.00(0.9930),这就是
闭环里"问一句窄问题、拿一个可判定答案"的结果。另外 r015 备注区有一条分数
只有 0.2344 的候选——两个读法只差一个"为"字,纸面上分不出高下,只能靠人工。
最后 9 个字段里 7 个直接放行,价税合计(0.645)和 销售方名称(0.649)两格
进人工队列,原因写在区域的 note 里。
contract-001 技术服务合同(场景 contract)
r009「合同金额(大写):伍拾捌万元整」里"元"字印得偏淡,首次解析把"元"读成
了"圆"(0.0001),重解析才读对(0.9930)。r010「合同金额:¥580,000.00」同样
印得偏浅,重解析把 0 看成 8 读成 ¥580,800.00(0.0000)。两条候选都留在账本
里,合同金额大写(0.645)和 合同金额(0.636)两格进人工队列。
大写和小写印的是同一个数(伍拾捌万元整 = 580,000.00),互为印证。
乙方名称是「南京海川信息技术有限公司」,发票上的销售方名称是「南京海川 软件股份有限公司」。两份文档各自都识别得没错,放在一起却对不上。
这一对在 scenarios.py 的 CROSS_RULES 里是显式配对的——合同叫"乙方"、发票
叫"销售方名称",靠字段名撞车是对不上的。跑 tracecell reconcile 会被报成主体
名称不一致。字段名同名但含义不同的(发票的"金额"和报销单的"金额")只在同场景
之间比对,否则会互比出假矛盾。
expense-001 员工报销单(场景 expense)
整张图有 0.9° 旋转、轻微噪点和亮度不均,坐标跟着旋转一起变换,bbox 仍然贴着 文字。
r011「合计金额:¥2,864.50」印得又浅又小,重解析把 6 看成 9 读成 ¥2,964.50
(0.0000)——纸面上没有这个数。而三条明细 1,480.00 + 386.50 + 1,098.00 = 2,964.50,比单据上印的合计多了一百块:单据自己的合计印错了。勾稽校验直接
报出这条 critical 级问题,两侧的坐标都带上。
r003 报销人「李思远」是手写体,重解析读成「李思源」(0.1425),字段置信度
0.640。
financial-statement-001 资产负债表(场景 financial_statement)
r009「所有者权益合计:¥14,100,000.00」印得偏浅偏淡,字号比其它行小一号,
首轮识别只拿到 0.9662 分。
勾稽关系上,资产总计 = 流动资产合计 + 非流动资产合计 这条通过(12,860,000 +
8,140,000 = 21,000,000),但 资产总计 = 负债合计 + 所有者权益合计 这条不通过:
6,800,000 + 14,100,000 = 20,900,000,差 100,000。这正是资产负债表该平没平的
情况,系统报成 critical 级问题。
另外 r009「营业收入:¥128,000.00」与当期发票的 128,600.00 差 600 元,两边
OCR 都读对了,只能靠跨文档对账发现。
purchase-contract-001 设备采购合同(场景 purchase_contract)
明细是三行表格:
智能网关设备 50 2,572.00 128,600.00
边缘网关设备 20 1,180.00 26,600.00 ← 金额转置错了,正确值 23,600.00
安装配件 100 45.00 4,500.00
金额合计 156,700.00
合同总价 177,071.00(大写:壹拾柒万柒仟零柒拾壹元整)
「边缘网关设备」的金额印成了 26,600.00,正确值是 20 × 1,180.00 = 23,600.00。 这一处被两条独立的规则抓到:
- 明细行勾稽:26,600.00 vs 23,600.00,差 3,000
- 明细合计:印的合计 156,700.00 是按正确行算的,而各行加起来是 159,700.00
关键在于 OCR 把印错的数读得很准(0.98 分)——它确实就印在纸上。抓住它的 是算术,不是模型。
另外合同总价还有一条跨文档矛盾:177,071.00 与同期发票的价税合计对不上。
数据结构
sessions/demo.json 的格式由 src/tracecell/providers/replay.py 定义。
bbox 是归一化坐标(0~1,左上原点),由绘制时记录的像素坐标换算而来。
category 取 text / table / figure / seal。candidates[].origin 取
primary / reparse-16x / vlm-review,candidates[].verdict 取 scored /
not_scored / failed。
fields[] 里的字段名都能被 src/tracecell/extractors.py 的正则抽到,字段值是
对应区域原文里的子串——用正则抽一遍,结果与这里声明的完全一致。
samples/labelled.json 是标定用的"分数 + 人工判断"标注集,由
scripts/make_labels.py 从 sessions/demo.json 导出,判定规则是"候选文本与
区域原文一致就算对",不看分数,所以不会循环论证。改了本目录的会话数据之后按
这个顺序重新生成:
python3 scripts/make_labels.py
tracecell calibrate samples/labelled.json
回放模式里的分数是录制时写死在数据里的,不是现场算出来的。要重新算就用
--provider minicpm 接上真实的 MiniCPM-OCR / Jev / MiniCPM-V 服务。