# Roster AI — solveur d'horaires (OR-Tools CP-SAT) Microservice d'**optimisation sous contraintes** (« façon Timefold », mais Python sans JVM) qui génère des horaires de techniciens. Agnostique du backend : il ne connaît ni Frappe ni ERPNext — on lui passe des techs + dispos + besoins de couverture, il rend des assignations optimales. ## Place dans l'architecture ``` [Ops « Planification »] → [targo-hub lib/roster.js] → [roster-solver /solve] │ lit techs/jobs (Dispatch Technician/Job, ERPNext) └→ écrit les assignations validées ``` Le hub rassemble les entrées (techs, compétences, dispos, congés, besoins par jour/zone), appelle `/solve`, puis écrit les assignations retenues. ## API - `GET /health` - `POST /solve` → corps : ```jsonc { "horizon": { "start": "2026-06-08", "days": 7 }, "shift_templates": [ { "id": "jour", "name": "Jour 8h-16h", "hours": 8 } ], "technicians": [ { "id": "T001", "name": "Marc", "skills": ["fibre"], "max_hours_week": 40, "max_days": 5, "cost_per_h": 38, "zone_home": "Montréal", "preferred_off": ["sam","dim"], "unavailable": ["2026-06-10"] } // dates de congé/pause ], "coverage": [ { "date": "2026-06-08", "shift": "jour", "zone": "Montréal", "required": 2, "required_skills": ["fibre"] } ], "weights": { "uncovered": 1000, "fairness": 5, "cost": 1, "preference": 8, "continuity": 4 }, "max_seconds": 10 } ``` Réponse : `assignments[]`, `coverage_report[]` (requis vs assigné vs **shortfall**), `tech_hours{}`, `spread_hours`, `total_shortfall`, `explanations[]`, `status`. ## Modèle de contraintes **Dures** (faisabilité) : - 1 shift max par tech par jour - compétences : un tech ne compte pour un poste que s'il a *toutes* les compétences requises - disponibilité : pas d'assignation un jour de congé/pause (`unavailable`) - `max_hours_week`, `max_days` **Souples** (objectif pondéré, minimisé) : - `uncovered` — postes non couverts (priorité écrasante → on voit les manques au lieu d'échouer) - `fairness` — écart d'heures max-min entre techs - `cost` — coût horaire total - `preference` — pénalité de travail un jour « préféré off » - `continuity` — *bonus* si la zone du poste = zone d'attache du tech (moins de déplacement) > Couverture en **soft** = le solveur rend toujours le meilleur horaire possible > même en sous-effectif, et **rapporte les manques** (ta demande « dispo vs requis »). ## Dév ```bash python -m venv .venv && . .venv/bin/activate pip install -r requirements.txt python test_solver.py # démo + vérifs sur sample_request.json uvicorn app:app --port 8090 # serveur ```