Bundle
dsh-tool-invoice
Invoice and receipt field extraction for DeepSeek Harness: pull numbers, dates, amounts and tax out of OCR text into a verifiable ledger.
- Source
- yuehancn
- License
- MIT
- Updated
- Updated 8 days ago
Readme
# 发票提取器 —— 让 dsh 把发票读成能对账的数字
> **提取字段 + 验算 + 汇总台账** —— 把发票里的号码、日期、购销方、金额、税额
> 抽成结构化字段,同时**替你检查 `金额 + 税额 = 价税合计` 到底成不成立**。
给 **DeepSeek Harness** 用的自建工具插件:3 个工具,把「读发票」从猜数字变成可审计。
> **Compatibility**: built and tested against dsh `0.2.0-rc.2` (preview).
> The `apply(ctx)` plugin spec is stable; verify against your own dsh version if needed.
---
## 一行安装
```bash
dsh plugin --profile desktop add github:yuehancn/dsh-tool-invoice
```
**支持的 profile**:`desktop`(桌面版)/ `web`(Web 版)。装完重启 dsh 即可用。
---
## 为什么需要它
让模型「看一下这张发票」,你得到的往往是一串看着合理、但**没有验算过**的数字。
而发票的全部可信度就在于一条恒等式:
```
金额 + 税额 = 价税合计
```
这个插件做三件普通正则做不到的事:
1. **验算,而不是抄写** —— 每条检查都同时给出「应该是什么」和「读到了什么」,
差了多少。对不上就是能行动的线索,而不是一个可疑的数字。
2. **给每条字段留下证据** —— 每个值都带着匹配到的标签(`销售方名称`)
和原句。会计可以复核,而不是盲信。
3. **不乱猜** —— `¥1130.00` 单独出现时可能是金额、税额或合计。
所以每条规则都要求**自己的标签在场**,没有标签就报「未识别」,
而不是挑一个看着像的。
**不 mock 一个字节**:288 条断言对着真实 `@deepseek-ai/dsh-tools` 与真实子进程跑过。
---
## 三个工具
### `invoice_status`
探本插件能不能自己读文件(有没有配文本提取命令),并列出它能识别的全部字段名。
### `invoice_extract(path | text)`
从一份发票里提取字段并验算。可以直接给**文件路径**,也可以给**你已经拿到的文本**。
识别的字段:
| 字段 | 中文标签 | 英文标签 |
|---|---|---|
| `code` / `number` | 发票代码 / 发票号码 | Invoice Code / No. |
| `date` | 开票日期 | Date of Issue |
| `seller` / `buyer` | 销售方 / 购买方 | Seller / Buyer |
| `sellerTaxId` / `buyerTaxId` | 纳税人识别号 | Tax ID |
| `amount` | 金额(不含税) | Amount Excluding Tax |
| `tax` | 税额 | Tax Amount |
| `total` | 价税合计(小写) | Total Amount |
| `totalInWords` | 价税合计(大写) | Amount in Words |
| `checkCode` | 校验码 | Check Code |
| `remark` | 备注 | Remarks |
返回的三条检查:
| 检查 | 含义 |
|---|---|
| `amount + tax == total` | 基本恒等式(容差可配:默认 ±2 分或 ±0.5%) |
| `digits agree with 大写 amount` | 小写数字与**大写金额**是否一致 —— 扫描件出错时这一条最先报警 |
| `implied tax rate is a standard bracket` | 反推税率是否落在 0/1/3/5/6/9/13% 这些标准档位上 |
还有一个 **`faithful`** 布尔值:所有检查通过 **且** 号码/日期/合计都在 → `true`。
绝大多数调用只需要看这一个字段。
### `invoice_ledger(paths, groupBy, outputName)`
把多张发票汇总成台账:总计、按字段分组的小计,并标出
**没验算过的行**、**读不出来的行**、以及**重复的发票号码**(重复报销的经典事故)。
加 `outputName` 会把台账写成 JSON。
---
## 容错到什么程度
真实发票文本很脏,所以解析器刻意宽松:
| 脏法 | 处理 |
|---|---|
| 全角数字 `1234` | 折叠成半角 |
| 全角标点 `¥ 1,000.00` | 折叠成 `¥ 1,000.00` |
| OCR 把 `0` 认成 `O`、`1` 认成 `l` | **仅在数字串内部**修复(不会碰公司名里的 O) |
| 千分位 `1,234.56` / 空格分隔 `1 234.56` | 都认 |
| 日期 `2026年10月02日` / `2026-10-02` / `2026/10/2` / `20261002` | 统一成 `YYYY-MM-DD` |
| 标签和值**不同行**(两栏排版) | 支持(标签独占一行时读下一行) |
| `发票号码:` 后为空、值在下一行 | 支持(不会把自己的冒号当成值) |
| 红字发票 `(1234.56)` | 识别为负数 |
| 大写金额 `壹仟贰佰叁拾肆元伍角陆分` | 解析成 `1234.56`,并交叉验证 |
| 英文发票(Stripe 收据等) | 同样支持 |
> 标签必须**出现在行首**。这是刻意的:`Tax` 会出现在
> `Amount Excluding Tax` 里面,如果允许任意位置匹配,就会把「不含税金额」
> 当成税额读走 —— 这是实测踩到过的坑,有专门的回归用例。
---
## 配置
```yaml
# ~/.dsh/profiles/<profile>/cordis.patch.yml
- id: tool-invoice
config:
outputDir: C:/Users/you/Documents/invoice-output
# 想让插件自己读 PDF/图片,就配一个「文件转文本」命令
textCommand: C:/poppler/Library/bin/pdftotext.exe
textArgs: ["-layout", "{input}", "{output}"]
toleranceCents: 2
```
| 配置项 | 默认 | 说明 |
|---|---|---|
| `outputDir` | `invoice-output` | 产物目录(中间文本、台账 JSON) |
| `timeoutMs` | 600000 | 单次调用预算(10 分钟) |
| `textCommand` | 空 | 留空则只接受文本输入;`invoice_extract` 仍可用 |
| `textArgs` | `["-layout","{input}","{output}"]` | 占位符 `{input}` / `{output}` |
| `tolerancePercent` | 0.5 | 恒等式的百分比容差 |
| `toleranceCents` | 2 | 恒等式的分容差 |
| `status` / `extract` / `ledger` | `true` | 按需关掉某个工具 |
**不配 `textCommand` 也完全可用** —— 只要你能拿到文本(比如先用
`dsh-tool-ocr` 插件、或从邮件正文里复制),`invoice_extract` 就直接可用。
`textCommand` 只是让你少一步手工。
**接 dsh-tool-ocr**:把 `textCommand` 指向 ocr 插件的 CLI 即可,两者串起来
就是「扫描件 → 文本 → 结构化发票」。
---
## 安全说明
- 只用 `spawn(command, argsArray)` —— **不拼 shell 字符串**,
路径里有空格、引号、中文都不会出事(有专门的中文带空格用例)。
- 只读写你指定的输入文件与 `outputDir`,不上传、不联网。
- 文本提取命令是**你自己配置的**可执行文件,插件不下载、不安装任何东西。
---
## 跑测试
```bash
mkdir -p node_modules/@deepseek-ai
cp -r "$HOME/.dsh/profiles/desktop/node_modules/@deepseek-ai/." node_modules/@deepseek-ai/
node _test/run-all.mjs
```
三个套件,**288 条断言全绿**(对着真实 `@deepseek-ai/dsh-tools` 跑,不 mock):
| 套件 | 断言 | 内容 |
|---|---|---|
| `test-logic.mjs` | 159 | 全角折叠与 OCR 混淆修复、金额解析(货币符号/千分位/负数/尾随噪声)、中文大写金额解析(含万/亿/角/分)、日期四格式、税号、**标签锚定与特异性**、三条验算、文档分类、台账汇总/分组/去重 |
| `test-integration.mjs` | 73 | 模块导出、注册数量、`defineTool` schema 归一化、三个开关、12 条错误路径、卡片标题、输出渲染 |
| `test-e2e.mjs` | 56 | 真的起子进程当文本提取器 → 真写文本 → 真提取;提取器退出非零/静默无输出的诊断;4 张发票的台账(含重复号与对不上的行);脏输入(全角+OCR混淆+两栏排版);英文发票;篡改大写金额的交叉验证 |
`test-e2e.mjs` 用自带的替身提取器 `_test/fixtures/mock-text.mjs`,
**不需要装 poppler、不需要联网**就能跑。
---
## 许可
MITInstall
dsh plugin --profile web add github:yuehancn/dsh-tool-invoice
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-tool-invoice from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.