T-13 补修:verify_convert_lots/apply 字面造数日期改真库日历链式回推(用户指出硬编码 · 全仓排查留痕)

This commit is contained in:
2026-09-12 12:09:57 +08:00
parent 5f7ad9180b
commit a7d50386b2
3 changed files with 66 additions and 14 deletions
@@ -722,7 +722,7 @@
**① 退役落地(convert_service.py 净删 468 行)**:删除 `convert_fund`(八步编排)+ 其 v1.0 专属依赖 `_plan_and_quote` / `_pick_rate`(死代码)/ `_finalize_from_core` / `_write_main_audit` + 17 个孤儿 import;模块 docstring 重写为 T+1 编排说明,`__all__` 收敛为 6 个在役符号(accept_convert / cancel_convert / t1_t2_dates / compensate_convert / rebuild_convert_response / PROCESSING)。**按实际修订 1 处**:开发计划原文「`rebuild_convert_response` T-13 退役」**不成立**——它是 T-9 查询接口 confirmed 分支的在役读路径,保留在役(`PROCESSING` 仅作 simulate 网关 202 判断常量,同样保留)。`verify_convert_service.py`(v1.0 链路真库脚本)`git rm`,git 历史留档。
**② 测试面改写(约 25 处映射落地)**:`test_convert_service.py` 全量重写(805→约 440 行):v1.0 十组用例退役,替代断言分布 = T-6/T-7/T-8 既有用例 + 补偿组 6 用例保留 + 新增 2(`confirm_mirror_write_failure_keeps_core_confirmed` 镜像失败不回滚 Core / `confirm_rejects_too_many_lots` 批次上限 rejected 零流水);**显式依赖注入踩坑留痕**:confirm_one 的 `core_writer`/`request_repo` 缺省会连真 MySQL(症状分别 = 写侧 FK 1452、rejected 被真库 rowcount=0 误判 skipped)——`_services` helper 全部五仓储钉同一 sqlite 引擎。`test_convert_concurrency.py` 全量重写为 T+1 并发面(v1.0 九用例 → 六用例):50 并发扣减争抢 → **并发 confirm_one 不超卖**(受理段允许 R-3 TOCTOU 超授、确认段 FIFO 哨兵兜底,硬不变量钉在确认后);紧池 80% → **紧池 100%**(受理全 accepted + `confirm_batch` 串行);同键并发单次转换 → **同键并发受理单唯一**(uk_idem + 客户级锁;3b/3c 占位竞态确定性交错用例随 v1.0 占位语义退役,无 T+1 对应物);死锁重试用例改多客户并发 confirm_one;性能探针改两段计量。trade_id 生成走 `format.new_id`(与 `convert_service._new_id` 两个符号),monkeypatch 罩不到 → `_confirm` 显式传 `id_factory`。
**③ 全量 pytest:830 passed / 7 skipped**(基线 849 → 830:v1.0 用例组退役净减,skipped 10 → 7 = 并发文件 stress 标记 7 → 4);全仓真库套件 **9/9 退出码 0**。
**④ 全量复跑抓出并修复过期脚本 1 处(本轮计划外产出)**:`verify_convert_lots.py`(T-10 产物)B/C/D 组仍按 v1.0「金额赎回」传 `amount=` —— T-9 已按 D26/R-6 改**份额申报**(`qty=`),`_maintain_lots` redeem 分支遇 `qty is None` 静默跳过(尽力而为语义)→ B/C FIFO 扣减失效、D 组兜底补建断言红(A 组申购不受影响,故首轮仅此脚本红)。修正为 `qty=` 申报后 **20/20 退出码 0**。「金额申购、份额赎回」行业铁律(2026-09-10 联网查证)的又一处落地。
**④ 全量复跑抓出并修复过期脚本 1 处(本轮计划外产出)**:`verify_convert_lots.py`(T-10 产物)B/C/D 组仍按 v1.0「金额赎回」传 `amount=` —— T-9 已按 D26/R-6 改**份额申报**(`qty=`),`_maintain_lots` redeem 分支遇 `qty is None` 静默跳过(尽力而为语义)→ B/C FIFO 扣减失效、D 组兜底补建断言红(A 组申购不受影响,故首轮仅此脚本红)。修正为 `qty=` 申报后 **20/20 退出码 0**。「金额申购、份额赎回」行业铁律(2026-09-10 联网查证)的又一处落地。**同轮补修(用户指出硬编码问题后全仓排查)**:`verify_convert_lots.py` 与 `verify_convert_apply.py` 的造数日期(原 `NAV_DATE=date(2026,9,4)` 等 9 处字面日期)违反 T-12 立下的「造数日期走真库交易日历、不猜自然日」模式 → 全部改 `previous_biz_day` 链式回推动态锚定(hold_days 自然日相对量保持不变,档位断言不受影响),复跑 **20/20 + 24/24** 退出码 0(动态生效自证:confirmed_at 随回推日漂移、断言同源跟随)。**全仓硬编码排查结论**:生产代码 `app/` 零字面日期、cutoff/SLA/死锁重试全走 settings(B1/R-14 整改完整);T-6~T-12 五个真库脚本本就零字面日期;`verify_convert_seed.py` 的 `AS_OF` 与种子 SQL 强绑定、`calc_convert_demo.py` 的示例日为 PRD 验收 20 强制锚定(改动态反而违约)——均属**有意固定**,保留;sqlite 单测固定日期+自插配套数据为确定性测试惯例,保留。
**⑤ 性能实测(n=59,剔除首个连接冷启动样本;本机 MySQL 8.0.46 / 模拟库 / 单线程顺序 / 每笔 1 份 / 空引擎 hook)**:受理段 P50 37.9 / P95 53.8 / max 57.9 ms(校验+落单+镜像+审计);确认事务(`apply_convert`)P50 10.0 / P95 25.3 / max 25.5 ms;确认全程 P50 41.4 / P95 61.3 / max 74.4 ms(折算+复核+扣减+流水+镜像+审计);**端到端(受理+确认两段)P50 79.2 / P95 110.3 / max 114.7 / mean 83.0 ms** —— PRD §9 第 18 条 **< 2s 硬门禁达成**(余量约 17 倍),数字已回填 PRD §9。
**⑥ 紧池重测(验收 31 载体)**:50 并发受理各 1000、池恰 50000(需求=供给)→ **受理 50/50(100%)**(R-3 占用校验:即使完全串行第 i 笔 available = 50000−(i−1)×1000 ≥ 1000,全 accepted 是确定性结果)→ `confirm_batch` 串行确认 **50/50(100%)**、扣减恰=池、守恒无超卖。**v1.0 同场景实测 80%(40/50,批次碎片争抢)→ T+1 100%,验收 31 设计目标达成**;v1.0 病根(确认前就扣份额 + 批次碎片争抢)由「受理不扣份额 + 确认串行(FR-C23)」结构性消除。
**⑦ 并发确认兜底(S2)**:50 路并发受理各 2000(允许 R-3 占用读数滞后超授,实测 25/50 accepted、其余 InsufficientShares 拦截)→ 并发 confirm_one 争抢 → 扣减 40000 ≤ 池 50000 + 守恒 + 每组恰 2 流水;LotConflict 让路单(退避 3 次耗尽)受理单保持 accepted(占用保持,下轮批处理可再确认,零扣减不碰资金安全)——并发 confirm_one 本身是超纲场景(生产由批处理锁保证串行),本用例证明的是「即使绕过锁,资金安全仍有兜底」。