Outils locaux (jamais en CI) de mise à jour des données du simulateur :
grilles tarifaires PDF des fournisseurs, calendriers Tempo/EJP et Zenflex,
prix spot EPEX FR. Point d'entrée recommandé : node update-all.mjs
(voir « Mise à jour globale » ci-dessous), qui orchestre les quatre scripts.
L'outil grilles tarifaires (update.mjs), pour chaque price_url des defineTarif du repo :
- télécharge la grille (requête conditionnelle + comparaison SHA-256 avec
manifest.json), - si elle a changé, extrait le texte du PDF (pdfjs-dist) et parse les prix,
- patche les fichiers
scripts/tarifs/**(uniquement les nombres etlastUpdate, jamais la structure), - met à jour
manifest.jsonet la section « Derniers tarifs » du README racine.
La revue humaine reste obligatoire : relire git diff scripts/tarifs, régénérer les goldens, puis commit.
cd import
npm ci # unique dépendance : pdfjs-dist (extraction de texte PDF)Node ≥ 22 requis (comme les tests du repo).
node update.mjs # run complet : check + download + parse + patch
node update.mjs --check-only # détection réseau seule, ne touche à rien
node update.mjs --dry-run # tout le pipeline sauf l'écriture
node update.mjs --force # re-parse même si le SHA-256 n'a pas changé
node update.mjs --offline # réutilise les PDFs du cache/ (dev, sans réseau)
node update.mjs --provider edf # limite à un fournisseur
node update.mjs --tarif "EDF - Tempo"
npm test # tests unitaires + snapshots des parsers (sans réseau)Workflow après un run qui a modifié des fichiers :
git diff scripts/tarifs # revue des valeurs
$env:UPDATE_GOLDEN='1'; node --test "tests/**/*.test.mjs"; $env:UPDATE_GOLDEN=$null # depuis la racine
git diff tests/golden # revue des snapshots de simulationOrchestrateur des quatre scripts (tempo, zenflex, spot, tarifs) : lancement
en sous-processus parallèles (APIs distinctes), sortie de chaque script
bufferisée et affichée à sa complétion, puis synthèse finale (statut, durée,
avertissements notables : INTERVENTION MANUELLE REQUISE, PARSER MANQUANT,
erreurs réseau/parsing, balise <script> à ajouter dans index.html à la
bascule d'année spot) et rappel goldens consolidé. Code de sortie : 0 si tout
OK, 1 sinon.
node update-all.mjs # run complet des 4 scripts
node update-all.mjs --dry-run # tout le pipeline sauf l'écriture
node update-all.mjs --check-only # tarifs en --check-only, les 3 autres en --dry-run
node update-all.mjs --only spot,tarifs # limite aux scripts listés (tempo, zenflex, spot, tarifs)
node update-all.mjs --full # tempo/zenflex/spot : ré-import complet de l'historique
node update-all.mjs --force # tarifs : re-parse même si SHA-256 inchangé
node update-all.mjs --sequential # l'un après l'autre (défaut : parallèle)
node update-all.mjs --goldens # enchaîne la régénération des goldens si tout est OK
npm run update-all "--" --dry-run # équivalent via npm (PowerShell avale un -- nu, d'où les guillemets)Matrice de forwarding des flags :
| Flag | tempo | zenflex | spot | tarifs (update.mjs) |
|---|---|---|---|---|
--dry-run |
--dry-run |
--dry-run |
--dry-run |
--dry-run |
--full |
--full |
--full |
--full |
— |
--force |
— | — | — | --force |
--check-only |
--dry-run |
--dry-run |
--dry-run |
--check-only |
Les flags de ciblage fin (--from/--to, --option TEMPO|EJP, --provider,
--tarif) ne sont pas forwardés : lancer le script individuel dans ce cas.
La revue humaine reste la même qu'avec les scripts individuels : relire
git diff scripts/tarifs scripts/tarifs-lib, régénérer les goldens
(automatisable avec --goldens), relire git diff tests/golden, puis commit.
| Statut | Sens |
|---|---|
A JOUR |
304 ou SHA-256 identique au dernier PDF réconcilié |
MODIFIE |
valeurs mises à jour dans les fichiers tarifs (détail affiché) |
GRILLE CHANGEE, VALEURS IDENTIQUES |
PDF ré-édité sans changement de prix (réconcilié) |
INTERVENTION MANUELLE REQUISE |
divergence structurelle : kVA ou type de jour ajouté/supprimé, priceOverrides présents d'un seul côté. L'outil ne patche jamais la structure : éditer le fichier tarif à la main, puis relancer |
NON GERE (HTML) |
price_url non-PDF (Alterna, Enercoop) : vérification manuelle |
PARSER MANQUANT |
PDF téléchargé et changement détecté, mais pas de parser (Engie, voir plus bas) |
VALEUR INTROUVABLE (patch refuse) |
le littéral attendu n'est plus dans le fichier : rien n'est écrit |
ERREUR RESEAU / ERREUR PARSING |
à investiguer (URL morte ? mise en page changée ?) |
Le SHA-256 du manifest n'est mis à jour que lorsqu'une grille est réconciliée (patch appliqué ou valeurs identiques) : une grille en intervention manuelle re-signale à chaque run tant que le fichier tarif n'a pas été adapté.
- Remplacement textuel ancré : un nombre n'est remplacé que si sa valeur actuelle est celle attendue ; tout-ou-rien par fichier.
- Revalidation VM : chaque fichier patché est rechargé avec la vraie factory
defineTarif(zéro erreur exigée) et chaque valeur est relue avant écriture. lastUpdateprend la date lue dans le PDF (« Applicable au … ») quand elle existe, sinon la date du jour.- Les puissances kVA listées par le PDF mais absentes du repo sont ignorées (TotalEnergies liste 3 à 36 kVA, le repo n'en modélise qu'un sous-ensemble). L'inverse (kVA du repo absent du PDF) bloque en manuel.
update-all.mjs orchestrateur : lance les 4 scripts en parallèle et synthétise (voir « Mise à jour globale »)
update.mjs grilles tarifaires PDF (voir « Usage » ci-dessus)
spot-update.mjs mise à jour des prix spot EPEX FR (voir « Prix spot » ci-dessous)
tempo-update.mjs mise à jour des calendriers Tempo/EJP (voir « Calendriers Tempo / EJP » ci-dessous)
zenflex-update.mjs mise à jour du calendrier des jours de sobriété Zenflex (voir « Calendrier Zenflex » ci-dessous)
manifest.json état persistant par URL { sha256, etag, lastChecked, lastApplied } — committé
lib/
tarif-defs.mjs énumère les defineTarif via node:vm (comme tests/helpers/legacyLoader.mjs)
registry.mjs URL -> fournisseur/parser ; URLs HTML non gérées
download.mjs fetch conditionnel + SHA-256 + cache/ (gitignoré)
pdf-text.mjs extraction pdfjs-dist -> items positionnés + reconstruction de lignes/cellules
table.mjs détection des tableaux kVA, cellules fusionnées (partition contiguë), buildOffer
fr-numbers.mjs nombres français ("19,27", chiffres éclatés "2 1 , 80"), €/kWh -> centimes
dates.mjs dates françaises ("1er février 2026", "1 juil. 2026", "12/06/2026", mots éclatés)
reconcile.mjs diff parser <-> repo : changements de valeurs vs divergences structurelles
spot-data.mjs logique pure des prix spot : jours locaux Paris, normalisation DST, sérialisation
tempo-data.mjs logique pure des calendriers Tempo/EJP : mapping statuts API, fusion, sérialisation
zenflex-data.mjs logique pure du calendrier Zenflex : config + parsing de l'API OPM (le reste vient de tempo-data.mjs)
patch-tarif.mjs remplacement ciblé dans les littéraux defineTarif (multi-blocs par fichier)
revalidate.mjs rechargement VM avec la vraie lib tarifs avant écriture
readme.mjs section « Derniers tarifs » du README racine
parsers/<fournisseur>.mjs un module par fournisseur : parse(doc, url) -> { gridDate, offers }
fixtures/<fournisseur>/ <slug>.json (items extraits), .txt (lisible), .expected.json (snapshot)
tools/
dump-text.mjs télécharge les PDFs et régénère les fixtures json+txt
try-parser.mjs exécute un parser sur ses fixtures et affiche la réconciliation avec le repo
gen-expected.mjs régénère les snapshots .expected.json
tests/ node --test : fr-numbers, patch-tarif, parsers (hermétiques, sans réseau)
Contrat parser : les clés de offers sont exactement les name des defineTarif ; prix kWh en centimes TTC, abonnements en € TTC/mois ; priceOverrides émis quand le PDF différencie certaines puissances (la valeur de référence est celle de la plus grande puissance).
Les tarifs spot (Sobry) consomment les prix EPEX FR Day-Ahead committés dans
scripts/tarifs-lib/spot/epex-fr-<année>.js. Mise à jour manuelle depuis
l'API energy-charts.info (données
Bundesnetzagentur | SMARD.de, CC BY 4.0) :
node spot-update.mjs # incrémental : reprend après la dernière date connue
node spot-update.mjs --dry-run # sans écriture
node spot-update.mjs --full # ré-importe tout depuis 2023-01-01
node spot-update.mjs --from 2026-01-01 --to 2026-01-31 # fenêtre expliciteParticularités : prix en EUR/MWh bruts (le moteur convertit), jours locaux
Europe/Paris normalisés à 24 valeurs (pas horaire) ou 96 (quart-horaire,
depuis le 2025-10-01), jours DST comblés/moyennés sur l'heure 02, jours
incomplets côté API omis (le simulateur les marque en erreur pour les
tarifs spot uniquement — c'est le cas du 2025-09-30, jour de la bascule de
granularité, publié tout à null par SMARD). À la bascule d'année, ajouter
la balise <script> du nouveau fichier dans index.html (le script le
signale). Logique pure testée dans tests/spot-data.test.mjs.
Les jours rouges/blancs Tempo (scripts/tarifs-lib/calendars/tempo-edf.js) et
les jours de pointe EJP (ejp-edf.js) sont mis à jour depuis l'API officielle
EDF api-commerce.edf.fr (celle du site particulier.edf.fr) :
node tempo-update.mjs # incrémental : reprend après la dernière date spéciale connue
node tempo-update.mjs --dry-run # sans écriture (rapport +ajoutés/-retirés)
node tempo-update.mjs --full # ré-importe tout l'historique depuis 2020-11-01
node tempo-update.mjs --option TEMPO # un seul calendrier (TEMPO ou EJP)
node tempo-update.mjs --from 2026-01-01 --to 2026-01-31 # fenêtre expliciteParticularités : l'API exige deux en-têtes (application-origine-controlee: site_RC, situation-usage: Jours Effacement), sinon 400 ; plage limitée à
un an par requête (le script tranche par année) ; les statuts par défaut
(TEMPO_BLEU, NON_EJP, HORS_PERIODE_EJP) et NON_DEFINI (jours futurs
pas encore annoncés — la couleur du lendemain tombe vers 11h) ne sont pas
stockés : les calendriers ne listent que les jours spéciaux. Tout statut
inconnu fait échouer le run. Le fichier est re-sérialisé entièrement
(revalidation VM avant écriture, comme le reste du dossier). Après mise à
jour : relire git diff scripts/tarifs-lib/calendars puis régénérer les
goldens (UPDATE_GOLDEN=1 node --test "tests/**/*.test.mjs"). Le premier
import (2026-07) a été fait en --full : il a purgé les erreurs des listes
historiques saisies à la main (16 dates Tempo mistypées « 2020 », doublons,
saison EJP 2025-2026 manquante). Logique pure testée dans
tests/tempo-data.test.mjs.
Les jours de sobriété Zen Week-End Flex
(scripts/tarifs-lib/calendars/zenflex-sobriete.js) sont mis à jour depuis
l'API OPM d'EDF particulier.edf.fr/services/rest/opm/getOPMStatut (aucun
en-tête particulier requis) :
node zenflex-update.mjs # incrémental : reprend après la dernière date connue
node zenflex-update.mjs --dry-run # sans écriture (rapport +ajoutés/-retirés)
node zenflex-update.mjs --full # ré-importe tout l'historique API depuis 2023-09-01
node zenflex-update.mjs --from 2026-01-01 --to 2026-01-31 # fenêtre expliciteParticularités : l'API se requête une date à la fois
(?dateRelevant=AAAA-MM-JJ) et répond { couleurJourJ, couleurJourJ1 }
(statut du jour demandé et du lendemain — le script ne requête donc qu'une
date sur deux). Seul ZENF_PM (sobriété) est stocké ; RAS, NON_DETERMINE
(futur / hors saison), ZENF_BONIF (jours bonifiés d'hiver) et ZENF_BONUS
(jours bonus d'été) sont ignorés — ces deux derniers ne sont pas pricés par
le tarif du repo —, tout autre statut fait échouer le run.
L'historique API commence à la saison 2023-2024 (les jours de sobriété
connus de 2020-2022 répondent RAS) : contrairement à Tempo/EJP, --full ne
remplace que la fenêtre re-fetchée et préserve toujours les jours antérieurs
au 2023-09-01 (saisie manuelle d'origine, y compris ses dates douteuses
« 2020/01/xx », invérifiables via l'API). Même workflow qu'au-dessus après un
run : git diff scripts/tarifs-lib/calendars puis goldens. Logique pure
testée dans tests/zenflex-data.test.mjs.
Le parser échoue bruyamment (ERREUR PARSING, nombre de tableaux/colonnes inattendu) — il ne patche jamais silencieusement faux. Marche à suivre :
node tools/dump-text.mjs --provider <fournisseur> # re-télécharge et régénère les fixtures
# inspecter fixtures/<fournisseur>/<slug>.txt, adapter parsers/<fournisseur>.mjs
node tools/try-parser.mjs <fournisseur> # itérer jusqu'à cohérence
node tools/gen-expected.mjs # re-snapshotter, relire le diff
npm test- Alterna, Enercoop :
price_urlen HTML, vérification manuelle. - Engie : les fiches descriptives décomposent les prix en fourniture + acheminement (TURPE CU/CU4/MUDT/MU4) + obligations, en €/an — la reconstruction du prix TTC mensuel est trop hasardeuse pour un parser fiable. Le changement de grille reste détecté par SHA-256 (
PARSER MANQUANT). Par ailleurs deux des troisprice_urlEngie du repo renvoient 404 (tranquillité, happy-heures-vertes) : à rafraîchir dans les fichiers tarifs. - Sobry : le PDF de la grille est un export design (texte vectorisé en tracés, zéro item texte pour pdfjs) — parser textuel impossible sans OCR. Le changement de grille reste détecté (
PARSER MANQUANT, mapping par URL exacte dansregistry.mjs). Mise à jour manuelle depuis le PDF :subscriptions[kVA] = (Total_CU4_HTVA + 15 % × Acheminement_HTVA) × 1,20(table « Abonnement mensuel C5 », grille CU4) etspotFormula(composantes p. 2-3 et 9, ×100 pour passer en centimes).reconcile.mjssait déjà comparerspotFormulasi un parser voit le jour. - Les nouvelles puissances proposées par un fournisseur (ex. un kVA ajouté au PDF) ne sont pas signalées si le repo ne les modélise pas.