diff --git a/tools/e2e_smoke_test.py b/tools/e2e_smoke_test.py index 9eee92f..475ddf8 100644 --- a/tools/e2e_smoke_test.py +++ b/tools/e2e_smoke_test.py @@ -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(): diff --git a/tools/seed_risk_alert_demo_data.py b/tools/seed_risk_alert_demo_data.py index 4d301ba..5418787 100644 --- a/tools/seed_risk_alert_demo_data.py +++ b/tools/seed_risk_alert_demo_data.py @@ -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, ),