Files
XingHuo/scripts/demo/rebuild_alerts.py
T

88 lines
3.3 KiB
Python
Raw 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.
#!/usr/bin/env python3
"""按 trade_id 幂等重放风控引擎(B9a 补偿 · trade_gateway engine_error 场景)。
背景(PRD FR-1 / 架构 §5.3):网关 INSERT core_trade 成功后 process_trade_event
异常时交易已成立——响应带 engine_error=true、审计 decision='risk_engine_error',
预警缺失。用本脚本按 trade_id 补放引擎(也可用于人工补放任何一笔已落库交易)。
幂等:trade_id 已存在于任一 risk_alert.payload(含 aml 单与已处置单)即跳过,
不重复出单、不重复 append 事件。重放以 core_trade.traded_at 为引擎事件时点,
RISK-004 窗口判定可复现(engine.py 模块注释口径),不受脚本执行时刻影响。
用法:
python scripts/demo/rebuild_alerts.py TRD-20260907-AB12CD34 [更多 trade_id ...]
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from app.repository.core_ro import CoreReadOnlyRepository # noqa: E402
from app.repository.risk_repository import RiskRepository # noqa: E402
from app.service.risk.engine import process_trade_event # noqa: E402
def rebuild_trade(
trade_id: str,
core: CoreReadOnlyRepository,
repo: RiskRepository,
) -> dict[str, Any]:
"""重放单笔已落库交易(CLI 与单测共用入口)。
返回 {"state": "rebuilt"|"skipped"|"missing", "trade_id", "alert_ids", ...}:
rebuilt 附 triggered_rules/aml_hit;skipped 附已入的预警单 alert_ids;
missing 表示 core_trade 无此流水(未做任何写操作)。
"""
trade = core.get_trade_by_id(trade_id)
if trade is None:
return {"state": "missing", "trade_id": trade_id, "alert_ids": []}
existing = repo.find_alerts_by_trade(trade_id)
if existing:
return {
"state": "skipped",
"trade_id": trade_id,
"alert_ids": [row["alert_id"] for row in existing],
}
result = process_trade_event(trade, core_ro=core, risk_repo=repo)
return {
"state": "rebuilt",
"trade_id": trade_id,
"alert_ids": result["alert_ids"],
"triggered_rules": result["triggered_rules"],
"aml_hit": result["aml_hit"],
}
def main() -> None:
parser = argparse.ArgumentParser(description="按 trade_id 幂等重放风控引擎(引擎异常补偿)")
parser.add_argument("trade_ids", nargs="+", help="core_trade 中的 trade_id,可一次多笔")
args = parser.parse_args()
core = CoreReadOnlyRepository()
repo = RiskRepository()
missing = 0
for tid in args.trade_ids:
out = rebuild_trade(tid, core, repo)
if out["state"] == "rebuilt":
rules = ",".join(out["triggered_rules"]) or "-"
print(f"[rebuilt] {tid}: rules={rules} alerts={out['alert_ids']} aml={out['aml_hit']}")
elif out["state"] == "skipped":
print(f"[skipped] {tid}: 已入预警单 {out['alert_ids']}(幂等跳过)")
else:
print(f"[missing] {tid}: core_trade 无此流水", file=sys.stderr)
missing += 1
if missing:
raise SystemExit(f"{missing}/{len(args.trade_ids)} 笔 trade_id 未找到")
if __name__ == "__main__":
main()