Skip to content

Repository files navigation

序时账对方科目自动生成工具

版本:v2.1.0

这份 README 同时面向 终端用户(直接用 exe 的人)和 开发者(看代码、改打包的人)。

  • 如果你只想用 exe 看前 7 章就够了
  • 第 8 章开始是源码、算法、Excel 格式等开发者内容
  • 业务内容与桌面 README 保持一致;桌面那份是 exe 旁说明,本文件是项目仓库说明

本次新增功能(2026年9月8日)

两个筛选都默认关闭。正常使用仍然选择Excel并生成对方科目。需要筛选时,先勾选对应选项,再选择文件;程序会用这份文件的列名和科目显示设置窗口,每个批次设置一次。

查夜间、周末和节假日记录

选择账里实际记录日期或时间的那一列,然后勾选要查的条件。夜间按你填写的正常工作起止时间判断,开始时刻包含在工作时段内,结束时刻不包含;也支持跨午夜的工作时段。

程序按每条记录的年份读取中国大陆放假调休日历。随程序提供2024、2025、2026年数据,补班的周六、周日按工作日处理。法定节假日与假期连休或调休分别标明。普通筛选不用联网,设置窗口的“更新放假调休日历”可获取本项目维护的日历文件。更新失败保留原文件,不上传序时账。

缺少某年的日历时,该年的周末调休和节假日检查会注明未做,不猜测未来安排。只有日期、没有真实时分秒时,跳过夜间检查。日期型Excel单元格不会因软件补了午夜时间而被误选。所选字段的含义仍以原表为准,记账日期不能证明实际操作时刻。

输出“入账时间筛选”和“筛选说明”,保留原始记录、来源文件、工作表、行号及选中原因。同一行命中多个条件只列一次。

日历依据为国务院办公厅的2024年通知2025年通知2026年通知,法定日期结合全国年节及纪念日放假办法区分。后续年度由项目维护者核对官方通知后更新 src/中国节假日.json,使用者不用逐年登记日期。

查疑似拆分审批的记录

例如要查银行付款是否被拆成几笔以绕过10万元审批,设置时选择银行存款科目和贷方,填入审批限额100000、候选最低金额90000。程序读取原始贷方金额,选出大于等于90000、严格小于100000的记录。9.8万元借贷各记一行时,只取所选方向的9.8万元,不相加为19.6万元。

可以多选科目。所选科目按该列原值匹配,先选择科目列,再从列表选择,不要求手填科目编码。审批限额与原有“异常金额阈值”分别设置。

需要把记录放在一起看时,再选择日期列和交易对方列。程序按同账套、同日、同对方归集候选,显示各笔及同组金额;没有这些资料就只列金额候选。金额来自原始业务行,不把生成对方科目时拆出的行再加一遍。红字保留在原始数据中并单独说明,核查时应结合冲销记录,不取绝对值凑限额。

输出“拆分审批筛选”和“筛选说明”。“同组金额”会在组内各行重复展示,不能再次相加。这里的多笔是原始记录,不自动证明存在多张付款单或同一项业务,更不直接认定违规。

命令行使用

主程序和独立CLI使用相同参数。下面以独立CLI为例,源码和随附 ledger-cli.exe 均支持。精简模式在明确启用筛选时,也会追加对应筛选表。

ledger-cli.exe 输入.xlsx 结果.xlsx --screen-date 记账时间 --screen-weekend --screen-holiday --screen-night --work-start 08:00 --work-end 18:00
ledger-cli.exe 输入.xlsx 结果.xlsx --split-subject-column 一级科目 --split-subject 银行存款 --split-direction 贷方 --approval-limit 100000 --split-minimum 90000 --split-date 记账时间 --split-party 对方单位

多选科目可重复指定 --split-subject。启用后参数不全时,原有分析继续,筛选表和说明明确记为未检查。桌面设置窗口也可以选择“本次只做原有分析”。


1. 这是什么工具?

你给它一张序时账表格,它帮你自动填上每一行分录的"对方科目",并告诉你哪笔分录有问题。

举个例子:

原始序时账(只有借贷两行):

会计月 凭证编号 一级科目 借方发生额 贷方发生额
1月 记-001 银行存款 10000 0
1月 记-001 应收账款 0 10000

处理后:

会计月 凭证编号 一级科目 借方发生额 贷方发生额 对方科目 匹配类型
1月 记-001 银行存款 10000 0 应收账款 标准模板
1月 记-001 应收账款 0 10000 银行存款 标准模板

工具会自动补出两个新列:

  • 对方科目:这一行的钱是和哪个科目来往的。
  • 匹配类型:这笔对方科目是怎么匹配上的(可信度高不高)。

除了补对方科目外,工具还会额外生成几张分析表,帮你找出可疑分录、判断数据是否被人为修改过。


2. 适合谁用?

人群 用途
审计人员 快速分析被审计单位的分录合理性
会计人员 检查自己做的账有没有问题
财务分析人员 分析资金流向和业务模式
税务稽查人员 发现可疑的异常分录

3. 如何使用?

3.1 双击启动

直接双击桌面上的 序时账分析器.exe,弹出主窗口后:

  1. "选择文件",选你的序时账 Excel
  2. (可选)调整"异常金额阈值",默认 10000 元
  3. "开始处理"
  4. 等进度条走完,结果文件会自动生成

3.2 命令行模式(批量处理)

如果你想批量跑或者不弹窗口,按住 Shift 右键桌面 → "在此处打开 PowerShell 窗口",输入:

序时账分析器.exe 输入文件.xlsx 输出文件.xlsx --no-gui

常用参数:

参数 作用 默认
--threshold 50000 异常分录筛选阈值(元) 10000
--no-gui 不弹窗口,纯命令行模式
--log-level DEBUG 日志详细程度 INFO

3.3 纯命令行版(给 agent / 脚本自动调用)

除了上面带界面的版本,项目 cli 文件夹里还有一个纯命令行版序时账分析器命令行版.py),专门给 AI agent、自动化脚本调用,不依赖图形界面库。

打包好的 execli\ledger-cli.exe,复制到任何 Windows 电脑都能直接跑,不用装 Python。

基本用法:

cli\ledger-cli.exe 输入文件.xlsx 输出文件.xlsx
cli\ledger-cli.exe 输入文件.xlsx 输出文件.xlsx --threshold 50000 --mode full

参数说明:

参数 作用 默认
输入文件 输入 Excel 路径(必填) -
输出文件 输出 Excel 路径(必填) -
--threshold 50000 异常分录筛选阈值(元) 10000
--mode summary 只输出一张"生成结果"表(精简,适合程序读取) summary
--mode full 输出全部 sheet(和原版一样,但无美化无图表) -
-i / --interactive 列识别不出时弹命令行选单逐列选 不加则缺列直接报错
--log-level DEBUG 日志详细程度 INFO

两种输出模式:

模式 输出内容 适合谁
summary(默认) 只一张"生成结果"表(含对方科目、匹配类型列),普通 Excel 格式 agent、脚本批量处理
full 全部 sheet(生成结果/原始数据/异常分录明细/异常分录/班福分析),普通格式无图表 需要完整分析结果时

和带界面版的区别:

  • 不弹窗口、不依赖图形界面库,适合服务器/批处理
  • 默认只输出生成结果表(精简),加 --mode full 才输出全部
  • 输出用普通 Excel 格式(无深海蓝美化、无班福图表),文件更小、程序更好读
  • summaryfull 模式生成的每个工作表首行表头均水平居中
  • 列识别不出时,默认直接报错退出(agent 友好);加 -i 可以在命令行一列一列手动选

重新打包 exe(开发者):

pyinstaller --noconfirm cli_build.spec

打包配置在 cli_build.spec(已排除 GUI 库,产物约 31MB)。验证通过后,将 dist\ledger-cli.exe 复制到项目的 cli 文件夹即可。


4. 输入数据是什么?

4.1 必须包含的列

列名 作用 例子
会计月 第几月的账 1月、1、2024-01、一月都行
凭证编号 这笔分录的凭证号 1、001、记-001
一级科目 会计科目名称 银行存款、应收账款、管理费用
借方发生额 借方金额 10000、5000.5
贷方发生额 贷方金额 10000、5000.5

4.2 可选的列

列名 什么时候要 例子
凭证种类 你想区分"记/收/付/转"时 记、收、付、转
账套 一个 Excel 里有多家公司数据时 A公司、B公司

列名不必完全一致。 程序会按关键词自动识别。如果识别不到,会弹窗让你手动选择列对列。

可识别的列名关键词:

程序需要的列 能识别的列名
会计月 月、日期、期间、制单日期
凭证编号 编号、凭证号
一级科目 科目、一级、名称
借方发生额 借方、借
贷方发生额 贷方、贷
账套 账套、公司、核算主体

4.3 使用前的数据清洗(必做)

🔴 重要提示:原始序时账表几乎都不能直接用,必须先做数据清洗,否则处理结果会失真或报错。

4.3.1 为什么要清洗?

常见问题 后果
没有"一级科目"列(只有明细科目) 对方科目匹配准确度大幅下降
表头有多行(合并单元格、空行、单位行混在表头里) 程序找不到表头,列识别错误
借贷两列同时有数 报警告,部分分录被丢弃
金额列混进文字(如"一百元"、"测试") 直接报错停止
多公司数据没加账套列 跨公司凭证被错误合并

4.3.2 一级科目怎么补?

实务中拿到的序时账常常只有明细科目(如"银行存款-工商银行-XX支行"),没有"一级科目"列。手工补太慢,标准做法是用 Excel 公式 + 科目余额表匹配:

步骤:

  1. 拿到科目余额表(一般企业财务系统都能导出),里面有一级科目代码对应一级科目名称
  2. 在序时账的明细科目列右边插入一列,假设原列叫 C 列、余额表中代码列叫 D 列、名称列叫 E 列
  3. 在新列里用 LEFT 函数取明细科目代码前 4 位:
    =LEFT(C2, 4)
    
  4. 用 VLOOKUP 到科目余额表里匹配一级科目名称:
    =VLOOKUP(LEFT(C2,4), 余额表!D:E, 2, FALSE)
    
  5. 公式下拉填充整列
  6. 把这一列重命名为"一级科目"
  7. 留几行检查匹配结果,大部分成功即可;有匹配不上的少数行,手工补一下

注意事项:

  • 不同企业的科目编码长度可能不是 4 位,请根据实际的科目余额表调整 LEFT 的位数(常见有 4 位、6 位、8 位等)
  • 同一个一级科目代码在余额表里只能对应一个名称
  • 如果代码和名称都不规律、匹配不上,那就只能用筛选+手工归类的笨办法

4.3.3 表头不规范怎么办?

常见表头问题: 第一行是单位名(如"XX 公司"),第二行是标题(如"2024 年序时账"),第三行才是列名;或者表头合并了单元格。

清洗目标:让第一行就是规范的列名行。

具体操作:

  1. 打开 Excel,按 Ctrl+End 看一下末尾
  2. 把表头以上的所有"多余行"(单位行、标题行、空行)整行删除
  3. 把表头里合并的单元格取消合并(开始 → 合并后居中 → 取消合并单元格)
  4. 表头每个单元格填上明确的列名(会计月、凭证编号、一级科目、借方发生额、贷方发生额)
  5. 检查表头是否只有一行,把多出来的空行删掉

提示: 处理完之后,第一行往下就是数据,列名清晰规范,可以保存后用本工具处理。

4.3.4 其他清洗要点

提示 说明
借贷不要同时有数 一行要么借方有数,要么贷方有数,不要两列都填。如果某行借贷都有数,要么是数据错误,要么是红字冲销(确认后再处理)
合并的数据单元格 程序会自动向下填充空值,不用手工处理
文件格式 仅支持 .xlsx.xls不要把 .csv 改名成 .xlsx,先在 Excel 里打开另存为 .xlsx
金额格式 支持 100001,0001,000.50;混进文字(如"一百元")会直接报错停止——不要指望程序自动当成 0
表内不要有空行 数据中间有空行会导致分组错乱,删掉
不要带小计行 小计/合计行会让"对方科目匹配"和"异常分录检测"误报,删掉

4.4 多公司账套数据怎么准备?

一个 Excel 含多公司时,加一列"账套"

账套 会计月 凭证编号 一级科目 借方发生额 贷方发生额
A公司 1月 记-001 银行存款 10000 0
A公司 1月 记-001 应收账款 0 10000
B公司 1月 记-001 银行存款 20000 0
B公司 1月 记-001 应付账款 0 20000

为什么不加账套列会出错? A 公司的"1月记-001"和 B 公司的"1月记-001"会被当成同一张凭证合并处理,对方科目匹配就会错。


5. 输出结果是什么?

结果文件命名为:原文件名 + "生成对方科目" + 原扩展名。

例如:2024年序时账.xlsx2024年序时账生成对方科目.xlsx

文件里包含 6 张工作表:

所有工作表第一行的非空表头均水平居中。

工作表 里面是什么 怎么用
生成结果 原始数据 + 自动填的"对方科目"列 + "匹配类型"列 这是主表,所有分析都基于它
原始数据 你原来上传的 Excel 内容备份 对照原始凭证用
异常分录明细 金额大且不符合常规的分录 看第 6.2 节
异常分录 异常分录按类型汇总 看第 6.2 节
班福分析 数据造假分析(含折线图) 看第 6.3 节
失败分组 处理失败的凭证组 出错时排查用

6. 怎么利用生成结果?

6.1 拿到"对方科目"和"匹配类型"列后做什么?

做数据透视分析。 现在每笔分录都有明确的去向,可以像查账单一样查账:

  • 某月"银行存款"流入了多少?流向哪些对手方?
  • 某月"管理费用"主要付给了谁?
  • 某客户/供应商全年交易额是多少?

匹配类型是质量信号:

匹配类型 可信度 说明
标准模板 ⭐⭐⭐⭐⭐ 命中内置的 256 条标准会计分录规则,可直接采信
结转损益规则 ⭐⭐⭐⭐ 识别为期末损益结转分录
结转损益规则(拆分) ⭐⭐⭐⭐ 损益结转中利润分配科目被拆到各行
结转损益规则(调整) ⭐⭐⭐ 损益结转时的微小误差调整行
算法生成 ⭐⭐⭐ 金额配对成功,但科目关系是算法推的,建议抽查
算法生成(兜底拆分) ⭐⭐ 复杂分录的兜底拆分,建议复核
算法生成(异常剩余) 实在匹配不上,必须人工核对

实务建议:⭐⭐⭐ 及以上的可以直接信任;⭐⭐⭐ 以下的建议人工复核。


6.2 异常分录识别(核心审计功能)

什么是异常分录?

同时满足以下条件的分录:

  • 金额超过阈值(默认 10000 元)
  • 没命中标准模板规则
  • 没命中结转损益规则

为什么要关注? 这些可能是:非常规业务、错误分录、造假痕迹。

怎么看?

  1. 打开 "异常分录明细" 工作表
  2. 按"金额"从大到小排序
  3. 重点看金额最大的那几笔
  4. 对照原始凭证核实

怎么调阈值? 不同规模公司建议不同:

公司规模 建议阈值
小公司 5000
中型公司 10000(默认)
大公司 50000 或更高

阈值越高,列出的异常分录越少、越聚焦大额可疑交易。


6.3 班福分析(数据造假识别)

这是什么? 班福定律(Benford's Law)是一个数学规律:自然产生的一组数字,首位数的分布不是随机的。

首位数 理论概率
1 30.1%
2 17.6%
3 12.5%
4 9.7%
5 7.9%
6 6.7%
7 5.8%
8 5.1%
9 4.6%

简单说:以"1"开头的金额应该最多(30%+),以"9"开头的应该最少(不到 5%)。

为什么能检测造假? 人编的数字往往不符合这个规律(要么均匀分布,要么偏好某些数字)。如果实际分布和理论分布差很多,数据可能被修改过。

怎么看? 打开 "班福分析" 工作表,看折线图:

  • 蓝线 = 实际数据分布
  • 橙线 = 理论分布
  • 两线越接近,数据越自然;偏差越大,越可疑

注意: 班福分析只是辅助证据,不是定罪铁证。有些真实业务本身就符合正态分布而非班福分布。


6.4 怎么用"对方科目"做来源去去向分析?

工具自动填上"对方科目"列后,每一个一级科目都能看清它的钱从哪来、去了哪。这就是会计上说的"科目来源去去向分析",是审计和分析最常用的视角。

下面按科目类别列出常见的分析套路:

6.4.1 损益类科目:钱花在了哪里?

核心思路:损益类科目(收入、成本、费用)的对方科目,主要应该是现金、银行存款、应收应付、相关资产类科目。如果看到异常的对方科目,就要重点核查。

你关注的科目 正常应该对应的对方科目 如果出现这些对方科目 → 可能有问题
主营业务收入、其他业务收入 银行存款、应收账款、应收票据、预收账款(红字)、现金 出现往来对抵(其他应收/其他应付)、关联方挂账等异常挂账科目
主营业务成本、其他业务成本 库存商品、原材料、银行存款、应付账款 出现"在建工程""研发支出"等非正常结转
销售费用、管理费用、研发费用 银行存款、库存现金、应付职工薪酬、累计折旧、其他应付款 出现关联方往来(如其他应收款-某股东)、长期资产科目
财务费用 银行存款、应付利息、长期借款 出现非利息相关科目(如营业外支出)
折旧与摊销 管理费用、制造费用、销售费用、生产成本、研发支出 看折旧摊销到底有没有按用途归集到对应成本费用科目,没归集就是异常
职工薪酬 管理费用、制造费用、生产成本、销售费用、研发支出、应付职工薪酬、银行存款 看薪酬有没有按部门/用途归集到对应成本费用科目

6.4.2 成本结转类:钱怎么流转的?

核心思路:成本结转类科目(生产成本、制造费用等)的对方科目应该形成清晰的"投入-产出"链条。

你关注的科目 正常应该对应的对方科目 分析重点
生产成本 原材料、应付职工薪酬、制造费用、银行存款(投入端) / 库存商品、主营业务成本(产出端) 看生产成本的来源主要是哪些科目(是不是正常的料工费投入),又结转到了哪些科目(最终是不是到了库存商品、主营业务成本)
制造费用 累计折旧、应付职工薪酬、水电费相关科目(银行存款、应付账款)、原材料 看制造费用归集是否完整,结转到生产成本时金额是否合理
研发支出-费用化支出 银行存款、原材料、应付职工薪酬、累计折旧 资本化条件是否符合会计准则

6.4.3 往来类科目:钱流出去/收回来正常吗?

核心思路:往来类科目(应收/应付)的对方科目变动必须符合业务实质。增加要有合理的来源,减少要有合理的去向,否则就是异常的"对抵"或挂账。

你关注的科目 正常增加时(借方)的对方科目 正常减少时(贷方)的对方科目 异常信号
应收账款 主营业务收入、其他业务收入、应收票据(背书转入)、银行存款(收到预收款转货款) 银行存款(实际收款)、应收票据、坏账准备(核销) 减少时对方是其他应付款、其他应收款、预付账款、长期挂账等 → 高度可疑,可能是走账、对抵、虚假回款
应付账款 原材料、库存商品、应付票据、银行存款(预付转应付)、相关成本费用 银行存款(实际付款)、应付票据、其他货币资金 增加时对方是其他应收款、其他应付款、银行存款(非真实付款)→ 异常挂账减少时对方是往来类而非货币资金 → 可能是对抵
预收账款 银行存款、库存现金(收到预收款冲销时) 主营业务收入、其他业务收入(确认收入时) 长期挂账不确认收入,或冲销时对方科目异常
预付账款 银行存款、库存现金(付款时) 原材料、库存商品、相关成本费用(收到货物/服务时) 长期挂账,或冲销到非货品/非费用科目
其他应收款 银行存款、库存现金(备用金、押金、垫付款) 银行存款、相关成本费用(收回或冲销时) 频繁与"其他应付款""应收账款""应付账款"对抵 → 高风险信号,可能隐藏关联方占款或资金体外循环
其他应付款 银行存款(收到押金等)、相关收入(应付款转收入) 银行存款、库存现金(实际支付时) 频繁与"其他应收款""应付账款""应收账款"对抵 → 高风险信号

6.4.4 货币资金类:钱从哪来、到哪去?

你关注的科目 重点看的对方科目
银行存款、库存现金 对方科目应该覆盖所有业务场景。重点看是否有大量"银企对抵":减少方是"应付账款"或"其他应付款",增加方又是"应收账款"或"其他应收款"——这种没有实际过账的转账是虚假回款的典型手法
其他货币资金(保证金、专户存款等) 保证金是否对应真实的合同/保函业务

6.4.5 实操建议

  1. 导出"生成结果"工作表,按"一级科目"分组,对每个科目查看"对方科目"列的金额分布
  2. 重点关注大额科目:金额大的科目,对方科目稍有异常就是大问题
  3. 关注同一对方科目反复出现:比如某笔款往来方反复出现且金额相同,可能是循环转账
  4. 关注时间集中度:短期内大量同向同金额交易,要警觉
  5. 结合"异常分录明细":金额大、没匹配上的分录,加上对方科目异常,是审计重点

6.5 匹配不上怎么办?

先看"匹配类型"列。 如果大量显示 ⭐⭐ 以下:

现象 可能原因 处理建议
大量"异常剩余" 数据借贷本身不平衡 检查原始凭证
"兜底拆分"很多 复杂多借多贷 抽查原始凭证,确认业务是否真实
部分匹配不上 科目特殊、金额有微小差异 微调阈值或检查科目命名

打开 "失败分组" 工作表,可以直接看到处理失败的凭证组有哪些。


7. 常见问题

问:双击 exe 弹出黑窗口一闪就没了?

答:这是因为程序需要依赖库(pandas/openpyxl 等)但当前电脑没装。本工具是已经打包好的单文件 exe,正常情况下双击会直接弹出主窗口。如果一闪而过,说明杀毒软件拦截了,尝试关闭杀软后重试,或右键 → "以管理员身份运行"。

问:处理时报"读取 Excel 失败"?

答:检查文件是不是被打开着(关闭 Excel 再处理)、文件格式是不是 .xlsx.xls、文件有没有损坏。

问:金额列里有"一百元"、"测试"这种文字,程序会怎么处理?

答:直接报错停止运行,告诉你哪一列第几行有问题。不会偷偷当成 0,避免结果被污染。改干净原始数据再处理。

问:借贷两列都有数会怎样?

答:这种情况会计上不合理,程序会报警告。建议改原始数据,确保一行只有一列有数。

问:处理速度慢怎么办?

答:先关掉其他程序释放内存。如果数据量巨大(几十万行以上),可以用命令行模式 --no-gui 跑,省去界面开销。

问:结果文件能直接发给领导看吗?

答:可以。生成结果采用专业排版格式(深海蓝表头、千分位数字、冻结首行),可直接交付。


8. 源码架构(开发者)

8.1 模块分层

本项目为模块化架构,目录结构如下:

序时账分析器/
├── main.py                主程序入口(自动选择 GUI 或命令行模式)
├── make_excel.py          Excel 输出美化模块(deep-navy 摩根系标准格式)
├── requirements.txt       依赖列表
├── LICENSE                开源许可证
├── cli/                   纯命令行版(给 agent / 脚本调用,详见 3.3 节)
│   ├── 序时账分析器命令行版.py
│   └── ledger-cli.exe     可直接运行的纯命令行版
├── tests/                 冗余约束与 Excel 输出回归检查
└── src/
    ├── core/              核心算法层
    │   ├── precision.py   金融级精度引擎
    │   ├── algorithms.py  折半枚举算法
    │   └── rules.py       256条会计分录规则库
    ├── pipeline/          处理流水线层
    │   ├── group_processor.py   凭证分组处理器
    │   ├── orchestrator.py      流水线编排器
    │   ├── anomaly.py           异常分录检测 + 班福定律分析
    │   └── validator.py         数据完整性校验
    ├── io/                输入输出层
    │   ├── reader.py      数据读取与预处理
    │   └── writer.py      结果输出(含图表)
    ├── gui/               图形界面层
    │   ├── app.py         主窗口(含列映射配置)
    │   ├── widgets.py     界面组件适配器
    │   ├── progress.py    进度消息队列
    │   └── log_redirector.py    日志重定向
    └── utils/             工具层
        └── logger.py      统一日志模块

8.2 分层职责

层级 模块 职责
核心层 precision + algorithms + rules 精度引擎、折半枚举算法、规则库
流水线层 group_processor + orchestrator + anomaly + validator 分组处理、流水线编排、异常检测、数据校验
输入输出层 reader + writer 读取预处理、结果输出(含图表)
界面层 app + widgets + progress + log_redirector 主窗口、列映射、组件适配器、进度条、日志重定向
工具层 logger 统一日志模块

8.3 设计原则

  • 每层职责单一:修改某个功能只需要改一个文件
  • 模块解耦:数据加载与界面完全解耦,通过参数注入列映射对话框
  • 进度通知统一:从全局变量改为回调函数参数传递
  • 入口清晰main.py 供日常 GUI/命令行使用,cli 目录保留专用自动化入口

9. 核心算法原理(开发者)

9.1 精度引擎:为什么用"厘"不用"元"?

问题: 电脑用二进制存储小数,有些十进制小数是无限循环的,导致 0.1+0.2 不等于 0.3。

解决方案: 把所有金额乘以 10000,转成整数"厘"(万分之一元)来计算。

原金额(元) 转换后(厘)
1 10000
0.1 1000
0.01 100
0.0001 1

好处:

  • 精确计算:整数运算不会丢精度
  • 速度快:整数运算比小数快
  • 可靠:不会出现 0.1+0.2 ≠ 0.3

容差设置: 默认容差 100 厘 = 0.01 元 = 1 分,两个金额差一分钱以内就算匹配成功。

9.2 凑数字算法:折半枚举 + 剪枝 + 递归

问题: 一笔借方对应多笔贷方时,要凑数字。比如借方 10000 元,贷方有 3000、5000、2000 三笔,要找出哪些加起来 = 10000。

9.2.1 为什么需要"剪枝"和"递归"?

朴素穷举(不做任何优化)的组合数是 2^N(N 条候选每条选/不选两种),N=20 时就有 100 万种,N=36 时接近 700 亿种——绝对不可能在合理时间内跑完。

所以必须用剪枝(Pruning,提前砍掉不可能的分支)和递归(Recursion,分而治之)来把搜索空间压到可接受范围。

9.2.2 递归:分而治之

思路: 把大问题拆成小问题,小问题的解可以组合出大问题的解。

折半枚举的两步递归:

  1. 左半区预计算(递归穷举所有组合)

    • 假设左半区有 N 条候选,递归枚举所有可能的组合(每个元素选或不选)
    • 对每个组合计算"金额总和",存入列表 L
    • 把 L 按金额从小到大排序
    • 例:左半区 [a, b, c] → 组合有 [][a][b][c][a,b][a,c][b,c][a,b,c],共 2³=8 个金额
  2. 右半区生成并二分查找

    • 递归枚举右半区的每个组合 R,计算金额总和 sum_R
    • 目标:在左半区金额列表 L 中,找一个金额 sum_L 满足 sum_L + sum_R ≈ target(在容差范围内)
    • 二分查找在已排序的 L 中找 target - sum_R,时间复杂度 O(log M)(M 是左半区组合数)

合并过程: 左半区每个组合都能和右半区每个组合拼接,所以理论上所有候选的组合都被覆盖了,但计算量从 O(2^N) 降到 O(2^(N/2) × log(2^(N/2)))——对 N=36 来说,大约 2^18 = 26 万次运算,几十毫秒搞定。

9.2.3 剪枝:砍掉不可能的分支

光靠分治还不够,必须在递归过程中尽早排除不可能成功的路径。本算法有 5 重剪枝:

# 剪枝策略 怎么做 效果
1 排序剪枝 候选金额从大到小排序,大的先尝试,更容易快速逼近目标 平均分支数减半
2 和超出剪枝 当前累计金额 > 目标 + 容差,立刻放弃这条路径 砍掉约 30% 分支
3 剩余不足剪枝 当前累计金额 + 剩余所有候选的总额 < 目标 - 容差,放弃这条路径 砍掉约 20% 分支
4 重复值剪枝 相同金额的候选只尝试一次(避免重复路径) 砍掉大量重复分支
5 目标为 0 短路 目标金额本身为 0,直接返回空组合,不需要递归 边界情况秒返回

9.2.4 算法伪代码

def find_combination(candidates, target, tolerance):
    """
    candidates: 候选金额列表(已从大到小排序)
    target: 目标金额
    tolerance: 容差
    返回:满足 sum ≈ target 的子集
    """
    candidates.sort(reverse=True)         # 排序剪枝 #1
    result = []

    def dfs(index, current_sum, chosen):
        # 剪枝 #5:刚好在容差范围内
        if abs(current_sum - target) <= tolerance:
            result.append(chosen[:])
            return True  # 找到一个就返回

        # 递归终止:候选用完
        if index >= len(candidates):
            return False

        # 剪枝 #2:当前和已超出目标
        if current_sum > target + tolerance:
            return False

        # 剪枝 #3:当前和 + 剩余最大可能 < 目标
        remaining_max = sum(candidates[index:])
        if current_sum + remaining_max < target - tolerance:
            return False

        # 剪枝 #4:相同金额只试一次
        seen_values = set()

        for i in range(index, len(candidates)):
            val = candidates[i]
            if val in seen_values:        # 剪枝 #4
                continue
            seen_values.add(val)

            chosen.append(i)
            if dfs(i + 1, current_sum + val, chosen):
                chosen.pop()
                return True
            chosen.pop()

        return False

    dfs(0, 0, [])
    return result

9.2.5 v1.8.0 升级效果对比

维度 旧版回溯法 新版折半枚举
计算方式 全部穷举 左右分治 + 二分查找 + 5 重剪枝
最大有效深度 约 20~25 条 稳定支持 36 条
长尾凑单场景 容易超时假死 毫秒级响应
N=20 耗时 ~1 秒 ~10 毫秒
N=36 耗时 数分钟(实际无法完成) ~100 毫秒

保护机制:

  • 最大迭代次数:300000 次(防止单边计算过大导致内存溢出)
  • 最大候选池:36 条(单边 18 条,最大组合数约 26 万,数十毫秒完成)

9.3 四阶段匹配流程

每张凭证按以下顺序尝试匹配:

9.3.1 阶段 0:结转损益特殊规则(最高优先级)

识别条件:

  • 科目以"本年利润"开头,或
  • 科目以"利润分配"开头

排除条件(不会误判):

  • 如果科目包含"费用"、"成本"、"折旧"、"减值"、"摊销",就不是损益结转

比如"利润中心折旧费"不会被误判为损益结转。

9.3.2 阶段 1:标准模板匹配

内置 256 条常见会计分录规则:

借方科目 贷方科目 业务含义
银行存款 主营业务收入 销售收款
管理费用 银行存款 支付费用
库存商品 主营业务成本 结转成本

匹配顺序:先 1 对 1(金额相等、科目符合规则)→ 再 1 对多(凑数字)→ 最后多对 1(凑数字)。

9.3.3 阶段 2:算法金额匹配

标准模板没匹配上时,用纯金额匹配,不管科目。

匹配顺序:先 1 对 1 → 再 1 对多 → 最后多对 1。

9.3.4 阶段 3:兜底拆分

把剩余的借方和贷方按顺序配对,金额按较小的那个拆分。

例如:剩余借方 50000、剩余贷方 30000,拆成 借方 30000 ↔ 贷方 30000(匹配成功)+ 借方剩余 20000(标记"未找到匹配")。

9.4 红字处理

情况 处理方式
借方是正数 正常作为借方处理
贷方是正数 正常作为贷方处理
借方是负数 转成正数,作为贷方处理
贷方是负数 转成正数,作为借方处理

10. Excel 输出格式规范(开发者)

输出采用 deep-navy 摩根系标准格式,由 make_excel.py 提供:

格式要素 说明
数据起始位置 从 B2 单元格开始写入
表头样式 深海蓝背景 #1F4E79、白字粗体、Arial 11、水平居中
行高 统一 18 磅
数字格式 千分位分隔,数字列自动蓝色字体
框线 上下粗框线 + 中间虚线,无竖线
网格线 隐藏 Excel 默认网格线
冻结 首行冻结,方便滚动查看
班福图表 正常保留

降级策略: make_excel.py 不可用时,程序自动回退到 to_excel 普通保存,不报错。


11. 更新日志

v2.0.8 补充(2026-08-09)-- 冗余精简与表头居中

  • 删除未被调用的旧列映射文件、终端进度条、重复日志通道和内部兼容包装,既有程序源码净减少 736 行
  • 保留双 GUI、独立 CLI、主说明文档和已发布 EXE,不改变核心匹配算法、入口参数及退出码
  • 主程序、专用 CLI 源码及 cli\ledger-cli.exe 生成的每个工作表首行表头统一水平居中
  • 重建 cli\ledger-cli.exe,体积由 32,368,746 字节降至 32,346,279 字节
  • 新增可重复的结构测试和 Excel 工作簿回归检查;逐格验证除首行水平对齐外无其他输出差异

v2.0.8 修复(2026-08-07)-- 结转损益匹配修复

本次修复了结转损益凭证(摘要为"结转当前损益")在"编码\名称"式科目下全部无法识别、以及表格末尾"总计"汇总行混入凭证的问题。

结转损益科目识别修复

  • 问题:判断是否损益结转时,只认科目名称以"本年利润"或"利润分配"开头的开头匹配;而账套科目多为"4103\本年利润"这类"编码\名称"格式,开头是数字,永远匹配不上,导致所有结转凭证被当成普通凭证乱配金额。
  • 修复:识别逻辑从"开头匹配"改为"包含匹配",只要科目名含有"本年利润"或"利润分配"即识别为损益结转科目;原有的排除项(含"费用/成本/折旧/减值/摊销"的不算)保持不变。

表尾"总计"汇总行剔除

  • 问题:很多账套导出文件末尾有一行"总计/合计/小计"汇总行(无凭证号、无科目名),读取时空凭证号被向下填充成上一个凭证号,导致该行被算进最后一张凭证,变成超大金额假分录。
  • 修复:读取后自动检测并剔除摘要为"总计/合计/小计"的汇总行,并在日志提示剔除行数;同时给处理环节加保险,科目名称为空的行直接跳过保留,绝不参与配对。

验证结果

  • 「未找到匹配」行数:17 → 0
  • 「对方科目」为空/NaN 行数:17 → 0
  • 输出中「总计」行数:18 → 0
  • 「结转损益规则」行数:0 → 354
  • 结转凭证全部 40 条分录对方科目均正确指向"本年利润"

v2.0.8 补充(2026-08-05)-- 新增纯命令行版(CLI)

新增独立的纯命令行版本(cli/序时账分析器命令行版.py),专为 AI agent 和自动化脚本设计,复用现有核心算法、不修改任何现有代码:

  • --mode summary(默认,只输出生成结果表)/ --mode full(全部 sheet)
  • --threshold 控制异常分录阈值
  • -i / --interactive 列识别不出时弹命令行选单逐列选
  • 普通格式输出(无美化、无图表),打包成独立 exe(dist/序时账分析器命令行版.exe,约 72MB)

11.1 v2.0.8(2026-07-24)-- 全面 review:数据完整性修复与体验优化

本次对全部源码做了一轮完整审查(含合成数据实测),修复了 3 个会导致"结果悄悄出错"的隐患和一批中小问题:

数据完整性修复(重点)

  1. 分组键空值静默丢行:凭证编号/凭证种类为空且无法向下填充时,这些行会被分组功能静默丢弃。修复方式:groupby 改用 dropna=False,空值行强制纳入处理。
  2. 生成结果行序随机:多进程并行按完成先后写入结果,行序每次都乱。修复方式:每行携带原始行号(_orig_idx),输出前按原始顺序稳定排序。
  3. 同行借贷双非零丢金额:某行借贷双方同时有金额时,贷方金额被静默丢弃且警告发在子进程里用户看不到。修复方式:拆成借/贷两个独立条目分别参与匹配(禁止自我配对),警告随结果回传主进程展示。

中小问题修复

  1. 版本号混乱main.py、GUI 标题(ctk/tk)、界面角落标签统一为 v2.0.8
  2. --no-gui 参数失效:此前定义了但从未生效,现在不带文件路径使用时会明确报错提示。
  3. CLI 退出码:处理失败(读文件失败/保存失败)时退出码为 1,成功为 0,方便批量脚本判断;保存链路全程回传成功状态。
  4. GUI 旧文件误报成功:此前只判断输出文件"是否存在",上次遗留的旧文件会被误报为本次成功;现在同时校验流水线返回状态和文件修改时间。
  5. 账套列误判:列名恰好叫"公司"等短名称时会被误当账套列参与分组。修复方式:账套列检测改为单向包含匹配。
  6. 空"异常分录"sheet 丢失:无异常数据时该 sheet 被静默跳过。修复方式:写入提示行保证 sheet 一定存在。
  7. 班福图表回退错位make_excel 不可用走 to_excel 回退时图表引用列偏移错误。修复方式:按实际写入路径动态计算起始列。
  8. make_excel 健壮性:NaN/NA/NaT 统一清洗为空再写入(pd.NA 会直接写崩);行写入从 iterrows 换为 itertuples,大文件写出更快;writer.py 导入 make_excel 增加项目根目录回退,打包环境也能找到。
  9. 金额容差统一:子集和求解默认容差从 0.005 元统一为 0.01 元,与其他环节一致。
  10. 空凭证编号显式警告:凭证编号向下填充时升级为 WARNING,提示"若非合并单元格所致,这些行将被并入上一张凭证"。

性能与清理

  1. 小数据免多进程:分组数 ≤200 时走单进程串行,省去进程池启动开销(实测小文件秒出结果)。
  2. 清理死代码:移除未被引用的 column_mapper.pyCTkProgressBarCustomInputDialog,以及误入仓库的 ers27651DocumentsCode 垃圾文件。

11.2 v2.0.7(2026-06-12)—— 全面代码复核

  1. 全量代码复核:审查全部 16 个业务代码文件,确认无致命 Bug
  2. 异常处理、精度处理、算法逻辑、数据流转均已完善
  3. Excel 输出使用 excel-master 技能最新版 make_excel,deep-navy 主题

11.3 v2.0.6(2026-06-10)—— Excel 输出改用摩根系 deep-navy 标准格式

输出从裸 to_excel 升级为深海蓝摩根系标准格式,专业美观。writer.pyExcelWriter + to_excel 改为调用 make_excel(theme='deep-navy'),美化模块随项目分发。

11.4 v2.0.5(2026-06-08)—— 版本号统一与健壮性修复

全局版本号统一

代码中多处硬编码 v2.0.0,README 已更新到 v2.0.4 但源代码未同步,用户无法从界面或命令行帮助确认实际运行的版本。修复方式:main.pysrc/gui/app.py 中所有版本号统一更新为 v2.0.5

GUI 组件适配器参数泄漏

_make_label 适配器函数在 CTK 模式下未清理 tkinter 专属的 fg 参数。修复方式:CTK 分支增加 kw.pop('fg', None)kw.pop('anchor', None) 清理逻辑。

借贷双非零数据静默丢弃

原始数据中某行同时存在借方和贷方非零金额(属于数据异常)时,旧代码的 if-elif 链会静默丢弃其中一方的金额,用户无法察觉。修复方式:_prepare_data 中增加检测,借贷双方同时非零时通过 logger 发出警告。

兜底拆分函数重复定义

_match_fallback 中的 set_split_amount 内部函数在 while 循环内重复定义,每次迭代都创建新函数对象。修复方式:函数定义移到循环外。

损益结转误差调整值精度

_process_profit_loss 中计算出的差额 diff 直接用浮点数赋值,可能因浮点误差输出极小非零金额(如 1e-12)。修复方式:差额赋值前通过 PrecisionEngine.to_decimal() 清洗为 2 位小数。

异常分录汇总金额翻倍

"异常分录"汇总工作表中同一笔异常分录金额被算成 2 倍(借方行和贷方行都加一遍)。修复方式:金额改为 max(借方金额, 贷方金额),只在借方行记录聚合。

多对方科目校验误判

科目名称本身包含逗号时被误判为"多个对方科目"。修复方式:校验规则改为匹配"分隔符后跟非空白字符"模式。

借贷差额校验提示优化

红字分录处理后借贷方合计产生等额差额,旧版本会弹"严重警告"。修复方式:差额一致时降为 INFO 级别,明确说明系红字分录处理所致。

移除已摒弃的单文件版

删除 对方科目生成.py(已摒弃的单文件版),统一使用模块化版本。

11.5 v2.0.4(2026-06-08)—— 异常分录金额修复

修复了"异常分录"汇总工作表金额翻倍的问题(详见 11.3 同名条目),以及多对方科目校验误判的低优先级修复。

11.6 v2.0.3(2026-06-03)—— 金额数据质量收紧

关键修复

旧版本会把无法识别的金额(如 abc测试一百元)偷偷当成 0 处理,带来金额失真、匹配错误、问题难排查三重风险。

修复方式:程序先检查金额列的每个值,非空但不能识别为数字时立刻报错并停止处理,报错信息明确指出哪一列、第几行有问题。

兼容性保留

  • 1234.56 正常支持
  • 1,234.56 千分位逗号正常支持
  • 空白单元格仍按原规则处理为 0

新增回归测试

  • 非法金额不再被静默转成零
  • 千分位金额格式仍可正常解析

11.7 v2.0.2(2026-06-03)—— 稳定性紧急修复

空输入或空输出结果时崩溃

旧版本在"结果校验"这一步会因默认存在借方/贷方/对方科目列而崩溃。修复方式:校验前先补齐空结果表需要的关键列。

多条损益类分录被错误合并到第一条

同一凭证里多条本年利润/利润分配相关分录时,旧版本会错误地都挂到第一条。修复方式:检测到多条损益类分录时不再强行套用特殊规则,回退到通用匹配流程。

11.8 v2.0.1(2026-06-03)—— 致命缺陷修复

v2.0.0 模块化重构后的紧急修复:

缺陷 严重程度 修复内容
图形界面导入路径错误 致命 修复了两个不存在的模块导入
命令行模式缺少入口函数 致命 补充了命令行模式的完整入口函数
图形界面跳过数据预处理 致命 改用标准预处理流程
进度条不更新 中等 修复进度回调函数未传递
图形界面日志不显示 中等 新增 GuiLogHandler
版本号不统一 统一所有位置的版本号

11.9 v2.0.0(2026-06-03)—— 模块化架构重构

从零开始的架构重构,把原来 2315 行的单文件拆分成 5 层模块化架构。

架构变化

层级 模块 负责
核心层 precision + algorithms + rules 精度引擎、折半枚举算法、规则库
流水线层 group_processor + orchestrator + anomaly + validator 分组处理、流水线编排、异常检测、数据校验
输入输出层 reader + writer 读取预处理、结果输出
界面层 app + column_mapper + widgets + progress + log_redirector 主窗口、列映射、组件适配器、进度条、日志重定向
工具层 logger 统一日志模块

日志系统升级

旧版用 print,新版用标准 logging,支持调试/信息/警告/错误四个级别,带时间戳和级别标记。

图形界面重构

旧版直接写 if/else 判断用哪种界面库,新版用适配器模式,统一组件工厂函数。

命令行模式增强

旧版手动判断 sys.argv,新版用 argparse,支持 --threshold--no-gui--log-level,自带 --help

模块解耦

进度通知从全局变量改为回调参数;数据加载与界面完全解耦,通过参数注入列映射对话框。

代码规模对比

指标 旧版(单文件) 新版(模块化)
文件数量 1 个 16 个
最大单文件行数 2315 行 约 320 行
架构层级 5 层清晰分层

11.10 v1.8.0(2026-03-07)—— 核心算法重构

  • 折半枚举算法:核心匹配算法从全部穷举升级为左右分治 + 二分查找
  • 最大有效计算深度从 20~25 条提升到 36 条
  • 候选池超过 36 条时自动截断

11.11 v1.7.0(2026-03-07)—— 多公司账套支持

  • 自动识别账套列,按账套 + 月份 + 凭证号分组
  • 支持 7 种账套列名变体
  • 输出文件中账套列放在第一列

11.12 v1.6.x(2026-02~03)—— 界面优化与兼容性修复

  • 1.6.6:界面更加紧凑,列映射直接嵌入主界面,异常阈值直接在主界面设置
  • 1.6.3:修复未安装增强界面库时程序无法运行的问题,修复进度条不显示
  • 1.6.2:引入分组处理器类,规则库从 423 条去重到 256 条
  • 1.6.0:全新现代化界面,卡片式布局

11.13 v1.5.0(2026-01-10)—— 架构重构

模块化设计,移除未使用的依赖库,进度条轻量化。

11.14 v1.4.0(2026-01-09)—— 重大功能更新

异常分录智能检测、班福定律分析、交互式列映射配置、多进程并行计算、高速读取引擎。

11.15 v1.3.0(2026-01-09)—— 稳定性修复

禁用日期智能解析避免月份合并问题,增强损益结转识别避免误判。

11.16 v1.2.0(2026-01-09)—— 性能飞跃

多进程并行计算,算法优化引入缓存,数据遍历加速。

11.17 v1.1.0(2026-01-09)—— 兼容性增强

合并单元格支持(自动向下填充),依赖库完善。


12. LICENSE

本项目采用 GNU GPL v3.0 许可证,详情见 LICENSE 文件。

About

序时账对方科目自动生成工具 - 自动分析借贷关系,匹配对方科目

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages