Chauffe-eau en HC de nuit les jours faibles : decision quotidienne du bilan, workflow ECS sous controle du depot (VRM, prevision du jour), energie 24 h
This commit is contained in:
13
tests/allers-retours/chauffe-eau.mjs
Normal file
13
tests/allers-retours/chauffe-eau.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
// Energie du chauffe-eau sur 24 h (07/10/2026). Usage : node tests/allers-retours/chauffe-eau.mjs
|
||||
import { energieSur24h } from '../../tools/chauffe-eau.mjs';
|
||||
let ok = 0, ko = 0;
|
||||
const v = (n, a, b) => { if (a === b) { ok++; console.log(`✅ ${n}`); } else { ko++; console.log(`❌ ${n} : attendu ${b}, obtenu ${a}`); } };
|
||||
const s = (...x) => x.map((state) => ({ state: String(state) }));
|
||||
v('journee-du-06-10', energieSur24h(s(12.739, 13.04, 14.23, 15.42, 16.57, 17.74, 18.15, 18.233)), 5.49);
|
||||
v('compteur-immobile-0-kwh', energieSur24h(s(18.254)), 0);
|
||||
v('indisponible-ignore', energieSur24h(s(10, 'unavailable', 11)), 1);
|
||||
v('remise-a-zero-sans-negatif', energieSur24h(s(10, 11, 0, 0.5)), 1.5);
|
||||
v('illisible-null', energieSur24h(s('unavailable')), null);
|
||||
console.log(ko ? `\n${ko} ECHEC(S)` : `\nCHAUFFE-EAU 24 H OK — ${ok}/${ok}`);
|
||||
process.exit(ko ? 1 : 0);
|
||||
@@ -122,6 +122,12 @@ const SUMMARIZE = {
|
||||
vehicleSolarKwh: o.vehicleMidday ? o.vehicleMidday.solarKwh : null,
|
||||
vehicleFloorAtDeparturePct: o.vehicleMidday ? o.vehicleMidday.floorAtDeparturePct : null,
|
||||
vehicleGridNeeded: o.vehicleMidday ? o.vehicleMidday.gridNeeded : false,
|
||||
// Chauffe-eau en HC de nuit les jours faibles (07/10).
|
||||
ecsDue: o.ecsNight ? o.ecsNight.due : null,
|
||||
ecsDecided: o.ecsNight ? o.ecsNight.decided : null,
|
||||
ecsOn: o.ecsNight ? (o.ecsNight.on ?? null) : null,
|
||||
ecsProjectedSocPct: o.ecsNight ? (o.ecsNight.projectedSocPct ?? null) : null,
|
||||
ecsRequiredSocPct: o.ecsNight ? (o.ecsNight.requiredSocPct ?? null) : null,
|
||||
}) : ({ usable: false, missing: o.missing.join(',') }),
|
||||
};
|
||||
|
||||
|
||||
@@ -522,6 +522,39 @@ export const balanceScenarios = [
|
||||
snapshot: jeep(snap2709('2026-09-27T15:00:00+02:00', 30, null, SOMBRE), 12),
|
||||
expect: { usable: true, vehicleActive: false, vehicleGridNeeded: false },
|
||||
},
|
||||
// ======== CHAUFFE-EAU EN HC DE NUIT LES JOURS FAIBLES (07/10/2026) ========
|
||||
{
|
||||
node: 'BALANCE',
|
||||
id: 'bilan-ecs-jour-sombre-eau-la-nuit',
|
||||
label: 'Matin 07:30, journee sombre (18 kWh), SOC 40 : la batterie n arrive pas au requis du soir -> chauffe-eau en HC de nuit',
|
||||
snapshot: Object.assign(snap2709('2026-09-27T07:30:00+02:00', 40, null, SOMBRE),
|
||||
{ night_hc_end_ms: Date.parse('2026-09-28T07:24:00+02:00'), ecs_nuit_jour: '2026-09-26|off|x' }),
|
||||
expect: { usable: true, ecsDue: true, ecsDecided: true, ecsOn: true },
|
||||
},
|
||||
{
|
||||
node: 'BALANCE',
|
||||
id: 'bilan-ecs-jour-ensoleille-eau-au-solaire',
|
||||
label: 'Matin 07:30, journee normale (25,1 kWh), SOC 70 : la batterie atteint le requis du soir -> chauffe-eau au solaire',
|
||||
snapshot: Object.assign(snap2709('2026-09-27T07:30:00+02:00', 70, null),
|
||||
{ night_hc_end_ms: Date.parse('2026-09-28T07:24:00+02:00'), ecs_nuit_jour: '2026-09-26|on|x' }),
|
||||
expect: { usable: true, ecsDue: true, ecsDecided: true, ecsOn: false },
|
||||
},
|
||||
{
|
||||
node: 'BALANCE',
|
||||
id: 'bilan-ecs-decision-figee-dans-la-journee',
|
||||
label: 'Deja decide aujourd hui : pas de nouvelle decision (sinon retirer le chauffe-eau changerait la projection -> oscillateur)',
|
||||
snapshot: Object.assign(snap2709('2026-09-27T13:00:00+02:00', 40, null, SOMBRE),
|
||||
{ night_hc_end_ms: Date.parse('2026-09-28T07:24:00+02:00'), ecs_nuit_jour: '2026-09-27|on|x' }),
|
||||
expect: { usable: true, ecsDue: false, ecsDecided: false },
|
||||
},
|
||||
{
|
||||
node: 'BALANCE',
|
||||
id: 'bilan-ecs-pas-de-decision-avant-la-fin-de-la-nuit',
|
||||
label: '00:30, nouvelle date mais fenetre HC de nuit pas encore passee : la decision de la veille vaut encore',
|
||||
snapshot: Object.assign(snap2709('2026-09-27T00:30:00+02:00', 40, null, SOMBRE),
|
||||
{ night_hc_end_ms: Date.parse('2026-09-27T07:24:00+02:00'), ecs_nuit_jour: '2026-09-26|on|x' }),
|
||||
expect: { usable: true, ecsDue: false, ecsDecided: false },
|
||||
},
|
||||
// ======== LES TROIS VEHICULES (28/09/2026) ========
|
||||
{
|
||||
node: 'BALANCE',
|
||||
|
||||
78
tests/water-heater/run.mjs
Normal file
78
tests/water-heater/run.mjs
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env node
|
||||
// Workflow « Water Heater Advisor ECS 6 Nodes » (07/10/2026) : la chaine
|
||||
// Normalize -> ... -> BUILD executee sur des snapshots, comme en production.
|
||||
// Usage : node tests/water-heater/run.mjs
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import vm from 'node:vm';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const CHAINE = ['NORMALIZE', 'OBSERVATION', 'ENERGY_MODEL', 'BASE_DECISION', 'ARBITRATION', 'OUTPUT_BUILDER', 'BUILD_ACTIONS'];
|
||||
const code = Object.fromEntries(CHAINE.map((n) => [n, fs.readFileSync(path.join(ROOT, `src/water-heater/${n}.js`), 'utf8')]));
|
||||
|
||||
function executer(snapshot) {
|
||||
let item = snapshot, sorties = {};
|
||||
for (const n of CHAINE) {
|
||||
const sb = { $input: { first: () => ({ json: item }), all: () => [{ json: item }] }, $env: {}, console };
|
||||
vm.createContext(sb);
|
||||
const out = vm.runInContext(`(function(){ ${code[n]} })()`, sb, { timeout: 5000 });
|
||||
sorties[n] = out;
|
||||
item = out[0].json;
|
||||
}
|
||||
return sorties;
|
||||
}
|
||||
|
||||
// Snapshot de base : matin d'un jour faible, contacteur ouvert, chauffe-eau sous tension.
|
||||
const BASE = {
|
||||
soc_percent: '45', battery_current_a: '-5', pv_power_w: '900', pv_power_10m_avg_w: '850',
|
||||
house_ac_power_w: '700', grid_power_w: '0', grid_state: 'off', victron_acin1_available: '0',
|
||||
today_remaining_kwh: '15', tomorrow_kwh: '25', forecast_source: 'vrm',
|
||||
advisor_enabled: 'on', notifications_enabled: 'off', hot_water_demand: 'normal',
|
||||
water_heater_switch: 'on', water_heater_energy_total_kwh: '18.25',
|
||||
ecs_nuit: 'on', hc_session_committed: 'off', ecs_24h_kwh: '5.5', off_jour_faible_prev: 'off',
|
||||
evcs_priority_active: 'off', forced_off_by_advisor: 'off',
|
||||
};
|
||||
const cas = [
|
||||
{ id: 'jour-faible-coupe-le-jour', snap: {}, attendu: { action: 'turn_off', desired: false, etat: 'jour_faible_coupe', owner: true } },
|
||||
{ id: 'jour-faible-chauffe-en-hc-edf', snap: { water_heater_switch: 'off', off_jour_faible_prev: 'on', grid_state: 'on', hc_session_committed: 'on' },
|
||||
attendu: { action: 'turn_on', desired: true, etat: 'jour_faible_chauffe_hc_edf', owner: true } },
|
||||
{ id: 'jour-faible-edf-ferme-hors-session-reste-coupe', snap: { water_heater_switch: 'off', off_jour_faible_prev: 'on', grid_state: 'on', hc_session_committed: 'off' },
|
||||
attendu: { action: 'none', etat: 'jour_faible_coupe', owner: true } },
|
||||
{ id: 'jour-faible-eau-froide-chauffe', snap: { water_heater_switch: 'off', off_jour_faible_prev: 'on', ecs_24h_kwh: '0.4' },
|
||||
attendu: { action: 'turn_on', desired: true, etat: 'jour_faible_eau_froide', owner: true } },
|
||||
{ id: 'jour-faible-energie-illisible-aucune-restriction', snap: { ecs_24h_kwh: 'indisponible' },
|
||||
attendu: { action: 'none', etat: 'jour_faible_energie_illisible', owner: true } },
|
||||
{ id: 'jour-normal-remise-sous-tension', snap: { ecs_nuit: 'off', water_heater_switch: 'off', off_jour_faible_prev: 'on' },
|
||||
attendu: { action: 'turn_on', desired: true, etat: 'jour_normal_remise_sous_tension', owner: false } },
|
||||
{ id: 'jour-normal-coupure-manuelle-respectee', snap: { ecs_nuit: 'off', water_heater_switch: 'off', off_jour_faible_prev: 'off' },
|
||||
attendu: { action: 'none', owner: false } },
|
||||
{ id: 'conseiller-desactive-rien', snap: { advisor_enabled: 'off' }, attendu: { action: 'none', owner: false } },
|
||||
];
|
||||
|
||||
let ok = 0, ko = 0;
|
||||
for (const c of cas) {
|
||||
const s = executer({ ...BASE, ...c.snap });
|
||||
const fd = s.ARBITRATION[0].json.finalDecision;
|
||||
const appels = s.BUILD_ACTIONS.map((x) => x.json);
|
||||
const sw = appels.find((a) => a.domain === 'switch' && a.data?.entity_id === 'switch.tt_chauffe_eau_pleumeur_switch');
|
||||
const own = appels.find((a) => JSON.stringify(a).includes('victron_water_heater_off_jour_faible'));
|
||||
const erreurs = [];
|
||||
if (fd.powerSwitchAction !== c.attendu.action) erreurs.push(`action ${fd.powerSwitchAction} (attendu ${c.attendu.action})`);
|
||||
if (c.attendu.desired !== undefined && fd.powerSwitchDesiredOn !== c.attendu.desired) erreurs.push(`desired ${fd.powerSwitchDesiredOn}`);
|
||||
if (c.attendu.etat && fd.jourFaibleState !== c.attendu.etat) erreurs.push(`etat ${fd.jourFaibleState} (attendu ${c.attendu.etat})`);
|
||||
if (fd.offJourFaible !== c.attendu.owner) erreurs.push(`propriete ${fd.offJourFaible}`);
|
||||
if (c.attendu.action !== 'none' && !sw) erreurs.push('aucun appel switch emis par BUILD');
|
||||
if (c.attendu.action === 'none' && sw) erreurs.push('appel switch emis alors qu aucune action');
|
||||
if (!own) erreurs.push('helper de propriete absent des appels');
|
||||
if (erreurs.length) { ko++; console.log(`❌ ${c.id} | ${erreurs.join(' ; ')}`); } else { ok++; console.log(`✅ ${c.id}`); }
|
||||
}
|
||||
// Prevision forte = le reste du jour seulement (06/10 : demain fort ne rend plus forte une journee faible).
|
||||
{
|
||||
const s = executer({ ...BASE, ecs_nuit: 'off', today_remaining_kwh: '8', tomorrow_kwh: '30' });
|
||||
const f = s.ENERGY_MODEL[0].json.energyModel.forecastStrength;
|
||||
if (f === 'weak') { ok++; console.log('✅ prevision-forte-sur-le-jour-seulement'); }
|
||||
else { ko++; console.log(`❌ prevision-forte-sur-le-jour-seulement | ${f}`); }
|
||||
}
|
||||
console.log(ko ? `\n${ko} ECHEC(S) — NE PAS POUSSER EN PRODUCTION` : `\nWATER HEATER OK — ${ok}/${ok}`);
|
||||
process.exit(ko ? 1 : 0);
|
||||
Reference in New Issue
Block a user