50 Hz · L'électricité de l'Europe en direct
novembre 202616 min de lectureEn cours

Faire une simulation calquée sur de vrais chiffres, c'est un petit défi que je voulais relever depuis longtemps. Je m'y étais déjà essayé avec Paradise, où je simulais la vie d'une ville sous terre : l'oxygène, l'eau, les calories et l'électricité dont ses habitants ont besoin chaque jour, tout ce que la ville dépense pour tenir.
Avec 50 Hz, j'ai voulu aller un peu plus loin sur le sujet de l'énergie, et sortir de la ville fermée pour regarder la vraie. Comment l'électricité est produite en Europe, avec quels moyens, comment on la stocke et on la restitue, et comment elle se déplace d'un pays à l'autre. C'est pour ça que j'ai créé 50 Hz.
Le nom vient d'une particularité de l'électricité : elle ne se stocke presque pas. À chaque seconde, l'Europe doit produire exactement ce qu'elle consomme, et le signe que l'équilibre tient, c'est la fréquence du courant, qui reste collée à 50 Hz. Tout le monde s'en sert, et presque personne ne sait d'où vient le courant qui sort de sa prise.
Ce que c'est
50 Hz, c'est une carte de l'Europe qui montre où l'électricité est produite, où elle est consommée et ce que les pays s'échangent, en direct. Elle est en ligne sur 50hz.kevin-dev.com.

En haut à gauche, il y a la fréquence du réseau continental, mesurée à la seconde.
Sur la carte, on voit le réseau haute tension et un disque pour chaque grande centrale. Sa couleur dit sa filière, et sa taille suit sa puissance.
Quand on passe la souris sur un pays, ses petites installations apparaissent et ses échanges avec les voisins se dessinent en arcs. Une carte dans le coin donne ce qu'il produit, ce qu'il consomme, et s'il importe ou exporte.

Un clic sur une centrale ouvre sa fiche : sa puissance, son rang dans le pays, la part qu'elle pèse dans sa filière, et ce que produit cette filière dans le pays au même moment. En dessous, une maquette du site.

Cette maquette mène à la deuxième moitié du site, « Comment ça marche ».
On y trouve chaque type de centrale : nucléaire, barrage, éolienne en mer, cycle combiné au gaz, batteries… Chacun a sa grande figure, avec des étapes numérotées en bas qu'on parcourt avec les flèches.
Le nucléaire en a quinze, de la fission jusqu'au poste électrique. Les circuits d'eau, la vapeur et l'électricité sont en couleur, pour qu'on puisse les suivre d'une étape à l'autre.

