1005 lines
35 KiB
Python
1005 lines
35 KiB
Python
"""
|
||
风险检测API路由
|
||
提供规则管理、任务管理、检测执行和结果查询接口
|
||
"""
|
||
from typing import List, Dict, Any, Optional
|
||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from loguru import logger
|
||
|
||
from app.database import get_async_session
|
||
from app.schemas.risk_detection import (
|
||
DetectionRuleCreate,
|
||
DetectionRuleUpdate,
|
||
DetectionRuleResponse,
|
||
DetectionTaskCreate,
|
||
DetectionTaskResponse,
|
||
DetectionExecutionRequest,
|
||
DetectionExecutionResponse,
|
||
DetectionResultResponse,
|
||
DetectionSummaryResponse,
|
||
)
|
||
|
||
from app.services.risk_detection.task_manager import TaskManager, DetectionScheduler
|
||
from app.services.entity_service import EntityService
|
||
from app.utils.helpers import get_current_user
|
||
from app.models.user import User
|
||
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.post("/rules", response_model=DetectionRuleResponse, status_code=status.HTTP_201_CREATED)
|
||
async def create_rule(
|
||
rule: DetectionRuleCreate,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
创建检测规则
|
||
"""
|
||
logger.info(f"创建检测规则: {rule.rule_name}")
|
||
|
||
# TODO: 实现创建检测规则的逻辑
|
||
# 这里需要将Schema转换为模型并保存到数据库
|
||
|
||
return {
|
||
"rule_id": "rule_123",
|
||
"rule_name": rule.rule_name,
|
||
"algorithm_code": rule.algorithm_code,
|
||
"description": rule.description,
|
||
"parameters": rule.parameters,
|
||
"is_enabled": rule.is_enabled,
|
||
"created_at": "2024-01-01T00:00:00",
|
||
}
|
||
|
||
|
||
@router.get("/rules", response_model=List[DetectionRuleResponse])
|
||
async def list_rules(
|
||
algorithm_code: Optional[str] = Query(None, description="算法编码"),
|
||
is_enabled: Optional[bool] = Query(None, description="是否启用"),
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
查询检测规则列表
|
||
"""
|
||
logger.info(f"查询检测规则: algorithm={algorithm_code}, enabled={is_enabled}")
|
||
|
||
# 获取算法列表并转换为规则格式
|
||
algorithms = await get_algorithm_list()
|
||
rules = []
|
||
|
||
for algo in algorithms:
|
||
# 根据算法代码过滤
|
||
if algorithm_code and algo['code'] != algorithm_code:
|
||
continue
|
||
|
||
# 创建规则对象
|
||
rule = {
|
||
"rule_id": f"rule_{algo['code'].lower()}",
|
||
"rule_name": algo['name'],
|
||
"algorithm_code": algo['code'],
|
||
"description": algo.get('description', ''),
|
||
"parameters": {param['name']: param.get('default', '') for param in algo.get('parameters', [])},
|
||
"is_enabled": is_enabled if is_enabled is not None else True,
|
||
"created_at": "2024-01-01T00:00:00",
|
||
}
|
||
rules.append(rule)
|
||
|
||
logger.info(f"返回规则列表,共 {len(rules)} 个规则")
|
||
return rules
|
||
|
||
|
||
async def get_algorithm_list():
|
||
"""
|
||
获取算法列表(模拟)
|
||
在实际项目中,这些数据可能存储在配置文件或数据库中
|
||
"""
|
||
algorithms = [
|
||
{
|
||
"code": "REVENUE_INTEGRITY_CHECK",
|
||
"name": "收入完整性检测",
|
||
"description": "检测平台充值与申报收入的匹配度",
|
||
"parameters": [
|
||
{
|
||
"name": "streamer_id",
|
||
"label": "主播ID",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入主播ID",
|
||
"default": "",
|
||
},
|
||
{
|
||
"name": "comparison_type",
|
||
"label": "比较类型",
|
||
"type": "select",
|
||
"required": False,
|
||
"default": "monthly",
|
||
"options": [
|
||
{"label": "月度", "value": "monthly"},
|
||
{"label": "季度", "value": "quarterly"},
|
||
{"label": "年度", "value": "yearly"}
|
||
],
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "PRIVATE_ACCOUNT_DETECTION",
|
||
"name": "私户收款检测",
|
||
"description": "识别使用私人账户收款的风险",
|
||
"parameters": [
|
||
{
|
||
"name": "account_no",
|
||
"label": "银行账号",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入银行账号",
|
||
"default": "",
|
||
},
|
||
{
|
||
"name": "threshold_amount",
|
||
"label": "私户转账金额阈值",
|
||
"type": "number",
|
||
"required": False,
|
||
"default": 10000,
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "INVOICE_FRAUD_DETECTION",
|
||
"name": "发票虚开检测",
|
||
"description": "检查发票与实际业务的匹配度",
|
||
"parameters": [
|
||
{
|
||
"name": "seller_tax_no",
|
||
"label": "销售方税号",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入销售方税号",
|
||
"default": "",
|
||
},
|
||
{
|
||
"name": "threshold_rate",
|
||
"label": "发票与订单差异率阈值",
|
||
"type": "number",
|
||
"required": False,
|
||
"default": 0.1,
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "EXPENSE_ANOMALY_DETECTION",
|
||
"name": "成本费用异常检测",
|
||
"description": "识别虚增成本、费用异常等问题",
|
||
"parameters": [
|
||
{
|
||
"name": "entity_id",
|
||
"label": "实体ID",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入实体ID",
|
||
"default": "",
|
||
},
|
||
{
|
||
"name": "entity_type",
|
||
"label": "实体类型",
|
||
"type": "select",
|
||
"required": False,
|
||
"default": "streamer",
|
||
"options": [
|
||
{"label": "主播", "value": "streamer"},
|
||
{"label": "MCN机构", "value": "mcn"},
|
||
{"label": "纳税人", "value": "taxpayer"}
|
||
],
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "TAX_RISK_ASSESSMENT",
|
||
"name": "税务风险综合评估",
|
||
"description": "综合分析各项检测结果",
|
||
"parameters": [
|
||
{
|
||
"name": "entity_id",
|
||
"label": "实体ID",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入实体ID",
|
||
"default": "",
|
||
},
|
||
{
|
||
"name": "include_algorithms",
|
||
"label": "包含算法",
|
||
"type": "checkbox-group",
|
||
"required": False,
|
||
"default": ["REVENUE_INTEGRITY_CHECK", "PRIVATE_ACCOUNT_DETECTION"],
|
||
"options": [
|
||
{"label": "收入完整性检测", "value": "REVENUE_INTEGRITY_CHECK"},
|
||
{"label": "私户收款检测", "value": "PRIVATE_ACCOUNT_DETECTION"},
|
||
{"label": "发票虚开检测", "value": "INVOICE_FRAUD_DETECTION"},
|
||
{"label": "成本费用异常检测", "value": "EXPENSE_ANOMALY_DETECTION"}
|
||
],
|
||
}
|
||
]
|
||
}
|
||
]
|
||
|
||
return algorithms
|
||
|
||
|
||
@router.get("/rules/{rule_id}", response_model=DetectionRuleResponse)
|
||
async def get_rule(
|
||
rule_id: str,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
获取检测规则详情
|
||
"""
|
||
logger.info(f"获取检测规则: {rule_id}")
|
||
|
||
# TODO: 实现获取检测规则的逻辑
|
||
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"规则不存在: {rule_id}",
|
||
)
|
||
|
||
|
||
@router.put("/rules/{rule_id}", response_model=DetectionRuleResponse)
|
||
async def update_rule(
|
||
rule_id: str,
|
||
rule_update: DetectionRuleUpdate,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
更新检测规则
|
||
"""
|
||
logger.info(f"更新检测规则: {rule_id}")
|
||
|
||
# TODO: 实现更新检测规则的逻辑
|
||
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"规则不存在: {rule_id}",
|
||
)
|
||
|
||
|
||
@router.delete("/rules/{rule_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||
async def delete_rule(
|
||
rule_id: str,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
删除检测规则
|
||
"""
|
||
logger.info(f"删除检测规则: {rule_id}")
|
||
|
||
# TODO: 实现删除检测规则的逻辑
|
||
|
||
|
||
@router.post("/tasks", response_model=DetectionTaskResponse, status_code=status.HTTP_201_CREATED)
|
||
async def create_task(
|
||
task: DetectionTaskCreate,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
current_user: User = Depends(get_current_user),
|
||
):
|
||
"""
|
||
创建检测任务
|
||
"""
|
||
logger.info(f"创建检测任务: {task.task_name}")
|
||
|
||
try:
|
||
from app.models.risk_detection import TaskType
|
||
|
||
# 处理自动实体绑定
|
||
entity_ids = task.entity_ids
|
||
entity_type = task.entity_type
|
||
|
||
# 如果没有提供实体信息,自动绑定
|
||
if not entity_ids or not entity_type:
|
||
logger.info(f"用户 {current_user.username} 未提供实体信息,使用自动绑定")
|
||
entity_service = EntityService(db)
|
||
|
||
# 获取用户关联的实体
|
||
user_entities = await entity_service.get_user_entities(current_user)
|
||
|
||
if not user_entities:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail=f"用户 {current_user.username} 未关联任何实体,请联系管理员设置用户实体关联"
|
||
)
|
||
|
||
entity_ids = user_entities
|
||
entity_type = "streamer" if current_user.entity_type == "streamer" else "mcn"
|
||
logger.info(f"自动绑定实体: {entity_ids}, 类型: {entity_type}")
|
||
|
||
task_manager = TaskManager(db)
|
||
|
||
created_task = await task_manager.create_task(
|
||
task_name=task.task_name,
|
||
task_type=TaskType(task.task_type),
|
||
entity_ids=entity_ids,
|
||
entity_type=entity_type,
|
||
period=task.period,
|
||
rule_ids=task.rule_ids,
|
||
parameters=task.parameters,
|
||
)
|
||
|
||
return {
|
||
"task_id": created_task.task_id,
|
||
"task_name": created_task.task_name,
|
||
"task_type": created_task.task_type.value,
|
||
"status": created_task.status.value,
|
||
"entity_type": created_task.entity_type,
|
||
"period": created_task.period,
|
||
"total_entities": created_task.total_entities,
|
||
"processed_entities": created_task.processed_entities,
|
||
"result_count": created_task.result_count,
|
||
"parameters": created_task.parameters,
|
||
"created_at": created_task.created_at.isoformat(),
|
||
"started_at": created_task.started_at.isoformat() if created_task.started_at else None,
|
||
"completed_at": created_task.completed_at.isoformat() if created_task.completed_at else None,
|
||
}
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"创建检测任务失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"创建任务失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.post("/tasks/{task_id}/execute", response_model=DetectionExecutionResponse)
|
||
async def execute_task(
|
||
task_id: str,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
执行检测任务
|
||
"""
|
||
logger.info(f"执行检测任务: {task_id}")
|
||
|
||
try:
|
||
task_manager = TaskManager(db)
|
||
|
||
result = await task_manager.execute_task(task_id)
|
||
|
||
return {
|
||
"task_id": result["task_id"],
|
||
"status": result["status"],
|
||
"summary": result["summary"],
|
||
"result_count": len(result.get("results", [])),
|
||
"executed_at": result["executed_at"],
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行检测任务失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"执行任务失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/tasks", response_model=List[DetectionTaskResponse])
|
||
async def list_tasks(
|
||
status: Optional[str] = Query(None, description="任务状态"),
|
||
task_type: Optional[str] = Query(None, description="任务类型"),
|
||
limit: int = Query(100, description="返回数量限制"),
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
查询检测任务列表
|
||
"""
|
||
logger.info(f"查询检测任务: status={status}, type={task_type}")
|
||
|
||
try:
|
||
task_manager = TaskManager(db)
|
||
|
||
from app.models.risk_detection import TaskStatus, TaskType
|
||
|
||
status_filter = TaskStatus(status) if status else None
|
||
type_filter = TaskType(task_type) if task_type else None
|
||
|
||
tasks = await task_manager.list_tasks(
|
||
status=status_filter,
|
||
task_type=type_filter,
|
||
limit=limit,
|
||
)
|
||
|
||
return [
|
||
{
|
||
"task_id": task.task_id,
|
||
"task_name": task.task_name,
|
||
"task_type": task.task_type.value,
|
||
"status": task.status.value,
|
||
"entity_type": task.entity_type,
|
||
"period": task.period,
|
||
"total_entities": task.total_entities,
|
||
"processed_entities": task.processed_entities,
|
||
"result_count": task.result_count,
|
||
"parameters": task.parameters,
|
||
"created_at": task.created_at.isoformat(),
|
||
"started_at": task.started_at.isoformat() if task.started_at else None,
|
||
"completed_at": task.completed_at.isoformat() if task.completed_at else None,
|
||
}
|
||
for task in tasks
|
||
]
|
||
|
||
except Exception as e:
|
||
logger.error(f"查询检测任务失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"查询任务失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/tasks/{task_id}", response_model=DetectionTaskResponse)
|
||
async def get_task(
|
||
task_id: str,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
获取检测任务详情
|
||
"""
|
||
logger.info(f"获取检测任务: {task_id}")
|
||
|
||
try:
|
||
task_manager = TaskManager(db)
|
||
task = await task_manager.get_task(task_id)
|
||
|
||
if not task:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"任务不存在: {task_id}",
|
||
)
|
||
|
||
return {
|
||
"task_id": task.task_id,
|
||
"task_name": task.task_name,
|
||
"task_type": task.task_type.value,
|
||
"status": task.status.value,
|
||
"entity_type": task.entity_type,
|
||
"period": task.period,
|
||
"total_entities": task.total_entities,
|
||
"processed_entities": task.processed_entities,
|
||
"result_count": task.result_count,
|
||
"summary": task.summary,
|
||
"parameters": task.parameters,
|
||
"error_message": task.error_message,
|
||
"created_at": task.created_at.isoformat(),
|
||
"started_at": task.started_at.isoformat() if task.started_at else None,
|
||
"completed_at": task.completed_at.isoformat() if task.completed_at else None,
|
||
}
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"获取检测任务失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"获取任务失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.post("/execute", response_model=DetectionExecutionResponse)
|
||
async def execute_detection(
|
||
request: DetectionExecutionRequest,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
current_user: User = Depends(get_current_user),
|
||
):
|
||
"""
|
||
执行风险检测
|
||
自动获取当前用户关联的所有实体进行检测
|
||
"""
|
||
logger.info(
|
||
f"执行风险检测: user={current_user.username}, "
|
||
f"entity={request.entity_id}, period={request.period}, rules={len(request.rule_ids)}"
|
||
)
|
||
|
||
try:
|
||
from app.models.risk_detection import TaskType
|
||
|
||
# 获取实体服务
|
||
entity_service = EntityService(db)
|
||
|
||
# 如果请求中未提供entity_id,自动使用当前用户的实体
|
||
target_entity_id = request.entity_id
|
||
target_entity_type = request.entity_type
|
||
|
||
# 如果未提供entity_id,自动从当前用户获取
|
||
if not target_entity_id:
|
||
if not current_user.entity_id or not current_user.entity_type:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="当前用户未关联任何实体,请先完成企业认证"
|
||
)
|
||
|
||
target_entity_id = current_user.entity_id
|
||
target_entity_type = current_user.entity_type
|
||
|
||
logger.info(
|
||
f"自动使用当前用户的实体: {target_entity_id} ({target_entity_type})"
|
||
)
|
||
|
||
# 获取当前用户的实体列表(用于检测范围限制)
|
||
user_entities = await entity_service.get_user_entities_by_user(current_user)
|
||
if not user_entities:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="当前用户没有可检测的实体"
|
||
)
|
||
|
||
logger.info(
|
||
f"当前用户共有 {len(user_entities)} 个实体,"
|
||
f"将检测实体: {target_entity_id}"
|
||
)
|
||
|
||
# 如果是MCN用户且未指定entity_id,检测所有关联主播
|
||
if current_user.entity_type == "mcn" and not request.entity_id:
|
||
# 检测所有主播
|
||
entity_ids = [
|
||
e["entity_id"]
|
||
for e in user_entities
|
||
if e["entity_type"] == "streamer"
|
||
]
|
||
if not entity_ids:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail="当前MCN机构下没有活跃的主播"
|
||
)
|
||
|
||
logger.info(f"MCN用户自动检测所有主播: {len(entity_ids)} 个")
|
||
|
||
# 批量检测所有主播
|
||
task_manager = TaskManager(db)
|
||
task = await task_manager.create_task(
|
||
task_name=request.task_name
|
||
or f"即时检测-{current_user.username}-{request.period}",
|
||
task_type=TaskType.ON_DEMAND,
|
||
entity_ids=entity_ids,
|
||
entity_type="streamer", # 批量检测都是主播类型
|
||
period=request.period,
|
||
rule_ids=request.rule_ids,
|
||
parameters=request.parameters,
|
||
)
|
||
|
||
# 执行任务
|
||
result = await task_manager.execute_task(task.task_id)
|
||
|
||
return {
|
||
"task_id": result["task_id"],
|
||
"status": result["status"],
|
||
"summary": result["summary"],
|
||
"result_count": len(result.get("results", [])),
|
||
"executed_at": result["executed_at"],
|
||
}
|
||
else:
|
||
# 检测单个实体(主播或MCN)
|
||
task_manager = TaskManager(db)
|
||
task = await task_manager.create_task(
|
||
task_name=request.task_name
|
||
or f"即时检测-{target_entity_id}-{request.period}",
|
||
task_type=TaskType.ON_DEMAND,
|
||
entity_ids=[target_entity_id],
|
||
entity_type=target_entity_type,
|
||
period=request.period,
|
||
rule_ids=request.rule_ids,
|
||
parameters=request.parameters,
|
||
)
|
||
|
||
# 执行任务
|
||
result = await task_manager.execute_task(task.task_id)
|
||
|
||
return {
|
||
"task_id": result["task_id"],
|
||
"status": result["status"],
|
||
"summary": result["summary"],
|
||
"result_count": len(result.get("results", [])),
|
||
"executed_at": result["executed_at"],
|
||
}
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"执行风险检测失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"执行检测失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/results", response_model=List[DetectionResultResponse])
|
||
async def list_results(
|
||
task_id: Optional[str] = Query(None, description="任务ID"),
|
||
entity_id: Optional[str] = Query(None, description="实体ID"),
|
||
entity_type: Optional[str] = Query(None, description="实体类型"),
|
||
risk_level: Optional[str] = Query(None, description="风险等级"),
|
||
limit: int = Query(100, description="返回数量限制"),
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
查询检测结果列表
|
||
"""
|
||
from app.models.risk_detection import DetectionResult
|
||
from sqlalchemy import select, and_
|
||
|
||
logger.info(
|
||
f"查询检测结果: task={task_id}, entity={entity_id}, "
|
||
f"level={risk_level}, limit={limit}"
|
||
)
|
||
|
||
try:
|
||
# 构建查询条件
|
||
conditions = []
|
||
if task_id:
|
||
conditions.append(DetectionResult.task_id == task_id)
|
||
if entity_id:
|
||
conditions.append(DetectionResult.entity_id == entity_id)
|
||
if entity_type:
|
||
conditions.append(DetectionResult.entity_type == entity_type)
|
||
if risk_level:
|
||
conditions.append(DetectionResult.risk_level == risk_level)
|
||
|
||
# 执行查询
|
||
stmt = select(DetectionResult)
|
||
if conditions:
|
||
stmt = stmt.where(and_(*conditions))
|
||
stmt = stmt.order_by(DetectionResult.detected_at.desc()).limit(limit)
|
||
|
||
result = await db.execute(stmt)
|
||
results = result.scalars().all()
|
||
|
||
# 转换为响应格式
|
||
response_list = []
|
||
for r in results:
|
||
# 构建结果项
|
||
result_item = {
|
||
"entity_id": r.entity_id,
|
||
"entity_type": r.entity_type,
|
||
"risk_level": r.risk_level.value if r.risk_level else "UNKNOWN",
|
||
"risk_score": r.risk_score or 0.0,
|
||
"description": r.description or "",
|
||
"suggestion": r.suggestion or "",
|
||
"risk_data": r.risk_data or {},
|
||
"evidence": r.evidence or [],
|
||
"detected_at": r.detected_at.isoformat() if r.detected_at else None,
|
||
}
|
||
|
||
response_list.append({
|
||
"id": r.id,
|
||
"task_id": r.task_id,
|
||
"rule_id": r.rule_id or "", # 如果为None则返回空字符串
|
||
"result": result_item
|
||
})
|
||
|
||
logger.info(f"查询到 {len(response_list)} 条检测结果")
|
||
return response_list
|
||
|
||
except Exception as e:
|
||
logger.error(f"查询检测结果失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"查询检测结果失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/results/{result_id}", response_model=DetectionResultResponse)
|
||
async def get_result(
|
||
result_id: int,
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
获取检测结果详情
|
||
"""
|
||
from app.models.risk_detection import DetectionResult
|
||
from sqlalchemy import select
|
||
|
||
logger.info(f"获取检测结果: {result_id}")
|
||
|
||
try:
|
||
stmt = select(DetectionResult).where(DetectionResult.id == result_id)
|
||
result = await db.execute(stmt)
|
||
r = result.scalar_one_or_none()
|
||
|
||
if not r:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"结果不存在: {result_id}",
|
||
)
|
||
|
||
# 构建结果项
|
||
result_item = {
|
||
"entity_id": r.entity_id,
|
||
"entity_type": r.entity_type,
|
||
"risk_level": r.risk_level.value if r.risk_level else "UNKNOWN",
|
||
"risk_score": r.risk_score or 0.0,
|
||
"description": r.description or "",
|
||
"suggestion": r.suggestion or "",
|
||
"risk_data": r.risk_data or {},
|
||
"evidence": r.evidence or [],
|
||
"detected_at": r.detected_at.isoformat() if r.detected_at else None,
|
||
}
|
||
|
||
return {
|
||
"id": r.id,
|
||
"task_id": r.task_id,
|
||
"rule_id": r.rule_id or "", # 如果为None则返回空字符串
|
||
"result": result_item
|
||
}
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"获取检测结果失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"获取检测结果失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/summary", response_model=DetectionSummaryResponse)
|
||
async def get_detection_summary(
|
||
entity_id: str = Query(..., description="实体ID"),
|
||
entity_type: str = Query(..., description="实体类型"),
|
||
period: str = Query(..., description="检测期间"),
|
||
db: AsyncSession = Depends(get_async_session),
|
||
):
|
||
"""
|
||
获取检测结果汇总
|
||
"""
|
||
from app.models.risk_detection import DetectionResult, DetectionTask
|
||
from sqlalchemy import select, and_, func, distinct
|
||
|
||
logger.info(f"获取检测汇总: entity={entity_id}, period={period}")
|
||
|
||
try:
|
||
# 查询该实体在指定期间的所有检测结果
|
||
stmt = select(DetectionResult).where(
|
||
and_(
|
||
DetectionResult.entity_id == entity_id,
|
||
DetectionResult.entity_type == entity_type,
|
||
)
|
||
)
|
||
|
||
result = await db.execute(stmt)
|
||
results = result.scalars().all()
|
||
|
||
if not results:
|
||
return {
|
||
"entity_id": entity_id,
|
||
"entity_type": entity_type,
|
||
"period": period,
|
||
"total_detections": 0,
|
||
"risk_distribution": {},
|
||
"avg_risk_score": 0.0,
|
||
"high_risk_count": 0,
|
||
"recommendations": [],
|
||
}
|
||
|
||
# 统计风险分布
|
||
risk_distribution = {}
|
||
total_score = 0.0
|
||
high_risk_count = 0
|
||
|
||
for r in results:
|
||
level = r.risk_level.value if r.risk_level else "UNKNOWN"
|
||
risk_distribution[level] = risk_distribution.get(level, 0) + 1
|
||
|
||
score = r.risk_score or 0.0
|
||
total_score += score
|
||
|
||
if level in ["CRITICAL", "HIGH"]:
|
||
high_risk_count += 1
|
||
|
||
avg_risk_score = total_score / len(results) if results else 0.0
|
||
|
||
# 生成建议
|
||
recommendations = []
|
||
if risk_distribution.get("CRITICAL", 0) > 0:
|
||
recommendations.append("发现极高风险项目,建议立即处理")
|
||
if risk_distribution.get("HIGH", 0) > 0:
|
||
recommendations.append("发现高风险项目,建议优先处理")
|
||
if risk_distribution.get("MEDIUM", 0) > 0:
|
||
recommendations.append("发现中等风险项目,建议安排时间处理")
|
||
if risk_distribution.get("LOW", 0) > 0:
|
||
recommendations.append("存在低风险项目,建议定期关注")
|
||
if not recommendations:
|
||
recommendations.append("未发现明显风险,继续保持")
|
||
|
||
return {
|
||
"entity_id": entity_id,
|
||
"entity_type": entity_type,
|
||
"period": period,
|
||
"total_detections": len(results),
|
||
"risk_distribution": risk_distribution,
|
||
"avg_risk_score": round(avg_risk_score, 2),
|
||
"high_risk_count": high_risk_count,
|
||
"recommendations": recommendations,
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取检测汇总失败: {str(e)}", exc_info=True)
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"获取检测汇总失败: {str(e)}",
|
||
)
|
||
|
||
|
||
@router.get("/algorithms", response_model=List[Dict[str, Any]])
|
||
async def get_algorithms():
|
||
"""
|
||
获取算法列表和参数配置
|
||
"""
|
||
logger.info("获取算法列表")
|
||
|
||
algorithms = [
|
||
{
|
||
"code": "REVENUE_INTEGRITY_CHECK",
|
||
"name": "收入完整性检测",
|
||
"description": "检测平台充值与申报收入的匹配度",
|
||
"parameters": [
|
||
{
|
||
"name": "streamer_id",
|
||
"label": "主播ID",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入主播ID",
|
||
"description": "要检测的主播唯一标识"
|
||
},
|
||
{
|
||
"name": "comparison_type",
|
||
"label": "比较类型",
|
||
"type": "select",
|
||
"required": False,
|
||
"default": "monthly",
|
||
"options": [
|
||
{"label": "月度", "value": "monthly"},
|
||
{"label": "季度", "value": "quarterly"},
|
||
{"label": "年度", "value": "yearly"}
|
||
],
|
||
"description": "收入比较的时间维度"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "PRIVATE_ACCOUNT_DETECTION",
|
||
"name": "私户收款检测",
|
||
"description": "识别使用私人账户收款的风险",
|
||
"parameters": [
|
||
{
|
||
"name": "account_no",
|
||
"label": "银行账号",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入银行账号",
|
||
"description": "要检测的银行账号"
|
||
},
|
||
{
|
||
"name": "threshold_amount",
|
||
"label": "私户转账金额阈值(元)",
|
||
"type": "number",
|
||
"required": False,
|
||
"default": 10000,
|
||
"min": 0,
|
||
"step": 1000,
|
||
"description": "超过此金额的私户转账将被标记为风险"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "INVOICE_FRAUD_DETECTION",
|
||
"name": "发票虚开检测",
|
||
"description": "检查发票与实际业务的匹配度",
|
||
"parameters": [
|
||
{
|
||
"name": "seller_tax_no",
|
||
"label": "销售方税号",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入销售方税号",
|
||
"description": "销售方的税务登记号"
|
||
},
|
||
{
|
||
"name": "threshold_rate",
|
||
"label": "发票与订单差异率阈值",
|
||
"type": "number",
|
||
"required": False,
|
||
"default": 0.1,
|
||
"min": 0,
|
||
"max": 1,
|
||
"step": 0.05,
|
||
"description": "超过此差异率将被标记为异常"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "EXPENSE_ANOMALY_DETECTION",
|
||
"name": "成本费用异常检测",
|
||
"description": "识别虚增成本、费用异常等问题",
|
||
"parameters": [
|
||
{
|
||
"name": "entity_id",
|
||
"label": "实体ID",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入实体ID",
|
||
"description": "要检测的实体唯一标识"
|
||
},
|
||
{
|
||
"name": "entity_type",
|
||
"label": "实体类型",
|
||
"type": "select",
|
||
"required": False,
|
||
"default": "streamer",
|
||
"options": [
|
||
{"label": "主播", "value": "streamer"},
|
||
{"label": "MCN机构", "value": "mcn"},
|
||
{"label": "纳税人", "value": "taxpayer"}
|
||
],
|
||
"description": "实体的类型"
|
||
},
|
||
{
|
||
"name": "threshold_multiplier",
|
||
"label": "异常倍数阈值",
|
||
"type": "number",
|
||
"required": False,
|
||
"default": 2.0,
|
||
"min": 1,
|
||
"step": 0.5,
|
||
"description": "费用增长超过此倍数将被标记为异常"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"code": "TAX_RISK_ASSESSMENT",
|
||
"name": "税务风险综合评估",
|
||
"description": "综合分析各项检测结果",
|
||
"parameters": [
|
||
{
|
||
"name": "entity_id",
|
||
"label": "实体ID",
|
||
"type": "string",
|
||
"required": True,
|
||
"placeholder": "请输入实体ID",
|
||
"description": "要检测的实体唯一标识"
|
||
},
|
||
{
|
||
"name": "entity_type",
|
||
"label": "实体类型",
|
||
"type": "select",
|
||
"required": False,
|
||
"default": "streamer",
|
||
"options": [
|
||
{"label": "主播", "value": "streamer"},
|
||
{"label": "MCN机构", "value": "mcn"},
|
||
{"label": "纳税人", "value": "taxpayer"}
|
||
],
|
||
"description": "实体的类型"
|
||
},
|
||
{
|
||
"name": "include_algorithms",
|
||
"label": "包含算法",
|
||
"type": "checkbox-group",
|
||
"required": False,
|
||
"default": [
|
||
"REVENUE_INTEGRITY_CHECK",
|
||
"PRIVATE_ACCOUNT_DETECTION",
|
||
"INVOICE_FRAUD_DETECTION",
|
||
"EXPENSE_ANOMALY_DETECTION"
|
||
],
|
||
"options": [
|
||
{"label": "收入完整性检测", "value": "REVENUE_INTEGRITY_CHECK"},
|
||
{"label": "私户收款检测", "value": "PRIVATE_ACCOUNT_DETECTION"},
|
||
{"label": "发票虚开检测", "value": "INVOICE_FRAUD_DETECTION"},
|
||
{"label": "成本费用异常检测", "value": "EXPENSE_ANOMALY_DETECTION"}
|
||
],
|
||
"description": "要综合评估的算法列表"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
|
||
logger.info(f"返回算法列表,共 {len(algorithms)} 个算法")
|
||
return algorithms
|