Files
X-Financial/server/src/app/api/v1/endpoints/risk_observations.py

147 lines
4.9 KiB
Python
Raw Normal View History

from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
2026-07-13 14:45:36 +08:00
from app.api.deps import get_current_user, get_db
from app.schemas.common import ErrorResponse
from app.schemas.risk_observation import (
RiskObservationDashboardRead,
RiskObservationFeedbackCreate,
RiskObservationFeedbackRead,
RiskObservationListRead,
RiskObservationRead,
)
from app.services.risk_observations import RiskObservationService
2026-07-13 14:45:36 +08:00
router = APIRouter(prefix="/risk-observations", dependencies=[Depends(get_current_user)])
DbSession = Annotated[Session, Depends(get_db)]
@router.get(
"",
response_model=RiskObservationListRead,
summary="查询风险观察列表",
description="按单据、风险等级、风险信号、状态和来源筛选统一风险观察池。",
)
def list_risk_observations(
db: DbSession,
claim_id: Annotated[str | None, Query(max_length=80)] = None,
run_id: Annotated[str | None, Query(max_length=80)] = None,
execution_log_id: Annotated[str | None, Query(max_length=80)] = None,
risk_level: Annotated[str | None, Query(max_length=20)] = None,
risk_signal: Annotated[str | None, Query(max_length=100)] = None,
status_value: Annotated[
str | None,
Query(alias="status", max_length=30),
] = None,
source: Annotated[str | None, Query(max_length=60)] = None,
limit: Annotated[int, Query(ge=1, le=200)] = 50,
offset: Annotated[int, Query(ge=0)] = 0,
) -> RiskObservationListRead:
items, total = RiskObservationService(db).list_observations(
claim_id=claim_id,
run_id=run_id,
execution_log_id=execution_log_id,
risk_level=risk_level,
risk_signal=risk_signal,
status=status_value,
source=source,
limit=limit,
offset=offset,
)
return RiskObservationListRead(items=items, total=total, limit=limit, offset=offset)
@router.get(
"/dashboard",
response_model=RiskObservationDashboardRead,
summary="查询风险看板聚合",
description="返回风险观察池的总量、分布、算法效果和近期高风险记录。",
)
def summarize_risk_observations(
db: DbSession,
window_days: Annotated[int, Query(ge=1, le=365)] = 30,
limit: Annotated[int, Query(ge=1, le=2000)] = 500,
) -> RiskObservationDashboardRead:
return RiskObservationService(db).summarize_dashboard(
window_days=window_days,
limit=limit,
)
@router.get(
"/claim/{claim_id}",
response_model=list[RiskObservationRead],
summary="查询单据风险观察",
description="按报销单 ID 返回该单据关联的风险观察,供单据详情证据链使用。",
)
def list_claim_risk_observations(claim_id: str, db: DbSession) -> list[RiskObservationRead]:
return RiskObservationService(db).list_claim_observations(claim_id)
@router.get(
"/execution-log/{execution_log_id}",
response_model=list[RiskObservationRead],
summary="查询数字员工工作记录风险观察",
description="按数字员工执行日志 ID 返回本次任务生成的风险观察。",
)
def list_execution_log_risk_observations(
execution_log_id: str,
db: DbSession,
) -> list[RiskObservationRead]:
return RiskObservationService(db).list_execution_log_observations(execution_log_id)
@router.get(
"/{observation_key_or_id}",
response_model=RiskObservationRead,
summary="读取风险观察详情",
description="按观察 key 或 ID 返回风险评分、证据链、图谱节点、制度引用和决策追踪。",
responses={
status.HTTP_404_NOT_FOUND: {
"model": ErrorResponse,
"description": "风险观察不存在。",
}
},
)
def get_risk_observation(
observation_key_or_id: str,
db: DbSession,
) -> RiskObservationRead:
observation = RiskObservationService(db).get_observation(observation_key_or_id)
if observation is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Risk observation not found",
)
return observation
@router.post(
"/{observation_key_or_id}/feedback",
response_model=RiskObservationFeedbackRead,
summary="写入风险观察反馈",
description="记录人工确认、误报、忽略、已处理或备注反馈,并同步更新观察状态。",
responses={
status.HTTP_404_NOT_FOUND: {
"model": ErrorResponse,
"description": "风险观察不存在。",
}
},
)
def create_risk_observation_feedback(
observation_key_or_id: str,
payload: RiskObservationFeedbackCreate,
db: DbSession,
) -> RiskObservationFeedbackRead:
try:
return RiskObservationService(db).create_feedback(observation_key_or_id, payload)
except LookupError:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Risk observation not found",
) from None