基金转换 T+1 模型:T-15/T-16/T-17 收官(842 绿 / 真库 9/9 / 转换线第 5 步完成)
T-15 D28 部分成交专项:
- sqlite +1 终态单幂等 skipped 口径裁定(ConfirmConflict 仅限读后状态才变窗口)
- 真库 confirm +3 组 19 项(E2 占用释放双向 / G2 actual_qty=0 → rejected / H2 撤单后确认)→ 102/102
- CONVERT_STRESS Barrier(2) 窄窗争批:恰 1 confirmed + 1 rejected
- 突变实证防御纵深:④可用量复核 / ⑤plan_lots / 事务内哨兵三道防线拆任一道仍收敛 rejected
T-16 D29 资金流中转:
- _insert_cash_flows 全仓唯一写入点(out/redeem 费前 + in/subscribe 净转入,remark=convert:{gid} 同事务)
- test_confirm_writes_no_cash_flow 反转为恰 2 条 + 金额对账(T-7 登记闭环)
- 真库 apply A2 组 + B 组 0 残留 → 30/30;confirm 反转断言 → 103/103
- FK 坑(core_cash_flow.fk_cf_customer 挡客户删除):5 处清理段补删
T-17 D30 share_class + A/C 互转:
- _validate_products 纯新增拦截分支:两端 share_class 非空且不同才查 allow_ac_convert(任一端=1 两向放行)
- 口径裁定 2 条(R-12 字面偏差,待用户追认):NULL 不参与 A/C 判定 / 同类走一般转换规则
- 联网核实(用户铁律):A/C 互转需管理人开通(摩根 2025-11 公告)= 开关真实载体;持有期重新起算与本仓一致
- 种子 ⑥ 段(双开对/双关对照/同类对照);sqlite +4 → 842 passed / 8 skipped;真库 accept H 组 → 43/43
- 突变:分支失效 → closed 红,还原绿
文档:开发计划 T-15~T-17 DoD 全勾 + 执行记录 · 基线 842 · TODO/MEMORY/AGENTS/交接文档 v4.5 终态(第 5 步收官 → 下一步第 6 步集成测试)
This commit is contained in:
@@ -365,3 +365,128 @@ tools 14 —— 全部零回归(2 位产品路径与接入前逐字节一致
|
||||
**T-15**(D28 部分成交专项测试 + 边界补强):用例矩阵(占用被抢 / 部分确认后
|
||||
释放 / 撤单与确认竞态 / `actual_qty=0 → rejected` R-10 补强)+ `CONVERT_STRESS`
|
||||
两单争同一批。入口:开发计划 T-15 节。
|
||||
|
||||
---
|
||||
|
||||
# T-15:D28 部分成交专项测试 + 边界补强(2026-09-12)
|
||||
|
||||
## 一、时序口径裁定(sqlite +1 → 838 passed / 8 skipped)
|
||||
|
||||
`test_confirm_after_cancel_conflicts`:撤单后确认 **不抛 ConfirmConflict**,返回
|
||||
`skipped + current_status='cancelled'` —— `confirm_one` 对**终态单(含
|
||||
cancelled)是幂等 skip**(批处理单笔容错语义),`ConfirmConflict` 仅在「读单
|
||||
之后状态才变」的并发窗口抛(`_confirm_request` 条件 UPDATE 哨兵 rowcount≠1)。
|
||||
断言:零流水零明细 + 受理占用释放。首版误断言抛异常(DID NOT RAISE)即该
|
||||
口径的实证。
|
||||
|
||||
## 二、真库 +3 组 19 项(verify_convert_confirm 83 → 102/102 退出码 0)
|
||||
|
||||
| 组 | 场景 | 关键断言 |
|
||||
|---|---|---|
|
||||
| E2 | 占用释放双向证明 | 确认前灌 15000 受理 → 再受理被 `InsufficientShares` 拦(占用生效)→ 确认吃掉物理份额 → 灌回 15000 再受理 accepted(占用记账不重复扣)→ E2b 置 expired 释放占用收尾 |
|
||||
| G2 | `actual_qty=0` 边界 | 占用全被抢的单确认后 `rejected`(R-10 补强),零流水零明细零持仓变动 |
|
||||
| H2 | 撤单后确认 | `skipped` + `current_status='cancelled'`,零残留 |
|
||||
|
||||
**R-10 语义实测钉死**:部分确认把可用**物理**份额全部吃掉(余量归零),
|
||||
「释放」的是受理占用而非物理份额——E2 组设计时曾误设余量即此坑。
|
||||
|
||||
## 三、CONVERT_STRESS 争批 + 突变验证(防御纵深实证)
|
||||
|
||||
- `test_two_confirms_race_same_lot_first_wins`:池 2 份 + 两单各 1 份,
|
||||
`Barrier(2)` 经 monkeypatch 钉在 `apply_convert` 事务门口,构造「两单都完成
|
||||
④ 可用量复核、⑤ plan_lots 校验」后同时进事务的窄窗口;断言恰 1
|
||||
confirmed + 1 rejected、扣减恰 1 份、恰 1 组 2 条流水、败者落 rejected。
|
||||
- **突变两次不红 = 防御纵深发现**:单独拆事务内扣减哨兵不红(④⑤ 复核拦住)、
|
||||
单独拆 ④ `partial_qty` 不红(⑤ plan_lots 拦住)→ 窄窗 + 同拆哨兵 ×3 全红、
|
||||
还原 ×3 全绿。**三道防线**(④可用量复核 / ⑤plan_lots 校验 / 事务内
|
||||
`remain_qty >= :q` 哨兵)拆任何一道,其余两道仍收敛 rejected——资金安全
|
||||
不依赖单点。
|
||||
|
||||
---
|
||||
|
||||
# T-16:D29 资金流中转建模(2026-09-12)
|
||||
|
||||
## 一、写入点(全仓唯一,反双重归属闭环)
|
||||
|
||||
`convert_core_repository._apply_convert_once` 在 `_insert_trades` 后加
|
||||
`_insert_cash_flows`:2 条 —— `out/redeem` = out_amount(**费前**)+
|
||||
`in/subscribe` = in_amount(净转入),remark = `convert:{gid}`、channel =
|
||||
`online`、occurred_at = traded_at;与流水/明细/持仓/受理置态**同一事务**,
|
||||
幂等由 T-7 确认权哨兵天然保证。R-11「中转」语义 = 确认事务内出+入两条对冲,
|
||||
无中间现金停留;费用不在资金流重复体现(与 core_trade 转出端同口径)。
|
||||
|
||||
## 二、DDL 与测试
|
||||
|
||||
- `tests/_ddl.py`:sqlite 版 `core_cash_flow` + 并入 REQUIRED_CONVERT_TABLES
|
||||
门禁(真库 01-ddl.sql 既有此表 F-14,无 ALTER)。
|
||||
- `test_confirm_writes_no_cash_flow` **反转为**
|
||||
`test_confirm_writes_exactly_two_cash_flows`(T-7 登记的「表建立后改 0 行
|
||||
断言」闭环):恰 2 条 / 两端金额 / remark / 幂等重确认无第 3 条。
|
||||
- `test_convert_core` 两回滚用例补资金流零残留断言。
|
||||
|
||||
## 三、真库(verify_convert_apply 24 → 30 项全绿)+ FK 新坑
|
||||
|
||||
- **A2 组 5 项**:条数恰 2 / `(out,redeem)+(in,subscribe)` 组合 / 两端金额 =
|
||||
req.out_amount·in_amount / 与 core_trade 逐条对账;**B 组 +1**:冲突路径
|
||||
资金流 0 残留。confirm 脚本的「确认不写资金流」断言(T-7 时代登记)同步
|
||||
反转 → **102 → 103 项全绿**(全套终态复跑时暴露;对账项首版 `r[0]` 取
|
||||
dict 行踩 KeyError,`_rows` 返回 mappings 须按列名取)。
|
||||
- **新坑(T-9 FK 坑同款)**:`core_cash_flow.fk_cf_customer` 挡住
|
||||
`core_customer` 删除 —— **共 5 处清理段补删**:verify_apply 与
|
||||
test_convert_integration(T-16 收尾批,integration 13 个 ERROR 定位到此根因)+
|
||||
verify_engine / verify_api / verify_compensate(T-17 收尾全套终态复跑批,
|
||||
库内旧残留引发连环 FK)。删除条件用 remark LIKE `'convert:%'` /
|
||||
`'convert:{_GROUP_LIKE}'` 精准定位,不误删既有种子。
|
||||
- **突变验证**:拆 `_insert_cash_flows` 调用 → 反转用例红(0 ≠ 2);还原 →
|
||||
core+confirm 37 全绿,MUTATION 零残留。
|
||||
|
||||
## 四、下一步
|
||||
|
||||
**T-17**(D30 share_class + A/C 互转):受理校验放行规则(R-12 口径裁定:
|
||||
种子 `share_class` NULL 不参与 A/C 判定走一般转换;两端非空且不同才查
|
||||
`allow_ac_convert`)+ 种子一对 A/C 产品 + 对照用例。入口:开发计划 T-17 节。
|
||||
|
||||
---
|
||||
|
||||
# T-17:D30 share_class + A/C 互转(2026-09-12 · 转换线收官)
|
||||
|
||||
## 一、口径裁定(R-12 字面两处偏差,实现取业务正确口径,登记待用户追认)
|
||||
|
||||
1. **`share_class` NULL 不参与 A/C 判定**:R-12 字面「不满足 → 400」若让 NULL
|
||||
参与比较,会打穿全部既有受理用例(种子产品 share_class 全 NULL)。实现 =
|
||||
**两端非空且不同**才触发 A/C 分支,NULL/同类走一般转换规则。
|
||||
2. **「share_class 相同 → 400」不采纳**:非空同类别跨基金互转(同管理人同 TA)
|
||||
本就走一般转换规则放行,无业务理由禁转;DoD 对照用例改为「同类放行」回归
|
||||
保护(sqlite + 真库 H3 双证据)。
|
||||
|
||||
## 二、实现与核实
|
||||
|
||||
- **实现(纯新增拦截分支)**:`convert_service._validate_products` ta_code 校验
|
||||
后,两端 share_class 非空且不同时查 `allow_ac_convert`——任一端 =1 即两向
|
||||
放行,全关 → `CrossEntityNotSupported`(错误码沿用,message 指明 A/C,任务书
|
||||
改法①)。放行路径零改动,历史产品零回归。
|
||||
- **联网核实**:A/C 互转 = 同一基金不同份额类别转换业务,**需管理人「开通」**
|
||||
(摩根行业睿选 2025-11 公告)= `allow_ac_convert` 真实载体;转换后**持有期
|
||||
重新起算**(公告明写,本仓转入新批次语义一致零改动);转换费 = 转出赎回费 +
|
||||
申购补差费(深证监局,A/C 补差费不在本仓范围)。
|
||||
- **种子 ⑥ 段**(09-seed-org.sql):双开对 110022'A'/005827'C',双关对照
|
||||
110023'A'/003095'C',同类对照 510300'C';其余产品保持 NULL。
|
||||
|
||||
## 三、验证(全量 842 passed / 8 skipped · 真库 43/43)
|
||||
|
||||
- sqlite +4:双开放行 / 单开放行(任一为 1)/ 双关 400(message + 不落单)/
|
||||
同类+NULL 放行回归保护。首跑发现:同用例第二次受理撞**在途占用**(150−100
|
||||
可用 50),改申请 50——受理语义的顺带实证。
|
||||
- 真库 H 组 6 项(accept 37 → 43 退出码 0):H1 双开 accepted / H2 双关 400
|
||||
语义 + 不落单 / H3 同类 `'A'→'A'` accepted。**H3 首跑撞种子 ⑤ 段
|
||||
`min_redeem_qty=100`**(申请 50 被 BelowMinQty 拦)→ 改申请 100:种子产品做
|
||||
载体必须通读既有段配置。
|
||||
- **突变验证**:A/C 分支恒 False → closed 用例红;还原 → 全绿,MUTATION 零残留。
|
||||
- **模拟库近似留痕**:真实「同基金 A/C」按基金合同绑定同组合,模拟库无
|
||||
fund_code 字段,判定以「同管理人同 TA + share_class 非空且不同」近似。
|
||||
|
||||
## 五、下一步
|
||||
|
||||
**T-1~T-17 全部完成,转换线收官**。剩余:全套 9 个真库脚本复跑 + 三任务
|
||||
(T-15/T-16/T-17)提交 + 交接文档/TODO/MEMORY 终态更新;之后按开发计划第 6 步
|
||||
做集成测试。
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
**⚡ 基金转换(convert)线 — 两条子线**:
|
||||
- **(一)v0.x 两阶段实时模型(已结项 2026-09-10)**:PRD **v0.9.3** + 架构 **v1.0.1** + 开发计划 **v1.0**,T-0 ~ T-13 全部完成(第 5+6 步闭环,最终 **739 passed / 10 skipped**,`CONVERT_STRESS=1` 并发 9/9,含死锁重试补丁 `43cf2a1`);**该模型已被下一线取代,其 PRD/架构/开发计划作废,代码保留作过渡基线**。
|
||||
- **(二)下一线 · T+1 受理/确认分离模型(进行中 · 2026-09-11)**:入口 **交接文档.md §B.11**(10 项真实业务拍板)· PRD **v1.1** + 架构 **v2.1** 已整体重写并经独立 AI 联网审查通过(0 阻断级),**已定稿**(2026-09-11 用户批准);**第 4 步开发计划 v2.0 已整体重做**(`docs/项目框架设计/开发计划-基金转换交易.md`,797 行:T-0~T-17 按架构 §15 重排 + §1.4 代码事实核对表 F-1~F-25 + §1.5 实现级裁定 R-1~R-15 + §5 十六条回归面 + §6 验收 31 条 + FR-C29/C30 全映射),**经三轮独立审核闭环(第一轮挑毛病 2 阻断+5 重要+3 可选 / 第二轮验证型 12 项零残留 / 第三轮白纸重审 C1~C5+O1~O4 + 验证型复核 22/22 落位),已定稿(2026-09-11 用户批准)**。**核心认知:不新增冻结列,在途占用由受理单推导;紧池 80% 病根在 T+1 串行确认下自动消失。** 下一步:**第 5 步 todo 开发进行中(T-1~T-14 ✅,**837 passed / 7 skipped** 零失败;真库 verify 套件 **9/9 退出码 0**:accept 37 / confirm 83 / engine 39 / api 89 / compensate 56 / apply 24 / lots 20 / seed 全 PASS / tools 14;T-14 = D27 产品舍入真正接入——`core_ro.get_share_digits` 回退链单点(规则行 → core_product.share_digits → 默认 2)+ 确认段(convert)/申购折算(subscribe)两处接入;**联网核实纠偏:种子「指数基金 4 位」证伪(ETF 联接/指数合同全 2 位)→ 11-seed v1.1 全 2 位**,开口 = PRD §2.5.3 易方达句修订 + rounding_mode=truncate 接入待用户拍板;下一步 T-15 D28 部分成交专项)**。**T-7 关键裁定:强制全转属受理段决策** —— 受理由 `plan_lots` 判定并把 `qty` 收敛为实际全转量 + 受理单 `remark='full_transfer'`,确认段**只继承不重判**(`plan_lots` 不传 `min_hold_qty`);重判会因 T→T+1 可用份额变化(部分成交/他人申赎/产品参数改)得出与受理承诺不一致的结论(擅自扩大客户指令)。**T-8 关键裁定**:引擎时机(R-7)的实现部分在 T-7 已完成,T-8 补的是**守护与验证**(AST 守护唯一调用点 / 受理不触引擎 / 确认链路真引擎正证+反证);⚠️ **T-8 不动去重口径** —— `amount_view` 三处消费方原样不动(属 T-11);`convert_fund` 的阶段 1.5 调用点随 T-9 删函数一并清除(只摘调用会让 v1.0 过渡函数「扣份额但不出预警单」,风控缺失比时机错更危险)。撤单范围:PRD §5.5 原表述与实现(仅 `accepted`)不矛盾、无需回填(第三轮 C1 订正)。已就绪:`fetch_nav.py` / `06-seed-nav.sql`(60 个真实净值日)/ `gen_trade_calendar.py` / `10-seed-trade-calendar.sql`(503 交易日)。
|
||||
- **(二)下一线 · T+1 受理/确认分离模型(进行中 · 2026-09-11)**:入口 **交接文档.md §B.11**(10 项真实业务拍板)· PRD **v1.1** + 架构 **v2.1** 已整体重写并经独立 AI 联网审查通过(0 阻断级),**已定稿**(2026-09-11 用户批准);**第 4 步开发计划 v2.0 已整体重做**(`docs/项目框架设计/开发计划-基金转换交易.md`,797 行:T-0~T-17 按架构 §15 重排 + §1.4 代码事实核对表 F-1~F-25 + §1.5 实现级裁定 R-1~R-15 + §5 十六条回归面 + §6 验收 31 条 + FR-C29/C30 全映射),**经三轮独立审核闭环(第一轮挑毛病 2 阻断+5 重要+3 可选 / 第二轮验证型 12 项零残留 / 第三轮白纸重审 C1~C5+O1~O4 + 验证型复核 22/22 落位),已定稿(2026-09-11 用户批准)**。**核心认知:不新增冻结列,在途占用由受理单推导;紧池 80% 病根在 T+1 串行确认下自动消失。** 下一步:**第 5 步 todo 开发已收官(2026-09-12,T-1~T-17 全部完成,**842 passed / 8 skipped** 零失败;真库 verify 套件 **9/9 退出码 0**:accept 43 / confirm 103 / engine 39 / api 89 / compensate 56 / apply 30 / lots 20 / seed 全 PASS / tools 14。T-15 = D28 部分成交专项(终态单幂等 skipped 口径 + 防御纵深三道防线实证 + Barrier 窄窗争批);T-16 = D29 资金流中转(core_cash_flow 全仓唯一写入点,out/redeem + in/subscribe 各 1 条 remark 带 gid 同事务,5 处清理段 FK 坑修复);T-17 = D30 A/C 互转(share_class 非空且不同才查 allow_ac_convert 开关,任一端=1 两向放行,联网核实 = 摩根 2025-11「开通」公告;口径裁定:NULL 不参与判定、同类走一般规则,待用户追认),下一步 = 第 6 步集成测试)**。**T-7 关键裁定:强制全转属受理段决策** —— 受理由 `plan_lots` 判定并把 `qty` 收敛为实际全转量 + 受理单 `remark='full_transfer'`,确认段**只继承不重判**(`plan_lots` 不传 `min_hold_qty`);重判会因 T→T+1 可用份额变化(部分成交/他人申赎/产品参数改)得出与受理承诺不一致的结论(擅自扩大客户指令)。**T-8 关键裁定**:引擎时机(R-7)的实现部分在 T-7 已完成,T-8 补的是**守护与验证**(AST 守护唯一调用点 / 受理不触引擎 / 确认链路真引擎正证+反证);⚠️ **T-8 不动去重口径** —— `amount_view` 三处消费方原样不动(属 T-11);`convert_fund` 的阶段 1.5 调用点随 T-9 删函数一并清除(只摘调用会让 v1.0 过渡函数「扣份额但不出预警单」,风控缺失比时机错更危险)。撤单范围:PRD §5.5 原表述与实现(仅 `accepted`)不矛盾、无需回填(第三轮 C1 订正)。已就绪:`fetch_nav.py` / `06-seed-nav.sql`(60 个真实净值日)/ `gen_trade_calendar.py` / `10-seed-trade-calendar.sql`(503 交易日)。
|
||||
**仓库地图:**
|
||||
|
||||
| 路径 | 状态 | 说明 |
|
||||
@@ -200,8 +200,8 @@ RBAC 联调账号:scripts/dev/rbac-seed-reference.md
|
||||
2. 改动属于 api / service / tool / repository 哪一层?
|
||||
3. 是否需 customer_id 归属与 JWT RBAC?
|
||||
4. Core 是模拟库只读还是 agent 库读写?
|
||||
5. 如何验证?(`python -m pytest` 全量(当前 **837 passed / 7 skipped**,风控基线 510 / v0.x convert 完结 739)· uvicorn 启动 + /health · SQL / sync 脚本 · 对照 REQUIREMENTS 验收列)
|
||||
6. **当前有哪两条并行线?**(① 风控/架构改进线:**已结项**(510 基线绿、§7.2 七项冒烟 7/7 PASS、`037ce7e` 已核实早已推送);② **基金转换线**:v0.x 实时模型已结项(739 绿,**已被 T+1 模型取代**);**下一线 T+1 受理/确认分离模型进行中(2026-09-11)**:PRD v1.1 / 架构 v2.1 / 开发计划 v2.0 重写完毕并经独立审查收敛,**已定稿**(2026-09-11 用户批准),第 5 步 todo 开发进行中(T-1~T-17;**当前进度 T-1~T-14 ✅,837 绿**))——动代码前先确认自己属于哪条线,别混淆前置条件。
|
||||
5. 如何验证?(`python -m pytest` 全量(当前 **842 passed / 8 skipped**,风控基线 510 / v0.x convert 完结 739)· uvicorn 启动 + /health · SQL / sync 脚本 · 对照 REQUIREMENTS 验收列)
|
||||
6. **当前有哪两条并行线?**(① 风控/架构改进线:**已结项**(510 基线绿、§7.2 七项冒烟 7/7 PASS、`037ce7e` 已核实早已推送);② **基金转换线**:v0.x 实时模型已结项(739 绿,**已被 T+1 模型取代**);**下一线 T+1 受理/确认分离模型:第 5 步 todo 开发已收官(2026-09-12,T-1~T-17 全部完成,**842 绿**;真库 9/9 退出码 0)**)——动代码前先确认自己属于哪条线,别混淆前置条件。
|
||||
7. **远程分支到底还在不在?**(**在**。`git ls-remote --heads origin` 实测 `refs/heads/risk-control-agent` = `fffb78a`。⚠️ **判断远程存亡只能用 `git ls-remote`** —— 2026-09-10 **同日误报两次**,勿再踩。)
|
||||
**本仓特有异常(2026-09-10 深挖确认,别误判为 stale ref)**:`.git/refs/remotes/**` **写入不落盘** ——
|
||||
`git update-ref refs/remotes/origin/X <sha>` 返回 0,但松散引用消失,**且整个 `refs/remotes/origin/` 目录被删**
|
||||
|
||||
+5
-3
@@ -9,7 +9,7 @@
|
||||
|
||||
**⚡ 并行新线 · 基金转换(convert)**—— **两条子线**:
|
||||
- **(一)v0.x 两阶段实时模型(已结项 2026-09-10)**:PRD **v0.9.3** + 架构 **v1.0.1** + 开发计划 **v1.0**,**T-0 ~ T-13 全部完成**(第 5 步 + 第 6 步闭环,**736 passed / 10 skipped**;7 个 convert 真库脚本复跑零回归;压测修复 2 处并发缺陷 + 死锁重试后补 `43cf2a1` 最终 **739 passed / 10 skipped**)。**注:该模型已被下一线取代(按下述),其设计文档作废,代码仍保留作过渡基线**。
|
||||
- **(二)下一线 · T+1 受理/确认分离模型(进行中 · 2026-09-11)**:入口 **交接文档.md §B.11** · PRD **v1.1** + 架构 **v2.1** 均已整体重写并经独立 AI 联网审查通过(0 阻断级),**已定稿**(2026-09-11 用户批准);**第 4 步开发计划 v2.0 已整体重做(`docs/项目框架设计/开发计划-基金转换交易.md`,T-1~T-17 按架构 §15 重排 + 代码事实 F-1~F-25 + 裁定 R-1~R-15 + 16 条回归面 + 验收 31 条 + FR-C29/C30 全映射),经三轮独立审核闭环(挑毛病 / 验证型 / 白纸重审 + 验证型复核 22/22 落位),已定稿(2026-09-11 用户批准)**。**第 5 步 todo 开发已开工(2026-09-11):T-1 ~ T-14 完成(全量 pytest 837 passed / 7 skipped 零失败;真库 verify 套件 9/9 退出码 0:accept 37 / confirm 83 / engine 39 / api 89 / compensate 56 / apply 24 / lots 20 / seed 全 PASS / tools 14,v1.0 链路 verify_convert_service 已随 convert_fund 退役删除),下一步 T-15(D28 部分成交专项 + 边界补强)**。撤单范围:PRD §5.5 原表述与实现(仅 `accepted`)不矛盾、无需回填(第三轮 C1 订正)。
|
||||
- **(二)下一线 · T+1 受理/确认分离模型(进行中 · 2026-09-11)**:入口 **交接文档.md §B.11** · PRD **v1.1** + 架构 **v2.1** 均已整体重写并经独立 AI 联网审查通过(0 阻断级),**已定稿**(2026-09-11 用户批准);**第 4 步开发计划 v2.0 已整体重做(`docs/项目框架设计/开发计划-基金转换交易.md`,T-1~T-17 按架构 §15 重排 + 代码事实 F-1~F-25 + 裁定 R-1~R-15 + 16 条回归面 + 验收 31 条 + FR-C29/C30 全映射),经三轮独立审核闭环(挑毛病 / 验证型 / 白纸重审 + 验证型复核 22/22 落位),已定稿(2026-09-11 用户批准)**。**第 5 步 todo 开发已收官(2026-09-12):T-1 ~ T-17 全部完成(全量 pytest 842 passed / 8 skipped 零失败;真库 verify 套件 9/9 退出码 0:accept 43 / confirm 103 / engine 39 / api 89 / compensate 56 / apply 30 / lots 20 / seed 全 PASS / tools 14,v1.0 链路 verify_convert_service 已随 convert_fund 退役删除),下一步 = 第 6 步集成测试**。撤单范围:PRD §5.5 原表述与实现(仅 `accepted`)不矛盾、无需回填(第三轮 C1 订正)。
|
||||
|
||||
### 基金转换线待办(推荐顺序)
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
- [x] **【T-13 ✅】**(最后一个 · **已完成 2026-09-10**):**全量回归 → 50 并发压测 → 性能实测补录 → PRD §9 第 18 条数字回填**。**DoD 三条**:① 性能实测值**已回填** PRD §9 第 18 条(v0.9.3,替换「预估 < 100ms」);② 50 并发结论(不超卖 / `100/200/400ms` 退避**不够用**)**已写入** `交接文档.md` §B.6.6;③ 实测**未超阈值** → 无需重定阈值(**未修改实测数据**)。**产出**:`tests/test_convert_concurrency.py`(9 条 · `CONVERT_STRESS` 门禁)+ `test_convert_service` +3 + 修复 `insert_placeholder` 三步法 / 幂等判定 `pending`→202;**736 passed / 10 skipped**、`CONVERT_STRESS=1` 并发 **9/9**、重灌双库复跑零回归、4 组突变验证全命中。
|
||||
- [ ] **【待用户裁定】幂等重放响应的数值位数偏差**(T-9 执行期发现,**未顺手改**):首次响应 2 位(`calc` 量化)vs 重放响应 4 位(`core_trade` `DECIMAL(18,4)` 直读)→ `"53456.95"` vs `"53456.9500"`,**数值相等**,违反 PRD「对外一律 2 位」展示契约,属 T-7 `_rebuild_quote` 范畴。集成测试已「比数值不比字符串」并钉住偏差
|
||||
|
||||
### 第 5 步 · T+1 受理/确认分离模型(T-1 ~ T-17 · 进行中)
|
||||
### 第 5 步 · T+1 受理/确认分离模型(T-1 ~ T-17 · ✅ 已收官 2026-09-12)
|
||||
|
||||
- [x] **T-1 DDL + 种子 ✅(2026-09-11)**:`01-ddl.sql` 新建 `core_convert_request`(6 态 ENUM 权威状态机)/ `core_trade_calendar` / `core_share_rule` + `core_product` 补 4 列;`11-seed-share-rule.sql`(42 行);`reset.ps1` 追加 10/11;`settings` 加 `convert_cutoff_time="15:00"` / `convert_confirm_sla_days=2`;`tests/_ddl.py` 三表同步;`verify_convert_seed.py` 加 DoD ⑨⑩⑪(503 交易日 / 42 行 / 6 态 ENUM,真库全 PASS)
|
||||
- [x] **T-2 calc 扩展 ✅**:`calc.py` 加 `product_round` / `redeem_amount` / `partial_qty`(`__all__` 同步);`test_convert_calc.py` +10 用例
|
||||
@@ -41,7 +41,9 @@
|
||||
- [x] **T-12 补偿 + SLA 清理切 T+1 链路 ✅(2026-09-12)**:① `cleanup_pending_convert.py` **整体重写**(扫描对象迁 Core:`list_inflight_before` 扫 `accepted/nav_pending` 且 `requested_at` 严格早于 cutoff → 条件 UPDATE 置 `expired`(S2 不硬删、天然幂等不加锁);SLA 边界 = 当前业务日上推 `convert_confirm_sla_days` 个交易日 00:00(`previous_biz_day` 链式,与 `_window_start` 同口径,日历缺失降级自然日并告警);`--days`/`--dry-run`;**不动镜像**(T-4 契约));② `compensate_convert` **从 v1.0 Deprecated 组救回在役**(详情侧 = Core 三件套前置校验(流水 ≥2 → `confirmed` → 明细非空,任一不满足 `missing` 零写入),补写与确认段第⑧步同口径 `sync_mirror` + `confirmed` 审计 `phase='confirm-compensate'`);③ `convert_request_repository` +`list_inflight_before`。**联网核实**(用户铁律):基金转换「无效申请」真实口径(多家基金公司业务规则:TA T+1 确认、无效资金退回 → `rejected` 映射成立)+ A 股连续休市最长 = 2020 春节 10 自然日(上交所通知,`_MAX_SCAN_DAYS=256` 依据成立)。**⚠️ 真库脚本暴露并修复**:`confirm_one` 第⑦步引擎异常只 log 不留痕(全仓唯一不落痕路径)→ except 补写 `decision='engine_error'` 审计 + `engine_error` 标志贯穿(D17 不阻断保留;test_convert_confirm 断言翻转 is True + 补审计断言)。**验证**:全量 **849 passed / 10 skipped**(846+3 零回归);真库 `verify_convert_compensate.py` **整体重写 T+1 链路 56/56 残留 0**(含幂等重跑;真库修正 1 条口径:`confirmed` 审计真链路恰 2 条 = 确认段 + 补偿各 1);**突变 4 组全被抓还原**(cleanup 窗口反转 5 红 / 前置守卫删除 1 红 / 真库 expired→cancelled 2 红 / 真库 nav_stale=True 1 红)。**勘误**:开发计划原写 `scripts/agent/rebuild_alerts.py` 实际在 `scripts/demo/`(零改动)
|
||||
- [x] **T-13 全量门禁 + convert_fund 退役 ✅(2026-09-12,提交 `3bc84a0`)**:① `convert_fund` 及 v1.0 依赖链净删 468 行(`rebuild_convert_response` 按实际改判**保留在役**:T-9 查询接口读路径);`verify_convert_service.py` git rm;`test_convert_service.py`(805→约 440 行)与 `test_convert_concurrency.py`(v1.0 九用例 → T+1 六用例)全量重写;grep 仅余 4 处注释性历史说明、零调用。② **门禁**:全量 **830 passed / 7 skipped**(849 → 830 为 v1.0 用例组退役净减);CONVERT_STRESS=1 **6/6**;真库 **9/9 退出码 0**。③ **性能实测(PRD §9 第 18 条已回填)**:端到端 P50 79.2 / P95 110.3 / max 114.7 ms —— < 2s 硬门禁余量 17 倍。④ **紧池 100%(验收 31 已回填 PRD)**:50/50 受理 + 50/50 串行确认(v1.0 同场景 80% → 病根结构性消除)。⑤ **计划外产出**:`verify_convert_lots.py` B/C/D 组仍按 v1.0 金额赎回传 `amount=`(T-9 已改份额申报,redeem 分支 `qty is None` 静默跳过)→ 修正为 `qty=` 后 20/20。
|
||||
- [x] **T-14 D27 产品舍入真正接入 + 联网核实纠偏 ✅(2026-09-12)**:**接入面 4 处单点收敛**——`core_ro.get_share_digits`(全仓唯一回退链:规则行 → `core_product.share_digits` → `calc.PLACES`=2)· `calc.in_qty` 加可选 `digits=PLACES`(默认行为逐字节不变,内部走 `product_round`)· 确认段(business_type=`convert`)· 网关申购折算(`subscribe`);redeem 份额申报(D26)无消费方。**边界裁定**:只接落库侧(DoD 即边界),展示维持 `q(D2)`(库内 DECIMAL(18,4) 本高于展示位,2 位产品零漂移)。**联网核实(用户铁律 · 本轮最重要产出)**:主流 2 位 ✅(民生加银/富时/新华/泰康)· **位数按各基金合同配置 = 行业真实做法** ✅(南方 2012 转换调整公告)· 「舍去 TRUNCATE」型真实存在 ✅(南方利众/宝元、邮储、华宝、申万菱信、东方)· 货基 2 位 ✅(中银/融通/鑫元)· **「指数基金份额 4 位」证伪** ❌(易方达沪深300非银 ETF 合同、汇添富深证300/华夏恒生/科创板 ETF 联接全 2 位;易方达创业板新能源 ETF 联接转换公告明写「转入份额保留两位」)→ **`11-seed-share-rule.sql` v1.1:3 产品 9 行 4→2 + 头注重写为核实出处**;「易方达 ETF 场外整数位」⚠️ 无证据且有反证(真实整数位载体 = 场内渠道,LOF 场内先 2 位再截位整数)。**测试 +7(830→837 零回归)**:calc 层 2 + confirm 段 3(回退链三层各 1,第一层带 business_type 干扰行)+ 申购段 2;4 位种子行 = D27 机制验证载体(docstring 钉明)。**突变 2 组**(confirm/gateway digits 改固定 2 → 各 2 红,还原零残留);真库 **9/9 退出码 0**(seed 重灌 42 行全 2 位生效)。**开口 2 项待用户拍板**:① PRD §2.5.3「易方达 ETF 场外取整数位」修订;② `rounding_mode=truncate` 消费接入(真实存在、T-14 范围外)。
|
||||
- [ ] T-15 ~ T-17(D28 部分成交专项 / D29 资金流中转 / D30 A/C 互转)
|
||||
- [x] **T-15 D28 部分成交专项测试 + 边界补强 ✅(2026-09-12)**:sqlite +1 `test_confirm_after_cancel_conflicts`(**时序口径裁定**:`confirm_one` 对终态单(含 cancelled)= 幂等 `skipped` + current_status 回显,`ConfirmConflict` 仅限「读单后状态才变」窗口);真库 verify_confirm +3 组 19 项(E2 占用释放双向证明 / G2 `actual_qty=0` → `rejected` 零残留 / H2 撤单后确认)→ **102/102**;R-10 实测钉死 = 部分确认吃掉全部物理可用、「释放」的是受理占用。**CONVERT_STRESS 争批**:`Barrier(2)` 钉 `apply_convert` 事务门口构造「两单都完成 ④⑤ 读」窄窗,断言恰 1 confirmed + 1 rejected。**突变 = 防御纵深实证**:单独拆事务内哨兵不红(④⑤ 拦)、单独拆 ④ partial_qty 不红(⑤ plan_lots 拦)→ 窄窗 + 同拆哨兵 ×3 红、还原 ×3 绿——④可用量复核 / ⑤plan_lots / 事务内哨兵三道防线拆任何一道仍收敛 rejected。全量 **838 passed / 8 skipped**。
|
||||
- [x] **T-16 D29 资金流中转建模 ✅(2026-09-12)**:`_apply_convert_once` 在 `_insert_trades` 后加 `_insert_cash_flows` —— **全仓唯一写入点**,2 条 `out/redeem`=out_amount(费前)+ `in/subscribe`=in_amount(净转入),remark=`convert:{gid}`、channel=online,与流水/明细/持仓/置态同事务(幂等由 T-7 哨兵保证);`tests/_ddl.py` 补 sqlite 表 + 门禁;`test_confirm_writes_no_cash_flow` **反转为** `test_confirm_writes_exactly_two_cash_flows`(T-7 登记「表建立后改 0 行断言」闭环);真库 apply **A2 组 5 项 + B 组冲突 0 残留 → 24 → 30 项**、confirm「不写资金流」断言反转 → **103 项**。**⚠️ FK 新坑(T-9 同款)**:`core_cash_flow.fk_cf_customer` 挡客户删除 → **5 处清理段补删**(verify_apply / test_convert_integration / verify_engine / verify_api / verify_compensate,remark LIKE 精准定位)。**突变**:拆写入调用 → 反转用例红,还原全绿。
|
||||
- [x] **T-17 D30 share_class + A/C 互转 ✅(2026-09-12 · 转换线收官)**:`_validate_products` ta_code 后**纯新增拦截分支**——两端 share_class 非空且不同 = A/C 场景,任一端 `allow_ac_convert=1` 两向放行、全关 → `CrossEntityNotSupported`(错误码沿用,message 指明 A/C)。**口径裁定 2 条(R-12 字面偏差,待用户追认)**:① NULL 不参与 A/C 判定(字面会打穿全部既有受理用例);② 「share_class 相同 → 400」不采纳(同类别跨基金走一般转换规则)。**联网核实**:A/C 互转需管理人「开通」(摩根行业睿选 2025-11 公告)= 开关真实载体;转换后持有期重新起算(本仓转入新批次一致零改动);转换费 = 赎回费 + 申购补差(本仓范围外)。**种子 ⑥ 段**(09-seed-org.sql:双开对/双关对照/同类对照,其余 NULL 零回归)。**测试 +4(838→842)**:双开/单开/双关 400/同类+NULL 回归保护;真库 accept **H 组 6 项 → 43/43**(H3 首跑撞种子 min_redeem_qty=100 教训:种子产品做载体须通读既有段);**突变**:分支恒 False → closed 红,还原绿。模拟库近似留痕:无 fund_code,判定 = 同管理人同 TA + share_class 非空且不同。
|
||||
|
||||
> **第 0~2 批结果(2026-09-10)**:基线 **510 passed** → 批 0 后 **516 passed / 3 skipped**(+3 T-0 用例 +3 T-0b 引擎用例)→ 批 1(T-1)后**仍 516 passed / 3 skipped**(只加表与种子,未加用例 → **零回归**);**T-1 数据层断言 8/8 PASS**(含新增断言 ⑧:费率档 ↔ `product_type` 匹配,越档即 FAIL)→ 批 2(T-2 + T-2b)后 **609 passed / 3 skipped**(**+93 纯函数用例**,零回归);T-2b 实算脚本 15/15 与 PRD §5.3 一致(退出码 0)。
|
||||
> ⚠️ **以下四条引用块均为 v0.x 两阶段实时线记录,其 T 编号与新线(T-1~T-17)无关、勿混**:新线 T-11 亦涉 `amount_view`,与 v0.x T-11「提升为公开函数」是两回事。
|
||||
|
||||
Reference in New Issue
Block a user