fix(seed): 演示预警的状态口径与代码对齐,并补 closed_at
## 结论:risk_action_service 没问题,我先前是误判
esolve(完成结案)写全了 status="已结案" + closed_at + handle_result(risk_action_service.py:105-107),
exclude 同样(:67-71)。我报的"结案没写 closed_at"看到的那条 ALDEMO0003
其实是**种子预置样本**,不是我以为的自己创建的那条 —— 我的冒烟脚本用
ALDEMO{时分秒} 命名,**撞上了种子的 ALDEMO0001-0003**,"已存在则跳过"把创建挡掉,
随后处置全 409,串起来就像"结案没写时间"。
真正的问题在种子数据,两处:
1) **状态口径与代码不一致**:ALDEMO0003 写成 status="已关闭",而
isk_action_service 里**没有任何动作会产生这个状态**(状态机只有
待处理 / 调查中 / 已排除 / 已结案)。演示时会被当成系统行为,排查时又查不到出处。
按它的 close_reason 语义(低风险 + 已核验为正常交易),它就是**关闭误报** → 改为"已排除"。
2) **终态缺 closed_at**:那条有 close_reason 却没有 closed_at,自相矛盾;
而且
isk_repository 按 closed_at 统计"当日结案",缺了它会被漏掉。
INSERT 补上该列,样本用 closed_at_offset_minutes 给值。
## 顺带修掉冒烟脚本的撞号
预警编号前缀 ALDEMO → E2E,注释里写明原因 —— 免得下次又操作到种子样本、
还把现象当成新 bug。
## 验证
- ALDEMO0003 现为 status=已排除 + closed_at 有值
- 库里所有终态预警(已排除/已结案)都带 closed_at;进行中的(调查中)不带 —— 口径一致
- 自己创建的两条走 resolve 后 status=已结案 且 closed_at/handle_result 都有值
- 冒烟重跑:40/40 通过,新造预警编号 E2E134351,处置闭环三段全 PASS
门禁:ruff 通过 / mypy 250 文件 0 错 / 单元+契约 1381 passed 2 skipped。
This commit is contained in:
@@ -160,7 +160,10 @@ def create_pending_alert() -> str | None:
|
||||
"""造一条「待处理」预警,供处置闭环验证(库里的常常都已被处置过)。"""
|
||||
|
||||
async def run() -> str:
|
||||
alert_no = f"ALDEMO{datetime.now(UTC).strftime('%H%M%S')}"
|
||||
# 前缀**刻意避开 `ALDEMO`**:那是 `seed_risk_alert_demo_data.py` 的演示样本编号
|
||||
# (ALDEMO0001-0003)。撞号过一次 —— 本脚本"已存在则跳过"的检查会把创建挡掉,
|
||||
# 随后处置全 409,看起来像"结案没写 closed_at",其实是操作了别人的样本。
|
||||
alert_no = f"E2E{datetime.now(UTC).strftime('%H%M%S')}"
|
||||
now = datetime.now(UTC).replace(tzinfo=None)
|
||||
async with SessionFactory() as session:
|
||||
async with session.begin():
|
||||
|
||||
@@ -115,11 +115,18 @@ ALERTS: tuple[dict[str, Any], ...] = (
|
||||
},
|
||||
"priority_score": 20,
|
||||
"event_status": "盘后预警",
|
||||
"status": "已关闭",
|
||||
# ⚠️ 状态必须是**代码能产出**的那几个(待处理 / 调查中 / 已排除 / 已结案)。
|
||||
# 这里原写作「已关闭」,而 `risk_action_service` 里没有任何动作会产生它 ——
|
||||
# 演示时会被当成系统行为、排查时又查不到出处。按 close_reason 的语义
|
||||
# (低风险 + 已核验为正常交易),它其实就是「关闭误报」。
|
||||
"status": "已排除",
|
||||
"ack_status": "已确认",
|
||||
"due_at_offset_minutes": None,
|
||||
"is_escalated": 0,
|
||||
"close_reason": "已核验为本人自动定投计划,属正常交易。",
|
||||
# 终态样本必须带关闭时间:只有 close_reason 却没有 closed_at 是自相矛盾的,
|
||||
# 而且 `risk_repository` 按 closed_at 统计"当日结案",缺了它这条会被漏掉。
|
||||
"closed_at_offset_minutes": -120,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -146,13 +153,14 @@ async def main() -> int:
|
||||
print(f"[跳过] {spec['alert_no']} 已存在")
|
||||
continue
|
||||
offset = spec["due_at_offset_minutes"]
|
||||
closed_offset = spec.get("closed_at_offset_minutes")
|
||||
await cursor.execute(
|
||||
"INSERT INTO fin_risk_alert ("
|
||||
" id, alert_no, customer_id, alert_type, alert_level, trigger_rule_codes,"
|
||||
" evidence_summary, evidence_snapshot, priority_score, event_status, status,"
|
||||
" ack_status, handler_id, due_at, is_escalated, close_reason,"
|
||||
" ack_status, handler_id, due_at, closed_at, is_escalated, close_reason,"
|
||||
" created_at, updated_at"
|
||||
") VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
|
||||
") VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
|
||||
(
|
||||
spec["id"], spec["alert_no"], CUSTOMER_ID, spec["alert_type"],
|
||||
spec["alert_level"], json.dumps(spec["trigger_rule_codes"], ensure_ascii=False),
|
||||
@@ -161,6 +169,7 @@ async def main() -> int:
|
||||
spec["priority_score"], spec["event_status"], spec["status"],
|
||||
spec["ack_status"], HANDLER_ID,
|
||||
(now + dt.timedelta(minutes=offset)) if offset else None,
|
||||
(now + dt.timedelta(minutes=closed_offset)) if closed_offset else None,
|
||||
spec["is_escalated"], spec.get("close_reason"),
|
||||
now, now,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user