Files
group_xinghuo_jinrong/docs/memory/2026-09-12.md
T

699 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)。