基金转换 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,零回归。
This commit is contained in:
2026-09-11 19:21:15 +08:00
parent 3b781f8823
commit f056fcd007
40 changed files with 6545 additions and 3105 deletions
+41 -1
View File
@@ -42,6 +42,8 @@ SQL_FILES = [
"07-seed-fee-rule.sql",
"08-seed-share-lot.sql",
"09-seed-org.sql",
"10-seed-trade-calendar.sql",
"11-seed-share-rule.sql",
]
# 档位覆盖参照日:与该批种子中 as_of / 净值日一致(2026-09-04)
@@ -177,7 +179,7 @@ def run_assertions() -> bool:
print(" ✗ 未覆盖全部 5 档 → 费率档演示无数据")
ok &= p4
# ---- 附加:主演示客户 CUST-9527 确为跨批次 ----
# ---- 附加:主演示客户 CUST-9527 确为跨批次 ----
cur.execute(
"SELECT product_id, COUNT(*) FROM core_share_lot WHERE customer_id='CUST-9527' "
"GROUP BY product_id ORDER BY product_id"
@@ -189,6 +191,44 @@ def run_assertions() -> bool:
print(" ✗ 主演示客户无跨批次数据")
ok &= p5
# ---- DoD ⑨ 交易日历种子(T-1 · 10-seed):全区间(2026~2027)交易日 = 503 ----
cur.execute("SELECT COUNT(*) FROM core_trade_calendar WHERE is_open = 1")
n_trade_days = cur.fetchone()[0]
p9a = n_trade_days == 503
print(f"⑨a 交易日历:全区间交易日(is_open=1)={n_trade_days}(期望 503)")
if not p9a:
print(" ✗ 交易日历种子与 10-seed 头注释 503 不符")
ok &= p9a
# ---- DoD ⑩ core_share_rule 种子(T-1 · 11-seed):覆盖 ≥ 1 个产品 × 3 类型 ----
cur.execute(
"SELECT COUNT(DISTINCT product_id), COUNT(*) FROM core_share_rule "
"WHERE business_type IN ('subscribe','redeem','convert')"
)
n_rule_products, n_rule_rows = cur.fetchone()
p10 = n_rule_products >= 1 and n_rule_rows >= 3
print(f"⑩ 份额舍入规则:{n_rule_products} 只产品 × {n_rule_rows} 行(期望 ≥1 产品且 ≥3 行)")
if not p10:
print(" ✗ core_share_rule 种子不足")
ok &= p10
# ---- DoD ⑪ core_convert_request 建表即含 6 态 ENUM(T-1 · 免二期 ALTER)----
cur.execute(
"SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() "
"AND TABLE_NAME = 'core_convert_request' AND COLUMN_NAME = 'status'"
)
req_row = cur.fetchone()
if req_row:
vals = set(re.findall(r"'(\w+)'", req_row[0]))
p11 = vals == {"accepted", "nav_pending", "confirmed", "rejected", "cancelled", "expired"}
print(f"⑪ core_convert_request.status = {req_row[0]}")
if not p11:
print(f" ✗ ENUM 值不全(实际 {vals})")
else:
p11 = False
print(f" ✗ 查不到 core_convert_request({req_row})")
ok &= p11
# ---- DoD ⑥ 向后兼容:不带新列的老 INSERT 仍可执行(架构风险 #9)----
cur.execute("START TRANSACTION")
try: