feat: 迁移奶龙风控业务模块与演示文档
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
"""风控 Agent 的只读工具处理器。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from typing import Any
|
||||
|
||||
from app.core.contracts import RequestContext
|
||||
from app.core.risk_contracts import RiskAlertEvidenceQuery, RiskAlertQuery
|
||||
from app.infrastructure.db import SessionFactory
|
||||
from app.repository.fund_query_repository import PageRequest
|
||||
from app.repository.risk_repository import RiskRepository
|
||||
from app.service.risk_judgement_service import (
|
||||
assess_alert_detail,
|
||||
assess_alert_list_item,
|
||||
)
|
||||
from app.service.risk_query_service import scope_from_context
|
||||
|
||||
|
||||
async def search_risk_alerts_tool(
|
||||
arguments: RiskAlertQuery,
|
||||
context: RequestContext,
|
||||
) -> dict[str, Any]:
|
||||
async with SessionFactory() as session:
|
||||
repository = RiskRepository(session, scope=scope_from_context(context))
|
||||
items: list[dict[str, Any]] = []
|
||||
offset = 0
|
||||
while True:
|
||||
page = await repository.list_alerts(
|
||||
customer_no=arguments.customer_no,
|
||||
product_code=arguments.product_code,
|
||||
product_name=arguments.product_name,
|
||||
risk_level=arguments.risk_level,
|
||||
rule_code=arguments.rule_code,
|
||||
start_time=arguments.start_time,
|
||||
end_time=arguments.end_time,
|
||||
page=PageRequest(limit=10, offset=offset),
|
||||
)
|
||||
for item in page.items:
|
||||
record = item.to_dict()
|
||||
record["disposition_hint"] = assess_alert_list_item(record)
|
||||
items.append(record)
|
||||
if page.next_offset is None:
|
||||
return build_alert_search_result(
|
||||
items,
|
||||
arguments.model_dump(mode="json", exclude_none=True),
|
||||
)
|
||||
offset = page.next_offset
|
||||
|
||||
|
||||
def build_alert_search_result(
|
||||
items: list[dict[str, Any]],
|
||||
filters: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""构建完整分组汇总和精简预警明细。"""
|
||||
compact_items = [_compact_alert_item(item) for item in items]
|
||||
rule_counts: Counter[str] = Counter()
|
||||
disposition_counts: Counter[str] = Counter()
|
||||
for item in compact_items:
|
||||
rule_counts.update(item.get("rule_codes") or ())
|
||||
hint = item.get("disposition_hint") or {}
|
||||
if isinstance(hint, dict) and hint.get("verdict"):
|
||||
disposition_counts[str(hint["verdict"])] += 1
|
||||
return {
|
||||
"total": len(compact_items),
|
||||
"filters": filters or {},
|
||||
"summary": {
|
||||
"customer_groups": _customer_groups(compact_items),
|
||||
"product_groups": _product_groups(compact_items),
|
||||
"risk_level_counts": dict(sorted(Counter(
|
||||
str(item.get("risk_level") or "未知")
|
||||
for item in compact_items
|
||||
).items())),
|
||||
"rule_counts": dict(sorted(rule_counts.items())),
|
||||
"disposition_counts": dict(sorted(disposition_counts.items())),
|
||||
"complete": True,
|
||||
},
|
||||
"items": compact_items,
|
||||
}
|
||||
|
||||
|
||||
def _compact_alert_item(item: dict[str, Any]) -> dict[str, Any]:
|
||||
fields = (
|
||||
"alert_no",
|
||||
"customer_no",
|
||||
"customer_name",
|
||||
"product_code",
|
||||
"product_name",
|
||||
"alert_type",
|
||||
"risk_level",
|
||||
"rule_codes",
|
||||
"evidence_summary",
|
||||
"status",
|
||||
"event_status",
|
||||
"created_at",
|
||||
"disposition_hint",
|
||||
)
|
||||
return {field: item.get(field) for field in fields if field in item}
|
||||
|
||||
|
||||
def _customer_groups(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
groups: dict[str, dict[str, Any]] = {}
|
||||
for item in items:
|
||||
customer_no = item.get("customer_no")
|
||||
if not customer_no:
|
||||
continue
|
||||
key = str(customer_no)
|
||||
group = groups.setdefault(
|
||||
key,
|
||||
{
|
||||
"customer_no": key,
|
||||
"customer_name": item.get("customer_name"),
|
||||
"alert_count": 0,
|
||||
"risk_levels": set(),
|
||||
},
|
||||
)
|
||||
group["alert_count"] += 1
|
||||
if item.get("risk_level"):
|
||||
group["risk_levels"].add(str(item["risk_level"]))
|
||||
return [
|
||||
{
|
||||
**group,
|
||||
"risk_levels": sorted(group["risk_levels"]),
|
||||
}
|
||||
for group in sorted(
|
||||
groups.values(),
|
||||
key=lambda value: (-value["alert_count"], value["customer_no"]),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def _product_groups(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
groups: dict[str, dict[str, Any]] = {}
|
||||
for item in items:
|
||||
product_code = item.get("product_code")
|
||||
product_name = item.get("product_name")
|
||||
key = str(product_code or product_name or "")
|
||||
if not key:
|
||||
continue
|
||||
group = groups.setdefault(
|
||||
key,
|
||||
{
|
||||
"product_code": product_code,
|
||||
"product_name": product_name,
|
||||
"alert_count": 0,
|
||||
"risk_levels": set(),
|
||||
},
|
||||
)
|
||||
group["alert_count"] += 1
|
||||
if item.get("risk_level"):
|
||||
group["risk_levels"].add(str(item["risk_level"]))
|
||||
return [
|
||||
{
|
||||
**group,
|
||||
"risk_levels": sorted(group["risk_levels"]),
|
||||
}
|
||||
for group in sorted(
|
||||
groups.values(),
|
||||
key=lambda value: (
|
||||
-value["alert_count"],
|
||||
str(value["product_code"] or value["product_name"]),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def get_risk_overview_tool(
|
||||
arguments: RiskAlertQuery,
|
||||
context: RequestContext,
|
||||
) -> dict[str, Any]:
|
||||
del arguments
|
||||
async with SessionFactory() as session:
|
||||
return await RiskRepository(session, scope=scope_from_context(context)).overview()
|
||||
|
||||
|
||||
async def get_alert_evidence_tool(
|
||||
arguments: RiskAlertEvidenceQuery,
|
||||
context: RequestContext,
|
||||
) -> dict[str, Any] | None:
|
||||
async with SessionFactory() as session:
|
||||
record = await RiskRepository(
|
||||
session,
|
||||
scope=scope_from_context(context),
|
||||
).get_alert_detail(arguments.alert_no)
|
||||
if record is None:
|
||||
return None
|
||||
detail = record.to_dict()
|
||||
detail["disposition_assessment"] = assess_alert_detail(detail)
|
||||
return detail
|
||||
Reference in New Issue
Block a user