Files
group_xinghuo_jinrong/docs/项目框架设计/基金转换-审查意见处置表.md
GaoYiYuan_0626 f056fcd007 基金转换 T+1 模型:T-1~T-6 落地(DDL/数据层/语义收窄/锁/受理事务)
AIcoding 第 5 步 todo 开发(开发计划 v2.0)前半段:

- T-1 3 张新表 DDL(core_convert_request 6 态 ENUM / core_trade_calendar /
  core_share_rule)+ 种子 + sqlite 单一事实源同步 + 列清单断言
- T-2/T-2b calc 扩展(product_round/redeem_amount/partial_qty)+ 真实净值实算回填
- T-3 convert_request_repository(6 态 + 条件 UPDATE 守卫)+ core_ro 三读方法
  + share_lot_repository.available_qty_with_inflight(R-3 在途占用推导)
- T-4 convert_repository.sync_mirror 成为 risk_convert_detail 唯一进度镜像写入口
  (旧三方法标 Deprecated,T-7 后删)
- T-5 locks.py 锁键构造器 convert_req_lock_key / convert_confirm_lock_key
- T-6 convert_service.accept_convert 受理事务(八步:锁→幂等→校验→受理日顺延
  →在途占用校验→落单→镜像+审计→202;不扣份额/不折算/不写流水)
  + tests/test_convert_accept.py(16 用例)
  + scripts/dev/verify_convert_accept.py(真库 36/36 一致)
  + trading_calendar.py 纯函数包(R-5)+ 21 用例

T-6 真库实测暴露并修复:confirm_eta 在日历数据边界抛 ValueError,会让已落库
的受理单在调用方眼里变 500;改为展示性字段容错 + 单测守护。

基线:798 passed / 10 skipped,零回归。
2026-09-11 19:21:15 +08:00

