Chantier etape 2 : profil horaire de consommation mesure
CONSUMPTION_PROFILE.js : compteurs d'energie cumules -> deltas horaires -> distribution par heure de la journee -> percentiles. La consommation du vehicule est retiree : c'est une charge pilotable, elle ne doit pas polluer le profil de la charge subie. Les remises a zero de compteur et les deltas implausibles sont jetes, jamais rattrapes par une valeur inventee, et une heure sans assez de jours observes est marquee non fiable au lieu d'etre publiee en silence. Valide sur 28 jours reels (639 heures, 0 reset) : 31,9 kWh/jour typique hors vehicule, 42,8 kWh au p80. Le profil tient dans un helper input_text (119 caracteres sur 255). Verification prealable de la methode : seulement 7,8 % des heures de deficit sont censurees. Censure et deficit sont physiquement presque exclusifs, donc la grandeur qui pilote la decision EDF est mesuree sur des donnees propres. Suite tests/energy-balance/ etendue aux deux noeuds : 14 scenarios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/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).
|
||||
// Suite de tests du chantier bilan énergétique.
|
||||
// Exécute le VRAI code des nœuds de src/energy-balance/ dans une sandbox vm,
|
||||
// avec le shim $input de n8n, sur des snapshots HA (réels + cas construits).
|
||||
//
|
||||
// Usage : node tests/energy-balance/run.mjs [--print] [--only=<motif>]
|
||||
|
||||
@@ -9,41 +9,58 @@ 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';
|
||||
import { scenarios as censorshipScenarios } from './scenarios.mjs';
|
||||
import { profileScenarios } from './scenarios-profile.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,
|
||||
};
|
||||
const codeCache = new Map();
|
||||
function runNode(nodeName, snapshot) {
|
||||
if (!codeCache.has(nodeName)) {
|
||||
codeCache.set(nodeName, fs.readFileSync(path.join(ROOT, `src/energy-balance/${nodeName}.js`), 'utf8'));
|
||||
}
|
||||
const sandbox = { $input: { first: () => ({ json: snapshot }) }, console };
|
||||
vm.createContext(sandbox);
|
||||
const out = vm.runInContext(`(function(){ ${CODE} })()`, sandbox, { timeout: 5000 });
|
||||
return out[0].json;
|
||||
return vm.runInContext(`(function(){ ${codeCache.get(nodeName)} })()`, sandbox, { timeout: 15000 })[0].json;
|
||||
}
|
||||
|
||||
function summarize(o) {
|
||||
return {
|
||||
const SUMMARIZE = {
|
||||
CENSORSHIP: o => ({
|
||||
pvCensored: o.censorship.pvCensored,
|
||||
reasons: o.censorship.reasons.join('|'),
|
||||
sampleExploitable: o.censorship.sampleExploitable,
|
||||
healthOk: o.health.ok,
|
||||
alerts: o.alerts.join('|'),
|
||||
};
|
||||
}
|
||||
}),
|
||||
CONSUMPTION_PROFILE: o => ({
|
||||
days: o.days,
|
||||
complete: o.complete,
|
||||
hours: o.profile.length,
|
||||
countersReset: o.quality.countersReset,
|
||||
unreliableHours: o.quality.unreliableHours.join(','),
|
||||
csvFitsHelper: o.csv.p80.length <= 255 && o.csv.p50.length <= 255,
|
||||
p50AtHour14: o.profile[14].p50,
|
||||
p80AtHour14: o.profile[14].p80,
|
||||
}),
|
||||
};
|
||||
|
||||
const all = [
|
||||
...censorshipScenarios.map(s => ({ node: 'CENSORSHIP', ...s })),
|
||||
...profileScenarios,
|
||||
];
|
||||
|
||||
let failed = 0;
|
||||
for (const sc of scenarios) {
|
||||
let run = 0;
|
||||
for (const sc of all) {
|
||||
if (ONLY && !sc.id.includes(ONLY)) continue;
|
||||
const got = summarize(runNode(sc.snapshot));
|
||||
run++;
|
||||
const got = SUMMARIZE[sc.node](runNode(sc.node, sc.snapshot));
|
||||
|
||||
if (PRINT) {
|
||||
console.log(`─── ${sc.id} | ${sc.label}`);
|
||||
console.log(`─── [${sc.node}] ${sc.id} | ${sc.label}`);
|
||||
console.log(' ' + JSON.stringify(got));
|
||||
continue;
|
||||
}
|
||||
@@ -69,4 +86,4 @@ 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`);
|
||||
console.log(`ENERGY BALANCE OK — ${run}/${run} scénarios conformes`);
|
||||
|
||||
Reference in New Issue
Block a user