Files
EVCS/tests/golden/scenarios.mjs
Tallagrand Thierry b287e5ba73 Fix E: latch 'charge terminée' en session active + coupure recovery_done (incident rebond 08-09/08)
- DECISION: branche completed prioritaire dans la session active (avant VDF/source-lost)
  → état collant écrit au 1er cycle status=3, relais encore fermé
- WINDOWS: garde puissance <=300W sur Fix D (anti-glitch)
- Hystérésis: turn_off_recovery_done (bypass min_on, dry-run 5 cas)
- golden 23→25 (session armée status 3, flap statut post-latch)
- HANDOFF §18
2026-08-09 06:31:33 +02:00

583 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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',
},
},
// ================= FIN DE CHARGE NUIT (P8) =================
{
id: 'night-vw-pleine-transition',
label: 'Nuit HC 04:00, cycle après vehicle_full_or_paused, SOC ≥ cible → recovery done, takeover relâché',
time: '2026-08-08T04:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '60', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'vehicle_full_or_paused',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-vw-pleine-autoentretien',
label: 'Nuit HC 05:00, veille auto-entretenue SOC ≥ cible, EDF déjà coupé → reste en recovery done',
time: '2026-08-08T05:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '55', allow_grid_hc_night: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_session_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-vw-pleine-batterie-basse-edf-maintenu',
label: 'Nuit HC 05:00, VW pleine MAIS SOC 30 ≤ cible 35 → takeover maintenu (recharge batterie), EVCS en veille',
time: '2026-08-08T05:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_session_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_session_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: false,
takeoverHelperService: 'turn_on',
},
},
// ===== FIX A/D 08/08/2026 : anti-rebond EDF post-charge (incident nuit 07-08/08) =====
{
id: 'night-recovery-atteinte-soc-cible',
label: 'Nuit HC 04:15, VW pleine, SOC 35 == cible (le cas du rebond) → recovery done, takeover coupé une fois pour toutes',
time: '2026-08-08T04:15:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '35', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_session_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-recovery-done-bande-collante',
label: 'Nuit HC 04:45, recovery done, SOC 34 dans la bande (cible−2 < 34 < 35) → PAS de ré-engagement (anti-rebond)',
time: '2026-08-08T04:45:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '34', allow_grid_hc_night: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-recovery-rearm-sous-bande',
label: 'Nuit HC 05:30, recovery done mais SOC 32 ≤ cible−2 → ré-engagement takeover (recharge batterie)',
time: '2026-08-08T05:30:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '32', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_session_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: false,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-vw-statut-charged',
label: 'Nuit HC 03:00, statut EVCS brut 3 (charged), sans état VDF préalable → fin de charge immédiate (Fix D)',
time: '2026-08-08T03:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '60', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
evcs_status_raw: '3',
prev_surplus_state: '',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
// ===== FIX E 09/08/2026 : latch "charge terminée" PENDANT la session active =====
// Incident nuit 08-09/08 : status 3 en session armée → source-lost zombie 25 min,
// état collant jamais écrit, statut retombe à l'ouverture du relais → rebond EDF.
{
id: 'night-charged-en-session-active',
label: 'Nuit HC 04:30, session ARMÉE (relais fermé, 0 W), statut 3 → arrêt propre + latch collant immédiat',
time: '2026-08-09T04:30:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '51', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'on',
charge_permission_now: 'on',
control_latch: 'on',
evcs_current_a: '0',
evcs_ac_power_w: '0',
evcs_status_raw: '3',
prev_surplus_state: 'charging_hold',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'stop_vehicle_full_or_paused',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-flap-statut-apres-latch',
label: 'Nuit HC 05:00, relais ouvert, statut retombé à 1, prev=night_recovery_done_idle → collant, AUCUN re-P7/EDF',
time: '2026-08-09T05:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '51', allow_grid_hc_night: 'on',
evcs_charging_switch: 'off',
evcs_status_raw: '1',
prev_surplus_state: 'night_recovery_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
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',
},
},
];