以配置文件驱动的通用数据匹配工具包:SQL 解析、数据缓存、匹配聚合、Excel 导出,支撑按配置执行的采购三单匹配与分类审计底稿。
本仓库是一套可配置的通用数据匹配工具包,将三单匹配所需的 SQL 解析、编码检测、数据缓存、匹配键构建、差异计算、分类与 Excel 导出抽象为独立模块,由 config.json 集中驱动匹配阈值、聚合方式、导出与筛选规则。相比固定脚本,它通过配置适配不同项目的数据源与口径,减少重复改码。
- SQL 解析(
sql_parser.py):解析INSERT INTO ... VALUES格式 SQL,自动提取列名,处理NULL与字符串引号,支持合并多个 SQL 文件(parse_values_line/parse_sql_values/extract_column_names/merge_sql_dataframes)。 - 文件工具(
file_utils.py):get_encoding()自动检测编码、ensure_dir()目录管理、read_excel_safe()安全读取 Excel。 - 数据缓存(
data_cache.py):DataCache基于 PKL 缓存,load_or_compute()命中缓存或重算,支持强制重算。 - 数据匹配(
data_matcher.py):normalize_code()去除.0后缀、create_match_key()构建匹配键、aggregate_dataframe()聚合、calculate_differences()差异列、classify_matches()分类、filter_by_field()字段筛选。 - Excel 导出(
excel_exporter.py):export_with_classification()按分类拆分到独立 sheet、create_summary_table()汇总表、format_excel_accounting()会计格式、自动处理 Excel 行数上限(EXCEL_MAX_ROWS)。 - 配置驱动(
config.py+config.example.json):Config类与get_config(),支持分层键(如matching.amount_threshold)与配置文件加载。
purchase-three-match-configurable/
├── README.md
├── LICENSE
├── .gitignore
├── __init__.py # 包入口,重导出各子模块(以 ToolKit 名义导入)
├── config.py # Config 类与默认配置
├── config.example.json # 配置示例(复制为 config.json 使用)
├── sql_parser.py # SQL 解析
├── file_utils.py # 编码检测 / 安全读 Excel
├── data_cache.py # PKL 数据缓存
├── data_matcher.py # 匹配键 / 聚合 / 差异 / 分类
├── excel_exporter.py # 分类导出与格式化
├── example_usage.py # 使用示例
├── config_usage_example.py # 配置使用示例
├── USAGE_GUIDE.md # 使用指南
├── CONFIG_REFERENCE.md # 配置参考
├── PROJECT_SUMMARY.md # 项目总结
└── requirements.txt # pandas / numpy / openpyxl / chardet
- Python ≥ 3.8。
- 依赖(见
requirements.txt):pandas>=2.0、numpy>=1.24、openpyxl>=3.1、chardet>=5.0。
git clone https://github.com/Gvmeakiss/purchase-three-match-configurable.git
cd purchase-three-match-configurable
pip install -r requirements.txt- 复制示例配置并修改:
cp config.example.json config.json- 在代码中以
ToolKit名义调用(见__init__.py重导出):
from ToolKit import Config, get_config, sql_parser, data_matcher, excel_exporter
config = Config('config.json') # 或从 get_config() 取默认配置
threshold = config.get('matching.amount_threshold')
output_dir = config.get_output_dir()
# 解析 SQL
expected_cols = ['order_no', 'item_code', 'quantity', 'amount']
df, encoding = sql_parser.parse_sql_values('data.sql', expected_cols)
# 构建匹配键并聚合
df['match_key'] = data_matcher.create_match_key(
df, key_cols=['order_no', 'item_code'],
normalize_func=data_matcher.normalize_code)- 运行内置示例:
python example_usage.py/python config_usage_example.py。
- 配置驱动匹配:
config.example.json定义matching(金额阈值amount_threshold=1.0、数量阈值quantity_threshold=0.01、匹配键分隔符、编码规范化)、aggregation(numeric_agg=sum/text_agg=first)、excel、paths、data_sources、classification、filtering、logging。 - 匹配键与差异:
create_match_key()按key_cols拼接(可选normalize_code去除.0),calculate_differences()生成差异列,classify_matches()依据classification.categories将记录分为「全部数据 / 2.Not test / 1.1完全匹配 / 1.2金额不一致 / 1.3数量不一致 / 1.4均不一致」。 - 分类导出:
export_with_classification()把各分类拆到独立 sheet 并自动创建汇总表,单 sheet 超过max_rows_per_sheet(默认 1,048,575)时自动分 sheet;format_excel_accounting()套用会计规范格式(冻结表头、NA 显示为N/A)。 - 性能与一致性:
DataCache以 PKL 缓存中间结果,删除 PKL 即强制重算;get_encoding()自动检测文件编码避免乱码。
- 输入:
INSERT INTO ... VALUES格式 SQL 文件或 Excel 文件;字段列由调用方在parse_sql_values()/read_excel_safe()中指定。 - 输出:按分类拆分的多 sheet Excel 工作簿 + 汇总表,口径与分类来自
config.json。
以 config.example.json 为准,关键分组:
| 分组 | 关键项 | 默认值 |
|---|---|---|
matching |
amount_threshold / quantity_threshold / normalize_code |
1.0 / 0.01 / true |
aggregation |
numeric_agg / text_agg |
sum / first |
excel |
max_rows_per_sheet / sheet_name_max_length / auto_format / freeze_header |
1048575 / 31 / true / true |
paths |
cache_dir / output_dir / log_dir |
PKL / output / logs |
classification |
categories(6 类) |
见上 |
filtering |
sales_org_field / sales_org_values / case_sensitive |
销售组织 / [1240,1250,1260] / false |
- 数据脱敏:不含真实客户业务数据;示例为脱敏/合成数据(如 NewHope、AQPP 等化名场景)。
- Excel 限制:单 sheet 最大 1,048,575 行,超出自动分 sheet;sheet 名最多 31 字符。
- 编码与缓存:用
get_encoding()自动检测编码;处理大文件注意内存,可删除 PKL 强制重算。
本仓库的可复用资产不是"某一次三单匹配",而是把匹配过程抽象成一条配置驱动的通用数据匹配流水线:口径变了改配置,代码不动。 The reusable asset is a config-driven matching pipeline, not a one-off script.
五阶段流水线 / Five-stage pipeline
| 阶段 | 实现 | 关键约定 |
|---|---|---|
| ① 解析 Parse | sql_parser.parse_sql_values() / extract_column_names() |
chardet 先探测编码再逐行扫描;parse_values_line() 用引号状态机切分,字符串内的逗号不误切、NULL → None、外层单引号剥离;列数不足的行丢弃、超出的列截断到 expected_cols;merge_sql_dataframes() 取列名并集、缺列补 NaN 后纵向拼接 |
| ② 缓存 Cache | data_cache.DataCache.load_or_compute() |
幂等约定:cache_name 命中 PKL 直接复用,未命中才执行 compute_func 并回写;force_reload=True 或删除 PKL 即强制重算,让重跑成本趋近于零 |
| ③ 匹配键 Key | data_matcher.create_match_key() + normalize_code() |
多列按 key_cols 顺序转字符串后拼成单列键;normalize_code() 统一 NaN → '' 并剥掉数值转文本产生的 .0 尾巴(混源数据错配的头号原因);键列不存在即 ValueError 快速失败 |
| ④ 聚合 Aggregate | aggregate_dataframe() |
groupby(key, as_index=False).agg(agg_dict) 把明细压成"一键一行",后续才能做 1:1 三方比对;数值列默认 sum、文本列默认 first;分组列若混进聚合字典直接报错 |
| ⑤ 差异与分类 Diff & Classify | calculate_differences() → classify_matches() |
见下方两条契约 |
差异—分类契约 / Diff & classification contract
- 差异列:
base_cols与compare_cols按位配对,两侧pd.to_numeric(errors='coerce')后相减并round(2),列名固定为差异-<基准列>;任一列缺失则跳过该对而不抛错。 - 分类维度:先按
key_cols是否含空值标出2.Not test(不可测样本),再以abs(差异)与阈值比较产出四个互斥布尔列1.1完全匹配/1.2金额不一致/1.3数量不一致/1.4均不一致,且全部与~2.Not test相与——不可测的行不进入任何匹配结论。 - 容差而非等值:金额阈值
matching.amount_threshold(默认1.0)、数量阈值matching.quantity_threshold(默认0.01),规避浮点与尾差噪音;无法转成数值的差异被填为哨兵值999,即默认判为不匹配(保守口径,宁可多报不可漏报)。
导出契约 / Export contract
export_with_classification():第一个 sheet 恒为「汇总表」,其后每个分类一个 sheet;未显式传categories时按上述布尔列自动拆分,空分类不出 sheet;sheet 名截断至excel.sheet_name_max_length,单分类超excel.max_rows_per_sheet时自动切为_P1/_P2...;空数据集直接跳过导出。create_summary_table():输出「分类 / 记录数 / 金额」并追加「总计」行,金额列由classification.summary_amount_col指定或按列名含「金额 / amount」自动识别——先给结论、再给底稿。format_excel_accounting():表头填色加粗、冻结首行、全表细边框、数字右对齐文本居中、列宽自适应(上限 50 字符),NaN统一显示为N/A,导出即可直接进入复核流程。
为什么配置驱动优于固定脚本 / Why config-driven beats hard-coded scripts
config.py 以「内置默认配置 + JSON 递归合并 + 点号路径读取」(Config.get('matching.amount_threshold'))把阈值、聚合方式、目录、分类名、筛选口径与 Excel 行为全部外置,get_config() 单例保证同一次运行内口径一致。换项目或换数据源时只改 config.json,避免为每个场景 fork 一份脚本而导致口径漂移与逻辑分叉;filter_by_field() 的字段筛选同样把两侧统一转字符串比较,规避"数字型编码 vs 文本型编码"这一经典陷阱。
Thresholds, aggregation, classification names and export behaviour live in config — one engine, many engagements.
- https://github.com/Gvmeakiss/purchase-three-match-toolkit
- https://github.com/Gvmeakiss/purchase-three-match-newhope
- https://github.com/Gvmeakiss/kpmg-da-skills
- https://github.com/Gvmeakiss/u8-inventory-valuation
MIT
Disclaimer: Personal project and personal views. Not affiliated with or endorsed by KPMG or any client.
本仓库为个人项目与个人观点,与任何前/现雇主及客户无关。