699 lines
48 KiB
Markdown
699 lines
48 KiB
Markdown
# 2026-09-12 工作日志
|
||
|
||
> 本日:基金转换线(T+1 受理/确认分离模型)**第 5 步 T-11、T-12、T-13 完成** —— T-11 core_tools 汇总去重 + 持仓过滤(确认段场景补用例,生产代码零改动);T-12 补偿 + SLA 清理切 T+1 链路 + **confirm_one 引擎异常留痕修复**(真库脚本暴露)。**用户新增铁律:不确定的外部真实业务事实必须联网核实,禁止凭训练记忆假设。**
|
||
|
||
---
|
||
|
||
## 一、接手与状态核对
|
||
|
||
读入:项目根 `交接文档.md`(v4.0,§0 + §B.11)→ 开发计划 v2.0 §1.4 / §1.5 / §5 / T-11 节 →
|
||
现状代码(`core_tools.py` / `rules.py` / `core_ro.py`)→ 既有测试(`test_core_tools.py` / `test_concentration_c4.py` / `test_convert_confirm.py`)。
|
||
|
||
**基线复核**:全量 `pytest -q`(系统 Python 3.13.14)沿用户上次实测 **844 passed / 10 skipped**(当日收尾实测 846,见下)。
|
||
|
||
## 二、T-11 · core_tools 汇总去重 + 持仓过滤 ✅
|
||
|
||
### 任务定位(与 v0.x T-11 的区别)
|
||
|
||
开发计划 v2.0 T-11 的改法两条都是「已实现 → 确认不回归 → 补用例」:
|
||
|
||
- 口径三处同调(F-10)与 `list_holdings` 的 SQL 层 `qty > 0` 过滤**均已就位**
|
||
(v0.x T-11 产物:`_amount_view` 提升公开 + SQL 去重 R-d + 归零行过滤);
|
||
- 本轮实质增量 = **把既有「直接插 SQL 模拟流水」的口径用例升级为 T+1 真实链路场景**
|
||
(回归面 R-回归 8「保留 + 补用例」/ R-回归 13「保留断言 + 补确认段场景」)——
|
||
T+1 模型下流水与持仓**只在确认段产生**(受理段零写入),直接插 SQL 的既有用例覆盖不了这一点。
|
||
|
||
### 落地(仅 `tests/test_core_tools.py` +2,生产代码零改动)
|
||
|
||
新增 `t1_env` fixture(`seed_suitability_matrix` + 最小受理/确认种子:C3 客户、转出 R2 债基
|
||
1 批 30000 份 + 持仓 30000、转入 R4 股基、两端 T 日净值 1.3604/1.9194、5 档费率、60 天日历)
|
||
+ `_quiet_th()`(全阈值推高让引擎静默,不依赖 settings 与 conftest autouse 隔离):
|
||
|
||
1. `test_confirm_then_sum_amount_counts_once`(验收 11 · 确认段场景):受理全转 30000 →
|
||
`confirm_one`(T+1=2026-09-07)落两条同 gid 流水 → 同窗口插一笔无组普通赎回 100000 →
|
||
`query_recent_trades`:`sum_amount` = 40812.00 + 100000 = **140812.00**(转入端不计)、
|
||
`total_count` = 3(明细全量)、`sum_trades_on_date(T+1)` 跨口径一致。
|
||
2. `test_confirm_full_transfer_holding_zero_row_excluded`(F-12 / R-回归 13 · 确认段场景):
|
||
先直查 SQL 证明转出端 `core_holding.qty=0` 行**仍在库**(台账留痕,D 决策),
|
||
再断言 `list_holdings` / `query_holdings` 只剩转入端、qty 与 `res["in_qty"]` 逐字节一致。
|
||
|
||
既有 3 条口径级用例与 `test_concentration_profile_filters_zero_qty`(集中度读取方)**保留不动**。
|
||
|
||
### 执行期裁定 2 条(留痕)
|
||
|
||
1. **转出端流水 `amount` = 费前转出金额**(40812.00 = 30000×1.3604,与 PRD §5.3.2 示例
|
||
68020.00 同语义;赎回费 204.06 单列 `redeem_fee` 字段)—— 初版锚点误按「费后 40607.94」
|
||
断言,真跑第一轮即抓出(`assert '40812.00' == '40607.94'`),已在用例注释钉死口径。
|
||
2. **`t1_env` 必须调 `_ddl.seed_suitability_matrix`**:C×R 矩阵是 `check_suitability` 的 L0
|
||
权威,裸 `create_sqlite_engine()` 缺矩阵 → 受理段全量 `forbidden`(`SUIT_RISK_MISMATCH`),
|
||
症状是 `confirm_one` 返回 `missing`(受理单根本没落)。与 conftest `sqlite_engine` fixture
|
||
同源(自检第 13 问:同一种子不得有两份实现)。
|
||
|
||
### 验证
|
||
|
||
- `pytest tests/test_core_tools.py` **5/5** → 全量 **846 passed / 10 skipped**(基线 844 + 2,零回归);
|
||
- **突变 2 组防假绿**(均精准命中后还原,`git status` 仅测试文件改动、无 MUTATION 残留):
|
||
① 去掉 `core_ro.list_holdings` 的 `AND h.qty > 0` → **精准 3 红**
|
||
(新确认段用例 + 既有 `test_query_holdings_excludes_zero_qty` + `test_concentration_profile_filters_zero_qty` 的跨口径一致性断言);
|
||
② `core_tools.query_recent_trades` 绕开 `amount_view` 直加全量 → **精准 3 红**
|
||
(新确认段用例 + 既有不翻倍用例 + 跨口径一致性断言);
|
||
- 真库 `verify_convert_tools.py` 复跑 **14/14、退出码 0、零残留**(v0.x 口径真库证据零回归)。
|
||
|
||
## 三、文档回写
|
||
|
||
开发计划 v2.0 T-11 节(DoD 全勾 + 执行记录)· `交接文档.md`(v4.0 → **v4.1**:头部版本块 /
|
||
§0 导航表 / §B.11 状态块 + T-11 完成条目)· `docs/memory/TODO.md`(进度行 + T-11 勾选)·
|
||
`docs/memory/MEMORY.md`(3 处进度)· `AGENTS.md`(进度行)· 本日志。
|
||
|
||
## 四、下一步
|
||
|
||
**T-12 补偿脚本改写**(R-14):`cleanup_pending_convert.py` 扫描对象迁 `core_convert_request`
|
||
(accepted/nav_pending 超 2 交易日 → expired)+ `rebuild_alerts.py` 凭 Core 侧数据补写。
|
||
入口:开发计划 T-12 节。
|
||
|
||
|
||
---
|
||
|
||
# T-12 执行记录(补偿 + SLA 清理切 T+1 链路 · 2026-09-12)
|
||
|
||
## 一、改动面(三处)
|
||
|
||
1. **`scripts/agent/cleanup_pending_convert.py` 整体重写**(R-14):扫描对象迁 Core——
|
||
`ConvertRequestRepository.list_inflight_before` 扫 `accepted/nav_pending` 且
|
||
`requested_at` **严格早于** cutoff → 逐单 `transition_status` 条件 UPDATE 置 `expired`
|
||
(S2 不硬删;并发双跑天然幂等,不加锁);SLA 边界 `_slab_cutoff` = 当前业务日
|
||
(今天开市取今天否则 `previous_biz_day` 回退)上推 `convert_confirm_sla_days` 个
|
||
**交易日** 00:00,与 `confirm_batch._window_start` 同口径,日历数据缺失降级自然日并
|
||
告警(宁可少清、不可误清);`--days` / `--dry-run` 参数、退出码 0/1、
|
||
**不动 agent 镜像**(T-4 契约:镜像不推 cancelled/expired)。
|
||
2. **`compensate_convert`(convert_service)从 `__all__` 的 v1.0 Deprecated 组救回在役**:
|
||
详情侧数据源 = **Core 三件套前置校验**(流水 ≥2 条 → 受理单 `status=confirmed` →
|
||
计费明细非空,任一不满足 `state=missing` **零写入不硬补**);补写与确认段第⑧步
|
||
同口径(`sync_mirror(completed)` 聚合口径 nav=T 日净值 / nav_date=受理日 /
|
||
fee=Σ逐批 / hold_days=min/max)+ `decision='confirmed'` 审计 `phase='confirm-compensate'`
|
||
+ `engine_error` 如实标记;预警侧幂等锚点不变(转出端 `out_trade_id` +
|
||
`has_engine_error_audit` 中间态「人工核对」提示)。`convert_fund` /
|
||
`rebuild_convert_response` 仍留 Deprecated(T-13 退役)。
|
||
3. **`convert_request_repository` +`list_inflight_before`**(T-12 专用捞单,复用 R-2
|
||
条件 UPDATE 守卫,不复制 SQL——自检第 13 问)。
|
||
|
||
**勘误**:开发计划 T-12 节原写 `scripts/agent/rebuild_alerts.py`,实际在 `scripts/demo/`
|
||
(零改动——薄封装委托 compensate_convert,签名未变)。
|
||
|
||
## 二、联网核实(用户铁律 2026-09-12)
|
||
|
||
> 用户原话:「你不确定的都要联网查找,这个项目要贴近真实项目,这是铁律,比如你刚才说的过年假期问题。」
|
||
|
||
1. **「基金转换申请失效」真实口径**(多家基金公司《开放式基金业务规则》:
|
||
华安 / 富国 / 嘉实 / 苏新 / 太平 / 德邦 / 民生加银):转出不可赎回或转入不可申购 →
|
||
申请无效;账户/份额冻结 → 无效;TA **T+1 确认、T+2 可查**;无效申请资金退回原账户;
|
||
太平基金:转换转出参照巨额赎回规则。**映射本项目**:`rejected`(T+1 复核不通过)=
|
||
真实「无效申请」,`expired` 定位为运维兜底(释放占用 = 份额退回效果)——设计贴近真实 ✓。
|
||
2. **A 股连续休市历史最长 = 2020 年春节 10 个自然日**(国务院办公厅延长春节假期;
|
||
上交所《关于2020年春节延长休市相关业务衔接安排的通知》:休市延至 2 月 2 日、
|
||
2 月 3 日恢复开市)——`trading_calendar._MAX_SCAN_DAYS=256` 的量级依据成立;
|
||
结论留痕进 verify 脚本头注释。**WebSearch 三次超时后改换关键词命中**;
|
||
verify 造数的受理日 / SLA 边界全部改 `previous_biz_day` 链式走**真库交易日历**
|
||
(对任意假期安排鲁棒,不猜自然日)。
|
||
|
||
## 三、⚠️ 真库脚本暴露并修复:confirm_one 引擎异常不留痕(本轮最重要产出)
|
||
|
||
**问题**:`confirm_one` 第⑦步引擎异常只 `logger.exception` 不落审计——
|
||
`engine_result=None` → confirmed 审计 / return dict 的 `engine_error=False`。
|
||
**全仓唯一不落痕路径**(v1.0 阶段 1.5 落 `decision='engine_error'` 审计
|
||
convert_service.py:1013、trade_gateway 落 `risk_engine_error`),且补偿侧
|
||
`has_engine_error_audit` 的「人工核对」保护对 T+1 主链路**永远失效**
|
||
(单测 `_prepare_failed_confirm_t1` 是手插审计造数,看不见这个洞)。
|
||
|
||
**修复**(confirm_service.py 第⑦步 except):补写 `decision='engine_error'` 审计
|
||
(summary 带 phase/accept_date/confirm_date/两流水号)+ `engine_error` 标志贯穿
|
||
confirmed 审计与 return dict;**D17「不阻断」语义保留**(测试注释钉死
|
||
「不阻断但留痕 + 标记」);`test_convert_confirm.py::test_engine_exception_...`
|
||
断言翻转 `is False → is True` + 补审计计数断言(audit_log 无 convert_group_id 列,
|
||
按 `input_summary LIKE %gid%` 查)。
|
||
|
||
## 四、测试与真库验证
|
||
|
||
- `tests/test_convert_service.py`:补偿用例 4 条改 T+1 断言 + 新增 2
|
||
(`missing_when_request_not_confirmed` / `inserts_mirror_when_row_absent`——
|
||
造数注意:`insert_audit_log` 需补齐 risk_score/handler_* 四个 None 键、无 created_at);
|
||
- `tests/test_demo_scripts.py`:cleanup 段重写(`_seed_calendar_allopen` 每天开市走
|
||
真实路径不测降级 + `_t12_cleanup` 新签名)+ 新增
|
||
`test_cleanup_window_matches_confirm_batch_window`(行为化等价:窗口下界日
|
||
confirm_batch 能捞到且 cleanup 不清;前一交易日两边都排除);
|
||
**方向教训:days 越大 → cutoff 越早 → 候选越少**(首版断言写反被自查抓出);
|
||
- `tests/test_convert_concurrency.py`:并发补偿改 T+1 造数(core 库插 confirmed
|
||
受理单 + agent 库 UPDATE 镜像 failed——两库引擎别用混,pymysql 1146)+
|
||
真库清场补 `core_convert_request` 删除(按 customer 与按 product 两路、先于
|
||
product 删——**FK 1451**);
|
||
- 全量 **849 passed / 10 skipped**(846 + 3,零回归);
|
||
- 真库 `scripts/dev/verify_convert_compensate.py` **整体重写为 T+1 链路**:
|
||
受理(真实 `resolve_accept_date` 定 T、`next_biz_day` 定 T1,脚本对任意运行日稳)
|
||
→ `confirm_one`(patch sync_mirror + engine_hook=boom,第⑦⑧步双失败真实落痕)
|
||
→ Core 三件套对表 → 补偿 rebuilt / 复跑 skipped + 人工核对 / 真 JSON 列 LIKE /
|
||
cleanup(ENUM 值域 / dry-run / 窗口同口径对表 `_window_start` / 幂等 / S2 / 镜像不推 /
|
||
NEW 恒不进候选)→ 残留清零,**56/56 退出码 0、残留 0**(含幂等重跑);
|
||
**真库断言口径修正 1 条**:`decision='confirmed'` 审计真链路下**恰 2 条**
|
||
(确认段 `phase='confirm'` + 补偿 `phase='confirm-compensate'` 各 1、各司其职)——
|
||
v1.0「主审计 1 条」是单测造数(未跑 confirm_one)的盲区,真库端到端才验得出。
|
||
|
||
## 五、突变验证 4 组(全部被抓后还原)
|
||
|
||
| # | 突变 | 结果 |
|
||
|---|---|---|
|
||
| 1 | cleanup `_slab_cutoff` 回推方向反转(previous→next_biz_day) | 单测 **5 红**(含窗口等价用例) |
|
||
| 2 | compensate 前置 `confirmed` 守卫删除(只留 request_row is None) | 单测 **精准 1 红** |
|
||
| 3 | cleanup 目标状态 expired→cancelled | 真库 **2 红**(ENUM/状态断言) |
|
||
| 4 | 补偿镜像 `nav_stale=True` | 真库 **1 红**(nav_stale 断言) |
|
||
|
||
## 六、文档回写
|
||
|
||
开发计划 v2.0 T-12 节(DoD 4/4 全勾 + 执行记录)· `交接文档.md`(v4.1 → **v4.2**)·
|
||
`docs/memory/TODO.md`(进度行 + T-12 勾选)· `docs/memory/MEMORY.md`(3 处进度)·
|
||
`AGENTS.md`(进度行)· 本日志。
|
||
|
||
## 七、下一步
|
||
|
||
**T-13**(全量回归 + 集成 + 并发 + 性能补录 + **`convert_fund` 退役**,用户 2026-09-11
|
||
裁定并入):全量 pytest 基线回填本文件头 · 7+2 真库 verify 套件退出码 0 ·
|
||
CONVERT_STRESS 并发 · 性能实测(端到端 < 2s 硬门禁)· 紧池成功率重测 ·
|
||
`convert_fund` / `rebuild_convert_response` 删除 + v1.0 依赖链
|
||
(约 25 处测试 + `verify_convert_service.py`)改写。
|
||
|
||
---
|
||
|
||
# T-13:全量门禁 + `convert_fund` 退役(2026-09-12 下午)
|
||
|
||
## 一、退役落地(convert_service.py 净删 468 行)
|
||
|
||
- 删除 `convert_fund`(v1.0 两阶段实时八步编排)+ 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)。
|
||
- **按实际修订**:开发计划原「`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 零流水 remain==200)。
|
||
- **显式依赖注入踩坑(留痕)**:`confirm_one` 的 `core_writer` / `request_repo`
|
||
缺省构造会**连真 MySQL** —— 症状一:写侧 FK 1452(事务整体回滚,真库零污染);
|
||
症状二:`rrepo.reject` 条件 UPDATE 在真库 rowcount=0 → rejected 被误判 skipped。
|
||
测试 helper `_services` 必须把五个仓储全部钉在同一 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(本轮实测内部重试吸收,外部退避 0 次);
|
||
- 性能探针 → 受理/确认/端到端三段计量。
|
||
- **monkeypatch 边界(留痕)**:`confirm_one` 的 trade_id 走 `format.new_id`
|
||
(与 `convert_service._new_id` 是两个符号),patch 罩不到 → `_confirm` /
|
||
`confirm_batch` 显式传 `id_factory=_new_id`。
|
||
- **并发语义裁定(S2)**:`LotConflict`(退避耗尽)= 并发 confirm_one(超纲场景,
|
||
生产由批处理锁保证串行 FR-C23)下的**零扣减让路终态**,受理单保持 accepted
|
||
占用保持、下轮批处理可再确认 —— 与 v1.0 用例 1 允许 LotConflict 同口径,
|
||
资金安全不变量独立成立。
|
||
|
||
## 三、门禁结果(六条 DoD 全勾)
|
||
|
||
| DoD | 结果 |
|
||
|---|---|
|
||
| 全量 pytest | **830 passed / 7 skipped** 退出码 0(849 → 830 = v1.0 用例组退役净减;skip 10 → 7 = 并发 stress 标记 7 → 4) |
|
||
| 真库 verify 套件 | **9/9 退出码 0**:accept 37 / confirm 83 / engine 39 / api 89 / compensate 56 / apply 24 / lots 20 / seed 全 PASS / tools 14 |
|
||
| CONVERT_STRESS=1 | **6/6** 退出码 0 |
|
||
| 性能(< 2s 硬门禁) | **端到端 P50 79.2 / P95 110.3 / max 114.7 / mean 83.0 ms**,余量约 17 倍(回填 PRD §9 第 18 条) |
|
||
| 紧池(验收 31) | **受理 50/50 + 串行确认 50/50 = 100%**、扣减恰=池守恒(v1.0 同场景 80%;回填 PRD §9 第 31 条) |
|
||
| convert_fund 残留 | grep 仅 4 处注释性历史说明、**零调用**;redeem 传 `amount=` 过期调用全仓零残留 |
|
||
|
||
## 四、计划外产出:全量复跑抓出过期脚本 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**。
|
||
「金额申购、份额赎回」行业铁律(2026-09-10 联网查证)的又一处落地。
|
||
|
||
## 五、性能实测口径(载体 `test_performance_probe_accept_confirm_end_to_end`)
|
||
|
||
n=59(n=60 剔除首个连接池冷启动样本)· 本机 MySQL 8.0.46 / 模拟库 / 单线程顺序 /
|
||
每笔 1 份 / 空引擎 hook:受理段 P50 37.9 / P95 53.8 / max 57.9;
|
||
确认事务(`apply_convert`)P50 10.0 / P95 25.3 / max 25.5;
|
||
确认全程 P50 41.4 / P95 61.3 / max 74.4;端到端 = 受理 + 确认两段之和。
|
||
|
||
## 五b、用户指出硬编码问题 → 全仓排查 + 补修(当日傍晚)
|
||
|
||
**用户批评**:「硬编码是个很低级的错误了,怎么你还会犯呢?再查一下还有没有。」——
|
||
指 `verify_convert_lots.py` 的字面造数日期(`NAV_DATE=date(2026,9,4)` 等)。T-12 已立
|
||
「verify 造数的受理日/SLA 边界全部改 `previous_biz_day` 链式走真库交易日历、不猜自然日」
|
||
的模式,本轮修 lots 脚本时只修了 `amount=`→`qty=`,**没有把同类脚本的日期硬编码一并清掉** ——
|
||
同一类错误在脚本层复发,教训:**修 A 类问题时必须同 grep 一遍全仓的 A 类模式**。
|
||
|
||
**补修**:`verify_convert_lots.py` + `verify_convert_apply.py` 共 9 处字面日期全部改
|
||
`previous_biz_day` 链式回推动态锚定(`_init_dates(admin)` 在 main 开头按真库日历算:
|
||
TRADE_DATE=回推 3 个交易日、C1/C2=再回推 3/2 交易日保 T+2 可扣、A1/A2/持仓 as_of 按
|
||
自然日相对量保持 hold_days 档位断言不变);复跑 **20/20 + 24/24 退出码 0**,
|
||
动态生效自证 = confirmed_at 随回推日漂移(09-04 → 09-09)而断言同源跟随。
|
||
|
||
**全仓硬编码排查结论(分类,非一刀切)**:
|
||
| 类别 | 结论 |
|
||
|---|---|
|
||
| 生产代码 `app/` | **零字面日期**;cutoff/SLA/死锁重试/批上限全走 settings(B1/R-14 整改完整) |
|
||
| T-6~T-12 五个真库脚本(accept/confirm/engine/api/compensate) | 零字面日期(各自任务期已按 T-12 模式合规) |
|
||
| `verify_convert_lots.py` / `verify_convert_apply.py` | **违例 → 本轮已修**(9 处) |
|
||
| `verify_convert_seed.py` 的 `AS_OF` | **有意固定**:与 08-seed-share-lot.sql 种子里的日期强绑定(验证对象就是那批固定数据),改动态反而错 |
|
||
| `calc_convert_demo.py` 的 `TRADE_DATE=2026-09-09` | **有意固定**:PRD 验收 20「示例与种子强绑定」(demo/集成测试/种子三方逐字节一致),改动态违反验收 |
|
||
| sqlite 单测固定日期(test_core_tools / test_trade_gateway 等) | **确定性测试惯例**:自插日历/净值配套数据、不连真库、自洽无漂移,保留 |
|
||
|
||
## 六、文档回写
|
||
|
||
开发计划 v2.0(文件头基线 830/7 + T-13 节 DoD 6/6 全勾 + 执行记录 ①~⑧)·
|
||
PRD v1.1(§9 第 18 条性能实测 + 第 31 条紧池 100% 回填)· `交接文档.md`(v4.2 → **v4.3**,
|
||
§B.11 状态段 + T-13 完成段 + 基线行)· `docs/memory/TODO.md`(进度行 + T-13 勾选,
|
||
T-14~T-17 待办拆出)· `docs/memory/MEMORY.md`(3 处进度)· `AGENTS.md`(进度行)· 本日志。
|
||
|
||
## 七、下一步
|
||
|
||
**T-14**(D27 产品舍入真正接入):`calc.py` `product_round` 接入 +
|
||
`core_share_rule` 种子 + 4 位产品用例(覆盖率不得让降级路径当覆盖)。
|
||
其后 T-15~T-17。提交:代码 `3bc84a0` + 文档(本次),均**不 push**(由用户定)。
|
||
|
||
---
|
||
|
||
# T-14:D27 产品舍入真正接入 + 联网核实纠偏(2026-09-12)
|
||
|
||
## 一、接入面(生产代码 4 处,全部单点收敛)
|
||
|
||
| 文件 | 改动 |
|
||
|---|---|
|
||
| `app/repository/core_ro.py` | 新增 **`get_share_digits(product_id, business_type)`** —— 全仓唯一回退链单点(自检第 13 问):`core_share_rule` 规则行优先 → `core_product.share_digits`(T-1 列,DEFAULT 2)→ `calc.PLACES`(2)。import `PLACES` 有 `convert_core_repository` 先例(repository → convert.calc),无环 |
|
||
| `app/service/convert/calc.py` | `in_qty` 加可选参数 `digits: int = PLACES`(默认行为逐字节不变),内部改走 `product_round`(nav<=0 校验保留单点);`product_round` docstring 更新为「已接入主链路」 |
|
||
| `app/service/convert/confirm_service.py` | 确认段第⑤步:`digits = core.get_share_digits(to_pid, _RULE_BT_CONVERT)` → `in_qty(in_amount, in_nav, digits)`;新增常量 `_RULE_BT_CONVERT = "convert"`(转入份额属「转换」业务,不取 `subscribe` —— 两型规则行可各配位数) |
|
||
| `app/gateway/trade_gateway.py` | 申购折算:`digits = core.get_share_digits(product_id, SUBSCRIBE)` → `sub_qty = product_round(amount / nav, digits)`(redeem 为份额申报 D26,无折算无消费方) |
|
||
|
||
**边界裁定(开发计划 DoD 即边界)**:T-14 只接**落库侧**位数;响应展示维持既有
|
||
`q(x, D2)` 收敛规格(库内 `DECIMAL(18,4)` 本就高于展示位,展示规格是独立维度,
|
||
2 位产品路径零漂移)。PRD §2.5.2「份额展示 2 位(可被产品配置覆盖)」的后半句
|
||
(展示位数按产品覆盖)**未落实**,登记开口待用户裁定。
|
||
|
||
## 二、联网核实(用户铁律 2026-09-12 · 本轮最重要产出)
|
||
|
||
> 用户指示:「不确定的不要盲目相信 prd 和交接文档,而是联网查询确认,要和真实项目一致」——
|
||
> T-14 的落地依据全部是外部事实断言,逐条联网核实:
|
||
|
||
| # | 断言(PRD/种子 v1.0 原口径) | 核实结论 | 依据 |
|
||
|---|---|---|---|
|
||
| 1 | 主流份额 2 位四舍五入 | ✅ 成立 | 民生加银转换规则第八条 / 富时基金业务规则 / 新华基金 / 泰康安泰(申购份额 2 位第 3 位四舍五入) |
|
||
| 2 | 位数按产品配置(D27 机制) | ✅ 成立且比 PRD 更硬 | **南方基金《关于调整旗下基金转换业务规则的公告》(2012)**:把统一的「四舍五入保留两位」改为「**根据各基金基金合同和招募说明书的相关规定**保留小数点后 2 位」——产品级配置的直接行业出处 |
|
||
| 3 | 舍入方式存在「舍去(TRUNCATE)」型 | ✅ 成立(rounding_mode 字段有现实载体) | 南方利众 C / 南方宝元转换公告(两位后**舍去**归基金资产)、邮储银行转换 FAQ、华宝未来混合、申万菱信业务规则、东方基金登记业务规则 |
|
||
| 4 | 南方截断 | ✅ 成立 | 同上(南方自家公告即「舍去」型) |
|
||
| 5 | 货基 1 元面值、份额 2 位 | ✅ 成立 | 中银货币 / 融通易支付 / 鑫元货币基金合同 |
|
||
| 6 | **「指数基金申购/转入确认份额允许 4 位小数」**(v1.0 种子给 3 产品配 4 位的唯一注释依据) | ❌ **证伪**:反证充分——易方达沪深300非银 ETF 基金合同、汇添富深证300 ETF 联接、华夏恒生 ETF 联接、科创板 ETF 联接全部 2 位;易方达创业板新能源 ETF 联接转换公告直接写「**转入本基金的份额计算结果保留到小数点后两位**」 | v1.0 注释声称「见 PRD 已知差异」,PRD §2.5.3 实际无此依据——注释与 PRD 互相「引用」但源头不存在 |
|
||
| 7 | 「易方达 ETF 场外取整数位」 | ⚠️ 未找到证据,且有反证(易方达自家 ETF/联接 2 位);真实「整数位」载体是**场内渠道**(招商瑞智优选 LOF 合同:场内申购先 2 位再截位到整数份),属渠道维度非产品维度 | PRD §2.5.3 该句**待用户裁定修订** |
|
||
|
||
**种子纠偏(`11-seed-share-rule.sql` → v1.1)**:PROD-510300 / PROD-510500 /
|
||
PROD-000002 共 9 行 4 → **2**(42 行数不变,`verify_convert_seed.py` DoD ⑩ 只断言行数,
|
||
不炸);头注重写为上表 1~5 的核实结论 + 出处;**刻意不配 `truncate` 行**——
|
||
T-14 只接 digits,配了不消费即是半成品(TRUNCATE 接入为登记开口)。
|
||
|
||
## 三、测试(+7 → 全量 837 passed / 7 skipped,零回归)
|
||
|
||
| 文件 | 用例 | 覆盖 |
|
||
|---|---|---|
|
||
| `test_convert_calc.py` | `test_in_qty_digits_four_places` / `test_in_qty_default_digits_matches_round2` | 纯函数层:4 位量化 / 默认 digits 与 `round2` 逐字节一致(DoD 3) |
|
||
| `test_convert_confirm.py` | `test_confirm_in_qty_four_places_via_share_rule`(+`subscribe` 型 2 位干扰行证 business_type 区分度) | 回退链第一层(DoD 1,非降级):in 流水 qty + 转入批次均 4 位 |
|
||
| | `test_confirm_in_qty_falls_back_to_product_share_digits` | 回退链第二层:无规则行 + `core_product.share_digits=4` → 4 位(用 4 位而非 2 位才能证明第二层真被消费,排除「恒 2」假实现) |
|
||
| | `test_confirm_in_qty_missing_rule_falls_back_default_two_places` | 回退链第三层:全缺(share_digits 置 NULL)→ 默认 2 位 == PRD 示例逐字节(DoD 2 显式降级 + DoD 3 链路锚点) |
|
||
| `test_share_lot.py` | `test_subscribe_qty_four_places_via_share_rule` / `test_subscribe_qty_falls_back_to_product_share_digits` | 申购侧对称两条(+`convert` 型干扰行) |
|
||
|
||
⚠️ 4 位种子行的定位 = **D27 机制验证载体**(非真实业务常态),已在三处用例 docstring
|
||
与 `product_round` docstring 钉明,防止后人误当真实口径。回退链三层各有用例,
|
||
降级路径非唯一覆盖(DoD 2 的 PRD 示例用例同样走缺规则路径)。
|
||
|
||
## 四、突变验证 2 组(精准命中后还原,MUTATION 零残留)
|
||
|
||
| # | 突变 | 结果 |
|
||
|---|---|---|
|
||
| 1 | `confirm_service` digits 接入改固定 `digits = 2` | **2 红**(4 位规则行 + product 回退两条;missing_rule 默认 2 位用例仍绿属预期) |
|
||
| 2 | `trade_gateway` digits 接入改固定 `digits = 2` | **2 红**(申购 4 位两条) |
|
||
|
||
## 五、真库门禁(9/9 退出码 0)
|
||
|
||
`verify_convert_seed.py` 重灌后全 PASS(⑩ 42 行落库生效,全 2 位)+
|
||
accept 37 / confirm 83 / engine 39 / api 89 / compensate 56 / apply 24 / lots 20 /
|
||
tools 14 —— 全部零回归(2 位产品路径与接入前逐字节一致)。
|
||
|
||
## 六、登记开口(2 项,待用户裁定,未顺手做)
|
||
|
||
1. **PRD §2.5.3 修订**:「易方达 ETF 场外取整数位」未找到证据且有反证(易方达自家
|
||
ETF/联接 2 位);真实整数位载体是场内渠道(LOF/ETF 场内先 2 位再截位整数)。
|
||
PRD 已定稿,修订须用户拍板。
|
||
2. **`rounding_mode='truncate'` 消费接入**:真实业务存在「舍去」型(南方/华宝/
|
||
申万菱信等,核实 #3),D27 字段已建但 calc 未消费(`product_round` 固定 HALF_UP)。
|
||
属 T-14 计划范围外(开发计划改法只接 digits),接入方案 = `product_round` 加 mode
|
||
参数 + 回退链透传 + 种子配一对 truncate 产品,待用户拍板是否补做及归哪个任务。
|
||
|
||
## 七、下一步
|
||
|
||
**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 步
|
||
做集成测试。
|
||
|
||
---
|
||
|
||
# T-14/T-17 开口闭环(2026-09-12 用户三项拍板)+ truncate 舍去法接入
|
||
|
||
## 一、三项拍板与执行
|
||
|
||
| # | 事项 | 拍板 | 执行 |
|
||
|---|---|---|---|
|
||
| 1 | PRD §2.5.3「易方达 ETF 场外取整数位」 | **改** | 已按核实结论修订(该句无证据且有反证,真实整数位载体为场内渠道),版本块加修订注记 |
|
||
| 2 | `rounding_mode='truncate'` 消费接入 | **补接** | 当轮完成(见下) |
|
||
| 3 | T-17 R-12 口径裁定(NULL 不参与判定 / 同类走一般规则) | **认可** | 开发计划 R-12 条目回写定稿 + T-17 执行记录同步 |
|
||
|
||
**审核教训留痕**:PRD v1.1 经独立 AI 三轮审查(0 阻断级)但「易方达 ETF 场外
|
||
取整数位」无出处一句未被任何一轮抓出——逻辑矛盾审查抓不住**外部事实断言**,
|
||
只能靠执行期联网核实兜底。「外部事实必须联网核实」铁律再添一例证。
|
||
|
||
## 二、truncate 接入(生产代码 3 处 + 单点扩展)
|
||
|
||
- **`core_ro.get_share_rounding`**:位数+模式回退链**单点**(规则行 →
|
||
core_product → 默认 `(2, 'half_up')`),逐层独立回退、mode 合法性读取即
|
||
校验(`half_up`/`truncate` 白名单);`get_share_digits` 转薄包装(T-14
|
||
既有调用与测试零破坏)。
|
||
- **`calc.product_round` / `in_qty`**:加 `mode` 参数(`_ROUND_MODES`:
|
||
half_up → ROUND_HALF_UP / truncate → ROUND_DOWN),非法 mode ValueError。
|
||
- **确认段(confirm_service)与网关申购折算(trade_gateway)**:两处透传。
|
||
- **载体**:09-seed-org.sql **⑦ 段** `PROD-005828.rounding_mode='truncate'`
|
||
(11-seed 规则行未覆盖 → 走 product 层回退;无任何转换场景引用 → 全套
|
||
零回归)。11-seed 头注「登记开口」同步改「已接入」。
|
||
|
||
## 三、验证(全量 849 passed / 8 skipped · 真库 9/9)
|
||
|
||
- **测试 +7(842 → 849)**:calc 4(truncate/half_up 分化 34945.53 vs .54、
|
||
整数边界向零、非法 mode、in_qty 透传 16.66 vs 16.67)+ 确认段 2(链路
|
||
truncate 舍去生效;回退链逐层独立 + 规则行整体优先遮蔽 product 层错配)+
|
||
申购段 1(对称,166.66 vs 166.67)。首跑踩坑:非法值断言放在规则行存在
|
||
之后 → DID NOT RAISE(规则行遮蔽 product 层)——即遮蔽语义本身,改用例
|
||
先删规则行。
|
||
- **突变**:两调用点同拆 mode 透传 → 2 链路用例红;还原绿,MUTATION 零残留。
|
||
- **真库 9/9 退出码 0**:09 ⑦ 段幂等灌库后 `verify_convert_seed` 新增 ⑫
|
||
断言(载体在位)通过;其余 8 脚本零回归。
|
||
|
||
## 四、下一步
|
||
|
||
**开口清零**。转换线(T-1~T-17 + 开口闭环)全部完成;下一步 = 开发计划
|
||
**第 6 步集成测试**。
|
||
|
||
---
|
||
|
||
# 第 6 步 · 集成测试 ✅(2026-09-12 · 转换线全流程收官)
|
||
|
||
> 范围对齐 v0.x 第 6 步惯例 + 开发计划 T-13 门禁总则 + PRD §9 验收 18/31
|
||
> 「待第 6 步重测」注记。**全程零代码改动**——纯验证性收口,无一处需要修复。
|
||
|
||
## 一、执行记录(五步全过)
|
||
|
||
| # | 步骤 | 结果 |
|
||
| --- | --- | --- |
|
||
| 1 | **按 SOP 重灌双库**:`verify_convert_seed.py`(DROP + 重建 00~11 全套 SQL + 自动补跑 `prepare_risk_demo.sql`,演示态「剩余 275 天」) | DoD 断言全 PASS(含 ⑨a 日历 503 交易日 / ⑩ 份额规则 42 行 / ⑫ truncate 载体) |
|
||
| 2 | **全量 pytest 回归** | **849 passed / 8 skipped,零失败**(重灌后真库集成模块全真跑,与基线一致) |
|
||
| 3 | **CONVERT_STRESS=1 并发套件**(真库) | **7/7 通过**(T-15 争批用例加入后由 6 演进为 7 条) |
|
||
| 4 | **全套 9 个真库 verify 脚本复跑** | **9/9 退出码 0**:accept 43 / apply 30 / lots 20 / tools 14 / confirm 103 / engine 39 / api 89 / compensate 56 / seed 全 PASS |
|
||
| 5 | **残留检查 + 收尾重灌** | grep `MUTATION` 零命中;grep `1 = 1` 唯一命中 `core_ro.py:662` 动态 WHERE 初始占位(正常模式、基线提交内,非突变残留);收尾重灌后 seed 复验全 PASS,库恢复干净演示态 |
|
||
|
||
## 二、两条「待第 6 步重测」验收的重测结论
|
||
|
||
- **验收 31 紧池成功率 100%**:`test_tight_pool_accept_all_then_confirm_100pct`
|
||
通过——50 并发受理各 1000、池恰 50000 → **受理 50/50 + `confirm_batch`
|
||
串行确认 50/50 = 100%**,扣减恰=池守恒无超卖。T+1 模型核心收益
|
||
(v0.x 同场景 80%)再次实测成立。
|
||
- **验收 18 性能**:`test_performance_probe_accept_confirm_end_to_end` 通过
|
||
(n=59)——**端到端 P50 78.8 / P95 94.8 / max 229.4 ms**(< 2s 硬门禁,
|
||
余量约 20 倍);受理段 P50 36.5 / 确认事务 P50 10.4 ms(均 < 100ms 预估)。
|
||
与 T-13 记录(P50 79.2 / P95 110.3)同量级,PRD §9 第 18 条已回填数字
|
||
仍准确,无需改写。
|
||
|
||
## 三、执行期归因留痕(2 条,均非缺陷)
|
||
|
||
1. **业务脚本跑完后 `seed --no-reset` 断言 FAIL = 预期防护行为**:9 个脚本
|
||
全跑完后批次 61→67 行、Σremain 漂移(`CUST-3001/PROD-510300` 转入批次
|
||
+108279 份;持仓快照滞后为 T+1 已知设计)→ seed 的**静态快照**断言
|
||
(②批次守恒 / ④档位覆盖)显式 FAIL。**seed 断言的权威时点 = 重灌后、
|
||
业务脚本运行前**;各脚本自身的隔离断言(清理后残留 0)为运行态权威。
|
||
收尾重灌即恢复(v0.x 集成测试同款现象,核查单⑥防护语义)。
|
||
2. **CONVERT_STRESS 用例数 6→7 是 T-15 演进而非回归**:
|
||
`test_two_confirms_race_same_lot_first_wins`(Barrier 窄窗争批,断言恰
|
||
1 confirmed + 1 rejected)为 T-15 新增;旧记录「6/6」以本次「7/7」为准。
|
||
|
||
## 四、结论与下一步
|
||
|
||
**第 6 步集成测试全部通过,基金转换线(T+1 受理/确认分离模型)AIcoding
|
||
六步全流程收官**:第 1 步讨论 ✅ → 第 2 步 PRD v1.1 ✅ → 第 3 步架构 v2.1 ✅
|
||
→ 第 4 步开发计划 v2.0 ✅ → 第 5 步 T-1~T-17 ✅ → **第 6 步集成测试 ✅**。
|
||
后续可选项(由用户拍板):分支推送 / 里程碑 tag 等收尾事项。
|
||
|
||
---
|
||
|
||
# 种子补拉 + 全套重测 ✅(2026-09-12 · 用户指示「大更新后重新抓数据生成种子再重测」)
|
||
|
||
## 一、净值种子更新(`06-seed-nav.sql` · 提交 `d2d00dd`)
|
||
|
||
- **重抓**:`python scripts/core/fetch_nav.py --days 60`(东方财富接口,14 产品 × 60 净值日),
|
||
抓取时间 2026-09-12 14:56,区间滚动为 **2026-06-19 ~ 2026-09-11**——补上 09-11(周五)净值。
|
||
- **diff 核对(零修订实证)**:新旧文件行级对比 = 仅新增 09-11 的 14 行 + 窗口起点
|
||
06-18 的 14 行滚动挤出 + 行尾符号调整(09-10 从末日 `;` 变中间日 `,`);
|
||
**同日期同产品净值值零变化**(09-10 两次抓取值逐项一致)。demo 锚定的
|
||
T=2026-09-09 净值(PROD-110022=1.3604 / PROD-003095=1.9194)不受影响。
|
||
- **实际价值**:下周一(09-14)起真库脚本 `previous_biz_day` 回推链锚定 09-11
|
||
时不再缺 T 日净值(旧种子截至 09-10,缺 09-11 会让确认段 nav_pending)。
|
||
|
||
## 二、交易日历种子(`10-seed-trade-calendar.sql` · 无需重生成,留痕依据)
|
||
|
||
- **联网核实**:2026 年上交所休市安排仍为 2025-12-22 年度公告(上证公告〔2025〕45 号),
|
||
**无临时调整**;脚本 `SSE_CLOSED_2026` 的中秋(09-25~27)/国庆(10-01~07)
|
||
区间与公告一致、种子休市行在位。
|
||
- **交叉校验**(`gen_trade_calendar.py --verify-nav`):用**最新**净值校验
|
||
「净值日 ⊆ 交易日」不变式——510300 共 169 个净值发布日(原 168 + 新增 09-11)
|
||
全部落在交易日,**0 偏差**;503 交易日不变。
|
||
|
||
## 三、新种子下全套重测(全绿)
|
||
|
||
| 项 | 结果 |
|
||
| --- | --- |
|
||
| 重灌双库(`verify_convert_seed.py`) | DoD 断言全 PASS |
|
||
| 全量 pytest | **849 passed / 8 skipped** 零失败 |
|
||
| CONVERT_STRESS=1 | **7/7** |
|
||
| 真库 verify 套件(按真实退出码复验) | **9/9 退出码 0**(accept 43 / apply 30 / lots 20 / tools 14 / confirm 103 / engine 39 / api 89 / compensate 56 / seed 全 PASS) |
|
||
| 收尾重灌 | 全 PASS,库恢复干净演示态 |
|
||
|
||
**方法论留痕**:种子数据类文件(净值/日历)的正确维护节奏 = 演示/跑测前
|
||
重跑 `fetch_nav.py` 补最新净值(脚本头注本就内置此指引),日历在交易所公布
|
||
新安排(年度公告/临时调整)时重跑 `gen_trade_calendar.py`——两者都是
|
||
「贴真实项目」铁律在数据层的落点。
|
||
|
||
---
|
||
|
||
# README 更新 + 分支推送 ✅(2026-09-12 · 用户指示)
|
||
|
||
## 一、README 更新(重点 = 本次更新 + 前端已留接口)
|
||
|
||
- **新增「最新更新(2026-09-12)」章节**:基金转换 T+1 全流程收官(AIcoding 六步、
|
||
集成测试 849 绿 / CONVERT_STRESS 7/7 / 真库 9/9 / 紧池 100% / 性能 P95 94.8ms)、
|
||
种子补拉(净值至 09-11)、关键能力清单、v0.x `convert_fund` 退役。
|
||
- **「前端接入契约」新增 3)基金转换**(契约逐项对照实现核对后写入):
|
||
① 受理 `POST /api/simulate/trade`(convert 分支,**份额申报** `qty`,202 + 受理回执;
|
||
回执字段按 `convert_service` 实际返回核对:`blocked/accepted/idempotent/status/
|
||
convert_group_id/client_request_id/requested_qty/qty/forced_full_transfer/
|
||
min_hold_action/accept_date/confirm_date/available_date/cancel_deadline/estimated`)
|
||
② 撤单 `POST /api/simulate/trade/convert/{gid}/cancel`(409 CANCEL_NOT_ALLOWED)
|
||
③ 查询 `GET /api/simulate/trade/convert/{gid}`(未确认 = processing 可轮询)
|
||
④ 确认批处理 `POST /api/admin/convert/confirm?accept_date=`(risk_officer);
|
||
错误码按 `errors.py` 常量逐一核对(TOO_MANY_LOTS/INSUFFICIENT_SHARES/
|
||
CROSS_ENTITY_NOT_SUPPORTED/CANCEL_NOT_ALLOWED/CONCURRENT_CONFLICT)。
|
||
- **其余**:头部定位改「风控 + 基金转换交付分支」、交付状态基线 510→849、
|
||
「核心接口一览」补 convert 四端点、快速启动补 `verify_convert_seed.py`
|
||
非交互重灌与 `fetch_nav.py` 补拉指引、文档地图补基金转换三文档、
|
||
「前端」章节从三件事扩为四件事(+基金转换受理/轮询/撤单)。
|
||
|
||
## 二、推送(用户授权)
|
||
|
||
- 本轮提交(README + 文档回写)随分支全部推送 `origin/risk-control-agent`;
|
||
推送后以 `git ls-remote` 校验远程 = 本地 HEAD(本仓 `refs/remotes/**` 写入
|
||
不落盘的已知异常,判断远程存亡只能用 `git ls-remote`)。
|
||
- 合并 main 仍由合并执行人负责(手册《合并注意事项-风控模块并入main.md》),
|
||
AI 不发起 PR / 不执行合并。
|
||
|
||
## 三、里程碑 tag `convert-m1`(用户授权「打,你自己决定」)
|
||
|
||
- **命名**:`convert-m1` —— 对齐既有 `risk-m1~m4` 的「模块-里程碑」命名惯例
|
||
(tag 本体英文、中文名走 MEMORY §0 对照表):**基金转换 · T+1 受理/确认分离
|
||
模型全流程收官**。
|
||
- **指向**:annotated tag 指向收官提交 `292770f`(含全部代码 + 文档 + README
|
||
契约的收官点);tag 对象 `70449f9`。
|
||
- **校验**:`git ls-remote --tags origin convert-m1` 实测在远程,
|
||
`convert-m1^{commit}` = `292770f` ✓。
|
||
- **与 risk-m4 的差别**:risk-m4 当时仅本地未 push;本 tag 随收官授权一并
|
||
推送远程(分支已在远程,tag 共享才有里程碑意义)。
|
||
|
||
## 四、合并请求(PR)创建 ✅(用户指示「直接帮我做」,中文描述)
|
||
|
||
- **PR #2 已创建**(state=open):
|
||
`http://47.106.207.27:3000/xinghuo/group_xinghuo_jinrong/pulls/2`
|
||
(`risk-control-agent` → `main`,Gitea API 201 实测)。
|
||
- **标题**:「风控监测 Agent 模块 + 基金转换 T+1 交易 并入 main
|
||
(143 提交 · 849 测试绿 · 里程碑 tag convert-m1)」。
|
||
- **描述正文(约 4000 字,全中文)含 5 块**:合并概览(两大交付 + tag)/
|
||
质量基线表(849 绿 / CONVERT_STRESS 7/7 / 真库 9/9 / 紧池 100% / 性能 /
|
||
接口实调)/ 合并执行要点(**基底更新利好:远程 main = `ecdf8b5` 已含
|
||
Wave 0 `3995cb4`,手册「origin/main 停在 Initial commit」最高风险项不再
|
||
成立**;**冲突实测 23 文件** = 手册 20 + 基金转换线新增 3 个 Core SQL
|
||
(01-ddl / 06-seed-nav / scripts/core/README),裁决原则引用手册 §2 +
|
||
《边界标注》§4,禁止批量 ours/theirs)/ 静默并入 14 文件提醒 / 合并后
|
||
必测清单(pytest 849 + 真库 9/9 + /health 鉴权冒烟 + convert 四端点冒烟)。
|
||
- **冲突清单为创建 PR 前实时实测**(`git merge-tree --write-tree main HEAD`),
|
||
非沿用手册旧值;合并范围实测 merge-base `1ddd44a` / 143 提交 / 200 文件
|
||
(+50364 −335)。
|
||
- **⚠️ 订正(用户指出)**:PR #2 base 分支错误(应为 `merger` 非同 `main`)→
|
||
已关闭(state=closed 留档),**重建为 PR #3(base=`merger`)**:
|
||
`http://47.106.207.27:3000/xinghuo/group_xinghuo_jinrong/pulls/3`。
|
||
**按 merger 重测的数字**(创建后又遇 merger 侧顾问线合入 `c7fa32e`,再订正):
|
||
merge-base `2d0e2fa`(merger 已吸收模块旧版)/ 分支侧领先 **54 提交 /
|
||
114 文件(+29989 −183)** / 冲突实测 **15 文件**(README / gateway_repository /
|
||
main.py / core_ro.py / response.py / `_ddl.py` / test_main.py / memory 四件套 /
|
||
06-seed-nav / scripts/core/README / reset.ps1)——裁决提示:模块新演进 vs
|
||
merger 侧独立修改(顾问线等),两侧实质内容都必须保留,禁止批量
|
||
ours/theirs;描述已同步为最新实测(PATCH 201)。
|