#!/usr/bin/env node // Releve le plafond de courant de charge : pv_boost 150 -> 180 A, pv_mid 130 -> 160 A. // // MESURE DU 14/09/2026 11:45, neuf releves consecutifs a douze secondes : // courant 148-150 A, colle au plafond de 150, jamais en dessous de 148 // tension 50,38-50,41 V pour une consigne de 51,5 V — 1,1 V de marge, donc // AUCUNE phase d absorption : ce n est pas la tension qui limite // cellules ecart 2 mV, aucune alarme, zero module bloquant la charge // solaire 4 a 6 chargeurs sur 6 declarent se retenir, en continu // bilan batterie 7,56 kW + maison 0,75 = 8,31 kW pour 8,4 kW produits // — la production se cale sur ce que le systeme absorbe : ecretage // // C est donc NOTRE plafond qui limite, pas le materiel. // // ⚠️ LE PARC N EST PAS CELUI QUE LE CERBO DECLARE. Il compte quatre US5000 et // cinq US2000, soit TREIZE equivalents US2000 ; le Cerbo ne voit que les quatre // US5000 (d ou `nombre_de_modules_en_ligne` = 4 et `capacite_installee` = // 400 Ah). Les 250 A qu il autorise sont donc calcules sur quatre modules et // s appliquent a treize equivalents. A 180 A cela fait moins de 14 A par // equivalent 2000. Corrobore par la mesure d energie : 0,312 kWh par point de // SOC, quand treize equivalents (~650 Ah a 50 V) en predisent 0,325. // // Le haut de charge n est PAS touche : au-dela de 90 % c est la tension de // cellule qui devient le facteur limitant, et le BMS l arbitre bien mieux qu un // palier de courant — il a demontre le 05/09 une reaction en CINQ SECONDES. import fs from 'fs'; const cible = process.argv[2] || 'workflows/victron-auto-charge-manager-v1.2.json'; const wf = JSON.parse(fs.readFileSync(cible, 'utf8')); const node = wf.nodes.find((n) => n.name === 'Compute Charge Targets'); if (!node) { console.error('noeud « Compute Charge Targets » introuvable'); process.exit(1); } let code = node.parameters.jsCode; const remplacements = [ // Les deux plafonds de la table. ['pv_boost 150 -> 180 A', /(pv_boost:\s*\{\s*current_a:\s*)150/, '$1180'], ['pv_mid 130 -> 160 A', /(pv_mid:\s*\{\s*current_a:\s*)130/, '$1160'], // ⚠️ ET LE RELEVEMENT ANTI-BRIDAGE, QUI EST MORT — constat, pas correctif. // // Le node porte un mecanisme cense detecter EXACTEMENT la situation du // 14/09 : « courant batterie a moins de 3 A du plafond, PV au-dessus de // 6 kW, SOC sous 95 » — et relever alors le plafond. La condition a bien ete // remplie ce matin, pendant une heure. Elle n'a rien releve. // // Parce que son relevement vaut `Math.max(targetCurrentA, soc >= 90 ? 80 : // 130)` et que le plafond du profil est deja au-dessus de ce plancher dans // TOUTES les bandes : // // SOC 61 pv_boost 180 vs plancher 130 -> max = 180, inchange // SOC 85 pv_mid 160 vs plancher 130 -> max = 160, inchange // SOC 92 pv_topoff 80 vs plancher 80 -> max = 80, inchange // // On remplace la constante par la valeur de la table dont elle a pris du // retard — un lecteur ne croira plus a un plancher a 130 A qui n'existe pas. // ⚠️ MAIS CELA NE LE RANIME PAS : le mecanisme reste un no-op dans les deux // formes, et c'est VOULU de ne pas le reveiller ici. Un plafond de securite // qui se releve tout seul parce qu'on bute dessus est precisement la forme // de boucle qui a produit six oscillateurs dans ce projet. Le ressusciter // demande sa propre reflexion, ses propres scenarios, et une decision du // proprietaire — pas un effet de bord d'un changement de palier. ['plancher anti-bridage : constante perimee -> valeur de la table', /targetCurrentA = Math\.max\(targetCurrentA, soc >= 90 \? 80 : 130\);/, `targetCurrentA = Math.max(targetCurrentA, // Valeurs de la table, et non des constantes qui prennent du retard. // ⚠️ Ce Math.max est un NO-OP : le plafond du profil est au-dessus du // plancher dans toutes les bandes. Constate le 14/09/2026 ; le // mecanisme n'a pas ete ranime deliberement (un plafond qui se releve // parce qu'on bute dessus est une boucle de retroaction). soc >= 90 ? cfg.profiles.pv_topoff.current_a : cfg.profiles.pv_mid.current_a);`], ]; let n = 0; for (const [quoi, motif, par] of remplacements) { if (!motif.test(code)) { console.error(`motif introuvable pour ${quoi} — deja applique ?`); process.exit(1); } code = code.replace(motif, par); n++; } node.parameters.jsCode = code; fs.writeFileSync(cible, JSON.stringify(wf, null, 2) + '\n'); console.log(`${n} plafond(s) releve(s) dans ${cible}`);