Skip to content

xcbbdg1-maker/invoice-guard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

发票 P0:分级解析 + 规则校验

零 LLM 调用,零 OCR 调用,零成本。

目标是证明架构的核心假设:

大部分发票根本不需要模型就能 100% 准确地解析出来。

状态:已跑通 ✅

14 个测试全过(stdlib unittest,零依赖)

样本发票 7 张:
  解析路径分布 : {'L1_XML': 4, 'L1_OFD': 1, 'L2_PDF': 1, 'L3_OCR': 1}
  免模型解析率 : 6/7 = 86%
  自动通过率   : 4/7 = 57%
  LLM/OCR 调用 : 0 次,成本 ¥0

其中被拦下来的那几张,正是应该被拦下来的:

样本 结果
数电票 XML(英文标签) ✅ 自动通过
数电票 XML(拼音标签) ✅ 自动通过 —— 候选标签名机制兼容不同开票平台
金额被篡改(100+6 却写 160) ❌ 拦截 AMOUNT_MISMATCH —— 勾稽抓住了
OFD(内嵌结构化数据) ✅ 自动通过
重复上传 ❌ 拦截 DUP_FILE + DUP_INVOICE + DUP_SUSPECT
扫描件图片 ❌ 明确标记 NEEDS_OCR —— 不假装能处理
PDF 文本层 ✅ 自动通过

第三行是重点:金额被改成一个"看起来很合理"的数字,勾稽校验把它揪出来了。 这正是将来 OCR 抠错一位、LLM 抄错数字时会发生的事 —— 校验层是整个系统的安全网。

快速开始

# P0 是零依赖的。解析 PDF 才需要装东西:
pip install pdfplumber

# 跑测试
python -m unittest discover -s tests -v

# 造样本发票(可选,需要 reportlab 才能造 PDF 样本)
python tests/make_fixtures.py

# 跑管线
python -m invoice.cli run tests/fixtures/

用你自己的发票

第一步:先 dump,对标签名(必做)

python -m invoice.cli dump 你的发票.xml
python -m invoice.cli dump 你的发票.pdf

⚠️ parsers/xml_parser.py 里的 FIELD_CANDIDATES 是一组候选标签名。 不同开票平台的数电票 XML 标签名不一致 —— 我没有猜你的票用哪一套。 dump 会把真实标签打出来,补进候选列表即可。补完 L1 就是无损的。

样本里特意放了一张"拼音标签"的票来验证这个机制:同一套代码, InvoiceNumberFphm 都能解析。你的票大概率是这两套之一。

第二步:跑管线

# 整个文件夹丢进去
python -m invoice.cli run ./我的发票/

# 带抬头校验
python -m invoice.cli run ./我的发票/ --company "你的公司" --tax-id 91xxxxxxxx

# JSON 输出,喂给下游
python -m invoice.cli run ./我的发票/ --json > out.json

输出里只盯一个数

免模型解析率。 它越高,说明你越不需要 OCR 和 LLM。

如果你的打车发票这个数接近 100%,就直接证明了: "图片 → OCR → 大模型提字段"那套方案在这些票上做的一切, 都是在花钱把 100% 的准确率降下来。

跑不通的那几张(标记 NEEDS_OCR 的),才是 P1 真正要解决的输入。

目录结构

invoice/
  sniff.py              L0 格式嗅探(看 magic bytes,不看扩展名)
  parsers/
    xml_parser.py       L1 数电票 XML —— 无损,免费,准确率 100%
    ofd_parser.py       L1 OFD(zip 容器,取内嵌结构化数据)
    pdf_parser.py       L2 PDF 文本层 + 正则 —— 不做 OCR
  validate.py           规则校验层 —— 金额勾稽、税率、税号、抬头、去重
  config.py             税率表(带生效日期)+ 租户配置
  models.py             统一发票模型(全 Decimal)
  pipeline.py           L0 路由 → 解析 → 校验 → 置信度路由
  cli.py                命令行
tests/
  test_validate.py      14 个测试
  make_fixtures.py      造样本发票

设计要点

1. 金额永远是 Decimal,不是 float。 0.1 + 0.2 != 0.3 在财务场景是会出事的。

2. 校验层发现问题时,永远不尝试修正。 修正是人的活。系统的职责是保证"错误不会安静地通过"。

3. 抠不到的字段留空,不猜。 留空 → 进人工复核。猜 → 一个看起来很合理的错误答案。后者危险得多。

4. 金额勾稽是整个系统的地基。

不含税 + 税额 = 价税合计       (硬等式,不成立就拦截)
不含税 × 税率 ≈ 税额           (容差内,允许分位进舍)
Σ 明细行金额 = 不含税合计      (容差内)

将来接了 OCR 和 LLM,是这一层把它们的错误抓出来的。 没有它,识别准确率再高也只是"看起来对"。

5. 税率表是数据,不是代码。 config.py 里带生效日期。政策一变,改配置不改代码。 ⚠️ 里面的档位是占位默认值,上线前请以当期税收政策核对。

6. P0 不用 pydantic。 纯解析 + 校验用不上它。零依赖 = clone 下来就能跑。 到 P2 上 FastAPI 时再引入 —— 那时它才真正有价值(请求体校验)。

已知局限

  • ⚠️ FIELD_CANDIDATES 的标签名需要你用真实发票 dump 后确认
  • ⚠️ PDF 正则是脆的,换版式可能失配 —— 但勾稽会兜住,不会静默出错
  • ⚠️ _extract_parties 靠"购方在前、销方在后"的顺序假设,不总成立
  • ❌ OFD 纯版式解析(无内嵌数据时)→ P1
  • ❌ XML 数字签名验签 → 接财政部电子凭证工具包,别自己造
  • ❌ OCR、LLM 科目分类、税局查验 → P1 / P3 / P4

下一步

  1. 把你的打车发票丢进一个文件夹,跑 dump 对标签名
  2. run,看免模型解析率能到多少
  3. 跑不通的票拎出来 —— 那才是 P1 的输入

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors