银行流水对账是财务审计的核心步骤,但在实际场景中,银行账单与企业明细账格式不一,日期存在时间偏差,且摘要描述大相径庭(如银行流水写着“汇入-张三”,明细账记为“收到货款”)。
为了实现全自动对账,三函代码开发了一款基于 Pandas 和 TF-IDF 的四级递进匹配对账引擎。本文将从算法原理和核心 Python 代码两个方面介绍其实现。
一、 对账算法流程
对账匹配引擎分为以下四个步骤:
- L1 精确匹配:比对交易金额、交易日期(相同)与借贷方向,直接过滤无争议凭证。
- L2 容差时间窗匹配:在金额一致的前提下,允许交易日期存在 ±3 天的清算偏差。
- L3 别名与关联方映射:引入客户名称、交易对手库映射别名表,解决命名差异。
- L4 摘要语义相似度匹配:利用 TF-IDF 计算两端摘要的词向量余弦相似度,进行智能兜底推荐。
[原始数据] ➔ [L1 精确匹配] ➔ [L2 时间窗匹配] ➔ [L3 别名映射] ➔ [L4 语义相似度] ➔ [未达账项列表]二、 Python Pandas 核心实现
以下是对账匹配引擎的具体 Python 实现代码:
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
def run_reconciliation(bank_df, ledger_df):
"""
bank_df: 包含 ['date', 'amount', 'direction', 'remark']
ledger_df: 包含 ['date', 'amount', 'direction', 'remark']
"""
# 格式化日期与金额
bank_df['date'] = pd.to_datetime(bank_df['date'])
ledger_df['date'] = pd.to_datetime(ledger_df['date'])
bank_df['amount'] = bank_df['amount'].round(2)
ledger_df['amount'] = ledger_df['amount'].round(2)
matched_pairs = []
# L1: 严格精确匹配
l1_merged = pd.merge(
bank_df, ledger_df,
on=['amount', 'direction', 'date'],
suffixes=('_bank', '_ledger'),
how='inner'
)
for idx, row in l1_merged.iterrows():
matched_pairs.append({
'bank_idx': row.name,
'ledger_idx': row.name,
'type': 'L1 精确匹配'
})
# 剔除已匹配记录
bank_rem = bank_df.drop([p['bank_idx'] for p in matched_pairs if 'bank_idx' in p], errors='ignore').copy()
ledger_rem = ledger_df.drop([p['ledger_idx'] for p in matched_pairs if 'ledger_idx' in p], errors='ignore').copy()
# L2: 金额一致,日期窗口偏差 ±3 天
for b_idx, b_row in bank_rem.iterrows():
# 寻找金额相同、方向一致、且日期在 ±3 天内的记录
candidates = ledger_rem[
(ledger_rem['amount'] == b_row['amount']) &
(ledger_rem['direction'] == b_row['direction']) &
(abs((ledger_rem['date'] - b_row['date']).dt.days) <= 3)
]
if not candidates.empty:
l2_idx = candidates.index[0]
matched_pairs.append({
'bank_idx': b_idx,
'ledger_idx': l2_idx,
'type': 'L2 容差匹配'
})
ledger_rem = ledger_rem.drop(l2_idx)
# L3/L4: 针对未匹配成功的记录,使用 TF-IDF 计算摘要相似度
bank_rem = bank_df.drop([p['bank_idx'] for p in matched_pairs if 'bank_idx' in p], errors='ignore').copy()
if not bank_rem.empty and not ledger_rem.empty:
for b_idx, b_row in bank_rem.iterrows():
candidates = ledger_rem[
(ledger_rem['amount'] == b_row['amount']) &
(ledger_rem['direction'] == b_row['direction'])
]
if not candidates.empty:
# 计算文本相似度
corpus = [b_row['remark']] + list(candidates['remark'])
vectorizer = TfidfVectorizer()
try:
tfidf = vectorizer.fit_transform(corpus)
sim = cosine_similarity(tfidf[0:1], tfidf[1:])
best_match_idx = np.argmax(sim)
if sim[0][best_match_idx] > 0.6: # 相似度阈值
l4_idx = candidates.index[best_match_idx]
matched_pairs.append({
'bank_idx': b_idx,
'ledger_idx': l4_idx,
'type': 'L4 语义匹配'
})
ledger_rem = ledger_rem.drop(l4_idx)
except ValueError:
pass # 文本过短或为空
return matched_pairs, bank_rem, ledger_rem三、 未达账项与差异穿透
匹配后,未达账项即存在于 bank_rem(银行有、企业未入账)和 ledger_rem(企业已入账、银行未扣款)中。
系统会自动将这些未匹配项汇总为标准的“银行存款余额调节表”。通过关联底层的影像库,审计师只需双击未达项,便可自动通过文件指纹调出对应的原始交易截图或发票图像,实现真正的细节穿透。