Files
group_fqcd_jr/app/service/risk_tools.py
T

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