Les données
Tout ce qui est sur la carte vient de sources publiques, citées en bas de l'écran.
| Ce qu'on voit | D'où ça vient |
|---|---|
| Production, consommation et échanges de l'Europe continentale | ENTSO-E Transparency Platform, avec un accès à demander |
| Production, consommation et échanges de la Grande-Bretagne | Elexon BMRS, disponible à tous |
| Fréquence continentale, à la seconde | Energy-Charts du Fraunhofer ISE |
| Les centrales, leur puissance et leur position | Global Energy Monitor croisé avec OpenStreetMap |
| Le réseau haute tension | PyPSA-Eur, d'après OpenStreetMap |
| La puissance installée de chaque pays | Ember |
Toutes ces données sont publiques. Mais chaque pays les publie à son rythme : certains toutes les quinze minutes, d'autres toutes les demi-heures ou toutes les heures, et pas tous avec le même retard.
Les centrales viennent de deux bases qui se trompent chacune à leur façon : Global Energy Monitor a souvent la bonne puissance mais une position approximative, OpenStreetMap a la bonne position mais pas toujours la puissance. Un script Python les apparie, d'abord par leurs identifiants communs, puis par la distance, et un fichier de corrections sourcées rattrape les cas que l'algorithme rate. Au final, la carte affiche près de 14 000 installations de 10 MW et plus.
Les sources de chaque pays
Pour chaque pays, il faut savoir d'où viennent sa production, sa consommation et ses échanges avec chacun de ses voisins. Tout ça est rangé dans un seul fichier, la fiche des sources, avec une règle : une seule source par chiffre, la meilleure qu'on ait. Le reste du code n'a ni condition ni switch sur les sources, il lit la fiche.
Elle commence par dire quel fournisseur sait lire quelle mesure :
type MeasureProviders = {
generation: 'entsoe' | 'elexon'
load: 'entsoe' | 'elexon'
exchange: 'entsoe' | 'elexon'
frequency: 'elexon' | 'energy-charts'
}
export type ProviderOf<M extends Measure> = MeasureProviders[M]
Puis elle tient trois tables. La première donne, pour chaque zone de la carte, les pays qu'elle couvre et la source de sa production et de sa consommation :
export const AREA_SOURCES = {
'DE-LU': { countries: ['DE', 'LU'], zones: ['DE-LU'], generation: 'entsoe', load: 'entsoe' },
'FR': { countries: ['FR'], zones: ['FR'], generation: 'entsoe', load: 'entsoe' },
'GB': { countries: ['GB'], zones: ['GB'], generation: 'elexon', load: 'elexon' },
'IE-SEM': { countries: ['IE', 'GB-NIR'], zones: ['IE-SEM'], generation: 'entsoe', load: null },
// …
}
Une zone peut couvrir plusieurs pays : l'Allemagne et le Luxembourg partagent le même marché de l'électricité, et ENTSO-E les publie ensemble. Un null dit franchement qu'aucune source ne publie le chiffre, comme la consommation de l'Irlande.
La deuxième table liste chaque frontière avec sa source, une ligne par paire de voisins :
export const BORDER_SOURCES: [BiddingZone, BiddingZone, ProviderOf<'exchange'>][] = [
['BE', 'FR', 'entsoe'],
['DE-LU', 'FR', 'entsoe'],
['GB', 'FR', 'elexon'],
// …
]
La troisième donne la fréquence de chaque réseau : Energy-Charts pour le continent, Elexon pour la Grande-Bretagne, et rien pour les pays nordiques et l'Irlande, que personne ne publie en direct.
Pour choisir une source, je regarde ce qu'elle mesure vraiment. ENTSO-E couvre tout le continent avec les mêmes définitions. Pour la Grande-Bretagne, Elexon donne ses six interconnexions câble par câble. Et pour les échanges, je ne prends que des flux physiques, ce qui passe vraiment dans les câbles, pas les programmes commerciaux que publie par exemple RTE dans éCO2mix.
Tout est typé à partir de cette fiche. Écrire frequency: 'entsoe' ne compile pas, puisque ENTSO-E ne publie pas la fréquence. Le registre qui va chercher les données a la même forme :
const FETCHERS: { [M in Measure]: Record<ProviderOf<M>, MeasureFetcher<M>> } = {
generation: { entsoe: entsoeGeneration, elexon: elexonGeneration },
load: { entsoe: entsoeLoad, elexon: elexonLoad },
exchange: { entsoe: entsoeExchange, elexon: elexonExchange },
frequency: { 'elexon': elexonFrequency, 'energy-charts': energyChartsFrequency },
}
Il doit avoir une lecture pour chaque fournisseur que la fiche autorise, et aucune autre. Des tests vérifient le reste : aucune zone ni frontière n'est déclarée deux fois, chaque frontière relie deux pays de la carte, et Elexon ne sert qu'à la Grande-Bretagne. Changer de source, c'est changer une valeur dans la fiche.
Ne jamais afficher un faux chiffre
L'autre règle, c'est de ne rien inventer. Je me l'étais déjà donnée sur Paradise, où chaque chiffre de la ville remontait à une source réelle. Ici, elle est écrite dans les décisions du projet, pour que chaque session de Claude la retrouve. La fréquence des pays nordiques et de l'Irlande n'est publiée nulle part en direct, donc le cadran ne la montre pas.
Mais il y a un autre piège, plus discret. Un pays, c'est une dizaine de séries : chaque filière, la consommation, chaque frontière. Elles n'arrivent pas toutes au même moment, et additionner le solaire de 14 h avec le nucléaire de 13 h donnerait un total qui n'a jamais existé. Un pays s'affiche donc au dernier instant que couvrent toutes ses séries :
export function latestCompleteInstant(series: PowerReading[][]) {
const candidates = [...new Set(series.flat().map((reading) => reading.time.getTime()))].toSorted(
(first, second) => second - first
)
for (const instantMs of candidates) {
const readings = readingsAt(series, instantMs)
if (readings) {
return { time: new Date(instantMs), readings }
}
}
return null
}
On part de l'instant le plus récent et on recule jusqu'au premier où rien ne manque. Si ce moment date de deux heures, tant pis : la carte l'affiche quand même, avec « il y a 2 h ». J'ai préféré ça à un pays vide, parce qu'un visiteur qui arrive et voit des trous partout va se demander si le site marche.
Les sources officielles se trompent aussi. Le 2 novembre à midi, ENTSO-E a publié 7 707 MW de solaire pour la Moldavie, qui n'a qu'un peu plus de 700 MW de panneaux installés selon Ember. Depuis, chaque valeur est comparée à deux fois la puissance installée du pays, et ce qui dépasse est écarté, comme si rien n'avait été publié. La marge est large exprès : le plafond ne doit attraper que les erreurs d'un ordre de grandeur, pas une vraie journée record.
Ce qui se passe derrière
L'application suit la même stack que ce site : AdonisJS avec Inertia et React 19, Postgres, le tout dans des conteneurs Docker sur mon VPS, sous Dokploy. À côté du serveur web tourne un deuxième conteneur, le worker, qui interroge les sources à leur rythme (toutes les 15 minutes pour ENTSO-E, beaucoup plus souvent pour la fréquence) et range les chiffres en base, tels qu'ils arrivent.
Tout ce qui ne change pas en direct est préparé à l'avance par un pipeline Python, lancé avec uv : le fond de carte, le réseau haute tension, les villes du fond de nuit, les centrales. Chaque produit a sa commande, qui projette, simplifie et écrit des fichiers binaires compacts, commités dans l'application. Les régénérer à chaque build aurait pris neuf minutes et plus de deux gigaoctets de sources rien que pour le fond de carte.
La carte elle-même est dessinée avec three.js et React Three Fiber, sans bibliothèque de cartographie. La recherche recommandait MapLibre et deck.gl, avec un gigaoctet et demi de tuiles. J'ai préféré three.js pur : on n'a besoin ni de globe, ni de projections au choix, ni de styles de carte, juste d'une Europe plate dont on maîtrise chaque pixel. Les coordonnées sont projetées une fois pour toutes dans le pipeline, dans la projection officielle de l'Union européenne pour les cartes à aires égales (EPSG:3035), et le navigateur n'a plus qu'à les afficher. Les centrales sont un seul nuage de points, avec un shader qui décide à chaque image lesquelles montrer selon le zoom : 1 GW et plus sur l'Europe, environ 300 MW sur un pays, toutes de près.
Trois jours et pas mal de virages
Le projet a démarré le 1er novembre et il est en ligne depuis le 3 au soir. Ça paraît rapide, mais il a changé plusieurs fois de direction en route.
Des modèles 3D, puis plus du tout
Dans ma tête, au départ, chaque centrale devait avoir son modèle 3D, posé à ses vraies coordonnées. J'ai installé Blender sur mon PC, et Claude l'a piloté par des scripts Python, sans jamais ouvrir l'interface : un script par type de centrale, exporté pour le web, avec plusieurs niveaux de détail. Voilà ce que ça donnait pour une centrale nucléaire :

Je les trouvais trop simples, alors on les a tous refaits, avec plus de détails et une dizaine d'agents en parallèle. Mais une fois sur la carte, le problème était ailleurs. Chaque site avait un sol coloré selon sa filière, et entre les noms de tous les pays, les arcs qui brillaient et ces rectangles posés sur un fond vide, la carte était devenue difficile à lire. Des patchs d'herbe verte, orange ou violette en plein milieu de l'Allemagne.

À côté de ça, j'avais découvert le skill Anatomy, qui dessine des figures isométriques très détaillées d'une machine, en SVG. On s'en servait pour la fiche des centrales, et les maquettes étaient plus belles et plus parlantes que les modèles 3D. Je me suis dit qu'on n'avait pas besoin des deux. On a gardé les maquettes, refaites en sites entiers à l'échelle réelle (une unité, un mètre), et on a supprimé toute la 3D : le projet Blender, les seize modèles, leur chargement côté client, près de 25 000 lignes. Sur la carte, une centrale est redevenue un simple disque, comme sur les cartes de référence.
Une carte qu'on a envie de regarder
Pour remplacer le fond noir et vide, j'ai étudié les cartes qui existent déjà sur le sujet. Sur Open Infrastructure Map, j'aime beaucoup le fait de voir chaque ligne, chaque pylône, chaque poste électrique, et les villes autour. On voit vraiment le trajet que fait le courant. Je ne voulais pas une copie, mais ce niveau de détail, mélangé à nos échanges entre pays, me paraissait intéressant.
On a donc ajouté le réseau haute tension, du 220 au 750 kV, avec les postes, et un décor de nuit discret : les villes, l'eau, les grands axes. Les lignes de 20 kV et les bâtiments sont restés de côté, parce qu'ils auraient imposé un système de tuiles. Plus tard, j'ai aussi demandé une version claire du site, carte comprise, et que les terres voisines se fondent dans la mer au lieu de s'arrêter net au bord de l'écran.

