fix(invoice-organizer): 统一报告文件名约定 / unify report file naming (#120)

## 问题 / Problem

真机三轮整理实测:Agent 全部写出 `报告/整理报告-<运行日期>.md`,而技能里只定义了 `报告/<YYYY-MM>.md`。

**原因不是模型不听话。** 测试范围跨了 2020-08 到 2024-09 共十几个月,`<YYYY-MM>`
在多月场景下无从填写——技能没给出这种情况的文件名,Agent 只能自己发明一个。

**后果不是难看,而是会攒垃圾。** `invoice-automation` 的定时任务每月重建的是 `报告/<上月
YYYY-MM>.md`;与按运行日期命名的手工报告永远不会互相覆盖,用户目录里会留下一堆内容重叠、无从分辨新旧的报告。

Three real-machine runs all produced `报告/整理报告-<run date>.md`, while the
skills only define `报告/<YYYY-MM>.md`. The runs spanned 2020-08 through
2024-09, so `<YYYY-MM>` was unfillable and the agent improvised. Because
the scheduled monthly job rebuilds `报告/<last month YYYY-MM>.md`, the two
naming schemes never overwrite each other and the user's directory
accumulates overlapping reports with no way to tell which is current.

## 改动 / Changes

- `invoice-workflow` 第 7 步补一张表,把两种文件名钉死:

  | 本次覆盖 | 文件名 | 例 |
  | --- | --- | --- |
  | 恰好一个自然月 | `报告/<YYYY-MM>.md` | `报告/2024-08.md` |
  | 跨多个月 | `报告/<起始 YYYY-MM>_<结束 YYYY-MM>.md` | `报告/2020-08_2024-09.md` |

并写明月份按**开票日期**归属(与台账同口径)、同名直接覆盖(报告是从 `.index/ledger.json`
全量重建的派生产物,不像台账需要先备份)、以及**为什么不许用运行日期命名**
- `invoice-ledger` 与 `references/月度报告模板.md` 同步该约定
- 目录布局示例与中英 USAGE 一并标注跨月形态

## 为什么不在本 PR 里 bump 版本 / Why no version bump here

`contentSource` 的 `ref` 必须 pin 到**已合并的 commit**,本 PR 合并前拿不到那个 SHA。先 bump
版本会让目录声称 1.0.1、却仍按指向 1.0.0 内容的 pin 去取文件——比不 bump 更糟。版本号、pin 与 changelog
留到后续 PR 一起改。

The `contentSource` ref must pin to an already-merged commit, which does
not exist until this PR lands. Bumping the version now would advertise
1.0.1 while still serving 1.0.0 content. Version, pin and changelog
follow in a second PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---

## 追加:台账四张表必须全建 / Also: all four ledger sheets must always exist

真机压测(只有 1 张发票)产出的 `台账.xlsx` **只有三张表**——Agent 判定「按销售方汇总」在单一销售方下冗余而略过:

```
发票测试(23 张) → ['明细', '月度汇总', '按销售方汇总', '异常']
发票压测(1 张)  → ['明细', '月度汇总', '异常']          ← 少一张
```


技能写的是「四张表,顺序固定」,但没说「即使为空也必须存在」。台账是要被重复重建、被用户自己写公式引用、被下一次整理覆盖的产物;表的位置一旦随数据量浮动,「第
3 张是按销售方汇总」这类引用就会在下个月悄悄指错。已在 `invoice-ledger` 里把这条钉死。

A stress run with a single invoice produced only three sheets — the
agent judged the per-seller summary redundant. The skill said "four
sheets, fixed order" but never said "even when empty". Sheet positions
that float with row count silently break user formulas and rebuilds; now
pinned.
This commit is contained in:
2026-09-04 11:56:48 -04:00
committed by GitHub
parent 98251c9a1d
commit 7560a58642
5 changed files with 49 additions and 9 deletions

View File

@@ -27,7 +27,7 @@ One new directory under your work directory:
``` ```
发票/ (invoices) 发票/ (invoices)
├── 台账.xlsx (ledger; 台账.csv when the xlsx dependencies are unavailable) ├── 台账.xlsx (ledger; 台账.csv when the xlsx dependencies are unavailable)
├── 报告/2024-08.md (reports) ├── 报告/2024-08.md (reports; invoices spanning months write 报告/2020-08_2024-09.md)
├── 归档/2024/08/20240815_<seller>_1959.98_24312000000000020002.pdf (archive, by year/month) ├── 归档/2024/08/20240815_<seller>_1959.98_24312000000000020002.pdf (archive, by year/month)
├── _inbox/ downloaded, not yet processed ├── _inbox/ downloaded, not yet processed
├── _quarantine/ parse failures and non-invoices, each with a .reason.txt ├── _quarantine/ parse failures and non-invoices, each with a .reason.txt

View File

@@ -27,7 +27,7 @@
``` ```
发票/ 发票/
├── 台账.xlsx依赖不可用时为 台账.csv ├── 台账.xlsx依赖不可用时为 台账.csv
├── 报告/2024-08.md ├── 报告/2024-08.md(票都开在同一月;跨月时是 报告/2020-08_2024-09.md
├── 归档/2024/08/20240815_某某酒店管理有限公司_1959.98_24312000000000020002.pdf ├── 归档/2024/08/20240815_某某酒店管理有限公司_1959.98_24312000000000020002.pdf
├── _inbox/ 刚下载、尚未处理 ├── _inbox/ 刚下载、尚未处理
├── _quarantine/ 解析失败或判定为非发票(每份都附一个 .reason.txt 说明原因) ├── _quarantine/ 解析失败或判定为非发票(每份都附一个 .reason.txt 说明原因)

View File

@@ -69,7 +69,11 @@ CSV 只出「明细」一张表,汇总数字放进月度报告的 Markdown 里
### 工作表结构xlsx ### 工作表结构xlsx
四张表,顺序固定 四张表,顺序固定。**四张都必须存在,一张都不许省**——哪怕某张只有零行或一行数据,
也要建出表头。台账是要被重复重建、被用户自己写公式引用、被下一次整理覆盖的产物,
表的位置一旦随数据量浮动,「第 3 张是按销售方汇总」这类引用就会在下个月悄悄指错。
(实测踩过:只有 1 张发票时 Agent 判定「按销售方汇总」冗余而略过,产出只有三张表。)
**① 明细** —— 一行一张发票,按开票日期升序 **① 明细** —— 一行一张发票,按开票日期升序
@@ -151,7 +155,10 @@ CSV 只出「明细」一张表,汇总数字放进月度报告的 Markdown 里
### 月度报告 ### 月度报告
模板见 `${SKILL_DIR}/references/月度报告模板.md`,写到 `报告/<YYYY-MM>.md` 模板见 `${SKILL_DIR}/references/月度报告模板.md`。文件名看**本次入账发票的开票月份
落在几个不同的 `YYYY-MM` 里**(不是用户给的时间范围有多长):全部同月写
`报告/<YYYY-MM>.md`,分布在两个及以上月份写 `报告/<最早 YYYY-MM>_<最晚 YYYY-MM>.md`
**不要用运行日期命名**,理由见 `invoice-workflow` 第 7 步。
用户要 PDF/DOCX 时用 `ExportDocument` 从这份 Markdown 转(该工具不能直接出 xlsx。要把台账文件交给用户时用 `SendUserMessage` 带附件(最多 10 个文件、每个 ≤10MB 用户要 PDF/DOCX 时用 `ExportDocument` 从这份 Markdown 转(该工具不能直接出 xlsx。要把台账文件交给用户时用 `SendUserMessage` 带附件(最多 10 个文件、每个 ≤10MB

View File

@@ -1,6 +1,7 @@
# 月度报告模板 # 月度报告模板
写到 `报告/<YYYY-MM>.md`。占位符用 `<>` 标出,全部替换后不应该留下任何尖括号 写到 `报告/<YYYY-MM>.md`(票分布在多个月份时写 `报告/<最早 YYYY-MM>_<最晚 YYYY-MM>.md`
占位符用 `<>` 标出,全部替换后不应该留下任何尖括号。
数字全部来自 `.index/ledger.json`,不要现算一遍别的口径——报告里的合计必须和台账「月度汇总」那一行逐字一致。 数字全部来自 `.index/ledger.json`,不要现算一遍别的口径——报告里的合计必须和台账「月度汇总」那一行逐字一致。

View File

@@ -208,10 +208,24 @@ IMAP 的两个坑:`messageId` 用 `"imap:<uid>"` 形式(列表返回的 `id`
按开票日期归到 `归档/<年>/<月>/`,文件名固定格式: 按开票日期归到 `归档/<年>/<月>/`,文件名固定格式:
``` ```
<YYYYMMDD>_<销售方名称>_<价税合计>_<发票号码>.<原扩展名> 归档/<YYYY>/<MM>/<YYYYMMDD>_<销售方名称>_<价税合计>_<发票号码>.<原扩展名>
``` ```
例:`20240815_示范酒店管理有限公司_1959.98_24312000000000020002.pdf` 例:`归档/2024/08/20240815_示范酒店管理有限公司_1959.98_24312000000000020002.pdf`
**这条路径是死的,不许「优化」。** 写盘前逐项对照,四项全中才允许写:
1. 第一层必须是 `归档/`——不是工作目录根,也不是 `archive/` / `已归档/`
2. 年、月两层之后**直接放文件**——不要再插一层 `<发票类型>/`(类型已经在台账的「发票类型」列里,
再切一层目录只会让「2024 年 8 月一共几张」需要跨目录数)
3. 日期是 `YYYYMMDD` **八位连写**,不是 `YYYY-MM-DD`——文件名按字典序排就是按日期排,
加了横杠仍然对,但与台账、报告、`.index` 里的其它日期写法不一致,也和这里的例子对不上
4. 最后一段是**发票号码**;旧版票的发票代码写在号码前面用 `-` 连接(`011002100311-08811701`
数电票没有代码就只有号码
实测踩过:同样是空目录起步,有一轮把七张票归成了
`<工作目录>/2022/11/增值税电子普通发票/2022-11-18_….pdf`——三条同时违反。
四条都对照一遍再写,不要凭印象。
销售方名称里的 `/ \ : * ? " < > |` 替换成 `_`,超过 40 个字符截断。目标已存在且 SHA-256 一致 → 静默跳过;哈希不同 → 保留两份(第二份加 `_2` 后缀)并在收尾里提示用户。 销售方名称里的 `/ \ : * ? " < > |` 替换成 `_`,超过 40 个字符截断。目标已存在且 SHA-256 一致 → 静默跳过;哈希不同 → 保留两份(第二份加 `_2` 后缀)并在收尾里提示用户。
@@ -225,14 +239,32 @@ IMAP 的两个坑:`messageId` 用 `"imap:<uid>"` 形式(列表返回的 `id`
### 第 7 步 · 报告Report ### 第 7 步 · 报告Report
加载 `invoice-ledger` 里的报告模板,写 `报告/<YYYY-MM>.md`。用户要 PDF 时用 `ExportDocument` 从这份 Markdown 转。 加载 `invoice-ledger` 里的报告模板。**文件名按本次整理覆盖的开票月份数决定,不许自己另起一套**
判据是**本次入账的发票,其开票月份落在几个不同的 `YYYY-MM` 里**——不是用户给的
时间范围有多长。「整理 8 月 1 到 15 日」只要票都开在 2024-08就是单月
「整理最近两个月」若恰好只有 9 月的票入账,同样是单月。
| 本次入账发票的开票月份 | 文件名 | 例 |
| --- | --- | --- |
| 全部落在同一个 `YYYY-MM` | `报告/<YYYY-MM>.md` | `报告/2024-08.md` |
| 分布在两个及以上 `YYYY-MM` | `报告/<最早 YYYY-MM>_<最晚 YYYY-MM>.md` | `报告/2020-08_2024-09.md` |
月份按**开票日期**归属(与台账同口径),不按收件日期,更不按今天的日期。
**不要用「整理报告-<今天>.md」这类以运行日期命名的文件。** 定时任务每月重建的是
`报告/<上月 YYYY-MM>.md`(见 `invoice-automation`);一旦手工整理写出按运行日期命名的报告,
两套命名永远不会互相覆盖,用户目录里会攒下一堆内容重叠、谁也不知道哪份是最新的报告。
同一文件名重复生成时**直接覆盖**(报告是从 `.index/ledger.json` 全量重建的派生产物,
不像台账那样需要先备份)。用户要 PDF 时用 `ExportDocument` 从这份 Markdown 转。
### 工作目录布局 ### 工作目录布局
``` ```
<primary 工作目录>/发票/ <primary 工作目录>/发票/
├── 台账.xlsx或 台账.csv ├── 台账.xlsx或 台账.csv
├── 报告/2024-08.md ├── 报告/2024-08.md 票都开在同一月;跨月时是 报告/2020-08_2024-09.md
├── 归档/2024/08/20240815_示范酒店管理有限公司_1959.98_24312000000000020002.pdf ├── 归档/2024/08/20240815_示范酒店管理有限公司_1959.98_24312000000000020002.pdf
├── _inbox/ 刚下载、尚未处理 ├── _inbox/ 刚下载、尚未处理
├── _quarantine/ 解析失败或判定为非发票(+ 同名 .reason.txt ├── _quarantine/ 解析失败或判定为非发票(+ 同名 .reason.txt