首页 / 文章

V Vanity
先玩一下
这篇讲匹配顺序为什么不能反;想亲手切换“先配对”和“先比金额”,打开 支付对账实验。同一批流水会跑出两套差异清单。

1对账不是“找不同”

渠道账单和我方流水对不上,是支付系统里每天都会发生的事。真正的工作量不在“发现对不上”,而在三件事:

这个项目做的是后端闭环:账单入库 → 分层确定性匹配 → 差异带归因 → 工单状态机推到结案,外加渠道回调的验签与防重放。单机一条命令能跑通全流程。

2为什么先配对、后比金额

假设本地有一笔 100 元的单,渠道账单是 99 元。如果先按“金额相等”匹配,这一对根本配不上,于是变成“本地单边 + 渠道单边”两条差异;原本只是一笔金额不符,现在要两个人分别查。误报直接翻倍。

正确的顺序是:先用业务键确认“这两笔是同一笔”,再在配对之后判金额、币种、手续费、状态。先配对后判定,一笔金额不符只会产出一条 AMOUNT_MISMATCH,主体明确,下一步也明确。

反例的代价
在实验里把策略切成“先比金额”,你会看到:差异条数不一定变少,但类型和下一步错了——金额不符被拆成两条单边,跨日清算被当成普通配对,重复出账被当成普通单边。

3L1 / L2 / L3 的分层

层顺序本身就是设计,不能调:

层键说明
L1a渠道 + 商户订单号 + 方向严格键,命中即配对
L1b渠道 + 渠道流水号账单里的商户单号被截断时,靠回调回填的流水号救回
L1c退款单号 / 原单号 + 等额退款行带的是原单号,L1a 配不上它
L2a渠道 + 方向 + 金额 + 分钟归一只在两侧残差集上跑
L2b渠道 + 方向 + 金额 + |Δt| ≤ 90s时间窗口更窄,仍要求唯一
L3更晚账单文件里的严格键跨日清算,报 DATE_SLIP

两个容易写错的细节:

L3 用清算日而不是时间差判跨日:钱晚一天到账时交易时间通常不变,比 Δt 只会漏。那条行由本轮占走,它自己那天不会再报一次。

4差异归因:下一步找谁

匹配层回答“这是什么差错”,归因层回答“那接下来找谁”。11 类差异各有稳定的处置码,例如:

差异处置码下一步
金额不符RC-AMOUNT-DIVERGED核对本币应结金额与红包/优惠拆分口径,差额方向以账单为准
账单缺行RC-BILL-ROW-MISSING向渠道索取该清算日全量账单并核对清算日历
跨日清算RC-SETTLEMENT-DATE-SLIP核对渠道清算日历,通常无需资金处置
重复出账RC-DUPLICATE-SETTLEMENT核对是否重复清算,要渠道出账凭证
证据不足RC-EVIDENCE-INSUFFICIENT补足唯一标识后再配,不许按金额硬猜

归因是规则表,不是模型:11 类差错的下一步是稳定的制度,不是推理。接缝留给以后真要按证据分支的时候;闸门不认来源,只认结论是否落在词表内、note 是否为空话、severity 是否在档位里。任一条不满足就整条丢掉,按“无归因”落库——带着一个查不到来源的结论落库,比没有结论更危险。

5用注入的差错做裁判

回归不能靠“跑一遍看起来对”。项目用 scripts/gen_recon_corpus.py 造 30 天 × 2 渠道的语料,10 类差错各注入 20 条,另留一个整日不注入做对照。golden 测试拿引擎结果与已知清单比:

injected=200  found=200  recall=1.0000  fp=0  fn=0

命中分布也是断言不是打印:该有命中的 L1a/L1b/L3 必须 > 0,这份语料里没有出场机会的 L1c/L2a/L2b 必须 == 0,六层相加必须等于配对数。同 seed 两次运行 manifest 字节相同——这条既是可复现声明,也是脚本自己的自检。

6实测与边界

324
passed(2026-10-23 复跑)
57–60s
本机全套墙钟区间
11
类差异
14
段 curl 闭环

能跑通的是本地闭环:demo_seed.py 播种一天真格式数据,docs/demo.sh 用 curl 从 0 到 13 共 14 段走完“上传账单 → 匹配 → 差异 → 工单 → 结案 → 回调验签/防重放”。数据是合成的,容器与格式取自公开渠道文档。

明确不做的:

这个项目最值得讲的是顺序和判据:先配对后判定、双向唯一、跨日用清算日、差异必须带下一步。代码量不是重点,能不能把“宁可不猜”写成可执行的约束才是。

想亲手看两种策略的差异,去 支付对账实验;完整边界在项目的 docs/single-node-limitations.md。