gigafibre-fsm/services/roster-solver/app.py
louispaulb f4138cdd75 Roster AI (planification) + prise de rendez-vous client
Solveur OR-Tools (services/roster-solver) : couverture, compétences,
équité, coût chargé, cadence/efficacité, capacité-par-job ; contraintes
dures/souples façon Timefold.

Hub (lib/roster.js) : génération via solveur, publication par réécriture
de semaine (anti-doublons), demande (effectif ou nb de jobs), cadence/coût/
compétences par tech, pause, congés (Tech Availability + approbation),
booking (slots roster-aware / fit 3-dispos / confirm) + portail public /book.
Réessai sur serialization failures frappe_pg ; appels ERP séquentiels.

Ops : page Planification (grille compacte « J8 », multi-shift, drag-select
+ undo/redo, modèles de semaine, éditeur cadence&coût, congés, SMS opt-in),
page Rendez-vous (répartiteur), jobColor tech en pause → tickets rouges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:42:44 -04:00

44 lines
1.1 KiB
Python

"""
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
app = FastAPI(title="Roster AI Solver", version="0.1.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
@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)})