dsh-invoice-tools
DSH native tools: parse Chinese e-invoice PDFs into structured JSON with amount cross-check, and generate expense reports (Markdown / xlsx)
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 24, 2026
- Updated
- Aug 24, 2026
Introduction
dsh-invoice-tools — 发票解析 / 报销单生成工具
[!IMPORTANT] 依赖前置:相邻
dsh-src检出(link:依赖) 本项目在开发形态下使用link:依赖指向相邻的 DeepSeek Harness 源码检出(dsh-src), 与当前仓库保持同一父目录布局(<parent>/dsh-src)。克隆本仓库后:
- 先把官方
deepseek-ai/deepseek-harness检出到与本仓库同级的dsh-src/目录,并执行其pnpm install && pnpm run build;- 再按下方「安装」一节执行本仓库的
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)
- XML 附件优先:读取 PDF 内嵌的结构化 XML(
/EmbeddedFiles命名树),按中英双语候选标签 白名单抽取:发票号码/代码、金额(不含税)、税额、价税合计、购买方、销售方、开票日期、查验平台网址。 - 文本层兜底:无 XML 时以文本正则抽取——票号正则
\d{8,20}+ 标签定位 (发票号码:、金额(大写)…(小写)¥x、开票日期:、购买方名称:等)。 - 勾稽校验:
价税合计 = 金额 + 税额(容差 0.01),不符时核对表标⚠。 - 置信度标注: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(文件系统服务)。两种挂载模式:
-
生产(推荐):
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。- insert: - id: invoice-tools name: 'dsh-invoice-tools' -
开发直挂:绝对路径(同 dsh-src/scratch-plugin 的做法):把 insert 行并入宿主
cordis.yml,name 写源码入口的绝对路径:- 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:
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。
开发与测试
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