133 lines
4.2 KiB
Python
133 lines
4.2 KiB
Python
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,
|
||
permission_user: User = Depends(require_project_member_or_manager),
|
||
current_user: User = Depends(get_current_user),
|
||
tenant_id: int = Depends(get_tenant_id),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""获取项目上下文中的宪章列表,用于前端选择器。"""
|
||
_ = (current_user, permission_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,
|
||
permission_user: User = Depends(require_project_member_or_manager),
|
||
current_user: User = Depends(get_current_user),
|
||
tenant_id: int = Depends(get_tenant_id),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""获取项目上下文中的实验列表(基于 linked_context_nodes 关联)。"""
|
||
_ = (current_user, permission_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})
|