Jour faible : charge maximale en HC de midi (bilan vise 100 %, plafond ACM = abonnement - maison + solaire)

This commit is contained in:
Tallagrand Thierry
2026-10-08 00:07:54 +02:00
parent 36ceb41933
commit 043fb85383
9 changed files with 136 additions and 11 deletions

View File

@@ -2098,5 +2098,28 @@ SOC atteint a 07:24 contre la cible. La fenetre de midi du 07/10 s'est fermee 2
- ⚠️ A verifier au prochain jour faible : plancher de midi publie, recharge HC (courant ACM, consigne), heure - ⚠️ A verifier au prochain jour faible : plancher de midi publie, recharge HC (courant ACM, consigne), heure
de fermeture EDF le soir (objectif : apres 01:24, pas en HP). de fermeture EDF le soir (objectif : apres 01:24, pas en HP).
### 08/10 00:07 — Jour faible : la HC de midi charge au MAXIMUM, sans valeur fixe
Constat du 07/10 (proprietaire) : HC de midi perdue (consigne tenue a la maison + ACM 30 A fixes, corriges a
14:17/14:22), et mon estimation « ~44 % a 14:24 » n'etait que la CIBLE « tenir jusqu'a 01:24 » (profil p50),
pas ce que l'installation peut charger. Physique (07/10 midi, maison ~5,8 kW BOOST + cuisine, PV ~1,5 kW) :
(10,8 kW = 90 % de 12 kVA - maison) x ~0,92 + PV, plafonne par les 140 A des deux MultiPlus (~7,3 kW) -> ~6 kW,
soit ~40 points en 2 h : 30 -> ~65-70 % etait possible ; 80 % depuis 30 % ne l'est pas (15 kWh = 7,5 kW).
**Decision du proprietaire : charger le max possible, adaptatif, pas de valeur fixe.** Un jour faible, un kWh
pris a midi remplace un kWh d'heures pleines du soir (ou au pire un kWh HC de nuit, ~10 % de pertes).
- **Bilan** (`BALANCE.js`) : fenetre HC de MIDI (la prochaine HC est la nuit) ET decision du jour `on`
(`ecs_nuit_jour`) -> plancher HC = 100 % (typique et prudent) ; cible et manque bougent ensemble (session
engagee ET financee). `hcReach.maxCharge`, `hcReach.floorReachPct` (le « tenir jusqu'a 01:24 » calcule, pour
comparaison) ; raison « Jour faible : charge maximale en HC de midi (tenir jusqu'a 01:24 demandait x %) ».
Nuit inchangee. Tests 90/90 (+3), deux mutations qui mordent (sans la condition jour faible : 4 ; sans la
condition midi : 1).
- **ACM** (`tools/patch-20261008-acm-plafond-solaire.mjs`) : plafond du courant calcule = min(140 A,
(90 % abonnement - maison + PV) / tension) — le solaire charge en courant continu, en plus de l'entree reseau.
L'import reste borne par la consigne du controleur (`GRID_SETPOINT_MAX_W` 10 kW). golden-acm 25/25 (+2 :
128,4 A au releve du 07/10, 140 A maison 2 kW), mutation qui mord (98 A sans le PV).
- Deploye 08/10 00:07. Sauvegarde ACM `acm.bak-20261008.json` (scratchpad) ; retour = `git revert` + push des
deux workflows.
- ⚠️ A verifier au prochain jour faible : courant ACM (~100-140 A), consigne au plafond, SOC a 14:24, et que la
PV n'est pas evincee (part DVCC des MPPT : priorite solaire de Victron, non verifiee en reel).
--- ---
*Document mis a jour le : 07/10/2026 (~22:45) — reprise de 16 h verifiee, EDF en HP a 18:03, bilan : reprise du soir et prevision recalee sur la matinee ; bilan HC de midi, reprise du soir apres coupure en pleine chauffe, mesure ECS sur 10 min ; recharge reseau HC : courant calcule (12 kVA) et consigne non tenue ; HC de midi : AUTO ne chauffe pas (BOOST), alerte v2, Jeep (contact, relecture, zone), recharge batterie bridee a 30 A par l'ACM ; jour faible = EDF les deux fenetres HC pour le chauffe-eau, alerte BOOST, Victron Mobile refait ; 1re decision jour faible en reel, mesure ECS reparee, douches 06:00/20:00, Grafana chauffe-eau, tableaux ECS ; chauffe-eau en HC de nuit les jours faibles (decision du bilan, workflow ECS sous controle du depot, VRM, V2.3 desactive) ; garde batterie reseau : voiture bridee a 6 A au lieu d'un arret ; garde-fou concierge 2 h ; allers-retours brefs de la borne retrouves dans l'historique HA (echange VW -> JEEP de 20 s), production 14,5 kWh contre 22 prevus, defaut garde batterie en mode reseau a trancher ; voiture a midi : la batterie n'est plus comptee comme du solaire (EDF automatique les jours de depart si le surplus ne couvre pas) ; nuit 05->06 : JEEP chargee sur EDF en complete (6,32 kWh), budget 0 du a la prevision, Windy/ECMWF contre VRM, a comparer ce soir ; concierge aligne sur la preuve fin de trajet ; a verifier : nuit 05->06 et mardi faible, comparaison VRM/Forecast.Solar ; JEEP identifiee aussi par sa fin de trajet, prevision de la nuit 05->06 ; identification JEEP reussie en reel, correction humaine qui prime (refus supprime), session du 04/10 coupee ; EVCS Master en quatre onglets ; Jeep lue par Uconnect et identifiee seule, capacite JEEP 10 kWh, concierge variante (a) ; releve par photo (SPARK), hass-uconnect en attente ; ecart semaine/week-end mesure (pas de profil week-end la nuit) ; nuit du budget : budget borne au plancher prudent, sans EDF = voiture bloquee meme EDF ferme (batterie seule) ; Energie : sauts corriges ; tableau Energie complete (Sud/Ouest, batterie par integration, borne ; aucun redemarrage HA) ; kilometrage des vehicules (km/kWh, capacite, releves de reference, agent cable 15/15) ; a verifier le 04/10 au matin : nuit du budget JEEP ; concierge : vehicule non confirme demande, heure de saisie ; Grafana vehicules en tableau ; Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10* *Document mis a jour le : 08/10/2026 (~00:15) — jour faible : charge maximale en HC de midi (bilan 100 %, ACM plafond + solaire) ; 07/10/2026 (~22:45) — reprise de 16 h verifiee, EDF en HP a 18:03, bilan : reprise du soir et prevision recalee sur la matinee ; bilan HC de midi, reprise du soir apres coupure en pleine chauffe, mesure ECS sur 10 min ; recharge reseau HC : courant calcule (12 kVA) et consigne non tenue ; HC de midi : AUTO ne chauffe pas (BOOST), alerte v2, Jeep (contact, relecture, zone), recharge batterie bridee a 30 A par l'ACM ; jour faible = EDF les deux fenetres HC pour le chauffe-eau, alerte BOOST, Victron Mobile refait ; 1re decision jour faible en reel, mesure ECS reparee, douches 06:00/20:00, Grafana chauffe-eau, tableaux ECS ; chauffe-eau en HC de nuit les jours faibles (decision du bilan, workflow ECS sous controle du depot, VRM, V2.3 desactive) ; garde batterie reseau : voiture bridee a 6 A au lieu d'un arret ; garde-fou concierge 2 h ; allers-retours brefs de la borne retrouves dans l'historique HA (echange VW -> JEEP de 20 s), production 14,5 kWh contre 22 prevus, defaut garde batterie en mode reseau a trancher ; voiture a midi : la batterie n'est plus comptee comme du solaire (EDF automatique les jours de depart si le surplus ne couvre pas) ; nuit 05->06 : JEEP chargee sur EDF en complete (6,32 kWh), budget 0 du a la prevision, Windy/ECMWF contre VRM, a comparer ce soir ; concierge aligne sur la preuve fin de trajet ; a verifier : nuit 05->06 et mardi faible, comparaison VRM/Forecast.Solar ; JEEP identifiee aussi par sa fin de trajet, prevision de la nuit 05->06 ; identification JEEP reussie en reel, correction humaine qui prime (refus supprime), session du 04/10 coupee ; EVCS Master en quatre onglets ; Jeep lue par Uconnect et identifiee seule, capacite JEEP 10 kWh, concierge variante (a) ; releve par photo (SPARK), hass-uconnect en attente ; ecart semaine/week-end mesure (pas de profil week-end la nuit) ; nuit du budget : budget borne au plancher prudent, sans EDF = voiture bloquee meme EDF ferme (batterie seule) ; Energie : sauts corriges ; tableau Energie complete (Sud/Ouest, batterie par integration, borne ; aucun redemarrage HA) ; kilometrage des vehicules (km/kWh, capacite, releves de reference, agent cable 15/15) ; a verifier le 04/10 au matin : nuit du budget JEEP ; concierge : vehicule non confirme demande, heure de saisie ; Grafana vehicules en tableau ; Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10*

