Bundle
dsh-invoice-tools
DSH 原生发票解析/报销单生成工具:读取工作区发票 PDF(XML 附件优先、文本层正则兜底),结构化 JSON + 金额勾稽校验,多张汇总生成报销单(Markdown / xlsx)。
- Source
- 988hj7tczd-oss
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 22 hours ago
Readme
# dsh-invoice-tools — 发票解析 / 报销单生成工具
> [!IMPORTANT]
> **依赖前置:相邻 `dsh-src` 检出(`link:` 依赖)**
> 本项目在开发形态下使用 `link:` 依赖指向相邻的 DeepSeek Harness 源码检出(`dsh-src`),
> 与当前仓库保持同一父目录布局(`<parent>/dsh-src`)。克隆本仓库后:
> 1. 先把官方 `deepseek-ai/deepseek-harness` 检出到与本仓库同级的 `dsh-src/` 目录,并执行其 `pnpm install && pnpm run build`;
> 2. 再按下方「安装」一节执行本仓库的 `pnpm install --offline && pnpm build` 与测试。
> 发布到 npm 的版本会尽量把 `link:` 依赖替换为 registry 真实版本;无法替换的内部包保持 `link:`,见各包 README 说明。
DSH(DeepSeek Harness)原生工具插件:读取工作区内的增值税电子发票文件(数电票/全电发票 PDF、图片),
结构化为 JSON(发票代码/号码/金额/税额/购买方/销售方/开票日期),多张汇总生成报销单(Markdown / xlsx)。
全程只读输入、写入输出文件;发票数据仅在本机处理,**不上传任何查验接口**;解析缓存按会话隔离
(工具无法取得会话上下文时回退为进程级缓存,详见「安全与隐私」)。
> 2026-08 调研结论:`dsh invoice payment 报销 发票` 实时搜索 0 结果,发票/报销方向无人做,本插件填补该空白。
## 功能一览
| 工具 | 作用 | 参数 |
| --- | --- | --- |
| `invoice_parse` | 单张/批量发票解析(文件或目录) | `path`(必填) |
| `invoice_summary` | 汇总结算 + 报销单生成 | `files?` / `out?` / `categories?` |
### 解析路径(invoice_parse)
1. **XML 附件优先**:读取 PDF 内嵌的结构化 XML(`/EmbeddedFiles` 命名树),按中英双语候选标签
白名单抽取:发票号码/代码、金额(不含税)、税额、价税合计、购买方、销售方、开票日期、查验平台网址。
2. **文本层兜底**:无 XML 时以文本正则抽取——票号正则 `\d{8,20}` + 标签定位
(`发票号码:`、`金额(大写)…(小写)¥x`、`开票日期:`、`购买方名称:` 等)。
3. **勾稽校验**:`价税合计 = 金额 + 税额`(容差 0.01),不符时核对表标 `⚠`。
4. **置信度标注**:XML 直读 = 高(high);正则 = 中(medium);关键字段缺失 = 低(low)并列出缺失清单。
### 汇总(invoice_summary)
- 合计/税额/价税合计、**按类别小计**(`categories` 映射,键 = 票号或文件名);
- **重复票检测**:同票号 + 同金额(保留首张,其余建议剔除);
- 生成 `报销单-<日期>.md`(默认)或 `xlsx`(`out` 参数)到工作区;
- `files` 缺省时复用 `invoice_parse` 的**解析缓存**;缓存 key 含会话维度
(`<会话id>:<绝对路径>`),工具拿不到会话 id 时回退 `global:` 前缀——即进程级缓存,见「安全与隐私」。
## 目录结构
```
dsh-invoice-tools/
├── cordis.yml # 装配清单(生产以裸包名 dsh-invoice-tools 挂载)
├── package.json # dsh.bundle.patch 声明 + 构建/测试脚本
├── tsconfig.json
├── src/
│ ├── index.ts # 装配 + 注册 2 工具
│ ├── tools/invoice-parse.ts # 单张/批量解析(fs 编排 + 核对表渲染 + 会话作用域缓存)
│ ├── tools/invoice-summary.ts# 汇总 + 报销单生成(版本守卫写入)
│ ├── pdf-extract.ts # 自写最小 PDF 解析器:文本层 + XML 附件 + 加密检测
│ ├── invoice-model.ts # 字段模型 + XML/正则抽取 + 金额勾稽校验
│ └── summary.ts # 报销单 md/xlsx 渲染 + 极小 zip 打包器
├── tests/smoke.e2e.ts # 离线冒烟测试(fixture PDF 内嵌生成)
└── README.md
```
## 装配方式
依赖:宿主已提供 `tools`(工具注册表)与 `fs`(文件系统服务)。两种挂载模式:
1. **生产(推荐):`dsh plugin add` 安装后以裸包名挂载**。`package.json` 的
`dsh.bundle.patch` 声明随包发布;`main` 指向编译产物 `lib/index.js`(发布前
`npm run build` 产出)。`cordis.yml` 中的 `name: 'dsh-invoice-tools'` 由 loader
按 Node 模块解析(`import('dsh-invoice-tools')`),**不要使用相对路径**——loader 对
`.` 开头的 name 按 bundle/profile 根目录解析,包内相对源码路径(`./src/index.ts`)
必然解析失败并使启动 fail-loud。
```yaml
- insert:
- id: invoice-tools
name: 'dsh-invoice-tools'
```
2. **开发直挂:绝对路径**(同 dsh-src/scratch-plugin 的做法):把 insert 行并入宿主
`cordis.yml`,name 写源码入口的绝对路径:
```yaml
- insert:
- id: invoice-tools
name: '/absolute/path/to/dsh-invoice-tools/src/index.ts'
```
## 使用示例
```
# 解析单文件 / 整目录
invoice_parse path='invoices/电子发票_2024.pdf'
invoice_parse path='invoices/' # 目录递归
# 汇总(缺省用上次 parse 缓存)→ 报销单-<日期>.md
invoice_summary
invoice_summary out='both' categories='{"发票号码123...":"差旅","文件B.pdf":"办公"}'
# 指定文件汇总(须先 parse)
invoice_summary files='["invoices/A.pdf","invoices/B.pdf"]' out='md'
```
输出示例(核对表):
```
| 源文件 | 发票号码 | 开票日期 | 金额 | 税额 | 价税合计 | 来源 | 置信度 | 勾稽 |
| 电子发票_2024.pdf | 24412000000012345678 | 2024-01-05 | 100.00 | 13.00 | 113.00 | XML | 高(XML) | ✓ |
```
## 安全与隐私
- **不读取任何凭证/密钥/证书/账号配置**;仅处理用户指定的发票文件,读取遵循宿主 fs 的
read 策略与沙箱,输出写入工作区。
- **不上传任何查验接口**:查验(verify)为显式 opt-in 能力,默认关闭,当前版本不实现任何网络请求。
- 文件读写通过主机的 `ctx.fs` 服务完成;观察策略由宿主 fs 事件管道(`fs/write-intent` /
`fs/observed`,宿主 fs-observation-policy 只挂事件、不注册服务)统一处理。本插件直接调用
`ctx.fs` 读写,**不做额外工作区包含校验**——插件自身不声称额外安全边界,信任边界完全落在
宿主 fs 沙箱与观察策略之上。
- 写盘编排(只影响插件自身行为,并非策略闸门):目标已存在时先 read,再以版本守卫
(`replaceIfVersion`)原子写入;不存在时 `createIfAbsent`。
- **隐私边界如实说明**:解析缓存按会话隔离(key 为 `<会话id>:<绝对路径>`,会话 id 取自工具
执行上下文的 `agent.id`);工具无法取得会话上下文时(非代理循环驱动等场景),缓存回退为
**进程级**——同进程内其它会话理论上可读到该缓存的解析结果,请勿向缓存中放入敏感发票数据。
- **OCR 不是首版依赖**:纯扫描件(图片 / 无文本层 PDF)返回明确"跳过原因",不中断批量;
后续可显式 opt-in 对接社区 vision 插件(如 `dsh-vision-guard` 的 `vision_analyze`)。
## xlsx 落盘说明
DSH 文件系统服务提供 UTF-8 文本信道(无二进制写 API),故 `invoice_summary out='xlsx'` 生成的
xlsx(标准 OOXML ZIP,可由 Excel/WPS/openpyxl 打开)以 **base64 文本**落盘为 `报销单-<日期>.xlsx.b64`:
```bash
base64 -d 报销单-20260823.xlsx.b64 > 报销单-20260823.xlsx # macOS/Linux
```
工具返回值同时携带 `xlsxBase64`,会话内可自行解码。文件头 `PK\x03\x04`、中央目录与
`xl/worksheets/sheet1.xml` 结构均由离线测试做结构断言;openpyxl 交叉验证在环境可用时执行,
缺失时测试显式打印 `-- SKIP openpyxl 交叉验证(python3/openpyxl 缺失)` 并计入 skipped
(node:test skip 语义,不再静默跳过)。
## 实现说明与已知限制
- PDF 解析为**自写最小解析器**(零第三方依赖,仅 Node 内置 zlib):
顺序扫描对象并精确跳过 stream;支持 FlateDecode/未压缩流、`Tj`/`TJ` 文本操作符、
latin1 与 UTF-16BE(hex)字符串、`/EmbeddedFiles` 附件树。**不依赖 xref 表**,对
老式 xref 与 xref 流均鲁棒。局限:不支持对象流内的间接引用流、JPEG2000 图像、页面渲染;
生产环境如需完整 PDF 语义,可替换为调用 pdfplumber/pypdf 的外部路径(调研已核实可用)。
- XML 附件抽取依赖中英双语候选标签白名单(`invoice-model.ts`);覆盖面外的 Schema
将导致该文件被跳过(reason=unparsable),不会生成错误数据。
- 金额仅解析阿拉伯数字(`¥`/`¥`/千分位);中文大写金额无法解析时字段缺失并降低置信度。
- 目录扫描递归深度上限 3;单文件读取上限 16MB。
## 开发与测试
```bash
npm install # 拉取类型依赖(@deepseek-ai/cordis / dsh-tools / typescript)
npm run typecheck # tsc --noEmit
npm run build # tsc → lib/
npm run test # build + node --test
npm run test:offline # 纯离线冒烟(Node ≥ 22.19,原生运行 .ts,不需要 npm install)
```
冒烟测试(`tests/smoke.e2e.ts`)内嵌 fixture 生成器,覆盖验收标准:
① 含 XML 附件的样例解析字段完整 + 勾稽通过 + confidence=high;
② 无 XML 文本型样例走正则路径 + confidence=medium;
③ 3 张样例汇总:合计正确 + 重复检测命中构造样本;
④ xlsx 结构断言(ZIP + 表头/行数据);openpyxl 交叉验证在环境可用时执行、缺失时显式标记跳过;
⑤ 异常文件(加密 PDF / 纯图片)返回明确跳过原因且不中断批量;
⑥ 勾稽不符样本标红提示。
## License
MIT
## 权限、失败边界与 DSH STORE 状态
- [PERMISSIONS.md](./PERMISSIONS.md):运行时读取面 / 命令面(固定 argv,非 shell)/ 写面 / 外部服务 / 失败边界 / 供应链 / 文件权限信号(无 chmod/chown、644、无 setuid/setgid)。
- [docs/store-evidence.md](./docs/store-evidence.md):一次性 Profile 安装 → 启动(工具注册清单)→ 卸载步骤、本地离线证据、待宿主补录真实运行记录说明,并逐项回应 DSH STORE 五类审查信号(仓库 canonical 匹配 / Node 声明 / 供应链 / 文件权限 / 命令权限)。
- STORE 复检由 dsh-safe-plugin-manager 每 3 小时自动执行;本仓库已按清单契约声明(package.json 的 `repository` / `engines.node` / `dsh.compatibility` / `dsh.permissions`)。
Install
dsh plugin --profile web add github:988hj7tczd-oss/dsh-invoice-tools
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-invoice-tools from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.