按标准字段模板合并多张 Excel/CSV,保留来源文件与来源部门列,业务唯一键查重,输出合并表/重复表/缺字段表
fin-table-mergeSKILL.md 为准。> **本地运行,零网络、免 API Key、数据不出电脑。**
> 四项诚实承诺:① 算不出就说算不出,绝不编造数字;② 高危动作强制人工确认;③ 幂等断点,绝不重复副作用;④ 全程留痕可审计。
# 多表批量合并
分几个系统导出来的销售表,列名一个写一个的;采购表和销售表的字段对不上,
硬合到一起要么丢列要么错行。人工 VLOOKUP 一拉就是一下午,还拉错两三行。
本包把这件事固化下来,**并且把"合完之后少了行"这件事当成硬错误**:
输入总行必须等于「合并表 + 重复表 + 缺字段表」,差一行就直接拒绝执行,
不会给你一张看起来完整、其实少了几行的合并表。
三个不讨喜但有用的设计:
1. **缺字段不硬凑**:模板里有、源表里没有的字段,该文件的行整表进缺字段表,
而不是拿空值糊上去——空值混进合并表,事后没人查得出来。
2. **来源可回溯**:合并表每行都带 `来源文件` / `来源行号` / `来源部门`,
报错时能直接翻到原表第几行。
3. **字段落位可复核**:另出一张 `mrg_字段对照.csv`,写清每个标准字段
从哪个文件的哪一列落下来的、是精确匹配还是模糊匹配。
## 输入
| 参数 | 说明 |
|---|---|
| `file...`(位置参数) | 待合并的 `.xlsx` / `.csv`,可给任意多个 |
| `--list` | 文件清单 txt,每行一个路径,`#` 开头是注释 |
| `--sheet` | xlsx 工作表名,默认第一个 sheet |
| `--template` | 标准字段模板(`.xlsx` / `.csv`) |
| `--field-map` | 字段映射 JSON,如 `'{"发票号码":"FP_NO","发票号":"FP_NO"}'` |
| `--key` | 业务唯一键,多列用逗号分隔,如 `发票号码,开票日期` |
| `--dept` | 统一的来源部门;表内有「部门」列时以表内为准 |
| `--out-dir` | 输出目录,默认 `_out` |
| `--execute` | 真正写盘;不加只预览 |
| `--operator` / `--rule-version` | 写进处理日志的操作人与规则版本 |
文件不存在 / 读不了 / 扩展名不是 `.xlsx` 或 `.csv` → **直接拒绝**,不给半个结果。
### 标准字段模板怎么写
**写法 A(推荐,两列映射表)**——表头含「原始列 / 业务字段 / 标准列名」字样,
其后每行一个字段定义,**原始列 → 标准列名**:
```csv
原始列,标准列名
发票号码,FP_NO
发票号,FP_NO
开票日期,FP_DATE
购方名称,FP_PARTY
客户名称,FP_PARTY
金额,FP_AMT
价税合计,FP_AMT
部门,FP_DEPT
申报部门,FP_DEPT
```
同一标准列名可以在多行出现(上面 `FP_NO` 有两个别名),
用于接住"A 表叫发票号码、B 表叫发票号"这类差异。
字段定义行必须连续写在表头下面,**遇到第一个不是两列的行就停止解析**
(样例数据请移出模板表,别让它被当成字段名)。
**写法 B(标准表)**——表头行就是标准列名,顺序即输出顺序,数据行一律忽略:
```csv
FP_NO,FP_DATE,FP_PARTY,FP_AMT,FP_DEPT
```
两条都不给时:**采用各表列名并集**(按首次出现顺序),不做字段映射,也不查重。
`--template` 与 `--field-map` 同时给,以 `--field-map` 为准。
**字段落位顺序**:标准列名精确 → 候选别名精确 → 标准列名唯一包含 → 别名唯一包含。
多种候选时落位方式会写进 `mrg_字段对照.csv`,便于复核是不是命中了你想命中的那列。
## 输出(六件)
| 文件 | 内容 |
|---|---|
| `mrg_合并表.csv` | 去重后的合并结果,附 `来源文件` / `来源行号` / `来源部门` |
| `mrg_重复表.csv` | 唯一键命中重复的行(保留全部重复行),附「第几次出现」 |
| `mrg_缺字段表.csv` | 缺模板字段的文件整表进这里,附缺失字段清单 |
| `mrg_字段对照.csv` | 标准字段 ← 来源文件:原始列 的落位关系 |
| `mrg_校验表.csv` | 逐文件的行数 / 有效行 / 缺字段行 / 重复行、字段映射明细、行守恒 |
| `fin-log.jsonl` | 处理日志(追加) |
## 用法
```bash
PY="<python 3.8+ 路径>" # 纯标准库,无需 pip install
"$PY" <skill目录>/scripts/table_merge.py 销售A.xlsx 销售B.csv 采购C.xlsx \
--template 字段模板.xlsx --key 发票号码,开票日期 --execute
```
先在预览模式确认方案再落盘(**默认不写盘**):
```bash
"$PY" table_merge.py 销售A.xlsx 销售B.csv --template 字段模板.xlsx --key 发票号码,开票日期
```
文件多到敲命令行费劲时,用清单:
```bash
"$PY" table_merge.py --list 清单.txt --template 字段模板.xlsx --key 发票号码,发票代码 \
--dept 财务共享中心 --out-dir _out/1月 --execute --operator 张三
```
`--dept` 给的是表内没有「部门」列的那类文件兜底,表内有「部门」列时以表内为准。
## 真实输出样例(实测)
三张自测表:销售 A(xlsx,含 1 行重复 + 1 行空行)、销售 B(csv,列名不同,含 1 行与 A 重复)、
采购 C(缺「开票日期」列)。
```
方案:标准字段模板 = mrg_模板_字段定义.xlsx(5 个标准字段)
输入文件 3 个 | 输入总行数 10(空行 1,已跳过)
· mrg_销售A.xlsx 工作表=Sheet1 行数=5 落位字段=5/5
· mrg_销售B.csv 工作表=csv 行数=3 落位字段=5/5
· mrg_采购C.xlsx 工作表=Sheet1 行数=2 落位字段=2/5
业务唯一键:发票号码 + 开票日期
来源部门:表内「部门」列优先,无则留空
合并结果:输入 10 行 → 合并表 6 行 / 重复表 2 行 / 缺字段表 2 行
--- 合并表预览(前 6 行,未写盘)---
FP2026001 | 2026-01-05 | 甲公司 | 1200.5 | 销售中心 | mrg_销售A.xlsx | 2 | 销售中心
FP2026002 | 2026-01-08 | 乙公司 | 3680 | 销售中心 | mrg_销售A.xlsx | 3 | 销售中心
FP2026003 | 2026-02-11 | 丙公司 | 9800 | 销售中心 | mrg_销售A.xlsx | 5 | 销售中心
FP2026004 | 2026-02-15 | 丁公司 | 2500.25 | 销售中心 | mrg_销售A.xlsx | 7 | 销售中心
FP2026005 | 2026-03-02 | 戊公司 | 15000 | 供应链部 | mrg_销售B.csv | 2 | 供应链部
FP2026006 | 2026-03-09 | 己公司 | 7600 | 供应链部 | mrg_销售B.csv | 4 | 供应链部
--- 重复表(前 10 行)---
mrg_销售A.xlsx 行4 第2次出现(首次在mrg_销售A.xlsx第2行)
mrg_销售B.csv 行3 第2次出现(首次在mrg_销售B.csv第3行)
--- 缺字段表(前 10 行)---
行2 mrg_采购C.xlsx 缺模板字段:FP_DATE、FP_DEPT、FP_PARTY、唯一键列:开票日期
行3 mrg_采购C.xlsx 缺模板字段:FP_DATE、FP_DEPT、FP_PARTY、唯一键列:开票日期
```
对应校验表片段:
```
输入总行数(非空) | 10
空行数(跳过) | 1
合并表行数 | 6
重复表行数 | 2
缺字段表行数 | 2
行守恒差异(入-合-重-缺) | 0
文件 · mrg_采购C.xlsx(Sheet1) | 输入 2 / 有效 0 / 缺字段 2 / 重复 0 / 未匹配字段 4
```
`mrg_字段对照.csv` 里能看到 B 表是靠别名命中的:
```
标准字段,标准列名,来源文件,原始列,落位,匹配方式
FP_NO,FP_NO,mrg_销售B.csv,发票号,输出第1列,精确·别名(发票号)
FP_PARTY,FP_PARTY,mrg_销售B.csv,客户名称,输出第3列,精确·别名(客户名称)
FP_DATE,FP_DATE,mrg_采购C.xlsx,,不落位,未匹配
```
## 拒绝权(什么情况下拒绝对不硬凑)
| 情况 | 处理 |
|---|---|
| 文件不存在 / 路径写错 | **拒绝**:列出路径,让确认后再跑 |
| 扩展名不是 `.xlsx` / `.csv` | **拒绝**:`扩展名 .pdf/.py 不支持(本包只认 .xlsx / .csv)` |
| 文件损坏、被 Excel 占用读不了 | **拒绝**:原话报出内核的读盘错误原因 |
| 一个输入文件都没给 | **拒绝**:`一个输入文件都没给` |
| 模板字段在源表里一个都匹配不上 | **拒绝**:列出试过的标准列名,建议改用 `--field-map` |
| 行守恒对不上(入 ≠ 合+重+缺) | **拒绝**:报出具体差值,不输出任何合并结果 |
| 模板解析不出任何字段 | **拒绝**:说明模板两种写法 |
被拒绝的输入**不会产生半个输出文件**,也不会部分写盘。
## 关键设计(容易踩的坑)
1. **行守恒是硬断言**:`输入总行 = 合并表 + 重复表 + 缺字段表`。
空行(整行空白)不计入任何一边,单独在校验表里报「空行数(跳过)」。
2. **查重口径**:同一唯一键只保留**首次出现**的那行进合并表,其余全进重复表,
并记「第几次出现」+ 首次出现的文件行号。没给 `--key` 就不查重,只做纵向拼接——
这是显式选择,不是漏做。
3. **唯一键写法**:可以是标准列名(如 `FP_NO`),也可以直接写源表里的原始列名
(如 `发票号码`),多列组合用逗号分隔。
4. **缺字段的粒度是文件**:源文件缺任一模板字段 → 该文件整表进缺字段表。
想让缺字段的行单独挑出来、其余照常合并,请先把该文件的字段补上再跑。
5. **模糊匹配的边界**:只有**唯一**包含该标准的源列才认,多个候选一律判为未匹配,
宁可进缺字段表也不猜。
6. **行号是原表行号**:从 2 开始(第 1 行是表头),跨文件不重排,
方便对照原表排查。
7. **同文件名不同目录**:只在输出里记文件名,不记目录;多个目录里有同名文件时,
行守恒仍然成立,但排查时要自己认目录。
8. **模板不要放样例数据**:写法 A 遇到第一个非两列行就停止解析,
写在它下面的样例行会被忽略并计入「忽略模板末尾 N 行」。
## 人工复核(必读)
- `mrg_字段对照.csv` 要逐条看过,特别是标了「模糊·含…」的行——
确认命中的是你要的那列,而不是碰巧包含同样字样的列
- 缺字段表里的文件,确认是"这张表本来就没这个字段"还是"导出时漏了"
- 重复表里如果只有 2 次出现、且来源不同文件,确认是不是同一笔业务分两处录入
- 合并后的口径(含税 / 不含税、本币 / 原币)由你确认,本包不做币种与税负换算
- 合并结果不能直接入账,需经对应财务岗位确认
## 相邻路线转交
| 用户要做的事 | 转交给 |
|---|---|
| 先把单张表的日期 / 金额 / 名称格式统一 | `fin-table-clean` |
| 合并后逐条查异常(负数、超阈值、状态矛盾) | `fin-anomaly-scan` |
| 外发或试跑前要脱敏 | `fin-data-mask` |
| 合并后按客户 / 产品看毛利 | `gross-margin-analysis` |
| 合并后按部门 / 项目分摊共同成本 | `cost-allocation` |
## 可直接复制的说法
```text
把这几张表按标准字段模板合并成一张,字段顺序按模板来。
每张表要标明来源文件和来源行号,方便我回头翻原表。
发票号和开票日期组合起来重复的,单独出一张重复表,标第几次出现。
缺模板字段的那张表别硬凑,整表单独出一张缺字段表。
合并完要核对行数:输入总行 = 合并表 + 重复表 + 缺字段表,对不上就直接说算不出,
不要给我一张看着完整其实少了几行的表。
列名字对不上的(比如发票号码 / 发票号、金额 / 价税合计),在模板里配好别名。
```
## 做完怎么算完成
- [ ] `mrg_字段对照.csv` 逐条看过,「模糊匹配」的行确认命中正确
- [ ] `mrg_缺字段表.csv` 里的文件已确认是"本来就没这字段"还是"导出漏了"
- [ ] `mrg_重复表.csv` 已逐条追查(是同一笔重复录入,还是合法的多笔)
- [ ] 校验表「行守恒差异」为 0
- [ ] 合并口径(含税 / 不含税、币种)已与业务确认
- [ ] 空行数量与来源已核对,确认不是漏读
---
本 Skill 采用 BUSL-1.1 许可(源码可见,可自由使用)。
版权所有 © 2026 律令科技(深圳)有限公司(@lexord-technology)。
本技能由律令科技(深圳)有限公司开发,**财友云(caiyouyun.cn)**发布与展示。
允许个人及企业内部免费使用、复制、修改,包括内部生产环境。
未经书面许可,不得将本 Skill 或其修改版作为商业服务对外销售、转售或提供托管服务。
各版本发布之日起 4 年后自动转为 Apache License 2.0。详见随包 `LICENSE.md`。日期统一 YYYY-MM-DD、金额转数值、名称按映射表替换、空值/重复/非数字进异常表,输出标准表+异常表+校验表
从大表抽样 20-50 行并对姓名/税号/银行卡/手机号/金额做保形脱敏,供试跑与外发,原表零改动
结构异常(空值/重复/格式/公式不成立)+ 业务异常(负数/超阈值/跳变/状态矛盾)分级,保留原始行号与影响金额
按发票号码+代码+金额+日期组合查重,拦住重复报销与重复入账
进销项发票台账,按月/客户/税率汇总,导出 CSV
多银行 CSV/Excel 流水归一化成统一字段与方向标记
流水与账面按金额+日期窗口匹配,输出已勾对/单边与余额调节表
从业务单据表按科目映射批量生成凭证草稿,借贷平衡预检
40+ 指标(偿债/营运/盈利/成长)+ 多期趋势与同业占位
增值税/附加/个税/印花税/所得税预缴,附公式与政策依据
金额大写、含税不含税互算、价税分离、尾差分摊、百分比反算
从带文本层的数电票 PDF 抽取号码/日期/金额/税率/购销方
SKILL.md 为准。