FlexSlot : orchestration GridPulse → GreenOps, slot en un clic

July 7, 2026

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 :

  1. Le formulaire /flexsource=manual, recommendation_action=NULL (comportement attendu).
  2. L'API POST /api/integrations/flex-slotssource=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