131 lines
14 KiB
Markdown
Raw Permalink 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.
# 基金转换 · 外部审查意见处置表
> ⚠️ **历史参考文档(v0.x 模型,2026-09-10 产物)**:本表结论对应的 PRD v0.8 / 架构 v1.0 已被 **T+1 受理/确认分离模型(PRD v1.1 / 架构 v2.1 / 开发计划 v2.0)取代**,保留作审查方法论留档,**当前以 v1.1/v2.1/v2.0 为准**。
>
> 日期:2026-09-10 · 审查结论原文:**❌ 不建议进入编码**(33 条:B×5 / I×7 / M×7 / L×7 / S×3 + 自检九问复核)
> 处置原则(用户红线):**逐条判定 = 接受 / 修正性接受 / 驳回**;驳回与修正性接受必须给出核实过的证据。
> 判定基准:**贴近真实业务**(证监会公告〔2025〕22 号 + 10 家基金公司转换公告,见架构 §0.5 来源清单)
> 结果:**接受 27 · 修正性接受 5 · 驳回 0 · 合并 1**(S-1 并入 B-1),另**自查补 2 条**(X-1 / X-2)
> 后续:本表结论 → PRD v0.8 → 架构设计 v1.0 → 独立 AI 评审 → 第 4 步开发计划
---
## 一、阻断级(B-1~B-5)
| # | 审查意见 | 判定 | 处置与证据 |
| --- | --- | --- | --- |
| B-1 | PRD「已定稿」与架构 §0 合规查证矛盾 | **接受** | PRD 前言改「**v0.7 已冻结**(架构校准发现合规问题),待出 v0.8」;架构 v0.2 加冻结声明;新增门控:PRD v0.8 未产出不得进第 4 步 |
| B-2 | 申购费率种子全超法定上限 | **接受** | 按架构 §13.3 重定:货基 0 / 债基·指数 **0.0030** / 其他混合 **0.0050** / 主动偏股 **0.0080**。依据 22 号文 §8。§5.3 示例、§9 第 19 条同步重算 |
| B-3 | 赎回费 7–30 日档低于法定下限、边界与 180 日不符 | **接受** | 新档:`<7 日 1.5%` / `7–30 日 1.0%` / `30–180 日 0.5%` / `180–365 日 0.25%` / `≥365 日 0`。依据 22 号文 §10。PRD §2.1、07 种子、§5.3、§9 同步 |
| B-4 | 份额 4 位向下舍去,与真实(2 位四舍五入)不符 | **接受** | 改 **2 位 `ROUND_HALF_UP`**;`rounding_diff` 语义改「可正可负,在基金资产列支」。`core_share_lot.remain_qty` 存储精度保持 DECIMAL(18,4)(真实 TA 内部精度高于展示),**计算与展示按 2 位** |
| B-5 | T+1 确认时序与真实不符 | **修正性接受**(范围需收窄,方向需澄清) | **只改「转入端新批次」的 `confirmed_at = T+1`**。证据:PRD §4.4 表格已区分三行(转入新批次 / 普通申购新批次 / 转出·赎回不产新批次);转出批次是**历史日期**,`hold_days = (交易日 − confirmed_at).days` 恒为正,不受影响。<br>**方向澄清**:审查称「当前设计使持有期比真实**多** 1 天」——实际相反。真实持有期**自确认日(T+1)起算**,比从 T 起算**少** 1 天(持有期更短、费率档更严格)。PRD Q1 精确化为:「不模拟 T+1 的**权益登记延迟**(转入份额立即可用),但 `confirmed_at` 取 T+1」 |
## 二、重要级(I-1~I-7)
| # | 审查意见 | 判定 | 处置与证据 |
| --- | --- | --- | --- |
| I-1 | 补差费口径未拍板(A 费率差 vs B 价外法两端差) | **接受** | PRD 新增 **Q9**:明确本期选 **B(价外法两端差)**,理由:中国申购费标准算法即价外法(净申购额 = 金额/(1+费率)),B 与之数学自洽,且为国投瑞银/东海/华商/金鹰/鹏华新版主流;A 以 `convert_diff_fee_mode` 配置保留,两口径均有单测。PRD §2.1 补双口径公式与数值差异示例 |
| I-2 | `hold_days` 缺边界定义 | **接受** | PRD §12 补:`hold_days = (交易日 − confirmed_at).days`(**不含申请日**);分档**左闭右开** `[min, max)`;**满 7 日归 7–30 日档(1.0%)**,满 30 日归 30–180 日档(0.5%)。与架构 §7 同步 |
| I-3 | 阶段二补偿自动/手动未声明 | **接受** | PRD §7.1 补:一期为「**监控告警 + 人工脚本补偿**」(可接受简化),SLA 24h;`cleanup_pending_convert.py` 由定时/手动触发。**另补审查未提的缺口 X-1:引擎执行时序**——阶段一提交后、阶段二之前同步跑引擎;引擎失败沿用现有 `engine_error` 模式(架构 §X) |
| I-4 | 强制全转触发条件不清晰 | **接受** | PRD §12 补判断流程图:① 份额足够(Σ remain_qty)→ ② 最低转出份额(申请 == 全部可转份额时**豁免** min_redeem)→ ③ **余额 < min_hold_qty 才触发强制全转**。审查例(持 6000、申请 5000、min_hold_qty=1000):余额 1000 **不低于**下限 → **不触发**,正常转 5000 |
| I-5 | `min_hold_action` 两种模式 PRD 未回应 | **接受** | 两种都做:`core_product` 加 `min_hold_action ENUM('force_transfer','force_redeem') DEFAULT 'force_transfer'`。依据:阈值大(100/500/1000 份)→ 强制赎回(财通/汇添富);阈值小(0.1/1 份)→ 强制全转(中银/东海) |
| I-6 | 份额精度对现有 510 用例影响未评估 | **接受(已实测,影响 = 0)** | 实测 `grep`:现有测试仅 **2 个文件**触碰 `core_holding`(`test_chat_tools.py`、`test_concentration_c4.py`),且只断言 `market_value` / `quantity`,**无任何份额精度断言** → 改动影响为 0。<br>**但实测发现真坑**:sqlite `_ddl.py:53` 列名 `quantity`,MySQL DDL 为 `qty`——convert 首次要 `UPDATE core_holding.qty`,该失配会在本次开发**第一次暴露**。T-1 必须补 `qty`/`cost_amount`/`as_of`/`pnl_pct` |
| I-7 | RISK-006 转换后集中度未定义 | **接受** | 明确:引擎在阶段一**提交后**执行,`concentration_profile` 读到的是**已更新**持仓(含转入端新持仓、已扣减的转出端)→ 转换的集中度连带效应天然被覆盖,无需新增规则 |
## 三、中等级(M-1~M-7)
| # | 意见 | 判定 | 处置 |
| --- | --- | --- | --- |
| M-1 | 重试判定 SQL 依赖 `idx_convert_group` | 接受 | 开发计划门控:**T-1(DDL 含索引)必须先于 T-7(幂等重试逻辑)** |
| M-2 | 409 `LOT_CONFLICT` 重试策略缺失 | 接受 | PRD §5.4 增行:调用方自行重试,建议最多 3 次,间隔 100/200/400ms |
| M-3 | 幂等键格式未指定 | 接受 | 客户端生成、格式自由、**≤64 字符**、建议含时间戳便于排查 |
| M-4 | `out_amount` 与 `convert_amount` 语义重叠 | 接受 | §5.3 注释分组:`out_amount` = 原始转出额;`convert_amount` = 扣赎回费后可用于转入的金额 |
| M-5 | `convert_batch_max_lots=200` 超限无错误码 | 接受 | 超限返回 **400 `TOO_MANY_LOTS`** |
| M-6 | `pnl_pct` 更新口径未同步 | 接受 | PRD FR-C13 补:`pnl_pct = (market_value − cost_amount) / cost_amount`,`cost_amount = 0` 时置 0 |
| M-7 | PRD 与架构同步依赖未管理 | 接受 | 硬性门控写入 PRD v0.8 前言 + 架构冻结声明(已加) |
## 四、低优先级(L-1~L-7)
| # | 意见 | 判定 | 处置 |
| --- | --- | --- | --- |
| L-1 | 架构校准结论未回流 PRD 评审记录 | 接受 | PRD v0.8 §13 新增「**架构校准轮(2026-09-10)**」记录,附本处置表索引 |
| L-2 | 主审计应关联组而非单条 | 接受 | `input_summary` **以 `convert_group_id` 为主键关联**,附 `out_trade_id` / `in_trade_id` |
| L-3 | 响应示例字段过多 | 接受(可选) | §5.3 按「基础 / 转出端 / 转入端 / 规则引擎 / 审计」分组注释 |
| L-4 | 「仅 R-02 可阻断」表述歧义 | 接受 | 加注:**503 / 409 为技术故障码,不适用「仅 R-02 可阻断」业务规则** |
| L-5 | 撤单未预留字段 | 接受 | `risk_convert_detail` **建表即加** `status ENUM(...,'cancelled')` 与 `cancelled_at`(新表,零 ALTER 成本) |
| L-6 | 种子版本管理 | **修正性接受** | 不单独建 CHANGELOG 文件(维护成本高、易与 SQL 漂移);改为**每个种子 SQL 文件头加版本注释块**(版本/日期/依据/变更摘要) |
| L-7 | `rebuild_lots.py`「回滚」措辞误导 | 接受 | 改措辞为「**批次快照重建,不是交易回滚**」 |
## 五、结构问题(S-1~S-3)
| # | 意见 | 判定 | 处置 |
| --- | --- | --- | --- |
| S-1 | 前言「已定稿」不符 | — | **并入 B-1** 处理 |
| S-2 | 评审记录过长(≈200 行,占 22%) | **修正性接受** | **不移到附录**。理由:用户红线要求「PRD 必须经独立 AI 审查、评审闭环可追溯」,删详录会破坏可追溯性。折中:正文前加「**阅读导航**」(新读者路径 + 每轮 3 行决策摘要表),详录保留原处 |
| S-3 | 待确认项需新增 | 接受 | 新增 **Q9 补差费口径 / Q10 份额精度 / Q11 T+1 建模 / Q12 min_hold_action** |
## 六、自查补充(审查未覆盖,本次一并收口)
| # | 发现 | 判定 | 处置 |
| --- | --- | --- | --- |
| X-1 | **引擎执行时序未定义**——PRD §7.0 七步里没有「跑规则引擎」这一步 | 补充 | 插入为第 6.5 步:阶段一(Core 提交)→ **阶段 1.5:同步跑引擎(读全量两条流水 + 更新后持仓)** → 阶段二(回写 + 审计)。引擎异常沿用 `engine_error` + 本地日志,不阻断已成立的交易 |
| X-2 | **两条流水的 `trade_status`**——真实 T 日申请为「待确认」,T+1 才 confirmed;而 `_eligible` 只认 confirmed | 补充 | 定:两条流水**直接写 `confirmed`**(沿用模拟库现有行为,否则规则引擎读不到),折算结果以 `estimated=true` 标识;PRD 注明此为「不模拟 T+1 权益登记延迟」的连带简化,与 Q1 一致 |
## 七、自检九问复核(审查评价 + 本轮补正)
| # | 问 | 审查评价 | 本轮补正 |
| --- | --- | --- | --- |
| 1~4 | 谁写 / 谁读 / 枚举 DDL / 跨库事务 | ✓ | 第 4 问「完整但补偿路径为手动」→ I-3 已明确一期手动 + SLA 24h |
| 5 | 种子数据 | ⚠️ 费率超上限 | B-2 / B-3 已重定合规值 |
| 6~8 | 并发 / 汇总语义 / 向后兼容 | ✓ | — |
| 9 | 示例自证 | ⚠️ 数字基于错误口径 | **T-2b:`scripts/dev/calc_convert_demo.py` 用生产同一套 `calc.py` 实算回填,禁止手算** |
| **10** | **(新增)外部事实核验** | — | 本次 4 项合规硬伤全部源于「只做内部自洽、未做外部核验」。新增第 10 问:涉及**费率 / 时限 / 资质 / 精度**等**行业有明文规定**的量,必须联网核对现行法规与真实业务公告并列来源 |
---
## 八、PRD v0.8 修订范围(下一步执行清单)
| 序 | 修订点 | 对应条目 |
| --- | --- | --- |
| 1 | 前言状态改「v0.7 已冻结,待 v0.8」+ 门控声明 | B-1 / M-7 |
| 2 | §1.4 非目标:撤单改「二期,字段已预留」 | L-5 |
| 3 | §2.1 补差费:双口径公式 + 选 B 的理由 + 数值差异例 | I-1 |
| 4 | §2.1 赎回费档位按 22 号文重排 | B-3 |
| 5 | §2.5 份额 2 位 `ROUND_HALF_UP`,误差在基金资产列支,`rounding_diff` 可正可负 | B-4 |
| 6 | §4.1 `risk_convert_detail` 加 `cancelled` / `cancelled_at`;`core_fee_rule` 加 `to_fund_ratio` | L-5 / D16 |
| 7 | §4.2 `core_product` 加 `min_hold_action` | I-5 |
| 8 | §4.3 种子费率改合规值;赎回费种子改新档 | B-2 / B-3 |
| 9 | §4.4 / Q1:`confirmed_at = T+1`(仅新批次),精确化表述 | B-5 |
| 10 | §5.3 响应:示例由脚本实算回填 + 分组注释 + 字段语义 | B-2 / M-4 / L-3 / T-2b |
| 11 | §5.4 增 `TOO_MANY_LOTS` + 409 重试策略 + 503/409 非业务阻断注 | M-2 / M-5 / L-4 |
| 12 | §7.0 插入「阶段 1.5 跑引擎」 | X-1 |
| 13 | §7.1 补偿路径:一期手动 + SLA 24h + 触发者 | I-3 |
| 14 | §7.3 审计关联 `convert_group_id` | L-2 |
| 15 | §10 新增 Q9~Q12 | S-3 |
| 16 | §12 补 `hold_days` 定义 + 强制全转判断流程图 | I-2 / I-4 |
| 17 | §13 新增「架构校准轮」评审记录 | L-1 |
| 18 | 种子 SQL 文件头加版本注释块 | L-6 |
| 19 | FR-C13 补 `pnl_pct` 口径;`rebuild_lots.py` 措辞订正 | M-6 / L-7 |
| 20 | 全文:示例数字按新费率/精度/时序重算(脚本实算) | B-2~B-5 |
> **执行状态(2026-09-10)**:以上 **20 项已全部落于 PRD v0.8**。
> 示例数字由 `scripts/dev/calc_convert_demo.py` 实算回填(口径 B:`diff_fee = 252.40`、`in_qty = 53456.95`、`rounding_diff = -0.0026`;口径 A 对照 `253.91` / `53455.36`)。
> **下一步**:架构设计 v0.2 按本表结论转 **v1.0** → 送独立 AI 评审 → 第 4 步开发计划。
---
## 九、用户拍板记录(2026-09-10)
行业多口径 2 项 + 架构取舍 3 项,用户确认「**按建议**」:
| # | 事项 | 拍板结果 | 依据 |
| --- | --- | --- | --- |
| 1 | 补差费默认口径 | **口径 B(价外法两端差)** | 与申购费价外法数学自洽;A 属近似写法,保留为 `convert_diff_fee_mode` 配置项 |
| 2 | 最低持有余额处置 | **两种都做**(`min_hold_action` ENUM 两值 + 种子按产品设定) | **法规无强制**(22 号文不涉及最低持有余额),两种均为真实基金合同约定 |
| 3 | P1 普通申赎无批次是否跳过 | **不跳过**:批次表覆盖全部交易类型;遇历史持仓无批次 → **兜底补建**,保证 FIFO 可执行、两表一致 | 跳过会导致批次表与持仓表永久失配 |
| 4 | P2 转出归零保留还是删行 | **保留(`remain_qty = 0`),不删行** | 与「审计只 INSERT」精神一致,保可追溯;避免 `core_convert_lot_detail` 断链 |
| 5 | P3 Core 侧明细是否对外查询 | **仅补偿,不对外查询** | 保持 Core 只读边界最小;对话侧走 agent 库粗粒度详情 |
> **落地位置**:第 1、2 项属业务口径 → 已落 **PRD v0.8**(§2.1.1 / §4.2 / §10 Q9·Q12,§10 已加拍板状态说明);
> 第 3~5 项属架构层 → **将在架构设计 v1.0 落实**(对应架构 v0.2 的 P1~P3 待拍板项)。