- tests/golden/run.mjs : chaîne complète des 5 nodes (code réel) en sandbox vm, horloge simulée par scénario, snapshot HA réel comme fixture. - 16 scénarios attendus figés après revue : nuit HC (P7), solaire, abondance P6, provocation P3, gardes SOC, force_grid/force_charge, stop, overload. - n8n-push.mjs exécute la suite avant tout PUT du workflow EVCS : golden rouge = push refusé (--skip-golden en échappatoire consciente). - Validé par mutation : P7 cassé volontairement -> 2 échecs détectés + push bloqué. - tools/fetch-snapshot-fixture.mjs : recapture d'un snapshot production.
367 lines
12 KiB
JavaScript
367 lines
12 KiB
JavaScript
// R3 — Scénarios golden EVCS. Chaque scénario = instant simulé + overrides du
|
|
// snapshot HA réel (tests/golden/fixture-snapshot.json) + résumé attendu.
|
|
// Les attendus sont FIGÉS après vérification manuelle : un diff = régression
|
|
// (ou évolution volontaire → mettre à jour l'attendu dans le même commit).
|
|
//
|
|
// Fenêtres (helpers réels) : night_hc 01:24-07:24 | morning 07:30-11:00 |
|
|
// midday 12:24-14:24 | evening 17:00-20:30 | sinon outside_window.
|
|
// Seuils : hard floor nuit 22 | cible matin forecast fort 35 (tomorrow_kwh 57) |
|
|
// soc_start_min 80 | morning_soc_min 35 | midday_soc_min 75.
|
|
|
|
const NIGHT = '2026-08-08T01:30:00+02:00';
|
|
const MORNING = '2026-08-08T09:00:00+02:00';
|
|
const MIDDAY = '2026-08-08T13:00:00+02:00';
|
|
const AFTERNOON = '2026-08-08T15:30:00+02:00';
|
|
const EVENING = '2026-08-08T18:00:00+02:00';
|
|
|
|
const VEHICLE_NIGHT = {
|
|
evcs_vehicle_connected: 'on',
|
|
evcs_device_connected: 'on',
|
|
pv_power_w: '0',
|
|
pv_power_10m_avg_w: '0',
|
|
pv_trend_w: '0',
|
|
house_ac_power_w: '700',
|
|
vebus_ac_out_l1_power_w: '700',
|
|
battery_current_a: '-14',
|
|
};
|
|
|
|
export const scenarios = [
|
|
|
|
// ================= NUIT HC (P7 + historique) =================
|
|
{
|
|
id: 'night-hc-p7-toggle-on',
|
|
label: 'Nuit HC, VW branchée, toggle HC nuit ON, SOC 53, EDF OFF → demande réseau (P7)',
|
|
time: NIGHT,
|
|
overrides: { ...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'on' },
|
|
expect: {
|
|
profile: 'night_hc',
|
|
sourceMode: 'grid_hc_allowed',
|
|
state: 'night_wait_grid_takeover',
|
|
decision: 'hold',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: true,
|
|
edfOnAction: true,
|
|
takeoverHelperService: 'turn_on',
|
|
},
|
|
},
|
|
{
|
|
id: 'night-hc-p7-edf-on-start',
|
|
label: 'Nuit HC, cycle suivant : EDF ON, batterie tenue +2A → démarrage charge HC',
|
|
time: NIGHT,
|
|
overrides: {
|
|
...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'on',
|
|
grid_state: 'on', victron_acin1_available: '1',
|
|
battery_current_a: '2', night_takeover_prev: 'on',
|
|
},
|
|
expect: {
|
|
profile: 'night_hc',
|
|
sourceMode: 'grid_hc_allowed',
|
|
state: 'start_grid_hc_min_current',
|
|
decision: 'start',
|
|
desiredCharging: true,
|
|
currentA: 6,
|
|
takeoverNeeded: true,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_on',
|
|
},
|
|
},
|
|
{
|
|
id: 'night-hc-toggle-off-historique',
|
|
label: 'Nuit HC, toggle OFF → comportement historique battery_assist_night (idle)',
|
|
time: NIGHT,
|
|
overrides: { ...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'off' },
|
|
expect: {
|
|
profile: 'night_hc',
|
|
sourceMode: 'battery_assist_night',
|
|
state: 'idle_wait_conditions',
|
|
decision: 'hold',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
{
|
|
id: 'night-soc-sous-cible',
|
|
label: 'Nuit HC, SOC 30 < cible matin 35 → grid_forced',
|
|
time: NIGHT,
|
|
overrides: { ...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on' },
|
|
expect: {
|
|
profile: 'night_hc',
|
|
sourceMode: 'grid_forced',
|
|
state: 'night_wait_grid_takeover',
|
|
decision: 'hold',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: true,
|
|
edfOnAction: true,
|
|
takeoverHelperService: 'turn_on',
|
|
},
|
|
},
|
|
{
|
|
id: 'night-hard-floor',
|
|
label: 'Nuit HC, SOC 20 ≤ hard floor 22 → hard stop protection batterie + demande réseau',
|
|
time: NIGHT,
|
|
overrides: { ...VEHICLE_NIGHT, soc_percent: '20', allow_grid_hc_night: 'on' },
|
|
expect: {
|
|
profile: 'night_hc',
|
|
sourceMode: 'grid_forced',
|
|
state: 'battery_current_hard_stop',
|
|
decision: 'stop_hard',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: true,
|
|
edfOnAction: true,
|
|
takeoverHelperService: 'turn_on',
|
|
},
|
|
},
|
|
{
|
|
id: 'night-sans-vehicule',
|
|
label: 'Nuit HC sans véhicule → bloqué, takeover relâché',
|
|
time: NIGHT,
|
|
overrides: { ...VEHICLE_NIGHT, evcs_vehicle_connected: 'off', soc_percent: '53' },
|
|
expect: {
|
|
profile: 'night_hc',
|
|
sourceMode: 'blocked',
|
|
state: 'vehicle_not_connected',
|
|
decision: 'stop_not_connected',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
|
|
// ================= JOUR SOLAIRE =================
|
|
{
|
|
id: 'morning-solaire-fort',
|
|
label: 'Matin, PV 4,5 kW, SOC 60, batterie +10A → charge solaire',
|
|
time: MORNING,
|
|
overrides: {
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200',
|
|
pv_trend_w: '100', battery_current_a: '10',
|
|
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
|
},
|
|
expect: {
|
|
profile: 'morning_solar',
|
|
sourceMode: 'solar_only',
|
|
state: 'start_min_current',
|
|
decision: 'start',
|
|
desiredCharging: true,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
{
|
|
id: 'morning-nuageux',
|
|
label: 'Matin nuageux, PV 300 W, SOC 40 → pas de démarrage',
|
|
time: MORNING,
|
|
overrides: {
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
soc_percent: '40', pv_power_w: '300', pv_power_10m_avg_w: '250',
|
|
pv_trend_w: '-50', battery_current_a: '-3',
|
|
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
|
},
|
|
expect: {
|
|
profile: 'morning_solar',
|
|
sourceMode: 'blocked',
|
|
state: 'idle_wait_source',
|
|
decision: 'hold',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
{
|
|
id: 'midday-abondance-p6',
|
|
label: 'Midi abondance P6 : PV avg 4,5 kW + batterie +25A, SOC 66 < plancher 75 → démarre quand même',
|
|
time: MIDDAY,
|
|
overrides: {
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
soc_percent: '66', pv_power_w: '5000', pv_power_10m_avg_w: '4500',
|
|
pv_trend_w: '200', battery_current_a: '25',
|
|
house_ac_power_w: '1200', vebus_ac_out_l1_power_w: '1200',
|
|
},
|
|
expect: {
|
|
profile: 'midday_hc_or_solar',
|
|
sourceMode: 'solar_only',
|
|
state: 'start_min_current',
|
|
decision: 'start',
|
|
desiredCharging: true,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
{
|
|
id: 'midday-hc-garde-soc',
|
|
label: 'Midi sans PV, SOC 50 > 35, HC midi autorisé + EDF ON → grid proposé mais gardé (soc>35)',
|
|
time: MIDDAY,
|
|
overrides: {
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
soc_percent: '50', pv_power_w: '100', pv_power_10m_avg_w: '100',
|
|
pv_trend_w: '0', battery_current_a: '-4',
|
|
grid_state: 'on', victron_acin1_available: '1',
|
|
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
|
},
|
|
expect: {
|
|
profile: 'midday_hc_or_solar',
|
|
sourceMode: 'grid_hc_allowed',
|
|
state: 'idle_wait_conditions',
|
|
decision: 'hold',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
{
|
|
id: 'apresmidi-provocation-p3',
|
|
label: 'Après-midi hors fenêtre, SOC 95, PV bridé 300 W (< hold 500) → provocation PV (solar_potential)',
|
|
time: AFTERNOON,
|
|
overrides: {
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
soc_percent: '95', pv_power_w: '300', pv_power_10m_avg_w: '300',
|
|
pv_trend_w: '0', battery_current_a: '2',
|
|
house_ac_power_w: '600', vebus_ac_out_l1_power_w: '600',
|
|
},
|
|
expect: {
|
|
profile: 'outside_window',
|
|
sourceMode: 'solar_potential',
|
|
state: 'start_forecast_solar_potential',
|
|
decision: 'start',
|
|
desiredCharging: true,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
|
|
// ================= STRATÉGIES MANUELLES =================
|
|
{
|
|
id: 'force-grid-jour',
|
|
label: 'force_grid_now l\'après-midi, EDF ON, SOC 60 → charge réseau forcée',
|
|
time: AFTERNOON,
|
|
overrides: {
|
|
victron_evcs_strategy: 'force_grid_now',
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
soc_percent: '60', pv_power_w: '500', pv_power_10m_avg_w: '500',
|
|
pv_trend_w: '0', battery_current_a: '2',
|
|
grid_state: 'on', victron_acin1_available: '1',
|
|
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
|
},
|
|
expect: {
|
|
profile: 'outside_window',
|
|
sourceMode: 'grid_forced',
|
|
state: 'start_grid_forced_min_current',
|
|
decision: 'start',
|
|
desiredCharging: true,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
{
|
|
id: 'force-charge-nuit',
|
|
label: 'force_charge_now la nuit (toggle HC ON) → démarrage manuel local (non-régression P7)',
|
|
time: NIGHT,
|
|
overrides: {
|
|
...VEHICLE_NIGHT, victron_evcs_strategy: 'force_charge_now',
|
|
soc_percent: '60', allow_grid_hc_night: 'on',
|
|
},
|
|
expect: {
|
|
profile: 'night_hc',
|
|
sourceMode: 'solar_potential',
|
|
state: 'start_manual_local_min_current',
|
|
decision: 'start',
|
|
desiredCharging: true,
|
|
currentA: 6,
|
|
takeoverNeeded: true,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_on',
|
|
},
|
|
},
|
|
{
|
|
id: 'strategie-stop',
|
|
label: 'Stratégie stop en journée → tout bloqué',
|
|
time: AFTERNOON,
|
|
overrides: {
|
|
victron_evcs_strategy: 'stop',
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
soc_percent: '80', pv_power_w: '3000', pv_power_10m_avg_w: '3000',
|
|
battery_current_a: '10',
|
|
},
|
|
expect: {
|
|
profile: 'outside_window',
|
|
sourceMode: 'blocked',
|
|
state: 'stopped_user',
|
|
decision: 'stop_stop_mode',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
|
|
// ================= SÉCURITÉ =================
|
|
{
|
|
id: 'securite-overload',
|
|
label: 'Charge solaire en cours + surcharge VE.Bus → protection (stop/step-down)',
|
|
time: EVENING,
|
|
overrides: {
|
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
|
evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on',
|
|
last_start_at: '2026-08-08 17:30:00',
|
|
soc_percent: '90', pv_power_w: '3500', pv_power_10m_avg_w: '3400',
|
|
battery_current_a: '5', evcs_current_a: '13', evcs_set_current_a: '13',
|
|
vebus_overload: 'on', vebus_l1_overload: 'on',
|
|
house_ac_power_w: '4800', vebus_ac_out_l1_power_w: '4800',
|
|
},
|
|
expect: {
|
|
profile: 'evening_high_soc',
|
|
sourceMode: 'solar_only',
|
|
state: 'vebus_overload_stop_min_current',
|
|
decision: 'stop',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
{
|
|
id: 'vehicule-debranche-jour',
|
|
label: 'Après-midi véhicule débranché → stop_not_connected (état de repos)',
|
|
time: AFTERNOON,
|
|
overrides: {
|
|
evcs_vehicle_connected: 'off', evcs_device_connected: 'on',
|
|
soc_percent: '93', pv_power_w: '1800', pv_power_10m_avg_w: '1800',
|
|
battery_current_a: '3',
|
|
},
|
|
expect: {
|
|
profile: 'outside_window',
|
|
sourceMode: 'solar_only',
|
|
state: 'vehicle_not_connected',
|
|
decision: 'stop_not_connected',
|
|
desiredCharging: false,
|
|
currentA: 6,
|
|
takeoverNeeded: false,
|
|
edfOnAction: false,
|
|
takeoverHelperService: 'turn_off',
|
|
},
|
|
},
|
|
];
|