"""数据分析 Agent 路由(D-01~D-12 / N-03/07/08)。""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from app.api.deps import get_auth_context from app.model.schemas.analyst import ( AnalystResponse, AssetCreateRequest, ChatRequest, ) from app.service.analyst_agent import AnalystAgent from app.utils.auth import AuthContext, assert_analyst_access router = APIRouter(prefix="/api/analyst", tags=["analyst"]) _agent: AnalystAgent | None = None def get_agent() -> AnalystAgent: global _agent if _agent is None: _agent = AnalystAgent() return _agent @router.post("/chat", response_model=AnalystResponse) def chat( req: ChatRequest, auth: AuthContext = Depends(get_auth_context), agent: AnalystAgent = Depends(get_agent), ) -> AnalystResponse: return agent.run(req.question, auth, req.session_id, req.trace_id) @router.get("/dashboard") def dashboard( auth: AuthContext = Depends(get_auth_context), agent: AnalystAgent = Depends(get_agent), ): """智能看数板(D-12)后端:按角色返回卡片。""" domain = assert_analyst_access(auth) role = next((r for r in auth.roles if r in ("analyst", "advisor", "risk_officer", "ops")), "analyst") cards = { "analyst": ["客户总数", "总持仓规模", "今日交易笔数与金额", "待处理预警数", "口径字典资产数"], "advisor": ["名下客户数", "名下资产规模", "盈亏分布", "风险等级分布"], "risk_officer": ["待处理预警数", "预警按类型分布", "近7天新增趋势"], "ops": ["近30天申购金额", "近30天赎回金额", "各产品类型规模TOP"], }.get(role, []) metrics: dict = {} if role == "analyst": try: res = agent.repo.execute_readonly( "SELECT (SELECT COUNT(*) FROM core_customer) AS customers, " "(SELECT COALESCE(SUM(market_value),0) FROM core_holding) AS holdings, " "(SELECT COUNT(*) FROM jinrong_agent.risk_alert WHERE status='pending_review') AS pending" ) if res["rows"]: metrics = dict(zip(res["columns"], res["rows"][0])) except Exception: # noqa: BLE001 pass return {"role": role, "domain": domain, "cards": cards, "metrics": metrics} @router.post("/assets") def create_asset( req: AssetCreateRequest, auth: AuthContext = Depends(get_auth_context), agent: AnalystAgent = Depends(get_agent), ): """沉淀资产(D-11):仅分析师可写。""" if "analyst" not in auth.roles: raise HTTPException(status_code=403, detail="仅分析师可沉淀资产") if req.kind not in ("dict", "few_shot", "template"): raise HTTPException(status_code=400, detail="kind 必须是 dict/few_shot/template") asset_id = agent.repo.insert_asset(req.kind, req.payload, auth.subject_id) return {"ok": True, "id": asset_id, "kind": req.kind} @router.get("/ops/metrics") def ops_metrics( auth: AuthContext = Depends(get_auth_context), agent: AnalystAgent = Depends(get_agent), ): """运营指标(N-08,P1 最小版)。""" if "analyst" not in auth.roles: raise HTTPException(status_code=403, detail="仅分析师可查看运营指标") res = agent.repo.execute_readonly( "SELECT COUNT(*) AS total, " "SUM(exec_status='blocked') AS blocked " "FROM jinrong_agent.analytics_query_log" ) return res["rows"][0] if res["rows"] else {"total": 0, "blocked": 0}