Contexte
FlexSlot est le troisième pilier de mon écosystème énergie / climat : le pont entre GridPulse (data réseau, créneaux verts) et GreenOps (pilotage ops, flex_slots).
GridPulse expose des fenêtres favorables ; GreenOps sait enregistrer des créneaux flex. Mais sans couche intermédiaire, l'opérateur doit relire la data, décider, puis ressaisir manuellement — avec un risque de source=manual et de perte de contexte réseau.
FlexSlot comble ce gap : data → recommandation actionnable → slot GreenOps en un clic.
Architecture
Trois services indépendants, trois déploiements :
- GridPulse (FastAPI · Railway) —
GET /api/v1/green-windows - FlexSlot (Next.js · Vercel) — moteur de reco + UI + Server Actions
- GreenOps (Next.js · Vercel · Supabase) —
POST /api/integrations/flex-slots
Pas de second backend Python pour le MVP : l'orchestration reste dans Next.js (Server Components, Route Handlers, Server Actions). GreenOps garde l'auth utilisateur ; FlexSlot appelle l'API d'intégration avec une clé de service (X-GreenOps-Service-Key).
Moteur de recommandation (V1)
Chaque heure reçoit un score GridPulse et une action :
| Action | Seuil score | Sens métier |
|--------|-------------|-------------|
| consume | ≥ 70 | Consommer maintenant |
| flex | 45–69 | Proposer un créneau flexible |
| shift | < 45 | Décaler la consommation |
La page /recommendations affiche la recommandation principale (meilleure fenêtre 6 h), le détail horaire et un outlook 12 h. Les règles sont documentées en clair — pas un modèle ML opaque, mais une couche de décision explicite et auditable.
Création de slot : pièges rencontrés
Confusion formulaire manuel vs API intégration
GreenOps expose deux chemins pour créer un flex_slot :
- Le formulaire
/flex→source=manual,recommendation_action=NULL(comportement attendu). - L'API
POST /api/integrations/flex-slots→source=flexslot, traçabilité complète.
Le bouton FlexSlot doit appeler uniquement l'API intégration. J'ai ajouté une validation côté FlexSlot (body.source === 'flexslot') et un retour UI explicite (slot id, source, action).
Décalage horaire fenêtre
Un extendWindowEnd() ajoutait +1 h à end_at avant l'envoi GreenOps — la fenêtre affichée dans FlexSlot ne correspondait pas à celle en base. Suppression de cette extension : les horaires sont alignés.
Variables d'environnement prod
Comme sur GridPulse, une URL sans https:// (gridpulse-production-ee61.up.railway.app) casse le fetch côté Vercel (Failed to parse URL). Même discipline : toujours préfixer le protocole dans les variables Vercel.
CORS côté GridPulse : ajouter l'origine FlexSlot (https://flex-slot.vercel.app) dans CORS_ORIGINS sur Railway.
Traçabilité en base
Migration GreenOps 004_flexslot_integration.sql — colonnes sur flex_slots :
source(flexslot/manual)recommendation_action(consume/flex/shift)gridpulse_score,gridpulse_window_start,gridpulse_window_end,gridpulse_avg_carbon
Le lien « Voir dans GreenOps » pointe vers /flex#slot-{id} pour scroller directement sur le créneau créé.
Export PDF
Route Handler GET /api/report — rendu serveur avec @react-pdf/renderer (runtime = "nodejs"). Piège classique : les glyphes Unicode (→, ₂) ne passent pas dans les polices PDF par défaut → remplacement par - et gCO2/kWh.
Compétences mises en œuvre
- Orchestrer plusieurs services avec des contrats d'API explicites, plutôt qu'un monolithe.
- Concevoir une couche de décision au-dessus de la data brute.
- Garantir la traçabilité bout en bout (source, action, fenêtre GridPulse) jusqu'en base.
- Débugger des intégrations prod (CORS, URLs, auth par clé) sur un flux réel.
Dépôt : github.com/ChristopheChollet/FlexSlot · Démo : flex-slot.vercel.app