View File

@@ -436,9 +436,30 @@ function coverage(slots, hcFloorPct = 0) {
const slotsP50 = buildSlots(consumptionP50); const slotsP50 = buildSlots(consumptionP50);
const slotsP80 = buildSlots(consumptionP80, true); const slotsP80 = buildSlots(consumptionP80, true);
const hcFloorTypicalPct = hcReachFloorPct(slotsP50, nextHcStartMs); const hcFloorReachPct = hcReachFloorPct(slotsP50, nextHcStartMs);
const hcFloorNightPrudentPct = hcNightReachActive ? hcReachFloorPct(slotsP80, nextNightHcStartMs) : 0; const hcFloorNightPrudentPct = hcNightReachActive ? hcReachFloorPct(slotsP80, nextNightHcStartMs) : 0;
const hcFloorPrudentPct = Math.max(hcReachFloorPct(slotsP80, nextHcStartMs), hcFloorNightPrudentPct); const hcFloorPrudentReachPct = Math.max(hcReachFloorPct(slotsP80, nextHcStartMs), hcFloorNightPrudentPct);
// ---------------------------------------------------------------------
// JOUR FAIBLE : LA HC DE MIDI CHARGE AU MAXIMUM (08/10/2026, proprietaire)
//
// Le 07/10, « tenir jusqu'a 01:24 » visait 44 % (profil habituel) : la soiree
// a ete plus lourde, EDF s'est ferme en heures pleines a 18:03. Un jour faible
// (decision du bilan apres 07:24, `ecs_nuit_jour` = on), l'apres-midi ne
// remplira pas la batterie : un kWh pris a midi remplace un kWh d'heures pleines
// du soir, ou au pire un kWh d'heures creuses de la nuit (meme prix, ~10 % de
// pertes). Viser juste ne protege de rien.
//
// La cible devient 100 % pendant la fenetre de MIDI (la prochaine HC est celle
// de la nuit) : ce qui est REELLEMENT charge est fixe par la physique, pas par
// un nombre — l'ACM regle le courant sur ce que l'abonnement laisse libre une
// fois la maison (et la voiture) servie, plus le solaire, plafonne par les
// 140 A des deux MultiPlus. 65, 70 ou 80 % selon la cuisine et le soleil.
// La nuit, la regle « tenir jusqu'a midi » est inchangee.
// ---------------------------------------------------------------------
const hcMiddayWindow = hcReachActive && hcNightReachActive && nextHcStartMs === nextNightHcStartMs;
const hcMaxCharge = hcMiddayWindow && ecsNuitAujourdhui;
const hcFloorTypicalPct = hcMaxCharge ? 100 : hcFloorReachPct;
const hcFloorPrudentPct = hcMaxCharge ? 100 : hcFloorPrudentReachPct;
const typical = coverage(slotsP50, hcFloorTypicalPct); const typical = coverage(slotsP50, hcFloorTypicalPct);
const prudent = coverage(slotsP80, hcFloorPrudentPct); const prudent = coverage(slotsP80, hcFloorPrudentPct);
@@ -893,6 +914,10 @@ return [{
windowEndMs: hcReachActive ? hcWindowEndMs : null, windowEndMs: hcReachActive ? hcWindowEndMs : null,
nextHcStartMs: hcReachActive ? nextHcStartMs : null, nextHcStartMs: hcReachActive ? nextHcStartMs : null,
floorPct: round1(Math.min(100, hcFloorTypicalPct)), floorPct: round1(Math.min(100, hcFloorTypicalPct)),
// Jour faible a midi : cible 100 % (charge maximale) ; floorReachPct garde
// le plancher « tenir jusqu'a 01:24 » calcule, pour comparaison.
maxCharge: hcMaxCharge,
floorReachPct: round1(Math.min(100, hcFloorReachPct)),
floorPrudentPct: round1(Math.min(100, hcFloorPrudentPct)), floorPrudentPct: round1(Math.min(100, hcFloorPrudentPct)),
nextNightHcStartMs: hcNightReachActive ? nextNightHcStartMs : null, nextNightHcStartMs: hcNightReachActive ? nextNightHcStartMs : null,
nightFloorPrudentPct: round1(Math.min(100, hcFloorNightPrudentPct)), nightFloorPrudentPct: round1(Math.min(100, hcFloorNightPrudentPct)),

View File

@@ -160,7 +160,9 @@ const reason = balance.crossing.found
// Fenetre HC (28/09) : dire quand c'est « tenir jusqu'a la prochaine HC » qui // Fenetre HC (28/09) : dire quand c'est « tenir jusqu'a la prochaine HC » qui
// fixe l'exigence, sinon la raison affiche un croisement qui ne l'explique pas. // fixe l'exigence, sinon la raison affiche un croisement qui ne l'explique pas.
const hc = balance.hcReach || {}; const hc = balance.hcReach || {};
const hcText = hc.active && hc.raisesRequirement const hcText = hc.active && hc.maxCharge
? ` Jour faible : charge maximale en HC de midi (tenir jusqu'à ${new Date(hc.nextHcStartMs).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' })} demandait ${hc.floorReachPct}%).`
: hc.active && hc.raisesRequirement
? ` Tenir jusqu'à la prochaine HC (${new Date(hc.nextHcStartMs).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' })}) → ${hc.floorPct}% en fin de fenêtre.` ? ` Tenir jusqu'à la prochaine HC (${new Date(hc.nextHcStartMs).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' })}) → ${hc.floorPct}% en fin de fenêtre.`
: ''; : '';
const hhmm = ms => new Date(ms).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' }); const hhmm = ms => new Date(ms).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' });

View File

@@ -105,6 +105,8 @@ const SUMMARIZE = {
// Tenir jusqu'a la prochaine fenetre HC (28/09). // Tenir jusqu'a la prochaine fenetre HC (28/09).
hcReachActive: o.hcReach ? o.hcReach.active : false, hcReachActive: o.hcReach ? o.hcReach.active : false,
hcFloorPct: o.hcReach ? o.hcReach.floorPct : null, hcFloorPct: o.hcReach ? o.hcReach.floorPct : null,
hcFloorReachPct: o.hcReach ? o.hcReach.floorReachPct : null,
hcMaxCharge: o.hcReach ? o.hcReach.maxCharge : null,
hcNightFloorPrudentPct: o.hcReach ? o.hcReach.nightFloorPrudentPct : null, hcNightFloorPrudentPct: o.hcReach ? o.hcReach.nightFloorPrudentPct : null,
// Nuit prevue par la soiree (28/09). // Nuit prevue par la soiree (28/09).
nightModelActive: o.nightModel ? o.nightModel.active : false, nightModelActive: o.nightModel ? o.nightModel.active : false,

View File

@@ -570,7 +570,35 @@ export const balanceScenarios = [
snapshot: Object.assign(snap2709('2026-09-27T12:30:00+02:00', 31, MIDI_2709, SOMBRE), snapshot: Object.assign(snap2709('2026-09-27T12:30:00+02:00', 31, MIDI_2709, SOMBRE),
{ ecs_nuit_jour: '2026-09-27|on|x', ecs_reprise_soir_kwh: '2.1' }), { ecs_nuit_jour: '2026-09-27|on|x', ecs_reprise_soir_kwh: '2.1' }),
// Verifie a la main : 2,1 kWh / 0,30 kWh par point = +7,0 points (38,1 -> 45,1). // Verifie a la main : 2,1 kWh / 0,30 kWh par point = +7,0 points (38,1 -> 45,1).
expect: { usable: true, hcReachActive: true, ecsRepriseKwh: 2.1, hcFloorPct: 45.1 }, // Depuis le 08/10, un jour faible a midi vise la charge maximale (100 %) ;
// le plancher « tenir jusqu'a 01:24 » reste calcule et publie a cote.
expect: { usable: true, hcReachActive: true, ecsRepriseKwh: 2.1, hcFloorReachPct: 45.1,
hcMaxCharge: true, hcFloorPct: 100 },
},
{
node: 'BALANCE',
id: 'bilan-hc-midi-jour-normal-pas-de-charge-max',
label: 'Meme midi, jour NON faible : la cible reste « tenir jusqu a 01:24 », pas de charge maximale',
snapshot: Object.assign(snap2709('2026-09-27T12:30:00+02:00', 31, MIDI_2709, SOMBRE),
{ ecs_nuit_jour: '2026-09-27|off|x' }),
expect: { usable: true, hcReachActive: true, hcMaxCharge: false, hcFloorPct: 38.1 },
},
{
node: 'BALANCE',
id: 'bilan-hc-midi-jour-faible-charge-max-engage-et-finance',
label: 'Jour faible a midi, SOC 31 : cible 100 % -> session engagee ET manque chiffre (69 points x 0,30)',
snapshot: Object.assign(snap2709('2026-09-27T12:30:00+02:00', 31, MIDI_2709, SOMBRE),
{ ecs_nuit_jour: '2026-09-27|on|x' }),
// Verifie a la main : (100 - 31) x 0,30 = 20,7 kWh.
expect: { usable: true, hcMaxCharge: true, requiredSocPct: 100, gridNeeded: true, gridEnergyNeededKwh: 20.7 },
},
{
node: 'BALANCE',
id: 'bilan-hc-nuit-jour-faible-pas-de-charge-max',
label: 'Fenetre HC de NUIT, decision jour faible du jour : la nuit garde « tenir jusqu a midi »',
snapshot: Object.assign(snap2709('2026-09-27T01:20:00+02:00', 72, NUIT_2709),
{ ecs_nuit_jour: '2026-09-27|on|x' }),
expect: { usable: true, hcReachActive: true, hcMaxCharge: false, hcFloorPct: 25.3 },
}, },
{ {
node: 'BALANCE', node: 'BALANCE',

View File

@@ -71,18 +71,38 @@ export const scenarios = [
time: h(ETE, '12:30'), time: h(ETE, '12:30'),
overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on', overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on',
night_target_soc: '44.1', grid_energy_needed_kwh: '3.91', house_ac_power_w: '5300', night_target_soc: '44.1', grid_energy_needed_kwh: '3.91', house_ac_power_w: '5300',
grid_subscription_va: '12000', battery_voltage_v: '51' }, grid_subscription_va: '12000', battery_voltage_v: '51', pv_power_w: '1550' },
expect: { mode: 'evcs_takeover_recover', courantA: 50 }, expect: { mode: 'evcs_takeover_recover', courantA: 50 },
}, },
{ {
id: 'acm-takeover-midi-borne-par-l-abonnement', id: 'acm-takeover-midi-borne-par-l-abonnement',
label: 'Meme midi, maison a 9,5 kW : il ne reste que 1,3 kW sous les 90 % des 12 kVA -> 25,5 A', label: 'Meme midi, maison a 9,5 kW, pas de solaire : il ne reste que 1,3 kW sous les 90 % des 12 kVA -> 25,5 A',
time: h(ETE, '12:30'), time: h(ETE, '12:30'),
overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on', overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on',
night_target_soc: '44.1', grid_energy_needed_kwh: '3.91', house_ac_power_w: '9500', night_target_soc: '44.1', grid_energy_needed_kwh: '3.91', house_ac_power_w: '9500',
grid_subscription_va: '12000', battery_voltage_v: '51' }, grid_subscription_va: '12000', battery_voltage_v: '51', pv_power_w: '0' },
expect: { mode: 'evcs_takeover_recover', courantA: 25.5 }, expect: { mode: 'evcs_takeover_recover', courantA: 25.5 },
}, },
// CHARGE MAXIMALE (08/10/2026) : jour faible, le bilan vise 100 % a midi ; le
// courant n'est plus fixe par la cible mais par la physique.
{
id: 'acm-takeover-midi-jour-faible-charge-max-solaire-compte',
label: 'Jour faible 12:30, SOC 31 -> 100 % (20,7 kWh), maison 5,8 kW, PV 1,55 kW : (10 800 - 5 800 + 1 550) / 51 = 128,4 A',
time: h(ETE, '12:30'),
overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on',
night_target_soc: '100', grid_energy_needed_kwh: '20.7', house_ac_power_w: '5800',
grid_subscription_va: '12000', battery_voltage_v: '51', pv_power_w: '1550' },
expect: { mode: 'evcs_takeover_recover', courantA: 128.4 },
},
{
id: 'acm-takeover-midi-jour-faible-charge-max-chargeurs',
label: 'Meme midi, maison 2 kW : 202 A possibles, borne aux 140 A des deux MultiPlus',
time: h(ETE, '12:30'),
overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on',
night_target_soc: '100', grid_energy_needed_kwh: '20.7', house_ac_power_w: '2000',
grid_subscription_va: '12000', battery_voltage_v: '51', pv_power_w: '1500' },
expect: { mode: 'evcs_takeover_recover', courantA: 140 },
},
{ {
id: 'acm-takeover-nuit-petit-besoin-plancher-10a', id: 'acm-takeover-nuit-petit-besoin-plancher-10a',
label: 'Nuit 01:30, 1 kWh a reprendre en 5,9 h : 3,8 A calcules -> plancher 10 A (la batterie est menagee)', label: 'Nuit 01:30, 1 kWh a reprendre en 5,9 h : 3,8 A calcules -> plancher 10 A (la batterie est menagee)',

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env node
// 08/10/2026 (proprietaire) : jour faible, la HC de midi charge au MAXIMUM
// possible (cible 100 % du bilan). Le plafond du courant calcule (07/10) ne
// comptait que le reseau libre sous l'abonnement ; le solaire charge la batterie
// EN PLUS, par les chargeurs MPPT (courant continu), sans passer par l'entree
// reseau. Plafond = min(140 A, (90 % de l'abonnement - maison + PV) / tension).
// L'import reste borne par la consigne reseau du controleur (10 kW maximum).
// Rejouable (idempotent).
import fs from 'fs';
const F = 'workflows/victron-auto-charge-manager-v1.2.json';
const wf = JSON.parse(fs.readFileSync(F, 'utf8'));
const node = wf.nodes.find((n) => n.name === 'Compute Charge Targets');
let c = node.parameters.jsCode;
if (c.includes('PLAFOND SOLAIRE (08/10/2026)')) { console.log('deja applique'); process.exit(0); }
const rep = (a, b) => { if (c.split(a).length !== 2) throw new Error('motif introuvable ou multiple : ' + a.slice(0, 60)); c = c.replace(a, b); };
rep(` const reseauLibreW = Number.isFinite(gridSubscriptionVa) && Number.isFinite(houseAcPowerW)
? gridSubscriptionVa * 0.9 - houseAcPowerW : NaN;`,
` // PLAFOND SOLAIRE (08/10/2026) : le solaire (MPPT, courant continu) charge
// la batterie en plus de ce que l'entree reseau laisse libre.
const reseauLibreW = Number.isFinite(gridSubscriptionVa) && Number.isFinite(houseAcPowerW)
? gridSubscriptionVa * 0.9 - houseAcPowerW + Math.max(0, pvPowerW) : NaN;`);
rep(`(abonnement : \${Math.round(reseauLibreW)} W libres)`, `(abonnement + solaire : \${Math.round(reseauLibreW)} W libres)`);
node.parameters.jsCode = c;
fs.writeFileSync(F, JSON.stringify(wf, null, 2) + '\n');
console.log('patch applique');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long