Les cartes markdown utilisaient un bloc YAML replie (content: >), qui remplace
les retours a la ligne par des espaces : les tableaux markdown perdaient leur
structure et s'affichaient comme une ligne de texte avec des barres verticales.
Passage en bloc litteral (content: |) sur les six cartes, et le tableau des
profils emet desormais chaque ligne sur la meme ligne que la boucle, sinon
Jinja intercale une ligne vide entre chaque rangee et casse le tableau.
Ajout d'un repli lisible quand les profils ne sont pas encore publies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Dashboard YAML dedie "bilan-energetique", declare dans configuration.yaml et
copie versionnee dans dashboards/ du depot.
Vue Bilan : la comparaison qui decidera du cablage — seuil calcule, seuil fixe
encore en vigueur, SOC reel — avec un commentaire automatique disant lequel des
deux est trop haut ou trop bas ; la decision immediate ; la prochaine traversee
deficitaire ; une alerte conditionnelle chiffrant la capacite manquante quand
meme une batterie pleine ne passe pas ; l'etat de bridage de la production avec
son motif ; deux graphiques d'historique sur 48 h.
Vue Profils et reglages : tableau horaire des quatre profils avec le taux de
censure par heure, capacite batterie modifiable, kill-switch, statut.
configuration.yaml sauvegarde dans SAVE_CONFIG/ avant modification,
check_config valide, redemarrage HA effectue, journal sans erreur nouvelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
5c — BALANCE ne repondait qu'a "combien jusqu'a ce que le solaire prenne le
relais". A 15 h la reponse est zero : exacte mais inutile, le deficit qui
approche etant celui de la nuit. Ajout de la prochaine traversee deficitaire
(heure de debut, energie, SOC a atteindre avant). Ce SOC n'est PAS ecrete a
100 % : au-dela, meme une batterie pleine ne passe pas, et le manque est
publie comme storage_shortfall_kwh — c'est le chiffre du dimensionnement.
5b — Workflow "EVCS Energy Profiles Daily V1" (N6UaB4ZozsbRPrFb), actif,
03:30 : lit /api/history/period sur 30 jours glissants et republie les quatre
CSV (conso p50 et p80, enveloppe de production, taux de censure).
CONSUMPTION_PROFILE devient DAILY_PROFILES et sait desormais decoder la
reponse brute de l'API en plus des series deja pretes.
Piege trouve : sans end_time, /api/history/period ne renvoie qu'UNE journee,
silencieusement. Mes premieres mesures "sur 3 jours" n'en couvraient qu'une.
Validation croisee : profils calcules depuis l'API REST (etats bruts, 11 j)
contre profils calcules depuis les statistiques long terme (28 j) — 0,20 kWh/h
d'ecart absolu moyen, par deux chemins de donnees independants.
BUILD_HA_CALLS_PROFILES refuse d'ecraser les profils en place si l'historique
ne couvre pas les 24 heures. Meme garantie de perimetre que le bilan.
Suite a 27 scenarios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Workflow n8n "EVCS Energy Balance V1" (id QoSfPeyGq35orF1K), actif, cycle de
5 min : snapshot HA -> CENSORSHIP et RESOURCE_ESTIMATE en parallele ->
PREPARE_BALANCE -> BALANCE -> BUILD_HA_CALLS -> appels de service HA.
MODE OMBRE garanti structurellement : BUILD_HA_CALLS n'ecrit que vers des
entites evcs_balance_* plus une trace logbook. La suite de tests echoue si une
ecriture sort de ce perimetre — verifie par mutation, l'injection d'un
turn_on sur switch.edf_general_ok_switch fait rougir la suite. Kill-switch
input_boolean.evcs_balance_enabled : a OFF, aucune ecriture du tout.
Package HA evcs_energy_balance.yaml : 18 helpers. Profils de consommation,
enveloppe de production et taux de censure amorces avec les valeurs calculees
sur 28 jours reels ; capacite utile posee a 15 kWh (7 modules actifs).
Outillage : gen-energy-balance-workflow.mjs, enregistrement dans
lib-node-map.mjs, suite cablee dans n8n-push.mjs (bloquante). Suite a 24
scenarios.
Premiere execution reelle : le bilan s'est declare indisponible faute
d'entrees, exactement comme prevu, au lieu d'inventer des valeurs. Le format
du snapshot (corps JSON dans `data`) et la lecture des profils CSV ont ete
corriges ensuite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
BALANCE.js ne pilote rien : il publie les faits qui remplacent les seuils fixes
— heure de croisement, energie a couvrir, SOC requis (remplacant de
grid_soc_off), energie reseau strictement necessaire, surplus disponible pour
le vehicule, indice de confiance. Sans une de ses entrees il se declare
inutilisable et les controleurs gardent leurs regles.
Deux niveaux de prudence. Le premier jet n'utilisait que le profil p80 et
exigeait 48 % de SOC pour franchir la matinee du 24/08, soit plus cher que le
seuil fixe de 30 % qu'il remplace. Corrige : la decision reseau, reevaluee a
chaque cycle et reversible, utilise le profil typique p50 ; le prudent p80 sert
a la planification. La marge de securite de 10 % ajoutee au depart etait une
constante arbitraire et un double comptage : mesure faite, p80(somme) egale
somme(p80) sur toutes les fenetres, les heures de consommation etant fortement
correlees. Marge supprimee.
Rejeu fige de la matinee du 24/08 (SOC 21 %, capacite 15 kWh, production
reellement mesuree) : croisement a 08 h, 0,82 kWh a couvrir contre 0,80 calcule
a la main, SOC requis 27,5 %, une session reseau de 0,97 kWh. Suite a 21
scenarios.
MANUEL_COMPLET section 0 : description materielle complete relevee dans VRM et
completee par le proprietaire — 2 MultiPlus-II 48/5000 en parallele, 53
panneaux sur trois orientations (10 est/sud-est, 30 sud, 10 ouest, 3 sur un
petit MPPT), parc Pylontech, borne 32 A. Explique pourquoi aucun modele
solaire mono-plan ne peut representer la production, et pourquoi Forecast.Solar
mal configure se trompe de repartition.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
RESOURCE_ESTIMATE.js. Recalage de la prevision : ecarte, la censure est
correlee au beau temps (facteur 4,29 a 9 h sur 27 echantillons contre 0,48 a
15 h sur 3), les donnees manquantes ne le sont pas au hasard. Forme
geometrique : ecartee, un plan unique ajuste sur les heures propres converge
sur plein est 20 degres et predit 2,0 % de la journee a 18 h contre 5,8 %
mesures — le champ a plusieurs orientations que ce modele ne represente pas.
Retenu : forme = enveloppe horaire demontree (comportement reel du champ, sans
modele), magnitude = prevision Forecast.Solar (meteo, insensible a notre
batterie). L'enveloppe de l'apres-midi etant elle-meme censuree, le noeud
publie un indice de confiance et signale les heures minorees au lieu de les
presenter comme fiables. Sans enveloppe, il se declare inutilisable plutot que
d'inventer une forme.
Defaut trouve au passage : l'orientation declaree a Forecast.Solar ne
correspond pas au champ. Matin 07-11 h, 15,1 % de la journee prevus contre
39,3 % reels ; apres-midi 14-18 h, 52,1 % contre 35,2 %. Les totaux
journaliers tiennent, la repartition intra-journaliere est inutilisable.
Suite portee a 17 scenarios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Parc reel : 8 US2000C en 2 packs de 4 avec hub, packs d'ages differents, un
module en panne et isole (7 actifs), un autre qui faiblit. Consequence :
batteryUsableCapacityKwh = 17,5 code en dur dans INPUT_PARSE surestime la
reserve reelle de 15 a 20 %. Aucun capteur de capacite ou de sante batterie
n'existe cote HA.
Energie perdue faute de puits : la premiere estimation (23,9 kWh/jour, 38 %)
etait fausse. Le facteur de recalage global de 1,22 est domine par les heures
de 8-9 h ou il vaut 4 a 7. Probleme structurel : la censure est correlee au
beau temps, donc les donnees manquantes ne le sont pas au hasard et aucun
facteur appris sur cet echantillon n'est transportable. Cela invalide le plan
initial de l'etape 3.
Estimation retenue, borne basse sans recalage, plafonnee par l'enveloppe
horaire reellement demontree : 11,8 kWh/jour en moyenne, 23 % de la ressource.
On jette donc plus qu'on n'importe (7,83 kWh/jour) : l'energie existe, il
manque le reservoir. +8 kWh utiles sont a la fois necessaires et remplissables.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Le proprietaire vise l'autonomie et doit decider combien de batteries ajouter.
La ressource gaspillee par la censure est exactement l'energie qu'un stockage
supplementaire capterait : le bilan doit donc aussi estimer l'energie perdue
faute de puits et le gain par kWh ajoute.
Constat mesure sur 27 jours : import reseau 7,83 kWh/jour en moyenne, un seul
jour sur 27 a zero ; decharge batterie 11,5 kWh/jour en moyenne, p90 14,3, max
16,1 sur 17,5 utiles. La batterie se vide presque integralement chaque jour :
c'est elle le facteur limitant, pas la puissance des onduleurs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
CONSUMPTION_PROFILE.js : compteurs d'energie cumules -> deltas horaires ->
distribution par heure de la journee -> percentiles. La consommation du
vehicule est retiree : c'est une charge pilotable, elle ne doit pas polluer le
profil de la charge subie. Les remises a zero de compteur et les deltas
implausibles sont jetes, jamais rattrapes par une valeur inventee, et une heure
sans assez de jours observes est marquee non fiable au lieu d'etre publiee en
silence.
Valide sur 28 jours reels (639 heures, 0 reset) : 31,9 kWh/jour typique hors
vehicule, 42,8 kWh au p80. Le profil tient dans un helper input_text
(119 caracteres sur 255).
Verification prealable de la methode : seulement 7,8 % des heures de deficit
sont censurees. Censure et deficit sont physiquement presque exclusifs, donc la
grandeur qui pilote la decision EDF est mesuree sur des donnees propres.
Suite tests/energy-balance/ etendue aux deux noeuds : 14 scenarios.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Sans injection reseau, PV_mesuree = min(ressource, conso + absorption batterie).
La mesure est donc censuree a droite des que la batterie est pleine ou que le
reseau alimente AC-IN. Mesure sur l'historique du 28/07 au 24/08 : 48 % des
heures de jour sont censurees, 77 a 96 % a partir de 13 h. Un profil de
production bati sur la mesure brute mesure en realite sa propre consommation.
src/energy-balance/HA_SNAPSHOT_TEMPLATE.json.tpl : 26 champs, dont tous les
signaux qu'aucun workflow ne lisait jusqu'ici — etats des trois MPPT, etat
VE.Bus, alarme de surcharge onduleur, ESS BatteryLife, etat du bus DC, statut
natif de la borne, prevision horaire, elevation du soleil.
src/energy-balance/CENSORSHIP.js : qualifie chaque echantillon (censure et ses
motifs, exploitabilite) et verifie en continu l'identite du bus DC
PV + VE.Bus = batterie, qui boucle a 95 W pres — un capteur qui derive devient
detectable. Ne decide rien.
tests/energy-balance/ : 10 scenarios, dont un snapshot reel du 24/08 14:29
(soleil 52 degres, prevision 3462 W, production bridee a 1159 W, SOC 94 %)
correctement classe censure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Journal leger pour ne rien perdre en route (documentation definitive a la fin,
comme convenu) : decisions actees, sorties attendues du bilan, et backlog des
defauts releves en chemin — doublon unique_id victron_vebus_dc_power,
evcsDedicatedMaxW code en dur, alarmes et etats MPPT jamais lus, controle de
vraisemblance PV + VE.Bus = batterie, etats zombies source_lost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
tools/ha-service.mjs comble le trou des MCP disponibles : ha-get-state lit un
etat, ha-set-helper ecrit un helper, mais aucun n'appelle un service. Or
homeassistant.check_config et homeassistant.restart sont indispensables apres
une modification de configuration.yaml (le recorder n'est pas rechargeable a
chaud). Meme schema que tools/n8n-push.mjs : fetch, jamais curl, jeton lu dans
.env, jamais en dur. Les services destructifs exigent --yes explicite.
.env ajoute a .gitignore : il contenait HA_TOKEN et n'etait pas exclu, un
git add -A l'aurait pousse sur le remote (le fichier n'etait pas encore suivi).
Note : configuration.yaml (hors depot, sur le NAS) recoit
recorder.purge_keep_days = 90 pour pouvoir rejouer un incident plusieurs
semaines apres coup. Sauvegarde dans SAVE_CONFIG/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Incident du 24/08 12:24-12:28 : bascule de profil vers midday_hc_or_solar
avec SOC 58 %. Plancher de demarrage = min(midday_soc_min 75, abondance P6
40) = 40 ; plancher de maintien = max(soc_stop_min 29, 75-15) = 60. Le
systeme avait donc le droit de DEMARRER ce qu'il n'avait pas le droit de
MAINTENIR : cycle limite a la periode de min_run (3 min) sur toute la bande
[40 %, 60 %], et ~2,6 kW de PV ecretes a chaque creux (PV 10,24 kW -> 7,64 kW
quand la borne s'arrete, ESS sans injection).
Deux volets indissociables dans BATTERY_CLOUD_LOCKS.js :
- effectiveSocHoldFloor = min(regle du profil, plancher de demarrage
effectif). Sans abondance, comportement inchange.
- l'abondance P6 se prouve au demarrage (batterie >= +20 A) mais se maintient
en charge (batterie >= 0) : sinon la charge du vehicule, qui detourne
precisement le surplus, detruit sa propre condition d'autorisation.
Golden 39 -> 40. Preuve par mutation sur midday-inversion-start-hold : code
d'avant -> stop / "Conditions de maintien perdues", apres -> step_up 19 A.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Fix G provoquait un yoyo EDF de 2-4 min (coupure a SOC 21 en bypass min_on
contre rallumage a SOC 20 en bypass cooldown) : son garde `soc > gridSocOn`
ne protegeait que l'instant de l'engagement sur SOC bas, pas la phase de
remontee 20 -> 30. jsCode restaure byte-identique a la version pre-Fix-G.
Le fixture de test portait les fallbacks du code (22/35) au lieu des valeurs
de production lues via MCP (20/30), et le scenario de non-regression testait
SOC 18 %, le seul point que Fix G epargnait. Suite reecrite avec les
garde-fous manquants h-recovery-band-no-cut-21/-25. Mutation : Fix G
reapplique -> 4/7 echecs, retire -> 7/7.
Documentation du diagnostic des deux oscillateurs (yoyo EDF et oscillation
de la charge EVCS en plein soleil) dans MANUEL_COMPLET 11.8 et HANDOFF 25.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
Ajout de la configuration MCP (5 serveurs n8n) et du CLAUDE.md qui charge
AGENTS.md/HANDOFF.md/MEMORY.md/docs/MANUEL_COMPLET.md, ainsi que les skills
n8n de référence (.claude/skills/*, docs uniquement, pas de code exécuté).
.claude/settings.local.json reste ignoré (déjà dans .gitignore).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Incident nuit 21-22/08 : la fenêtre HC nuit se ferme pendant que le véhicule
est encore en session active (pas encore flag "charge terminée"). L'EVCS
relâche correctement son override, mais émet un état transitoire générique
(stop_source_lost/idle_wait_source) qu'aucune règle de coupure immédiate du
workflow Grid Hysteresis (Fix C/E2) ne reconnaît. EDF restait alors maintenu
des heures par la boucle générique jusqu'au seuil statique gridSocOff,
plafonnant le MPPT Victron à ~100W et privant la maison de solaire.
Nouvelle règle générique (Compute Grid Hysteresis) basée sur evcsGridOverride
plutôt que sur une liste blanche de noms de surplus_state — couvre donc aussi
tout futur état non énuméré. Garde anti-glitch sur lecture HA invalide.
Nouvelle suite tests/golden-hysteresis/ (7 scénarios, première pour ce
workflow), câblée bloquante dans tools/n8n-push.mjs. Déployé et validé
(golden-hysteresis 7/7, golden EVCS 39/39 inchangé).
Écarte le diagnostic incomplet laissé par un autre agent dans HANDOFF.md
(edf_general_ok_off côté EVCS aurait créé un deuxième écrivain non coordonné
sur le même switch).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- WINDOWS: dayVehicleFullIdle (statut 3 ou VDF, héritages jour<->nuit),
sourceMode blocked même face au solaire
- DECISION: branche collante day_vehicle_full_idle au-dessus des gardes
- échappatoires force_charge_now/force_grid_now (golden)
- golden 33->36, validé live 10:04:30 (latch en 16 s sur la VW pleine)
- HANDOFF §22 + MANUEL états collants
- DECISION: branche done/recovery_done remontée dans la cascade principale
(avant mustStopHard) → le collant se réécrit chaque cycle, survit aux
micro-charges de la VW et aux hard stops batterie
- branche Fix E interne supprimée (remplacée par la branche haute)
- golden 25→26 (night-hard-stop-necrase-pas-done)
- HANDOFF §19
- src/nodes/*.js (6 nodes + template snapshot) = source de vérité unique.
- n8n-push.mjs reconstruit le JSON depuis src/ avant syntaxe+golden+PUT
(src gagne toujours; n8n-extract.mjs pour resynchroniser si édition UI).
- n8n-build.mjs --check pour vérifier la synchro.
- Copies historiques déplacées dans archive/ (mortes, dérivées de la prod).
- .gitignore: backups locaux de rollback.
- AGENTS.md: règle R2 pour toutes les futures sessions (dont Qwen local).
- Validé: idempotence, mutation/restauration, pipeline push complet en réel.
- vehicleChargeCompleted COLLANT via prev_surplus_state (nouveau champ snapshot):
vehicle_full_or_paused -> night_session_done_idle auto-entretenu jusqu'au
débranchement ou fin de fenêtre.
- Fix inversion 04/08: le latch takeover se libère à la fin de charge
(était: || vehicleChargeCompleted qui le GARDAIT).
- P7 relâché quand charge finie; sourceMode nuit -> blocked (zéro retry).
- SOC > cible: takeover OFF -> hystérésis coupe EDF. SOC <= cible: takeover ON
-> EDF maintenu + ACM recharge batterie (spec 'sauf si sous seuil').
- night_session_done_idle ne matche aucun pattern no-demand de l'hystérésis
(condition de fonctionnement de l'override batterie basse).
- Golden: 19/19 (3 nouveaux scénarios P8, 16 existants inchangés).
- Root cause 1: battery_assist_night ne démarrait jamais (floor décharge 0A
impossible la nuit) -> idle_wait_conditions jusqu'à dérive du SOC au trigger.
- Root cause 2: allow_grid_hc_night parsé mais jamais utilisé.
- Fix: nightGridHcPreferred dans WINDOWS_SOURCEV2 + BATTERY_CLOUD_LOCKS ->
grid_hc_allowed dès l'ouverture de la fenêtre HC (EDF ON auto, ACM hold 2A).
- Toggle OFF = comportement historique. Dry-run 7 scénarios validés.
- Outils: n8n-pull/n8n-push (push REST direct, backup auto, node --check).
- Déployé en production le 07/08/2026 15:45, exécutions vérifiées success.
- Add afternoonProbeEligible in WINDOW_SOURCEV2.js
- Enable hybrid_probe sourceMode in outside_window when probe conditions met
- Add afternoon probe parameters in INPUTPARSE (inputparse.js + inputparseP2.js)
- Update probeStartOk in BATTERY_CLOUD_LOCKS to accept afternoon probe
- Update blockStillRelevant for probe_failed to include outside_window
Fixes the issue where strong afternoon sun with SOC>85%
was blocked by high cloudDebt and no probe mechanism.