从零写一个审计底稿核对工具(一):需求与设计
这是「审计底稿核对工具」系列的第一篇。这个系列会完整记录一个从审计现场需求出发、写到能给自己和同事日常用的小工具是怎么长出来的——需求、设计、实现、踩坑、迭代,一个不落。
起因:一个周五晚上 11 点的需求
事情起源于一次底稿复核。复核经理指出:明细表里有 3 家单位的年初余额与上年审定表对不上。我打开两份底稿人工核对——一个科目 300 多行,逐行肉眼找差异,找到第二个小时的时候我开始怀疑人生。
那天晚上我列了个清单,发现底稿核对类工作有三个特征:
- 规则明确:两边应该相等、钩稽关系固定,机器完全可判;
- 重复量大:一个项目几十张明细表,每张都可能是几百行;
- 容错率为零:人眼核对 1000 行,错 1 行就是风险。
规则明确 + 重复量大 + 容错率低——这三条凑齐,就是写工具的标准信号。
需求收敛:从”什么都想核”到”先核三件事”
第一版需求文档我写了 20 多条,回过头看一半是伪需求。砍到最后,核心就三件事:
1. 余额勾稽核对
输入:本年明细表(科目余额表或辅助余额表)、上年审定表。 逻辑:按”单位 + 科目”为 key 左右匹配,输出四类结果——两边一致 / 本年有上年无(新增)/ 上年有本年无(注销)/ 两边都有但金额不等(差异)。
2. 发生额与序时账核对
输入:明细表发生额、序时账按科目汇总。
逻辑:明细表借方累计 - 序时账借方汇总 = 0,科目级逐一校验。
3. 结果输出
不是打印一堆日志,而是输出一份带颜色标注的 Excel 差异报告:白行=一致,黄行=新增/注销,红行=金额差异。审计师拿到手能直接往下追。
经验:工具的第一用户是你自己。凡是”以后可能用到”的功能,全部砍掉,第一版只做每周都会重复做的事。
技术选型:Python + openpyxl,够了
考虑过的方案和放弃理由:
| 方案 | 结论 |
|---|---|
| Excel 公式 / VLOOKUP | 每张表都要重写公式,不可复用 |
| VBA | 单机绑定,版本管理困难,同事不敢开宏 |
| Power Query | 数据整形强,但复杂勾稽逻辑调试痛苦 |
| Python + openpyxl | 跨表读取、pandas 匹配、openpyxl 输出带格式报告,全链路通吃 |
加一个约束:只用 pandas + openpyxl,不引入其他重型依赖。审计电脑环境千奇百怪,依赖越少,“发给同事就能跑”的概率越高。后来我用 PyInstaller 打包成单 exe,彻底消灭环境问题。
数据模型:把”底稿”抽象成统一结构
底稿核对最大的坑是格式不统一:同样一张余额表,不同项目、不同人做的列名、行位置、合并单元格玩法都不一样。所以设计的第一件事是建一个中间层:
@dataclass
class BalanceRow:
"""一行余额记录:工具内所有核对逻辑只认这个结构"""
company: str # 单位名称(规范化后)
account: str # 科目编码
account_name: str # 科目名称
opening: float # 期初余额
closing: float # 期末余额
debit: float # 借方发生额
credit: float # 贷方发生额
读取层负责把各种格式的 Excel “翻译”成 BalanceRow 列表:
┌─────────────┐ 读取+列名映射 ┌──────────────┐ 勾稽引擎 ┌──────────────┐
│ 本年明细表 │ ──────────────> │ BalanceRow │ ───────────> │ 差异结果集 │
│ 上年审定表 │ ──────────────> │ 列表(统一) │ │ (含差异类型) │
└─────────────┘ └──────────────┘ └──────┬───────┘
│ openpyxl
┌──────▼───────┐
│ 带颜色标注的 │
│ Excel 报告 │
└──────────────┘
这个分层带来两个好处:
- 勾稽引擎完全不关心原始格式,新增一种底稿格式只需要加一个”读取器”;
- 单元测试好写:构造
BalanceRow列表就能测引擎,不需要造几十个真实 Excel。
列名识别上,我做了一个别名表("单位名称" / "往来单位" / "客户" → company),覆盖了项目上见过的十几种叫法。这招是从账龄重算器里抄来的——实践中非常好用。
差异分类:给结果打上”审计语言”的标签
输出差异时不要只说”不相等”。我定义的差异类型:
MISMATCH:两边都有、金额不等(红)——最优先处理;ONLY_CURRENT:本年新增(黄)——检查是否为分立、新设;ONLY_PRIOR:上年有本年无(黄)——检查是否合并、注销、科目重分类;ROUNDING:差异绝对值 < 1 元(灰)——通常为四舍五入,标记后自动忽略。
ROUNDING 这个类型是被现实教育出来的:第一版工具输出的”差异”里有三分之一是 0.01 元的尾差,淹没了真差异。
下一篇
设计篇就到这里。下一篇写实现:读取层的列名识别怎么做鲁棒、匹配时”相似但不相等”的单位名称怎么处理( fuzzy match 的教训)、以及 openpyxl 写带格式报告的几个技巧。
工具成型后会放到工具箱,欢迎下载试用、回来提需求。