"""API routes for evaluation campaigns (评估活动). This ticket covers persistence and create/query only — no scheduling or child Run spawning. Those arrive in later tickets. """ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from sqlmodel import Session from agenteval.models import Campaign, CampaignPlanEntry from agenteval.storage.repository import CampaignRepository, ScenarioRepository, TargetRepository from agenteval.web.deps import get_db router = APIRouter() class CreateCampaignRequest(BaseModel): name: str target_id: str window_seconds: int = Field(gt=0) time_scale: float = Field(default=1.0, gt=0) plan: list[CampaignPlanEntry] = Field(min_length=1) @router.get("") async def list_campaigns(session: Session = Depends(get_db)) -> list[dict]: return [c.model_dump() for c in CampaignRepository(session).list_all()] @router.post("") async def create_campaign( request: CreateCampaignRequest, session: Session = Depends(get_db), ) -> dict: if not TargetRepository(session).get(request.target_id): raise HTTPException(status_code=404, detail="target not found") scenario_repo = ScenarioRepository(session) for entry in request.plan: if not scenario_repo.get(entry.scenario_id): raise HTTPException( status_code=404, detail=f"scenario not found: {entry.scenario_id}", ) campaign = Campaign( name=request.name, target_id=request.target_id, window_seconds=request.window_seconds, time_scale=request.time_scale, plan=request.plan, ) campaign = CampaignRepository(session).create(campaign) return campaign.model_dump() @router.get("/{campaign_id}") async def get_campaign(campaign_id: str, session: Session = Depends(get_db)) -> dict: campaign = CampaignRepository(session).get(campaign_id) if not campaign: raise HTTPException(status_code=404, detail="campaign not found") return campaign.model_dump()