feat(ai): add tenant-safe hierarchical expense learning

This commit is contained in:
caoxiaozhu
2026-07-16 14:30:41 +08:00
parent 6bdf65bc24
commit ee88a36baf
65 changed files with 6909 additions and 232 deletions

View File

@@ -5,7 +5,7 @@ from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from app.api.deps import get_current_user, get_db
from app.api.deps import CurrentUserContext, get_current_user, get_db
from app.schemas.common import ErrorResponse
from app.schemas.risk_observation import (
RiskObservationDashboardRead,
@@ -16,8 +16,9 @@ from app.schemas.risk_observation import (
)
from app.services.risk_observations import RiskObservationService
router = APIRouter(prefix="/risk-observations", dependencies=[Depends(get_current_user)])
router = APIRouter(prefix="/risk-observations")
DbSession = Annotated[Session, Depends(get_db)]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]
@router.get(
@@ -28,6 +29,7 @@ DbSession = Annotated[Session, Depends(get_db)]
)
def list_risk_observations(
db: DbSession,
current_user: CurrentUser,
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,
@@ -42,6 +44,7 @@ def list_risk_observations(
offset: Annotated[int, Query(ge=0)] = 0,
) -> RiskObservationListRead:
items, total = RiskObservationService(db).list_observations(
tenant_id=current_user.tenant_id,
claim_id=claim_id,
run_id=run_id,
execution_log_id=execution_log_id,
@@ -63,10 +66,12 @@ def list_risk_observations(
)
def summarize_risk_observations(
db: DbSession,
current_user: CurrentUser,
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(
tenant_id=current_user.tenant_id,
window_days=window_days,
limit=limit,
)
@@ -78,8 +83,15 @@ def summarize_risk_observations(
summary="查询单据风险观察",
description="按报销单 ID 返回该单据关联的风险观察,供单据详情证据链使用。",
)
def list_claim_risk_observations(claim_id: str, db: DbSession) -> list[RiskObservationRead]:
return RiskObservationService(db).list_claim_observations(claim_id)
def list_claim_risk_observations(
claim_id: str,
db: DbSession,
current_user: CurrentUser,
) -> list[RiskObservationRead]:
return RiskObservationService(db).list_claim_observations(
claim_id,
tenant_id=current_user.tenant_id,
)
@router.get(
@@ -91,8 +103,12 @@ def list_claim_risk_observations(claim_id: str, db: DbSession) -> list[RiskObser
def list_execution_log_risk_observations(
execution_log_id: str,
db: DbSession,
current_user: CurrentUser,
) -> list[RiskObservationRead]:
return RiskObservationService(db).list_execution_log_observations(execution_log_id)
return RiskObservationService(db).list_execution_log_observations(
execution_log_id,
tenant_id=current_user.tenant_id,
)
@router.get(
@@ -110,8 +126,12 @@ def list_execution_log_risk_observations(
def get_risk_observation(
observation_key_or_id: str,
db: DbSession,
current_user: CurrentUser,
) -> RiskObservationRead:
observation = RiskObservationService(db).get_observation(observation_key_or_id)
observation = RiskObservationService(db).get_observation(
observation_key_or_id,
tenant_id=current_user.tenant_id,
)
if observation is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -136,9 +156,15 @@ def create_risk_observation_feedback(
observation_key_or_id: str,
payload: RiskObservationFeedbackCreate,
db: DbSession,
current_user: CurrentUser,
) -> RiskObservationFeedbackRead:
try:
return RiskObservationService(db).create_feedback(observation_key_or_id, payload)
return RiskObservationService(db).create_feedback(
observation_key_or_id,
payload,
tenant_id=current_user.tenant_id,
actor=current_user.name or current_user.username,
)
except LookupError:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,