#!/usr/bin/env node // 07/10/2026 (proprietaire) : en recharge reseau HC (takeover EVCS), le courant // de charge n'est plus fixe a 30 A (profil night_slow, prevu pour 6 h de nuit : // a midi 2 h x 1,4 kW = 2,8 kWh, la cible de 44 % n'etait pas atteignable, SOC // 31 -> 35 %). Il vient de l'energie a reprendre et du temps restant dans la // fenetre, borne par ce que l'abonnement EDF (12 kVA) laisse une fois la maison // servie et par le chargeur des deux MultiPlus. 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('COURANT CALCULE (07/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 nightTargetSoc = num(input.night_target_soc, 35);", `const nightTargetSoc = num(input.night_target_soc, 35); // COURANT CALCULE (07/10/2026) : entrees de la recharge reseau HC. const houseAcPowerW = num(input.house_ac_power_w, NaN); const gridSubscriptionVa = num(input.grid_subscription_va, NaN); const gridEnergyNeededKwh = num(input.grid_energy_needed_kwh, NaN);`); rep(` mode = 'evcs_takeover_recover'; effectiveProfile = 'night_slow'; targetCurrentA = cfg.profiles.night_slow.current_a; targetVoltageV = cfg.profiles.night_slow.voltage_v; reason.push(\`Takeover EVCS : recharge vers cible matin \${holdTargetSoc}% (SOC \${soc}%)\`);`, ` mode = 'evcs_takeover_recover'; effectiveProfile = 'night_slow'; targetCurrentA = cfg.profiles.night_slow.current_a; targetVoltageV = cfg.profiles.night_slow.voltage_v; // COURANT CALCULE (07/10/2026) : energie a reprendre (bilan, sinon SOC x // 0,30 kWh/point mesure le 20/09) / temps restant dans la fenetre HC, +15 % // de marge ; borne bas 10 A, haut = min(chargeur des 2 MultiPlus 140 A, // (90 % de l'abonnement - maison) / tension). Recalcule a chaque cycle : // il suit la maison et rattrape le retard en fin de fenetre. const fin = inMiddayHC ? cfg.windows.middayHC_end : (inNightHC ? cfg.windows.nightHC_end : null); const restantH = fin === null ? null : Math.max(0.25, ((fin - now.minNow + 1440) % 1440) / 60); const energieKwh = Number.isFinite(gridEnergyNeededKwh) && gridEnergyNeededKwh > 0 ? gridEnergyNeededKwh : Math.max(0, holdTargetSoc - soc) * 0.30; const tensionV = Number.isFinite(batteryVoltageV) && batteryVoltageV > 40 ? batteryVoltageV : 51; if (restantH !== null && energieKwh > 0) { const besoinA = energieKwh * 1000 / restantH / tensionV * 1.15; const reseauLibreW = Number.isFinite(gridSubscriptionVa) && Number.isFinite(houseAcPowerW) ? gridSubscriptionVa * 0.9 - houseAcPowerW : NaN; const plafondA = Math.min(140, Number.isFinite(reseauLibreW) ? Math.max(0, reseauLibreW / tensionV) : 140); targetCurrentA = Math.round(Math.max(10, Math.min(plafondA, Math.ceil(besoinA / 5) * 5)) * 10) / 10; reason.push(\`Recharge reseau HC : \${round1(energieKwh)} kWh en \${round1(restantH)} h -> \${targetCurrentA} A\` + (Number.isFinite(reseauLibreW) ? \` (abonnement : \${Math.round(reseauLibreW)} W libres)\` : '')); } reason.push(\`Takeover EVCS : recharge vers cible matin \${holdTargetSoc}% (SOC \${soc}%)\`);`); node.parameters.jsCode = c; const tpl = wf.nodes.find((n) => n.name === 'HA Snapshot via Template').parameters.bodyParameters.parameters[0]; const a = ' "night_target_soc": states("input_number.victron_evcs_night_target_soc"),'; if (tpl.value.split(a).length !== 2) throw new Error('gabarit : motif introuvable'); tpl.value = tpl.value.replace(a, a + ` "house_ac_power_w": states("sensor.victron_consumption_power_l1"), "grid_subscription_va": states("input_number.victron_grid_subscription_va"), "grid_energy_needed_kwh": states("input_number.evcs_balance_grid_energy_needed_kwh"),`); fs.writeFileSync(F, JSON.stringify(wf, null, 2) + '\n'); console.log('patch applique');