La protection regardait la maison, pas ce qu on demande au parc
Deux fermetures ce dimanche midi. Mesurees a l instant de la decision, contacteur encore ouvert donc production non censuree : maison 4025 et 4981 W, mais parc reellement sollicite 1132 et 772 W pour un plafond de 5100. La decharge batterie mesuree aux memes instants — 1345 et 1223 W — colle a maison moins PV, pas a la maison brute. Le solaire couvrait la cuisson ; la regle a ferme le contacteur pour rien, 0,50 kWh importes en plein soleil. Elle n avait bien fonctionne hier soir que parce qu il n y avait plus de PV. Les deux seuils portent desormais sur max(0, maison - PV). Contacteur ouvert cette grandeur EST la decharge batterie ; ferme, un bridage du MPPT la fait surestimer, donc l erreur pousse a rester ferme et jamais a relacher tot. Golden 20 -> 24, dont les deux releves de midi et le contrefactuel du nuage. Le test de mutation a montre que la premiere serie ne couvrait que le seuil de fermeture : il a fallu ecrire le cas du soleil revenu pour qu elle morde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
109
tools/patch-20260906b-demande-sur-le-parc.mjs
Normal file
109
tools/patch-20260906b-demande-sur-le-parc.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env node
|
||||
// La regle de protection comptait la maison BRUTE, pas ce que le parc est
|
||||
// reellement sollicite de fournir.
|
||||
//
|
||||
// Dimanche 06/09, deux fermetures du contacteur, mesurees a la resolution
|
||||
// native (InfluxDB) A L'INSTANT OU LA DECISION A ETE PRISE, contacteur encore
|
||||
// ouvert — donc sur une production non censurée :
|
||||
//
|
||||
// fermeture 1 (12:07, cuisson seule) maison 4 025 W parc sollicite 1 132 W
|
||||
// fermeture 2 (13:34, cuisson + JEEP) maison 4 981 W parc sollicite 772 W
|
||||
//
|
||||
// Controle croise : la decharge batterie mesuree aux memes instants valait
|
||||
// 1 345 et 1 223 W — elle colle a `maison - PV`, pas a la maison brute.
|
||||
//
|
||||
// Le plafond du parc est de 5 100 W. Le parc etait donc a 22 % et 15 % de sa
|
||||
// limite, et la regle a ferme le contacteur pour rien : le solaire (3 a 4 kW)
|
||||
// couvrait la cuisson. Elle n'avait bien fonctionne hier soir que parce qu'il
|
||||
// n'y avait plus de PV — la maison brute valait alors la demande sur le parc.
|
||||
//
|
||||
// Correctif : les deux seuils portent sur `max(0, maison - PV)`.
|
||||
//
|
||||
// ⚠️ POURQUOI CE N'EST PAS UN OSCILLATEUR, alors que la grandeur n'est pas
|
||||
// invariante a la decision (fermer le contacteur peut brider le MPPT, §28) :
|
||||
// - contacteur OUVERT, `maison - PV` EST la decharge batterie, la grandeur
|
||||
// physique exacte ;
|
||||
// - contacteur FERME, brider le MPPT fait BAISSER PV, donc MONTER
|
||||
// `maison - PV` : l'erreur pousse a rester ferme, jamais a relacher tot ;
|
||||
// - et le relachement exige de toute facon le dwell (6 a 20 min), tres
|
||||
// au-dela du transitoire d'etablissement mesure a ~150 s.
|
||||
// L'erreur est donc unilaterale et bornee, dans la direction qui menage le parc.
|
||||
//
|
||||
// `acOverSafeLimit` n'est PAS touche : il compare la charge au debit des
|
||||
// onduleurs, qui portent la maison entiere quelle qu'en soit la source.
|
||||
//
|
||||
// node tools/patch-20260906b-demande-sur-le-parc.mjs
|
||||
|
||||
import fs from 'node:fs';
|
||||
|
||||
const F = 'workflows/victron-grid-hysteresis-v1.json';
|
||||
const wf = JSON.parse(fs.readFileSync(F, 'utf8'));
|
||||
const calc = wf.nodes.find((n) => n.name === 'Compute Grid Hysteresis');
|
||||
if (!calc) throw new Error('noeud introuvable');
|
||||
|
||||
function remplace(t, a, b, quoi) {
|
||||
if (!t.includes(a)) throw new Error(`ancre introuvable (${quoi}) : ${a.slice(0, 70)}`);
|
||||
return t.replace(a, b);
|
||||
}
|
||||
|
||||
let c = calc.parameters.jsCode;
|
||||
|
||||
if (c.includes('demandeParcW')) {
|
||||
console.log(' deja fait');
|
||||
} else {
|
||||
// 1. la grandeur
|
||||
c = remplace(c,
|
||||
'const chargeAuDessusDuParc = plafondDechargeW > 0 && houseAcPowerW >= seuilFermetureW;',
|
||||
`// CE QUE LE PARC EST REELLEMENT SOLLICITE DE FOURNIR (06/09/2026)
|
||||
// La maison brute n'est pas la demande sur la batterie : le solaire en couvre
|
||||
// une partie. Mesure du 06/09 a l'instant des deux fermetures, contacteur
|
||||
// encore ouvert : maison 4 025 et 4 981 W, parc sollicite 1 132 et 772 W pour
|
||||
// un plafond de 5 100 W — deux fermetures pour rien. Controle croise : la
|
||||
// decharge batterie valait alors 1 345 et 1 223 W.
|
||||
//
|
||||
// Contacteur ouvert, cette grandeur EST la decharge batterie. Ferme, elle est
|
||||
// le contrefactuel « ce que le parc devrait fournir si on ouvrait », et un
|
||||
// eventuel bridage du MPPT par la presence du reseau la fait SURESTIMER —
|
||||
// erreur unilaterale, qui pousse a rester ferme, jamais a relacher tot.
|
||||
const demandeParcW = Math.max(0, houseAcPowerW - pvPowerW);
|
||||
|
||||
const chargeAuDessusDuParc = plafondDechargeW > 0 && demandeParcW >= seuilFermetureW;`,
|
||||
'fermeture');
|
||||
|
||||
c = remplace(c,
|
||||
'const chargeSousSeuilBas = plafondDechargeW > 0 && houseAcPowerW < seuilRelachementW;',
|
||||
'const chargeSousSeuilBas = plafondDechargeW > 0 && demandeParcW < seuilRelachementW;',
|
||||
'relachement');
|
||||
|
||||
c = remplace(c,
|
||||
"const setChargeHighLastAt = plafondDechargeW > 0 && !chargeSousSeuilBas;",
|
||||
"const setChargeHighLastAt = plafondDechargeW > 0 && !chargeSousSeuilBas;",
|
||||
'ecriture'); // inchange : suit desormais demandeParcW via chargeSousSeuilBas
|
||||
|
||||
// 2. les raisons
|
||||
c = remplace(c,
|
||||
' `Charge maison ${Math.round(houseAcPowerW)}W au-dessus de ${Math.round(seuilFermetureW)}W `\n + `(80% du plafond de decharge BMS ${Math.round(plafondDechargeW)}W) : le reseau prend la difference`);',
|
||||
' `Demande sur le parc ${Math.round(demandeParcW)}W (maison ${Math.round(houseAcPowerW)}W - PV ${Math.round(pvPowerW)}W) `\n + `au-dessus de ${Math.round(seuilFermetureW)}W, soit 80% du plafond de decharge BMS ${Math.round(plafondDechargeW)}W : le reseau prend la difference`);',
|
||||
'raison fermeture');
|
||||
|
||||
c = remplace(c,
|
||||
" ? `Charge maison retombee a ${Math.round(houseAcPowerW)}W depuis ${round1(minutesDepuisChargeHaute || 0)} min : `",
|
||||
" ? `Demande sur le parc retombee a ${Math.round(demandeParcW)}W depuis ${round1(minutesDepuisChargeHaute || 0)} min : `",
|
||||
'raison dwell');
|
||||
|
||||
c = remplace(c,
|
||||
" : `Charge maison ${Math.round(houseAcPowerW)}W encore au-dessus de ${Math.round(seuilRelachementW)}W : on garde le reseau`);",
|
||||
" : `Demande sur le parc ${Math.round(demandeParcW)}W (maison ${Math.round(houseAcPowerW)}W - PV ${Math.round(pvPowerW)}W) encore au-dessus de ${Math.round(seuilRelachementW)}W : on garde le reseau`);",
|
||||
'raison maintien');
|
||||
|
||||
// 3. le debug
|
||||
c = remplace(c, ' plafond_decharge_w: Math.round(plafondDechargeW),',
|
||||
' plafond_decharge_w: Math.round(plafondDechargeW),\n demande_parc_w: Math.round(demandeParcW),',
|
||||
'debug');
|
||||
|
||||
calc.parameters.jsCode = c;
|
||||
console.log(' calcul : les deux seuils portent sur maison - PV');
|
||||
}
|
||||
|
||||
fs.writeFileSync(F, JSON.stringify(wf, null, 2) + '\n');
|
||||
console.log(`\n${F} ecrit.`);
|
||||
Reference in New Issue
Block a user