tasks.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. """Task management router for Web-Local system dispatch (P4-M2)."""
  2. from typing import Optional, List, Dict, Any
  3. from fastapi import APIRouter, HTTPException
  4. from pydantic import BaseModel, Field
  5. from ..services.task_manager import get_task_manager
  6. router = APIRouter(prefix="/api/tasks", tags=["Tasks"])
  7. class CreateTaskRequest(BaseModel):
  8. """Request to create a new simulation task."""
  9. plan_id: Optional[int] = Field(default=None, description="Associated plan ID")
  10. plan_data: Dict[str, Any] = Field(..., description="Full plan data")
  11. parameters: List[Dict[str, Any]] = Field(..., description="List of parameter sets to simulate")
  12. task_name: Optional[str] = Field(default=None, description="Optional task name")
  13. priority: int = Field(default=5, ge=1, le=10, description="Task priority (1-10)")
  14. class ProgressUpdateRequest(BaseModel):
  15. """Request to update task progress."""
  16. current_point: int = Field(..., ge=0, description="Current point index (0-based)")
  17. total_points: Optional[int] = Field(default=None, description="Total points")
  18. current_params: Optional[Dict[str, Any]] = Field(default=None)
  19. elapsed_time: Optional[float] = Field(default=None)
  20. class ResultsReportRequest(BaseModel):
  21. """Request to report final results."""
  22. results: List[Dict[str, Any]] = Field(..., description="List of simulation results")
  23. metrics: Optional[Dict[str, Any]] = Field(default=None)
  24. logs: Optional[str] = Field(default=None)
  25. duration: Optional[float] = Field(default=None)
  26. status: str = Field(default="completed", description="completed / failed")
  27. @router.post("")
  28. def create_task(request: CreateTaskRequest):
  29. """Create a new simulation task."""
  30. try:
  31. manager = get_task_manager()
  32. task = manager.create_task(
  33. plan_id=request.plan_id,
  34. plan_data=request.plan_data,
  35. parameters=request.parameters,
  36. task_name=request.task_name,
  37. priority=request.priority,
  38. )
  39. return task
  40. except Exception as e:
  41. raise HTTPException(status_code=500, detail=f"Task creation failed: {str(e)}")
  42. @router.get("")
  43. def list_tasks(
  44. status: Optional[str] = None,
  45. plan_id: Optional[int] = None,
  46. limit: int = 50,
  47. offset: int = 0,
  48. ):
  49. """List tasks with optional filters."""
  50. manager = get_task_manager()
  51. return manager.list_tasks(status=status, plan_id=plan_id, limit=limit, offset=offset)
  52. @router.get("/{task_id}")
  53. def get_task(task_id: str):
  54. """Get task details by ID."""
  55. manager = get_task_manager()
  56. task = manager.get_task(task_id)
  57. if not task:
  58. raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
  59. return task
  60. @router.post("/{task_id}/dispatch")
  61. def dispatch_task(task_id: str):
  62. """Mark task as dispatched (ready for local executor)."""
  63. try:
  64. manager = get_task_manager()
  65. return manager.dispatch_task(task_id)
  66. except ValueError as e:
  67. raise HTTPException(status_code=400, detail=str(e))
  68. except Exception as e:
  69. raise HTTPException(status_code=500, detail=str(e))
  70. @router.post("/{task_id}/progress")
  71. def update_progress(task_id: str, request: ProgressUpdateRequest):
  72. """Update task progress from local executor."""
  73. try:
  74. manager = get_task_manager()
  75. return manager.update_progress(
  76. task_id=task_id,
  77. current_point=request.current_point,
  78. total_points=request.total_points,
  79. current_params=request.current_params,
  80. elapsed_time=request.elapsed_time,
  81. )
  82. except ValueError as e:
  83. raise HTTPException(status_code=404, detail=str(e))
  84. except Exception as e:
  85. raise HTTPException(status_code=500, detail=str(e))
  86. @router.post("/{task_id}/results")
  87. def report_results(task_id: str, request: ResultsReportRequest):
  88. """Report final results from local executor."""
  89. try:
  90. manager = get_task_manager()
  91. return manager.report_results(
  92. task_id=task_id,
  93. results=request.results,
  94. metrics=request.metrics,
  95. logs=request.logs,
  96. duration=request.duration,
  97. status=request.status,
  98. )
  99. except ValueError as e:
  100. raise HTTPException(status_code=404, detail=str(e))
  101. except Exception as e:
  102. raise HTTPException(status_code=500, detail=str(e))
  103. @router.get("/{task_id}/results")
  104. def get_task_results(task_id: str):
  105. """Get task results file content."""
  106. manager = get_task_manager()
  107. results = manager.get_task_results(task_id)
  108. if results is None:
  109. raise HTTPException(status_code=404, detail=f"Results for task {task_id} not found")
  110. return results
  111. @router.post("/{task_id}/cancel")
  112. def cancel_task(task_id: str):
  113. """Cancel a pending or running task."""
  114. try:
  115. manager = get_task_manager()
  116. return manager.cancel_task(task_id)
  117. except ValueError as e:
  118. raise HTTPException(status_code=400, detail=str(e))
  119. except Exception as e:
  120. raise HTTPException(status_code=500, detail=str(e))
  121. @router.get("/{task_id}/download")
  122. def download_task_file(task_id: str):
  123. """Download task JSON file for local executor."""
  124. manager = get_task_manager()
  125. task = manager.get_task(task_id)
  126. if not task or not task.get("task_file"):
  127. raise HTTPException(status_code=404, detail=f"Task file for {task_id} not found")
  128. import os
  129. if not os.path.exists(task["task_file"]):
  130. raise HTTPException(status_code=404, detail="Task file not found on disk")
  131. from fastapi.responses import FileResponse
  132. return FileResponse(task["task_file"], filename=f"{task_id}_task.json", media_type="application/json")