blackopsrepl's picture
Upload 30 files
50f82a1 verified
"""
FastAPI REST endpoints for the maintenance scheduling application.
"""
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from uuid import uuid4
from typing import Dict, List
from dataclasses import asdict
import logging
from .domain import MaintenanceSchedule, MaintenanceScheduleModel
from .converters import schedule_to_model, model_to_schedule
from .score_analysis import ConstraintAnalysisDTO, MatchAnalysisDTO
from .demo_data import generate_demo_data, DemoData
from .solver import solver_manager, solution_manager
logger = logging.getLogger(__name__)
app = FastAPI(docs_url='/q/swagger-ui')
data_sets: Dict[str, MaintenanceSchedule] = {}
# ************************************************************************
# Demo Data Endpoints
# ************************************************************************
@app.get("/demo-data")
async def get_demo_data_list() -> List[str]:
"""Get available demo data sets."""
return [demo.name for demo in DemoData]
@app.get("/demo-data/{demo_name}", response_model=MaintenanceScheduleModel)
async def get_demo_data_by_name(demo_name: str) -> MaintenanceScheduleModel:
"""Get a specific demo data set."""
try:
demo = DemoData[demo_name]
schedule = generate_demo_data(demo)
return schedule_to_model(schedule)
except KeyError:
raise HTTPException(status_code=404, detail=f"Demo data '{demo_name}' not found")
# ************************************************************************
# Schedule Endpoints
# ************************************************************************
@app.get("/schedules")
async def list_schedules() -> List[str]:
"""List the job IDs of all submitted schedules."""
return list(data_sets.keys())
@app.post("/schedules")
async def solve_schedule(model: MaintenanceScheduleModel) -> str:
"""
Submit a schedule for solving.
Returns the job ID that can be used to track progress and retrieve results.
"""
job_id = str(uuid4())
schedule = model_to_schedule(model)
data_sets[job_id] = schedule
solver_manager.solve_and_listen(
job_id,
schedule,
lambda solution: data_sets.update({job_id: solution})
)
return job_id
@app.get("/schedules/{job_id}", response_model=MaintenanceScheduleModel)
async def get_schedule(job_id: str) -> MaintenanceScheduleModel:
"""Get the current solution for a job."""
schedule = data_sets.get(job_id)
if not schedule:
raise HTTPException(status_code=404, detail="Schedule not found")
schedule.solver_status = solver_manager.get_solver_status(job_id)
return schedule_to_model(schedule)
@app.get("/schedules/{job_id}/status")
async def get_schedule_status(job_id: str) -> dict:
"""Get the status and score for a job (lightweight, without full solution)."""
schedule = data_sets.get(job_id)
if not schedule:
raise HTTPException(status_code=404, detail="Schedule not found")
solver_status = solver_manager.get_solver_status(job_id)
return {
"score": str(schedule.score) if schedule.score else None,
"solverStatus": solver_status.name if solver_status else None,
}
@app.delete("/schedules/{job_id}", response_model=MaintenanceScheduleModel)
async def stop_solving(job_id: str) -> MaintenanceScheduleModel:
"""Terminate solving and return the best solution so far."""
solver_manager.terminate_early(job_id)
schedule = data_sets.get(job_id)
if not schedule:
raise HTTPException(status_code=404, detail="Schedule not found")
schedule.solver_status = solver_manager.get_solver_status(job_id)
return schedule_to_model(schedule)
# ************************************************************************
# Score Analysis Endpoint
# ************************************************************************
@app.put("/schedules/analyze")
async def analyze_schedule(model: MaintenanceScheduleModel) -> dict:
"""
Analyze the constraints in a schedule.
Returns detailed information about which constraints are satisfied or violated.
"""
schedule = model_to_schedule(model)
analysis = solution_manager.analyze(schedule)
constraints = []
for constraint in getattr(analysis, 'constraint_analyses', []) or []:
matches = [
MatchAnalysisDTO(
name=str(getattr(getattr(match, 'constraint_ref', None), 'constraint_name', "")),
score=str(getattr(match, 'score', "0hard/0soft")),
justification=str(getattr(match, 'justification', ""))
)
for match in getattr(constraint, 'matches', []) or []
]
constraints.append(ConstraintAnalysisDTO(
name=str(getattr(constraint, 'constraint_name', "")),
weight=str(getattr(constraint, 'weight', "0hard/0soft")),
score=str(getattr(constraint, 'score', "0hard/0soft")),
matches=matches
))
return {"constraints": [asdict(constraint) for constraint in constraints]}
# ************************************************************************
# Static Files
# ************************************************************************
app.mount("/", StaticFiles(directory="static", html=True), name="static")