Commit Graph
4 Commits
Author SHA1 Message Date
GaoYiYuan_0626 cde0c226fe 基金转换 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 步集成测试)
2026-09-12 13:42:49 +08:00
GaoYiYuan_0626 e04b44c44a 基金转换 T+1 模型:T-9 api 层(受理 202 + 撤单/确认/查询接口 + 网关分派 + redeem 份额申报)
三接口路径以 PRD 为准(裁定 11/12):
- app/api/convert_admin.py 新增 POST /api/simulate/trade/convert/{gid}/cancel(交易 owner 闸门)
  + POST /api/admin/convert/confirm?accept_date=(risk_officer)+ GET /api/simulate/trade/convert/{gid}(查询 scope 闸门)
- simulate.py convert 分支受理 202 + 受理回执(两类 202 分辨 accepted/processing)
- trade_gateway 分派改调 accept_convert(SUPPORTED_TRADE_TYPES 维持 2 类型)
- redeem 份额申报(D26/R-6:qty 入参,_redeem_quote = qty * T 净值 - 赎回费)
- errors.py 新增 CANCEL_NOT_ALLOWED / CONCURRENT_CONFLICT(均 409)

修正 2 处口径缺陷(测试打穿揭示,非只改测试):
- _rebuild_quote 的 out_nav 改直读 core_convert_lot_detail.nav(反推致 1.3604 -> 1.3600 漂移)
- _accept_idempotent 字段面扩展与受理回执对齐(幂等重试客户端可取到预计确认日)

验证:端到端 13 passed(T+1 两段链路重写);全量 834 passed / 10 skipped;
真库 verify_convert_api.py 88/88,存量 4 脚本零回归;3 组突变验证命中后还原
2026-09-11 20:37:45 +08:00
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
GaoYiYuan_0626 d0097d6004 基金转换 T-9:API 模型 + 网关分派(HTTP 层 convert 走通)+ 展示位数口径修复
一、T-9 本体:HTTP 层 convert 端到端走通

- api/simulate.py:TradeRequest 三型字段分池(subscribe/redeem → product_id+amount;
  convert → from/to_product_id + qty + 可选 client_request_id)+ model_validator 分支校验;
  未知类型放行给网关抛 400(保住既有 purchase → 400 断言);model_dump(exclude_none=True);
  PROCESSING → 202;异常捕获由 except LookupError 收窄为 except NotFoundError
  (原写法把 KeyError 这类编程错误静默转成 404,实测掩盖 convert 分支真实诊断)。
- gateway/trade_gateway.py:移除 convert 显式拒绝,新增 _submit_convert 分派
  (只做参数映射 + 仓储装配);convert 不写 trade_request 审计(审计归 convert_service)。
- utils/response.py:错误体合入 exc.extra(TOO_MANY_LOTS 的 batch_count/max_lots);
  既有 ApiError 无 extra 属性 → 老错误体逐字节不变。
- utils/trace.py + main.py:正则收敛单点定义。执行期发现 trace.py 与 main.py 各有一份
  内容完全相同的白名单副本 —— S4 要防的「漂移」其实已经发生,现将常量上移 trace.py
  成公开 HEADER_ID_PATTERN(同时解决 main→simulate 反向导入成环)。

二、展示位数口径修复(执行期发现 → 联网核验 → 修复 → 文档订正)

发现:同一逻辑响应两种写法 —— 首次 "53456.95" vs 幂等重放 "53456.9500",数值相等、字符串不等。
根因不是 T-7 写错,是契约缺位:§2.5 只规定「金额/份额 2 位」,净值、费率、申请份额的
回显位数根本没定义 → 实现只能 str(Decimal) 原样出网 → 位数随数据来源漂移。

修复:convert_service 新增 _q(value, unit) + _D2/_D4 规格常量作对外唯一出口 ——
金额/份额 2 位、净值/费率/份额尾差 4 位;响应 + 审计 summary + 异常日志共用该出口;
原 _s() 全部替换。首次路径幂等(除 requested_qty/actual_qty/lot[].qty 由 4 位补齐 2 位外不变)。

依据(2026-09-10 联网核验 7 家管理人公告):金额/份额「四舍五入保留至小数点后两位」;
「申请转换份额精确到小数点后两位」;净值保留 4 位第 5 位四舍五入(中欧/国泰公告由 3 位提高至 4 位);
费率以百分比 2 位表示。已知不统一:易方达 ETF 场外份额取整数位、南方基金取截断 → 取主流口径
并记入 PRD 已知差异(未来接真实 TA 需按基金合同配置化)。

三、文档订正

- PRD → v0.9.2:§2.5 拆 2.5.1 计算精度 / 2.5.2 展示位数(新增按字段分类的规格表 + 外部依据);
  §5.3 示例 requested_qty/actual_qty/lot_breakdown[].qty 4 位 → 2 位(原示例与 §2.5
  「计算与对外展示按 2 位」自相矛盾,属漏改);字段类型约定补「位数不自由 + 两条路径须逐字节一致」。
- 架构 → v1.0.1:§1 原则 11 补「str() 前必须按 §2.5.2 量化」,无结构变更。

四、验证

- 新增 tests/test_convert_integration.py(8 条真 MySQL 端到端,CNV-TEST-/TRD-TEST- 前缀隔离):
  折算与 PRD §5.3 逐项吻合、两条流水同组、持仓与批次如实变动、明细 completed + 审计、
  幂等重试不产生第二组、跨主体 400、未知类型 400,以及
  「首次与重放逐字段逐字节相等」+「展示位数规格」两条新闸门。
- test_trade_gateway.py +17(11 条错误码映射全表参数化 · 202 · 200 透传 · 不写 trade_request 审计)。
- test_integration_risk.py:R15 处置 —— 端到端已迁入新文件,原槽位改造为
  test_invalid_type_400_and_no_new_trade_audit(改用 purchase 触发),保住「校验失败不落审计」不变量。
- pytest -q → 697 passed / 3 skipped(基线 672 +25,零回归)。
- 真库复跑:T-6 24/24 · T-7 35/35 · T-8 31/31;calc_convert_demo.py 15/15。
- 突变验证 4 组:关掉 convert 分派 → 21 条红;关掉错误体 extra 展开 → 精准 1 条;
  关掉 client_request_id 正则 → 精准 1 条;关掉 _q() 展示量化 → 2 条红
  (assert '50000.0000' == '50000' 直接复现原缺陷)。均已恢复。
2026-09-10 18:00:20 +08:00