Chantier etape 1 : detection de la censure de la production PV
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
This commit is contained in:
72
tests/energy-balance/run.mjs
Normal file
72
tests/energy-balance/run.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env node
|
||||
// Suite de tests du nœud CENSORSHIP (chantier bilan énergétique).
|
||||
// Exécute le VRAI code de src/energy-balance/CENSORSHIP.js dans une sandbox vm,
|
||||
// avec le shim $input de n8n, sur des snapshots HA (réel + cas construits).
|
||||
//
|
||||
// Usage : node tests/energy-balance/run.mjs [--print] [--only=<motif>]
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import vm from 'node:vm';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { scenarios } from './scenarios.mjs';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const CODE = fs.readFileSync(path.join(ROOT, 'src/energy-balance/CENSORSHIP.js'), 'utf8');
|
||||
|
||||
const PRINT = process.argv.includes('--print');
|
||||
const ONLY = (process.argv.find(a => a.startsWith('--only=')) || '').split('=')[1];
|
||||
|
||||
function runNode(snapshot) {
|
||||
const sandbox = {
|
||||
$input: { first: () => ({ json: snapshot }) },
|
||||
console,
|
||||
};
|
||||
vm.createContext(sandbox);
|
||||
const out = vm.runInContext(`(function(){ ${CODE} })()`, sandbox, { timeout: 5000 });
|
||||
return out[0].json;
|
||||
}
|
||||
|
||||
function summarize(o) {
|
||||
return {
|
||||
pvCensored: o.censorship.pvCensored,
|
||||
reasons: o.censorship.reasons.join('|'),
|
||||
sampleExploitable: o.censorship.sampleExploitable,
|
||||
healthOk: o.health.ok,
|
||||
alerts: o.alerts.join('|'),
|
||||
};
|
||||
}
|
||||
|
||||
let failed = 0;
|
||||
for (const sc of scenarios) {
|
||||
if (ONLY && !sc.id.includes(ONLY)) continue;
|
||||
const got = summarize(runNode(sc.snapshot));
|
||||
|
||||
if (PRINT) {
|
||||
console.log(`─── ${sc.id} | ${sc.label}`);
|
||||
console.log(' ' + JSON.stringify(got));
|
||||
continue;
|
||||
}
|
||||
|
||||
const diffs = [];
|
||||
for (const [k, want] of Object.entries(sc.expect)) {
|
||||
if (JSON.stringify(got[k]) !== JSON.stringify(want)) {
|
||||
diffs.push(` ${k}: attendu ${JSON.stringify(want)}, obtenu ${JSON.stringify(got[k])}`);
|
||||
}
|
||||
}
|
||||
if (diffs.length) {
|
||||
failed++;
|
||||
console.log(`❌ ${sc.id} | ${sc.label}`);
|
||||
diffs.forEach(d => console.log(d));
|
||||
} else {
|
||||
console.log(`✅ ${sc.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (PRINT) process.exit(0);
|
||||
console.log('');
|
||||
if (failed) {
|
||||
console.log(`${failed} ÉCHEC(S) — NE PAS POUSSER EN PRODUCTION`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`ENERGY BALANCE OK — ${scenarios.length}/${scenarios.length} scénarios conformes`);
|
||||
Reference in New Issue
Block a user