""" Roster AI — API HTTP (FastAPI) autour du solveur OR-Tools CP-SAT. Endpoints : GET /health → liveness POST /solve → résout un horaire (payload = voir README/sample_request.json) Pensé pour tourner à côté du hub (targo-hub l'appelle), pas exposé publiquement. """ from fastapi import FastAPI from fastapi.responses import JSONResponse from pydantic import BaseModel from typing import Any import os from solver import solve_roster from route_solver import solve_route app = FastAPI(title="Roster AI Solver", version="0.2.0") API_TOKEN = os.environ.get("ROSTER_SOLVER_TOKEN", "") class SolveRequest(BaseModel): horizon: dict shift_templates: list technicians: list coverage: list = [] weights: dict | None = None max_seconds: float | None = None class RouteRequest(BaseModel): jobs: list vehicles: list matrix: list | None = None speed_kmh: float | None = None default_service_min: int | None = None rank_weight: float | None = None drop_penalty: int | None = None max_seconds: float | None = None @app.get("/health") def health(): return {"ok": True, "service": "roster-solver"} @app.post("/solve") def solve(req: SolveRequest): try: result = solve_roster(req.model_dump()) return result except Exception as e: # noqa: BLE001 — renvoyer l'erreur proprement au hub return JSONResponse(status_code=400, content={"status": "ERROR", "message": str(e)}) @app.post("/route") def route(req: RouteRequest): """Optimisation de TOURNÉES (VRPTW + compétences + temps sur place). Voir route_solver.solve_route.""" try: return solve_route(req.model_dump()) except Exception as e: # noqa: BLE001 return JSONResponse(status_code=400, content={"status": "ERROR", "message": str(e)})