87 lines
2.9 KiB
Python
87 lines
2.9 KiB
Python
"""线索看板与处置 API(R7/R17/R18/R20)。
|
||
|
||
注意:不提供删除线索的端点(R19 线索不可删,独立性硬约束)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.api.schemas import (
|
||
AdjudicateRequest,
|
||
AssignRequest,
|
||
ClueOut,
|
||
DashboardSummary,
|
||
)
|
||
from app.clues import service as clue_svc
|
||
from app.clues.models import Clue, ClueStatus, ConfidenceTier
|
||
from app.db import get_session
|
||
|
||
router = APIRouter(prefix="/clues", tags=["clues"])
|
||
|
||
|
||
@router.get("", response_model=list[ClueOut])
|
||
def list_clues(
|
||
status: ClueStatus | None = Query(default=None),
|
||
scenario_code: str | None = Query(default=None),
|
||
confidence: ConfidenceTier | None = Query(default=None),
|
||
session: Session = Depends(get_session),
|
||
) -> list[Clue]:
|
||
return clue_svc.list_clues(
|
||
session, status=status, scenario_code=scenario_code, confidence=confidence
|
||
)
|
||
|
||
|
||
@router.get("/summary", response_model=DashboardSummary)
|
||
def summary(session: Session = Depends(get_session)) -> DashboardSummary:
|
||
"""运营看板汇总(R18/R21 的基础指标)。"""
|
||
clues = session.query(Clue).all()
|
||
by_status: dict[str, int] = {}
|
||
by_conf: dict[str, int] = {}
|
||
by_scenario: dict[str, int] = {}
|
||
total_amount = 0.0
|
||
for c in clues:
|
||
by_status[c.status.value] = by_status.get(c.status.value, 0) + 1
|
||
by_conf[c.confidence.value] = by_conf.get(c.confidence.value, 0) + 1
|
||
by_scenario[c.scenario_code] = by_scenario.get(c.scenario_code, 0) + 1
|
||
total_amount += c.amount_involved or 0.0
|
||
return DashboardSummary(
|
||
total=len(clues),
|
||
by_status=by_status,
|
||
by_confidence=by_conf,
|
||
by_scenario=by_scenario,
|
||
total_amount_involved=total_amount,
|
||
)
|
||
|
||
|
||
@router.get("/{clue_id}", response_model=ClueOut)
|
||
def get_clue(clue_id: uuid.UUID, session: Session = Depends(get_session)) -> Clue:
|
||
clue = session.get(Clue, clue_id)
|
||
if clue is None:
|
||
raise HTTPException(status_code=404, detail="线索不存在")
|
||
return clue
|
||
|
||
|
||
@router.post("/{clue_id}/assign", response_model=ClueOut)
|
||
def assign_clue(
|
||
clue_id: uuid.UUID, req: AssignRequest, session: Session = Depends(get_session)
|
||
) -> Clue:
|
||
clue = session.get(Clue, clue_id)
|
||
if clue is None:
|
||
raise HTTPException(status_code=404, detail="线索不存在")
|
||
return clue_svc.assign(session, clue, assignee=req.assignee, actor=req.actor)
|
||
|
||
|
||
@router.post("/{clue_id}/adjudicate", response_model=ClueOut)
|
||
def adjudicate_clue(
|
||
clue_id: uuid.UUID, req: AdjudicateRequest, session: Session = Depends(get_session)
|
||
) -> Clue:
|
||
clue = session.get(Clue, clue_id)
|
||
if clue is None:
|
||
raise HTTPException(status_code=404, detail="线索不存在")
|
||
clue_svc.adjudicate(session, clue, confirmed=req.confirmed, actor=req.actor, note=req.note)
|
||
return clue
|