190 lines
6.1 KiB
Python
190 lines
6.1 KiB
Python
"""风控 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
|