"""Task management router for Web-Local system dispatch (P4-M2).""" from typing import Optional, List, Dict, Any from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field from ..services.task_manager import get_task_manager router = APIRouter(prefix="/api/tasks", tags=["Tasks"]) class CreateTaskRequest(BaseModel): """Request to create a new simulation task.""" plan_id: Optional[int] = Field(default=None, description="Associated plan ID") plan_data: Dict[str, Any] = Field(..., description="Full plan data") parameters: List[Dict[str, Any]] = Field(..., description="List of parameter sets to simulate") task_name: Optional[str] = Field(default=None, description="Optional task name") priority: int = Field(default=5, ge=1, le=10, description="Task priority (1-10)") class ProgressUpdateRequest(BaseModel): """Request to update task progress.""" current_point: int = Field(..., ge=0, description="Current point index (0-based)") total_points: Optional[int] = Field(default=None, description="Total points") current_params: Optional[Dict[str, Any]] = Field(default=None) elapsed_time: Optional[float] = Field(default=None) class ResultsReportRequest(BaseModel): """Request to report final results.""" results: List[Dict[str, Any]] = Field(..., description="List of simulation results") metrics: Optional[Dict[str, Any]] = Field(default=None) logs: Optional[str] = Field(default=None) duration: Optional[float] = Field(default=None) status: str = Field(default="completed", description="completed / failed") @router.post("") def create_task(request: CreateTaskRequest): """Create a new simulation task.""" try: manager = get_task_manager() task = manager.create_task( plan_id=request.plan_id, plan_data=request.plan_data, parameters=request.parameters, task_name=request.task_name, priority=request.priority, ) return task except Exception as e: raise HTTPException(status_code=500, detail=f"Task creation failed: {str(e)}") @router.get("") def list_tasks( status: Optional[str] = None, plan_id: Optional[int] = None, limit: int = 50, offset: int = 0, ): """List tasks with optional filters.""" manager = get_task_manager() return manager.list_tasks(status=status, plan_id=plan_id, limit=limit, offset=offset) @router.get("/{task_id}") def get_task(task_id: str): """Get task details by ID.""" manager = get_task_manager() task = manager.get_task(task_id) if not task: raise HTTPException(status_code=404, detail=f"Task {task_id} not found") return task @router.post("/{task_id}/dispatch") def dispatch_task(task_id: str): """Mark task as dispatched (ready for local executor).""" try: manager = get_task_manager() return manager.dispatch_task(task_id) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/{task_id}/progress") def update_progress(task_id: str, request: ProgressUpdateRequest): """Update task progress from local executor.""" try: manager = get_task_manager() return manager.update_progress( task_id=task_id, current_point=request.current_point, total_points=request.total_points, current_params=request.current_params, elapsed_time=request.elapsed_time, ) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/{task_id}/results") def report_results(task_id: str, request: ResultsReportRequest): """Report final results from local executor.""" try: manager = get_task_manager() return manager.report_results( task_id=task_id, results=request.results, metrics=request.metrics, logs=request.logs, duration=request.duration, status=request.status, ) except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/{task_id}/results") def get_task_results(task_id: str): """Get task results file content.""" manager = get_task_manager() results = manager.get_task_results(task_id) if results is None: raise HTTPException(status_code=404, detail=f"Results for task {task_id} not found") return results @router.post("/{task_id}/cancel") def cancel_task(task_id: str): """Cancel a pending or running task.""" try: manager = get_task_manager() return manager.cancel_task(task_id) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/{task_id}/download") def download_task_file(task_id: str): """Download task JSON file for local executor.""" manager = get_task_manager() task = manager.get_task(task_id) if not task or not task.get("task_file"): raise HTTPException(status_code=404, detail=f"Task file for {task_id} not found") import os if not os.path.exists(task["task_file"]): raise HTTPException(status_code=404, detail="Task file not found on disk") from fastapi.responses import FileResponse return FileResponse(task["task_file"], filename=f"{task_id}_task.json", media_type="application/json")