from __future__ import annotations from .shared import * @router.post("/cost-optimization/analyze") async def analyze_cost_optimization( request: CostOptimizationAnalyzeRequest, current_user: User = Depends(get_current_user), tenant_id: int = Depends(get_tenant_id), db: AsyncSession = Depends(get_db), ): """生成并持久化成本优化建议。""" _ = current_user service = CostOptimizationService(db) try: data = await service.analyze_cost_optimization( truth_node_id=request.truth_node_id, tenant_id=tenant_id, period=request.period, project_id=request.project_id, ) return success_response(data=data) except ValueError as exc: return JSONResponse( status_code=400, content=error_response(ErrorCode.BAD_REQUEST, str(exc)), ) except Exception as exc: return JSONResponse( status_code=500, content=error_response(ErrorCode.INTERNAL_ERROR, f"成本优化分析失败: {exc}"), ) @router.get("/context/projects/{project_id}/truth-nodes") async def list_project_truth_nodes( project_id: int, current_user: User = Depends(get_current_user), tenant_id: int = Depends(get_tenant_id), db: AsyncSession = Depends(get_db), ): """获取项目上下文中的宪章列表,用于前端选择器。""" _ = current_user result = await db.execute( select(TruthNode).where( and_( TruthNode.tenant_id == tenant_id, TruthNode.project_id == project_id, ) ).order_by(TruthNode.created_at.desc()) ) nodes = result.scalars().all() return success_response( data={ "items": [ { "id": n.id, "title": n.title, "status": n.status, "node_type": n.node_type, } for n in nodes ] } ) @router.get("/context/projects/{project_id}/experiments") async def list_project_experiments( project_id: int, current_user: User = Depends(get_current_user), tenant_id: int = Depends(get_tenant_id), db: AsyncSession = Depends(get_db), ): """获取项目上下文中的实验列表(基于 linked_context_nodes 关联)。""" _ = current_user truth_ids_result = await db.execute( select(TruthNode.id).where( and_( TruthNode.tenant_id == tenant_id, TruthNode.project_id == project_id, ) ) ) truth_ids = {int(row[0]) for row in truth_ids_result.all()} if not truth_ids: return success_response(data={"items": []}) exp_result = await db.execute( select(Experiment) .where(Experiment.tenant_id == tenant_id) .order_by(Experiment.created_at.desc()) ) experiments = exp_result.scalars().all() def _linked_ids(raw) -> set[int]: ids = set() if isinstance(raw, list): for x in raw: try: ids.add(int(x)) except (TypeError, ValueError): continue return ids items = [] for e in experiments: if e.project_id is not None: if int(e.project_id) != int(project_id): continue items.append( { "id": e.id, "title": e.title or f"Experiment {e.id}", "status": e.status, "target_metric": e.target_metric, } ) continue # 迁移兼容:project_id 为空时,回退到 linked_context_nodes 交叉判断。 if _linked_ids(e.linked_context_nodes).isdisjoint(truth_ids): continue items.append( { "id": e.id, "title": e.title or f"Experiment {e.id}", "status": e.status, "target_metric": e.target_metric, } ) return success_response(data={"items": items})