Travailler avec Claude
Tout le code de 50 Hz a été écrit par Claude Code, mais pas dans une seule conversation. Une conversation principale a supervisé le projet pendant les trois jours, et elle a lancé d'autres conversations pour chaque morceau du travail. C'est une fonction récente de Claude Code : des sessions qui se lancent et se parlent entre elles.
D'abord des questions
Maintenant, je commence tous mes projets par une phase de questions-réponses. J'utilise pour ça le skill grill-me de Matt Pocock, que j'ai adapté : Claude pose toutes les questions à trancher, une par une, avec une recommandation pour chacune. Quels pays couvrir, quel seuil de puissance pour les centrales, quoi faire d'un pays qui ne publie rien, quelle licence pour la base des installations… C'est là que j'ai écarté MapLibre.
Ça m'oblige à me remettre en question sur ce que je voulais vraiment aborder, et à poser noir sur blanc ce que je veux avant la première ligne de code.
Avec ces réponses, on a écrit le cahier des charges dans un document Claude. C'est un document partagé, que je peux éditer moi-même, et il a servi à suivre tout le projet : la vision, les règles, les sources, les décisions et ce qui restait ouvert.
Un superviseur et des sessions
Ensuite, la conversation principale est devenue le superviseur. Elle gardait la vue d'ensemble et répartissait les tâches. Pour chaque morceau (le fond de carte, l'ingestion, les maquettes, le design), elle écrivait un brief complet et me proposait une nouvelle session, que j'autorisais d'un clic. Chaque session avait une seule responsabilité et sa propre branche, et plusieurs tournaient en même temps. Quand l'une d'elles avait fini ou bloquait, elle écrivait au superviseur, qui me faisait la synthèse.
Quand je n'étais pas disponible, je lançais les tâches longues et lourdes, comme la génération des modèles 3D ou l'export du fond de nuit. Elles tournaient sans que j'aie besoin de surveiller ce qui se passait.
Mes règles
Dans les projets que je fais avec Claude, je commence par poser les règles que j'aime utiliser. Pour 50 Hz :
- un module par domaine, et chaque module ne s'importe que par son entrée publique ;
- pas de commentaire inutile, le nommage porte l'intention ;
- des noms de fonctions et de fichiers qui disent ce qu'ils font, et des booléens en
isouhas; - les classes Tailwind directement dans les composants, pas dans des constantes ;
- un commit par fonctionnalité, plutôt qu'une série de petits commits ;
- l'application n'est pas en production, donc pas de migration ni de compatibilité pour des données qui n'existent pas.
Comme sur ce site, celles qui se vérifient sont devenues des règles de lint. Et avant chaque fusion, la session qui avait travaillé devait relire tout ce qu'elle avait produit, et me rendre un rapport : ce qui est corrigé, ce qui est laissé et pourquoi, ce qui attend ma décision.
Les ADR
Chaque choix important est noté dans un ADR, un petit document qui dit ce qu'on a décidé, pourquoi, et ce qu'on a écarté. Il décrit la décision en vigueur : il se réécrit quand elle change, et l'historique reste dans git.
Grâce à eux, une nouvelle session de Claude connaît tous les choix déjà faits avant de toucher au code, et ne retente pas une piste abandonnée. Et c'est une documentation qui sert autant à un développeur comme moi qu'à un agent.
Ce qui reste à moi
Claude a écrit tout le code, fait les recherches, dessiné les maquettes et rédigé les textes. Mon rôle, c'était de diriger. Je décidais des tâches à aborder et dans quel ordre, et je donnais mes idées sur la façon de traiter certains sujets : three.js plutôt que MapLibre, une seule source par chiffre, pas de faux chiffres, l'abandon de la 3D.
À chaque fois qu'une session terminait, je relisais le code en gros, pour repérer les façons de faire qui ne me plaisaient pas. Et quand Claude me résumait ce qui restait à faire, je prenais le temps de comprendre ce qu'il disait, et de le reformuler avec lui. C'est comme ça que je suis resté dans le projet du début à la fin, et que j'ai pu le faire avancer. C'est aussi comme ça que j'ai repéré des choix d'organisation incohérents, qui auraient rendu le code difficile à suivre à la fin. La fiche des sources est née de là : je ne m'y retrouvais plus entre les sources, et c'était le signe que la logique était mal rangée.
Où en est le projet
50 Hz est en ligne depuis le 3 novembre. Pour l'instant, c'est surtout une carte en direct : la partie simulation, qui m'avait lancé dans le projet, reste à faire. Elle est prévue pour plus tard, avec la possibilité de couper une centrale pour voir la fréquence réagir, et de rejouer le black-out ibérique du 28 avril 2025. Avant ça, il y a la carte sur mobile, et une vue qui suit le courant jusqu'à chez soi, depuis le poste électrique le plus proche. Le code reste privé pour l'instant.