// 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'; // Fenêtre d'établissement du réseau (31/08/2026) : horodatage de la dernière // transition d'AC-IN, en secondes epoch. Le harnais fige l'horloge sur `time`, // donc on exprime l'âge de la transition par rapport à cet instant simulé. const MORNING_TS = Date.parse(MORNING) / 1000; // Budget voiture de la nuit (30/09) : releve du 30/09 01:25. const BUDGET_NUIT_BASE = { soc_percent: '57', allow_grid_hc_night: 'on', night_battery_charge_enabled: 'on', night_battery_done_prev: 'off', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-16', evcs_charging_switch: 'off', balance_status: 'ok', balance_last_run: '30/09 01:20', balance_required_soc: '39.2', balance_required_soc_prudent: '44.7', balance_energy_to_cover_kwh: '5.2', balance_night_vehicle_budget_kwh: '3.7', }; const EN_CHARGE_BATTERIE = { evcs_charging_switch: 'on', charge_permission_now: 'on', control_latch: 'on', // P12 charge doucement : 6 A, batterie a -25 A (le garde dur de la bande // 40-60 % est a -30 A). evcs_current_a: '6', evcs_ac_power_w: '1400', battery_current_a: '-25', prev_surplus_state: 'charging_hold_battery_assist_night', }; const VEHICLE_NIGHT = { // 30/09 : les scenarios de nuit historiques decrivent le mode « charge // complete » (EDF finit la charge). Le mode par defaut « sans EDF » a ses // propres scenarios (budget-*). night_charge_mode: 'complete', 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', }; const BUDGET_NUIT = { ...VEHICLE_NIGHT, ...BUDGET_NUIT_BASE }; 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', }, }, // ===== LE CONSEILLER NE FIXE PLUS LA CIBLE (13/09/2026) ===== // Les trois scenarios du premier cablage (applique / perime / toggle OFF) // sont remplaces par celui-ci, qui pinne l'ABSENCE de cablage : toutes les // conditions de l'ancienne regle sont reunies — toggle ON, statut ok, // horodatage frais, valeur dans les bornes — et la cible doit rester celle // des regles de prevision. Si quelqu'un recable un jour, ce test tombe. // Reference : night-soc-sous-cible (SOC 30 < cible regle 35 → grid_forced). { id: 'advisor-cible-ignoree', label: 'Nuit HC, SOC 30, helpers advisor au complet et frais → IGNORES, cible des regles (grid_forced)', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on', advisor_apply_night_target: 'on', advisor_night_target_soc: '22', advisor_status: 'ok', advisor_last_run: '08/08 01:20', }, 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: 'p9-depart-vw-grid', label: 'Matin 08:00, VW sélectionnée, départ 08:50, toggle P9 ON, SOC 45, PV nul → réseau autorisé + EDF demandé', time: '2026-08-10T08:00:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '45', evcs_vehicle: 'VW', departure_time: '08:50:00', departure_time_jeep: '06:30:00', allow_grid_departure_urgent: 'on', }, expect: { profile: 'morning_solar', sourceMode: 'grid_hc_allowed', state: 'wait_grid_source', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: true, takeoverHelperService: 'turn_on', }, }, { id: 'p9-depart-jeep-passe', label: 'Matin 08:00, JEEP sélectionnée (départ 06:30 passé), toggle P9 ON → aucune fenêtre, bloqué', time: '2026-08-10T08:00:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '45', evcs_vehicle: 'JEEP', departure_time: '08:50:00', departure_time_jeep: '06:30:00', allow_grid_departure_urgent: 'on', }, expect: { profile: 'morning_solar', sourceMode: 'blocked', state: 'idle_wait_source', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { // ZERO (moto, 28/09/2026) : elle a SA propre heure de depart. Meme matin que // p9-depart-vw-grid, VW a 08:50 : la ZERO part a 08:30, donc la fenetre P9 // est ouverte pour elle aussi. id: 'p9-depart-zero-sa-propre-heure', label: 'Matin 08:00, ZERO sélectionnée, départ ZERO 08:30, toggle P9 ON → réseau autorisé sur SON heure', time: '2026-08-10T08:00:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '45', evcs_vehicle: 'ZERO', departure_time: '08:50:00', departure_time_jeep: '06:30:00', departure_time_zero: '08:30:00', allow_grid_departure_urgent: 'on', }, expect: { profile: 'morning_solar', sourceMode: 'grid_hc_allowed', edfOnAction: true }, }, { id: 'p9-depart-zero-passe', label: 'CONTREFACTUEL — ZERO partie a 07:00 : sa fenetre est passee, meme si celle de la VW (08:50) serait ouverte', time: '2026-08-10T08:00:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '45', evcs_vehicle: 'ZERO', departure_time: '08:50:00', departure_time_jeep: '06:30:00', departure_time_zero: '07:00:00', allow_grid_departure_urgent: 'on', }, expect: { profile: 'morning_solar', sourceMode: 'blocked', edfOnAction: false }, }, { id: 'p9-done-idle-collant', label: 'Matin 08:10, VW pleine détectée (prev=vehicle_full_or_paused) pendant la fenêtre départ → veille collante, réseau relâché', time: '2026-08-10T08:10:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '45', evcs_vehicle: 'VW', departure_time: '08:50:00', departure_time_jeep: '06:30:00', allow_grid_departure_urgent: 'on', grid_state: 'on', victron_acin1_available: '1', battery_current_a: '2', prev_surplus_state: 'vehicle_full_or_paused', }, expect: { profile: 'morning_solar', sourceMode: 'blocked', state: 'departure_done_idle', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'p9-toggle-off', label: 'Matin 08:00, VW départ 08:50 mais toggle P9 OFF → comportement historique (bloqué, pas de réseau)', time: '2026-08-10T08:00:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '45', evcs_vehicle: 'VW', departure_time: '08:50:00', departure_time_jeep: '06:30:00', allow_grid_departure_urgent: 'off', }, expect: { profile: 'morning_solar', sourceMode: 'blocked', state: 'idle_wait_source', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, // ===== P10 09/08/2026 : mémoire "véhicule plein" en journée ===== // Incident 09/08 09:49 : VW pleine, le surplus solaire retentait une charge // toutes les ~15 min (statut "Charged", 0 W) → usure relais. Désormais // collant jusqu'au débranchement, avec héritage des états done de la nuit. // ===== Fix H (07/09/2026) : credibilite du statut "charged" ===== // Releve reel : la borne a annonce "charged" a 14:28:18 apres 2 min 42 s de // charge et 0,10 kWh livre, ce qui a bloque le vehicule deux heures. Les deux // coupures (vraie fin de charge et interruption) sont ELECTRIQUEMENT // identiques, mesurees a la seconde : seule la duree de session les separe. { id: 'charged-interrompu-ne-latche-pas', label: 'Statut 3 apres 2 min 42 s et 0,10 kWh livre (releve 07/09) -> interruption, PAS de collant', time: AFTERNOON, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '96', pv_power_w: '2600', pv_power_10m_avg_w: '2500', pv_trend_w: '0', battery_current_a: '2', house_ac_power_w: '2100', vebus_ac_out_l1_power_w: '2100', evcs_charging_switch: 'on', charge_permission_now: 'on', control_latch: 'on', evcs_current_a: '0', evcs_ac_power_w: '0', evcs_status_raw: '3', evcs_charging_time: '2m 42s', evcs_session_energy_kwh: '0.1', prev_surplus_state: 'charging_hold', }, expect: { profile: 'outside_window', sourceMode: 'solar_only', state: 'charging_step_up_battery', decision: 'step_up', desiredCharging: true, currentA: 7, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'charged-session-etablie-latche', label: 'CONTREFACTUEL : meme statut 3 apres 1 h 32 et 4,30 kWh (releve VW 06/09) -> vraie fin, collant', time: AFTERNOON, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '96', pv_power_w: '2600', pv_power_10m_avg_w: '2500', pv_trend_w: '0', battery_current_a: '2', house_ac_power_w: '2100', vebus_ac_out_l1_power_w: '2100', evcs_charging_switch: 'on', charge_permission_now: 'on', control_latch: 'on', evcs_current_a: '0', evcs_ac_power_w: '0', evcs_status_raw: '3', evcs_charging_time: '1h 32m 0s', evcs_session_energy_kwh: '4.3', prev_surplus_state: 'charging_hold', }, expect: { profile: 'outside_window', sourceMode: 'blocked', state: 'day_vehicle_full_idle', decision: 'stop_vehicle_full_or_paused', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'charged-energie-nulle-latche', label: 'CONTREFACTUEL : statut 3 avec ZERO kWh livre (vehicule plein a l arrivee, cas P10) -> collant', time: AFTERNOON, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '96', pv_power_w: '2600', pv_power_10m_avg_w: '2500', pv_trend_w: '0', battery_current_a: '2', house_ac_power_w: '2100', vebus_ac_out_l1_power_w: '2100', evcs_charging_switch: 'on', charge_permission_now: 'on', control_latch: 'on', evcs_current_a: '0', evcs_ac_power_w: '0', evcs_status_raw: '3', evcs_charging_time: '3m 20s', evcs_session_energy_kwh: '0', prev_surplus_state: 'charging_hold', }, expect: { profile: 'outside_window', sourceMode: 'blocked', state: 'day_vehicle_full_idle', decision: 'stop_vehicle_full_or_paused', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'night-charged-interrompu-ne-latche-pas', label: 'Nuit HC : statut 3 apres 3 min et 0,10 kWh -> interruption, la charge HC continue', time: NIGHT, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', night_session_active: 'on', allow_grid_hc_night: 'on', night_charge_mode: 'complete', soc_percent: '55', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', battery_current_a: '0', house_ac_power_w: '800', vebus_ac_out_l1_power_w: '800', evcs_charging_switch: 'on', charge_permission_now: 'on', control_latch: 'on', grid_switch: 'on', acin1_available: '1', evcs_current_a: '0', evcs_ac_power_w: '0', evcs_status_raw: '3', evcs_charging_time: '3m 0s', evcs_session_energy_kwh: '0.1', prev_surplus_state: 'charging_step_up_grid_hc', }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', state: 'night_wait_grid_takeover_runtime', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: true, edfOnAction: true, takeoverHelperService: 'turn_on', }, }, { id: 'day-vw-pleine-statut3', label: 'Matin PV fort, session armée 0 W, statut 3 → day_vehicle_full_idle collant (stop propre, plus de 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', 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: 'morning_solar', sourceMode: 'blocked', state: 'day_vehicle_full_idle', decision: 'stop_vehicle_full_or_paused', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'day-full-heritage-nuit', label: 'Matin PV fort, prev=night_recovery_done_idle (nuit finie pleine) → AUCUNE re-tentative 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', evcs_status_raw: '1', prev_surplus_state: 'night_recovery_done_idle', }, expect: { profile: 'morning_solar', sourceMode: 'blocked', state: 'day_vehicle_full_idle', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'day-full-echappatoire-force-charge', label: 'Matin PV fort, mémoire plein active MAIS force_charge_now → la charge repart (échappatoire utilisateur)', time: MORNING, overrides: { victron_evcs_strategy: 'force_charge_now', 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', evcs_status_raw: '1', prev_surplus_state: 'day_vehicle_full_idle', }, expect: { profile: 'morning_solar', sourceMode: 'solar_only', state: 'start_manual_local_min_current', decision: 'start', desiredCharging: true, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, // ===== P11 12/08/2026 : reset du collant au changement de véhicule ===== // Incident : JEEP chargée (night_recovery_done_idle) → débranchée → VW // branchée à sa place → l'état collant survivait et bloquait la VW neuve. // Signal : charging_time repart de 0 (< 90 s) = session neuve → on neutralise. { id: 'p11-changement-vehicule-nuit', label: 'Nuit HC, prev=night_recovery_done_idle (JEEP finie) MAIS charging_time 15 s (VW fraîche) → collant neutralisé, retest', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '54', allow_grid_hc_night: 'on', prev_surplus_state: 'night_recovery_done_idle', evcs_charging_time: '15s', }, 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: 'p11-changement-vehicule-jour', label: 'Jour PV fort, prev=day_vehicle_full_idle MAIS charging_time 20 s (nouveau véhicule) → retest 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', evcs_status_raw: '1', prev_surplus_state: 'day_vehicle_full_idle', evcs_charging_time: '20s', }, 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: 'p11-session-etablie-reste-collant', label: 'Jour PV fort, prev=day_vehicle_full_idle, charging_time 2h (session établie) → reste collant (pas de reset abusif)', 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', evcs_status_raw: '1', prev_surplus_state: 'day_vehicle_full_idle', evcs_charging_time: '2h 05m 12s', }, expect: { profile: 'morning_solar', sourceMode: 'blocked', state: 'day_vehicle_full_idle', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, // ============ P11-bis : SWAP RAPIDE, BORNE FIGEE (19/09/2026) ============ // Incident du 19/09 : VW debranchee ~14:50, JEEP branchee, mais le swap n'a // dure que 14 s → la borne a GELE son etat (charging_time reste a 1h25, status // fige). P11 (base sur charging_time <= 90s) restait aveugle → le collant VW // bloquait la JEEP. Le last_changed du capteur de connexion, lui, a bouge : // c'est le second signal de fraicheur. { id: 'p11bis-swap-rapide-borne-figee', label: 'Jour PV fort, prev=day_vehicle_full_idle, charging_time FIGE 1h25 MAIS reconnexion il y a 30 s → retest (second signal)', 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', evcs_status_raw: '1', prev_surplus_state: 'day_vehicle_full_idle', // la borne n'a PAS reinitialise : charging_time fige haut evcs_charging_time: '1h 25m', // mais la connexion a change il y a 30 s (le swap rapide) evcs_vehicle_connected_ts: String(MORNING_TS - 30), }, expect: { profile: 'morning_solar', sourceMode: 'solar_only', state: 'start_min_current', decision: 'start', desiredCharging: true, currentA: 6, }, }, { id: 'p11bis-session-etablie-reconnexion-vieille', label: 'CONTREFACTUEL : charging_time 1h25 ET reconnexion il y a 2h → PAS frais → reste collant (pas de reset abusif)', 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', evcs_status_raw: '1', prev_surplus_state: 'day_vehicle_full_idle', evcs_charging_time: '1h 25m', evcs_vehicle_connected_ts: String(MORNING_TS - 7200), }, expect: { profile: 'morning_solar', sourceMode: 'blocked', state: 'day_vehicle_full_idle', decision: 'hold', desiredCharging: false, }, }, { id: 'reset-borne-force-retest', label: 'Bouton reset borne ON : session pourtant etablie (charging_time 1h25 + reconnexion vieille) → retest force + ack off', 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', evcs_status_raw: '1', prev_surplus_state: 'day_vehicle_full_idle', evcs_charging_time: '1h 25m', evcs_vehicle_connected_ts: String(MORNING_TS - 7200), reset_borne: 'on', }, expect: { state: 'start_min_current', decision: 'start', desiredCharging: true, resetBorneAck: '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-done-pas-de-reengagement', label: 'Nuit HC 05:30, recovery done et SOC 32 sous la cible → AUCUN ré-engagement (la recovery est une passe unique, 27/08)', 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', // Collant désormais : la recovery ayant eu lieu, l'état reste // 'night_recovery_done_idle' même sous la cible — c'est lui qui autorise // la coupure EDF immédiate côté hystérésis (E2) dès SOC >= grid_soc_off. state: 'night_recovery_done_idle', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { // L'oscillateur mesuré : nuit du 26-27/08, Jeep pleine restée branchée, // cible 35. L'ancienne bande de 2 points ré-engageait le takeover pile à // SOC 33 -> EDF ON 04:24/05:31/07:05, OFF 05:16/06:50/07:30, 2,5 kWh // importés une nuit où le lendemain était annoncé à 43 kWh. id: 'night-oscillateur-nuit-2627', label: 'Nuit HC 05:31, recovery done et SOC 33 (cible 35 − 2) → le takeover NE se ré-engage pas : plus d\'oscillateur', time: '2026-08-27T05:31:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '33', allow_grid_hc_night: 'on', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-11', night_takeover_prev: 'off', evcs_charging_switch: 'off', prev_surplus_state: 'night_recovery_done_idle', }, expect: { profile: 'night_hc', sourceMode: 'blocked', state: 'night_recovery_done_idle', takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { // Le filet de sécurité doit rester intact : sous le plancher dur, le // takeover se ré-engage même si la recovery a déjà eu lieu, parce que // batteryBelowHardFloor est un terme INDÉPENDANT de nightGridTakeoverNeeded. id: 'night-recovery-done-plancher-dur', label: 'Nuit HC 05:31, recovery done mais SOC 21 sous le plancher dur 22 → ré-engagement du takeover (sécurité)', time: '2026-08-27T05:31:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '21', allow_grid_hc_night: 'on', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-11', night_takeover_prev: 'off', evcs_charging_switch: 'off', prev_surplus_state: 'night_recovery_done_idle', }, expect: { profile: 'night_hc', takeoverNeeded: true, 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', }, }, { id: 'night-hard-stop-necrase-pas-done', label: 'Nuit HC 06:55, VW pleine relance une micro-charge sur batterie (−60 A) → le hard stop N\'ÉCRASE PAS le collant (Fix F)', time: '2026-08-09T06:55:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '40', allow_grid_hc_night: 'on', battery_current_a: '-60', evcs_charging_switch: 'on', charge_permission_now: 'on', control_latch: 'on', evcs_current_a: '6', evcs_ac_power_w: '1400', evcs_status_raw: '2', prev_surplus_state: 'night_recovery_done_idle', }, 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', }, }, // ================= 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-inversion-start-hold', label: 'INVARIANT maintien<=demarrage : midi, SOC 58 dans la bande [40,60], charge 18 A en cours sous abondance → doit CONTINUER (incident 24/08 12:24-12:28)', time: MIDDAY, 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 11:30:00', // min_run largement écoulé // Relevé VRM du 24/08 12:25 : PV 10,24 kW, charges 3815 W (dont EVCS 3502 W), // batterie +127 A. SOC 58 % : au-dessus du plancher de démarrage sous // abondance (40 %) mais sous le plancher de maintien de la règle midi // (max(29, 75-15) = 60 %). Avant correctif : arrêt, puis redémarrage au // cycle suivant → cycle limite de 3 min et PV écrêtée. soc_percent: '58', pv_power_w: '10240', pv_power_10m_avg_w: '9800', pv_trend_w: '200', battery_current_a: '127', evcs_current_a: '18', evcs_set_current_a: '18', evcs_command_current_a: '18', evcs_ac_power_w: '3502', house_ac_power_w: '3815', vebus_ac_out_l1_power_w: '3815', }, expect: { profile: 'midday_hc_or_solar', sourceMode: 'solar_only', state: 'charging_step_up_battery', decision: 'step_up', desiredCharging: true, currentA: 19, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, // ======== REPLI SANS BILAN : LA BORNE N'EST PLUS EMPRUNTEE (18/09/2026) ======== // Quand la garde du bilan tombe, la consigne revenait a `maison + plafond de // borne` meme sans voiture -- le defaut d'origine, revenu au moment ou le // systeme est deja degrade. Ce scenario fige le comportement corrige, et rien // d'autre dans la suite ne le couvre : sans lui, remettre le plafond brut // dans la branche de repli laisse les 105 autres scenarios verts. // ⚠️ L'attendu est calcule sur la FIXTURE, jamais sur une mesure de // production : maison 900 W + part voiture nulle, plus la marge d'un pas de // courant. Le rejouer sur l'installation vivante donnerait autre chose sans // que rien ne soit casse. { id: 'consigne-repli-sans-vehicule-n-emprunte-plus-la-borne', label: "REPLI 18/09 — midi, fenetre HC autorisee, AUCUN vehicule, bilan indisponible : la consigne couvre la maison au lieu d'emprunter le plafond de la borne", time: MIDDAY, overrides: { evcs_vehicle_connected: 'off', 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', evcs_ac_power_w: '0', evcs_current_a: '0', grid_setpoint_w: '700', // Aucun champ `balance_*` : la garde tombe, on est dans la branche de repli. }, expect: { profile: 'midday_hc_or_solar', sourceMode: 'grid_hc_allowed', gridSetpointW: 1130, }, }, { 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', // 03/09/2026 — echantillon rendu COHERENT EN ENERGIE. Il portait PV 300 W, // maison 600 W et batterie +2 A : un trou de 300 W que rien n'alimente, // etat physiquement inatteignable reseau ouvert. Il passait quand meme, // parce que le demarrage ne regardait que la production. Corrige en gardant // l'intention d'origine — production mesuree SOUS pv_hold_min (500) et sous // pv_start_min (700), donc seul solar_potential peut demarrer — avec une // maison qui, elle, ferme le bilan : c'est la signature de la censure // (batterie pleine, MPPT cale sur la maison, excedent ~0). label: 'Après-midi hors fenêtre, SOC 95, PV bridé 430 W (< hold 500) mais maison 400 W → censure, excédent +30 W → provocation PV (solar_potential)', time: AFTERNOON, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '95', pv_power_w: '430', pv_power_10m_avg_w: '430', pv_trend_w: '0', battery_current_a: '2', house_ac_power_w: '400', vebus_ac_out_l1_power_w: '400', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', }, 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', }, }, // ===== 03/09/2026 : un demarrage se decide sur l'EXCEDENT, pas sur la PV ===== // Les deux premiers scenarios sont des releves REELS du 03/09, pris a 54 min // d'intervalle. Leurs lectures de production ne different que de 11 W // (769 W puis 758 W) : pour l'ancien code, deux fois "758 >= 700, il y a du // soleil". Ce qui les separe est la MAISON — 755 W puis 1202 W — donc // l'excedent : +14 W (production refusee, batterie pleine, rien a absorber) // puis -444 W (la batterie porte deja la maison, la nuit tombe). // La paire est le coeur du correctif : sans elle, aucune preuve que la // nouvelle grandeur discrimine ce que l'ancienne confondait. { id: 'excedent-censure-demarre', label: "Relevé du 03/09 18:50 (pv_censored=ON) : PV 769 W, maison 755 W, batterie 0 A, SOC 94 → excédent +14 W = ressource refusée → la voiture DOIT démarrer", time: '2026-08-08T18:50:00+02:00', overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '94', battery_current_a: '0', pv_power_w: '769.1', pv_power_10m_avg_w: '800', pv_trend_w: '0', house_ac_power_w: '755', vebus_ac_out_l1_power_w: '755', evcs_ac_power_w: '0', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', }, expect: { profile: 'evening_high_soc', sourceMode: 'solar_only', state: 'start_min_current', decision: 'start', desiredCharging: true, currentA: 6, }, }, { id: 'excedent-deficit-ne-demarre-pas', label: "Relevé du 03/09 19:44 (l'incident) : PV 758 W mais maison 1202 W, batterie déjà à -12 A, SOC 94 → excédent -444 W → AUCUN démarrage, ni chemin solaire ni assistance batterie", time: '2026-08-08T19:44:30+02:00', overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '94', battery_current_a: '-12.2', pv_power_w: '758.3', pv_power_10m_avg_w: '790', pv_trend_w: '0', house_ac_power_w: '1202', vebus_ac_out_l1_power_w: '1202', evcs_ac_power_w: '0', }, expect: { profile: 'evening_high_soc', sourceMode: 'solar_only', state: 'idle_wait_conditions', decision: 'hold', desiredCharging: false, }, }, { // Exerce le chemin "soleil reel" (rawSolarStartOk), que la paire ci-dessus // ne touche pas : elle passe par l'assistance batterie. Batterie a 60 %, // donc pas de censure possible — la production mesuree EST la ressource, et // elle est deja prise par la maison. L'ancien code demarrait (3000 >= 700). id: 'excedent-grosse-production-grosse-maison', label: "Matin, PV 3000 W mais maison 2800 W et SOC 60 (pas de censure possible) → excédent 200 W < seuil de démarrage → pas de charge malgré une forte production", time: MORNING, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '60', battery_current_a: '-4', pv_power_w: '3000', pv_power_10m_avg_w: '3000', pv_trend_w: '0', house_ac_power_w: '2800', vebus_ac_out_l1_power_w: '2800', evcs_ac_power_w: '0', }, expect: { profile: 'morning_solar', sourceMode: 'solar_only', state: 'idle_wait_conditions', decision: 'hold', desiredCharging: false, }, }, // ===== 03/09/2026 : la provocation doit etre justifiee par les chargeurs ===== // Un excedent proche de zero est AMBIGU. Meme releve, meme excedent, deux // situations opposees a 51 min d'ecart le 03/09 : // 18:50 excedent +14 W, trackers LIMITES -> ressource refusee, on y va // 19:41 excedent +1 W, trackers MPPT_ACTIVE -> plus rien a donner, on reste // Aucun seuil de puissance ne les separe ; les chargeurs, eux, le disent. { id: 'provocation-trackers-a-fond-ne-demarre-pas', label: "Relevé du 03/09 19:41 : excédent +1 W (équilibre au crépuscule), SOC 94, mais les 7 trackers en mppt_active → plus rien à révéler → PAS de provocation", time: '2026-08-08T19:41:00+02:00', overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '94', battery_current_a: '-1.4', pv_power_w: '816.9', pv_power_10m_avg_w: '850', pv_trend_w: '0', house_ac_power_w: '816', vebus_ac_out_l1_power_w: '816', evcs_ac_power_w: '0', mppt_operation_modes: 'mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active', }, expect: { profile: 'evening_high_soc', state: 'idle_wait_conditions', decision: 'hold', desiredCharging: false, }, }, { // Le repli doit etre sur le comportement ANTERIEUR, jamais sur une // supposition : integration muette => on ne sait pas => on autorise comme // avant. Meme releve que ci-dessus, seules les lectures changent. id: 'provocation-trackers-illisibles-comportement-anterieur', label: "Même relevé, mais chargeurs illisibles (unavailable) → on ne sait pas → repli sur le comportement antérieur, la charge démarre", time: '2026-08-08T19:41:00+02:00', overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '94', battery_current_a: '-1.4', pv_power_w: '816.9', pv_power_10m_avg_w: '850', pv_trend_w: '0', house_ac_power_w: '816', vebus_ac_out_l1_power_w: '816', evcs_ac_power_w: '0', mppt_operation_modes: 'unavailable,unknown,', }, expect: { profile: 'evening_high_soc', state: 'start_min_current', decision: 'start', desiredCharging: true, currentA: 6, }, }, // ===== 03/09/2026 : la cible du matin est une energie, pas un palier ===== // Releve de la nuit 02-03/09 a 03:00 : le bilan demandait 50,4 % pour tenir // jusqu'au croisement de 9 h, la regle des paliers disait 35 %, le SOC etait // a 45 %. EDF a ete relache 5 points sous la ligne, la batterie s'est videe // a 20 % vers 06:30, et le reseau est revenu de 07:24 a 10:33 AU PLEIN TARIF. { id: 'cible-bilan-tient-le-reseau', label: "Nuit HC 03:00 (relevé du 03/09), VW pleine, SOC 45 : le bilan demande 50,4 % → cible relevée → le réseau est MAINTENU au lieu d'être relâché", time: '2026-08-08T03:00:00+02:00', overrides: { ...VEHICLE_NIGHT, 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', soc_percent: '45', balance_required_soc: '50.4', balance_energy_to_cover_kwh: '4.3', balance_status: 'ok', balance_last_run: '08/08 02:58', }, expect: { profile: 'night_hc', state: 'night_session_done_idle', takeoverNeeded: true, takeoverHelperService: 'turn_on', }, }, { // CONTREFACTUEL : exactement la nuit du 03/09. Bilan indisponible => regle // des paliers (35 %) => SOC 45 >= 35 => relache. C'est ce qui s'est passe. id: 'cible-bilan-perime-relache-comme-avant', label: "Même relevé, bilan vieux de 2 h → repli sur le palier 35 % → EDF relâché (le comportement qui a coûté 1,49 kWh au plein tarif)", time: '2026-08-08T03:00:00+02:00', overrides: { ...VEHICLE_NIGHT, 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', soc_percent: '45', balance_required_soc: '50.4', balance_energy_to_cover_kwh: '4.3', balance_status: 'ok', balance_last_run: '08/08 01:00', }, expect: { profile: 'night_hc', state: 'night_recovery_done_idle', takeoverNeeded: false, takeoverHelperService: 'turn_off', }, }, { // 01/10/2026, DECISION DU PROPRIETAIRE : le bilan fixe la cible DANS LES // DEUX SENS (« 35 % c'est idiot si le besoin est prevu 26,6 % »). Ce // scenario figeait l'ancien sens unique ; il fige desormais l'inverse. id: 'cible-bilan-abaisse-la-cible', label: "Nuit HC, SOC 30, le bilan ne demande que 25 % → la cible est 25 %, le palier 35 % n'est plus maître → le réseau est relâché", time: '2026-08-08T03:00:00+02:00', overrides: { ...VEHICLE_NIGHT, 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', soc_percent: '30', balance_required_soc: '25', balance_energy_to_cover_kwh: '0.5', balance_status: 'ok', balance_last_run: '08/08 02:58', }, expect: { profile: 'night_hc', takeoverNeeded: false, takeoverHelperService: 'turn_off', }, }, { // PLAFOND 90 % : le bilan publie sans ecretage des valeurs > 100 (c'est // voulu, §26 : signal de dimensionnement). Sans plafond ici, la cible // deviendrait inatteignable et EDF resterait allume toute la nuit a // charger la batterie — en evincant au passage la production du matin. id: 'cible-bilan-plafonnee-a-90', label: "Nuit HC, SOC 92, le bilan demande 118,9 % (stockage insuffisant) → cible plafonnée à 90 % → recovery terminée, EDF relâché", time: '2026-08-08T03:00:00+02:00', overrides: { ...VEHICLE_NIGHT, 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', soc_percent: '92', balance_required_soc: '118.9', balance_energy_to_cover_kwh: '14.54', balance_status: 'ok', balance_last_run: '08/08 02:58', }, expect: { profile: 'night_hc', state: 'night_recovery_done_idle', takeoverNeeded: false, takeoverHelperService: 'turn_off', }, }, // ===== 04/09/2026 : LA SESSION TENAIT LE CONTACTEUR, PAS L'ENERGIE ===== // Releve de la nuit du 03-04/09. La session unique a fonctionne — une seule // bascule du contacteur, EDF ferme jusqu'a 07:24. Mais a 02:50, la voiture // finie, le sourceMode est passe a 'blocked' et la consigne reseau est // retombee de 5078 W a 50 W. Contacteur ferme, heures creuses, et la maison // a tire 770 W SUR LA BATTERIE pendant trois heures et demie : // SOC 44 % -> 17 %, 2,60 kWh pris a la batterie quand le reseau etait la. { id: 'consigne-session-engagee-couvre-la-maison', label: "Nuit 03:30, session engagée mais voiture finie (source bloquée) → la consigne DOIT couvrir la maison au lieu de retomber au repos", time: '2026-08-08T03:30:00+02:00', overrides: { ...VEHICLE_NIGHT, allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', battery_current_a: '-16', night_takeover_prev: 'on', evcs_charging_switch: 'off', evcs_ac_power_w: '0', house_ac_power_w: '770', vebus_ac_out_l1_power_w: '770', prev_surplus_state: 'night_session_done_idle', night_grid_session_committed_prev: 'on', soc_percent: '40', balance_required_soc: '57.2', balance_energy_to_cover_kwh: '7.58', balance_status: 'ok', balance_last_run: '08/08 03:28', }, expect: { profile: 'night_hc', takeoverNeeded: true, // 770 W de maison + le plafond de courant en marge. Ce qui compte n'est // pas la valeur exacte mais qu'on ne retombe PAS au repos. gridSetpointW: 5595, }, }, { // CONTREFACTUEL. La difference est PHYSIQUE et non un drapeau force : le // SOC est au-dessus de la ligne du bilan, donc la nuit est couverte, donc // aucune session ne s'engage — et la consigne doit bien retomber au repos. // Sans ce scenario, le correctif pourrait fuir hors session. // // ⚠️ Premiere redaction fausse : elle portait DEUX fois `balance_status` // dans le meme objet. En JavaScript la derniere gagne, donc le scenario // testait l'inverse de ce que son libelle annoncait — et il l'a signale en // renvoyant 5595. Ne jamais construire un contrefactuel en surchargeant une // clef deja presente. // ⚠️ ATTENDU MIS A JOUR LE 06/09/2026 : la consigne de repos est passee de // 200 a 700 W. Elle ne s'applique que contacteur FERME sans session reseau, // et a 200 W une plaque qui cycle faisait passer le point de fonctionnement // sous zero (mesure du 06/09, fenetre 12:07-12:22 : 9 % d'echantillons // negatifs, minimum -477 W, mediane des negatifs -146 W). Les deux seules // raisons de fermer le contacteur sans session EVCS sont « batterie au // plancher » et « la maison demande plus que le parc ne sait donner » : // dans les deux cas on veut que le reseau contribue plus que 200 W. id: 'consigne-sans-engagement-retombe-au-repos', label: "Même relevé mais SOC 70 au-dessus de la ligne : aucune session engagée → la consigne retombe à la valeur de repos", time: '2026-08-08T03:30:00+02:00', overrides: { ...VEHICLE_NIGHT, allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', battery_current_a: '-16', night_takeover_prev: 'off', evcs_charging_switch: 'off', evcs_ac_power_w: '0', house_ac_power_w: '770', vebus_ac_out_l1_power_w: '770', prev_surplus_state: 'night_recovery_done_idle', night_grid_session_committed_prev: 'off', soc_percent: '70', balance_required_soc: '57.2', balance_energy_to_cover_kwh: '7.58', balance_status: 'ok', balance_last_run: '08/08 03:28', }, expect: { profile: 'night_hc', takeoverNeeded: false, gridSetpointW: 700, }, }, // ===== 03/09/2026 : SESSION RESEAU UNIQUE DE LA NUIT ===== // Engagement sur le bilan, relachement a la FIN DE LA FENETRE — jamais sur // un seuil de SOC. Deux raisons, toutes deux mesurees le 03/09 : un seuil de // relachement que le SOC repasse ensuite rouvre l'oscillateur du 27/08, et // il porterait de toute facon sur une grandeur fausse (la conversion // kWh -> points du bilan est lineaire a 15 kWh alors que la jauge vaut // ~17 kWh au-dessus de 42 % et 8 a 12 kWh en dessous). // // Le premier scenario etait, quelques heures plus tot, epingle comme LIMITE // CONNUE : le reseau restait relache alors que le SOC etait repasse sous la // ligne. C'est exactement ce que ce correctif change. { id: 'session-unique-tient-sous-la-ligne', label: "Nuit HC 05:30, recovery déjà latchée, SOC 30 sous le requis 37,1 % → la session réseau s'ENGAGE et tient", time: '2026-08-08T05:30:00+02:00', overrides: { ...VEHICLE_NIGHT, allow_grid_hc_night: 'on', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-14', night_takeover_prev: 'off', evcs_charging_switch: 'off', prev_surplus_state: 'night_recovery_done_idle', soc_percent: '30', balance_required_soc: '37.1', balance_energy_to_cover_kwh: '2.3', balance_status: 'ok', balance_last_run: '08/08 05:28', }, expect: { profile: 'night_hc', takeoverNeeded: true, takeoverHelperService: 'turn_on', sessionCommittedService: 'turn_on', }, }, { // LA PREUVE ANTI-OSCILLATEUR. Une fois engagee, la session ne se relache // PAS parce que le SOC est repasse au-dessus de la ligne. Sans ce // scenario, un relachement sur seuil passerait les tests et recreerait le // cycle de 31 min corrige le 27/08. id: 'session-unique-ne-relache-pas-sur-le-soc', label: "Session déjà engagée, SOC 45 repassé AU-DESSUS du requis 37,1 % → le réseau reste tenu, et rien n'est réécrit", time: '2026-08-08T05:30:00+02:00', overrides: { ...VEHICLE_NIGHT, 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', night_grid_session_committed_prev: 'on', soc_percent: '45', balance_required_soc: '37.1', balance_energy_to_cover_kwh: '2.3', balance_status: 'ok', balance_last_run: '08/08 05:28', }, expect: { profile: 'night_hc', takeoverNeeded: true, takeoverHelperService: 'turn_on', sessionCommittedService: null, }, }, { // LA SORTIE, et la seule : la fenetre se ferme. Condition monotone, donc // aucune oscillation possible. id: 'session-unique-fin-de-fenetre', label: "08:00, hors fenêtre HC, session encore marquée engagée → réseau relâché et bit nettoyé", time: '2026-08-08T08:00:00+02:00', overrides: { ...VEHICLE_NIGHT, allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', battery_current_a: '2', night_takeover_prev: 'on', evcs_charging_switch: 'off', night_grid_session_committed_prev: 'on', soc_percent: '45', balance_required_soc: '37.1', balance_energy_to_cover_kwh: '2.3', balance_status: 'ok', balance_last_run: '08/08 07:58', }, expect: { takeoverNeeded: false, takeoverHelperService: 'turn_off', sessionCommittedService: 'turn_off', }, }, { id: 'session-unique-bilan-indisponible', label: "Même relevé qu'en tête, bilan périmé → aucun engagement, comportement historique (réseau relâché)", time: '2026-08-08T05:30:00+02:00', overrides: { ...VEHICLE_NIGHT, allow_grid_hc_night: 'on', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-14', night_takeover_prev: 'off', evcs_charging_switch: 'off', prev_surplus_state: 'night_recovery_done_idle', soc_percent: '30', balance_required_soc: '37.1', balance_energy_to_cover_kwh: '2.3', balance_status: 'ok', balance_last_run: '08/08 03:00', }, expect: { profile: 'night_hc', takeoverNeeded: false, takeoverHelperService: 'turn_off', sessionCommittedService: null, }, }, // ===== 03/09/2026 : le plancher du soir vient de l'energie a couvrir ===== // Tous ces scenarios ont un VRAI excedent (PV 2000 W, maison 1000 W) et des // chargeurs qui se retiennent : les gardes de §36 et §37 sont donc passantes, // et le plancher est le SEUL discriminant. { id: 'bilan-plancher-soir-durci', label: "Soir, SOC 94 et vrai excédent, mais le bilan demande 99,7 % pour tenir jusqu'au croisement → le plancher passe de 85 à 99,7 → pas de charge", time: EVENING, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', battery_current_a: '2', pv_power_w: '2000', pv_power_10m_avg_w: '2000', pv_trend_w: '0', house_ac_power_w: '1000', vebus_ac_out_l1_power_w: '1000', evcs_ac_power_w: '0', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', soc_percent: '94', balance_required_soc: '99.7', balance_energy_to_cover_kwh: '11.66', balance_status: 'ok', balance_last_run: '08/08 17:58', }, expect: { profile: 'evening_high_soc', state: 'idle_wait_conditions', decision: 'hold', desiredCharging: false, }, }, { // SENS UNIQUE : le bilan ne doit JAMAIS relacher le plancher historique. // Sans ce scenario, remplacer Math.max par une affectation directe // passerait inapercu et autoriserait des charges qui n'ont pas lieu // aujourd'hui. id: 'bilan-plancher-soir-ne-relache-pas', label: "Soir, SOC 70, le bilan ne demande que 60 % → le plancher historique 85 % reste maître, pas de charge", time: EVENING, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', battery_current_a: '2', pv_power_w: '2000', pv_power_10m_avg_w: '2000', pv_trend_w: '0', house_ac_power_w: '1000', vebus_ac_out_l1_power_w: '1000', evcs_ac_power_w: '0', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', soc_percent: '70', balance_required_soc: '60', balance_energy_to_cover_kwh: '4.2', balance_status: 'ok', balance_last_run: '08/08 17:58', }, expect: { profile: 'evening_high_soc', state: 'idle_wait_conditions', decision: 'hold', desiredCharging: false, }, }, { id: 'bilan-perime-regle-historique', label: "Soir, SOC 94, bilan exigeant (99,7 %) mais vieux de 2 h → repli sur la règle historique 85 % → la charge démarre", time: EVENING, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', battery_current_a: '2', pv_power_w: '2000', pv_power_10m_avg_w: '2000', pv_trend_w: '0', house_ac_power_w: '1000', vebus_ac_out_l1_power_w: '1000', evcs_ac_power_w: '0', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', soc_percent: '94', balance_required_soc: '99.7', balance_energy_to_cover_kwh: '11.66', balance_status: 'ok', balance_last_run: '08/08 16:00', }, expect: { profile: 'evening_high_soc', state: 'start_min_current', decision: 'start', desiredCharging: true, currentA: 6, }, }, { id: 'bilan-statut-degrade-regle-historique', label: "Soir, SOC 94, bilan frais mais statut non 'ok' → repli sur la règle historique → la charge démarre", time: EVENING, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', battery_current_a: '2', pv_power_w: '2000', pv_power_10m_avg_w: '2000', pv_trend_w: '0', house_ac_power_w: '1000', vebus_ac_out_l1_power_w: '1000', evcs_ac_power_w: '0', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', soc_percent: '94', balance_required_soc: '99.7', balance_energy_to_cover_kwh: '11.66', balance_status: 'donnees_insuffisantes', balance_last_run: '08/08 17:58', }, expect: { profile: 'evening_high_soc', state: 'start_min_current', decision: 'start', desiredCharging: true, currentA: 6, }, }, // ===== 01/09/2026 : le depassement souple de la batterie doit DURER ===== // Incident du 01/09 (14:26 et 14:56) : SOC 94, batterie en float, courant // oscillant entre +1,8 et -2,6 A d'un echantillon a l'autre. Le controleur // comparait un echantillon instantane a un plancher de 0 A toutes les 30 s : // pile ou face. Chaque arret coutait ~0,45 kWh de production refusee, la // voiture etant le seul preneur d'une batterie pleine. // La table des bandes SOC declarait deja une tolerance de 12 min a SOC > 90 ; // elle n'etait branchee nulle part. Aucun seuil n'est deplace ici. { id: 'batt-float-bruit-ne-coupe-pas', label: "Relevé du 01/09 14:26 : SOC 94, batterie en float à -1 A, charge à 20 A → le bruit de régulation NE DOIT PAS arrêter la charge", time: AFTERNOON, 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 14:00:00', soc_percent: '94', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3600', pv_power_w: '6600', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '6000', vebus_ac_out_l1_power_w: '6000', battery_current_a: '-1', }, expect: { profile: 'outside_window', sourceMode: 'solar_only', state: 'charging_hold', decision: 'hold', desiredCharging: true, currentA: 20, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'batt-decharge-soutenue-coupe', label: "CONTREFACTUEL : mêmes mesures, mais le dépassement dure depuis 20 min > tolérance 12 min de la bande gt90 → la garde reprend la main et arrête", time: AFTERNOON, 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 14:00:00', soc_percent: '94', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3600', pv_power_w: '6600', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '6000', vebus_ac_out_l1_power_w: '6000', battery_current_a: '-1', battery_soft_breach_at: '2026-08-08 15:10:00', }, expect: { profile: 'outside_window', sourceMode: 'solar_only', state: 'stop_conditions_lost', decision: 'stop', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'batt-pointe-maison-ne-coupe-pas', label: "Relevé du 01/09 14:56 : pointe maison à 8 kW, batterie à -42 A → réduction graduée au plafond système (18 A), PAS un arrêt", time: AFTERNOON, 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 14:00:00', soc_percent: '94', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3600', pv_power_w: '9100', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '8000', vebus_ac_out_l1_power_w: '8000', battery_current_a: '-42', }, expect: { profile: 'outside_window', sourceMode: 'solar_only', state: 'charging_step_down_system_cap', decision: 'step_down_fast', desiredCharging: true, currentA: 18, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'batt-decharge-dure-arret-immediat', label: "SÉCURITÉ : batterie à -120 A, sous le seuil DUR de la bande (-100 A) → arrêt immédiat, aucune tolérance. La tolérance est du confort, jamais du risque", time: AFTERNOON, 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 14:00:00', soc_percent: '94', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3600', pv_power_w: '1000', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '9000', vebus_ac_out_l1_power_w: '9000', battery_current_a: '-120', }, expect: { profile: 'outside_window', sourceMode: 'solar_only', state: 'battery_current_hard_stop', decision: 'stop_hard', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'batt-plancher-demarrage-reste-instantane', label: "FRONTIÈRE : véhicule branché mais à l'arrêt, batterie à -1 A → on NE DÉMARRE PAS sur une batterie qui se décharge. Le plancher de démarrage reste instantané", time: AFTERNOON, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '94', pv_power_w: '6600', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '6000', vebus_ac_out_l1_power_w: '6000', battery_current_a: '-1', }, expect: { profile: 'outside_window', sourceMode: 'solar_only', state: 'idle_wait_conditions', decision: 'hold', desiredCharging: false, 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', }, }, // ======= FENÊTRE D'ÉTABLISSEMENT DU RÉSEAU (31/08/2026) ======= // Mesuré ce jour-là : à la fermeture du contacteur, le réseau reprend toute la // charge de la maison, le solaire bascule vers la batterie (ou se fait brider // s'il n'y a plus de tampon : 2663 -> 559 W), puis l'import redescend à la // consigne en ~150 s. Pendant ce temps la boucle de 30 s prend cinq décisions // sur des mesures qui ne décrivent aucun régime permanent. // Chaque cas est apparié à son contrefactuel hors fenêtre : sans cet appariement // le test ne prouverait rien. { id: 'etablissement-maintien-charge', label: 'Réseau fermé il y a 30 s, PV effondrée à 600 W, batterie à -15 A depuis 10 min : dans la fenêtre, la mesure batterie est neutralisée, la charge NE DOIT PAS être arrêtée', time: MORNING, 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 08:00:00', soc_percent: '60', evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16', evcs_ac_power_w: '3680', house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400', pv_power_w: '600', pv_power_10m_avg_w: '3900', pv_trend_w: '-2000', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800', battery_current_a: '-15', battery_soft_breach_at: '2026-08-08 08:50:00', acin1_last_changed_ts: String(MORNING_TS - 30), grid_switch_last_changed_ts: String(MORNING_TS - 30), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "charging_step_down_battery", decision: "step_down", desiredCharging: true, currentA: 14, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, { id: 'etablissement-termine-arret-normal', label: 'CONTREFACTUEL : mêmes mesures ET même dépassement de 10 min, transition vieille de 400 s → fenêtre fermée, la garde reprend la main et arrête (10 min > tolérance 4 min de la bande 40_60)', time: MORNING, 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 08:00:00', soc_percent: '60', evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16', evcs_ac_power_w: '3680', house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400', pv_power_w: '600', pv_power_10m_avg_w: '3900', pv_trend_w: '-2000', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800', battery_current_a: '-15', battery_soft_breach_at: '2026-08-08 08:50:00', acin1_last_changed_ts: String(MORNING_TS - 400), grid_switch_last_changed_ts: String(MORNING_TS - 400), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "stop_conditions_lost", decision: "stop", desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, { id: 'etablissement-securite-non-suspendue', label: "SÉCURITÉ : dans la fenêtre, batterie à -40 A → l'arrêt dur reste actif. La fenêtre suspend le confort, jamais le risque", time: MORNING, 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 08:00:00', soc_percent: '60', evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16', evcs_ac_power_w: '3680', house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400', pv_power_w: '600', pv_power_10m_avg_w: '3900', pv_trend_w: '-2000', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800', battery_current_a: '-40', acin1_last_changed_ts: String(MORNING_TS - 30), grid_switch_last_changed_ts: String(MORNING_TS - 30), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "battery_current_hard_stop", decision: "stop_hard", desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, { id: 'etablissement-ouverture-aucune-fenetre', label: "ASYMÉTRIE : réseau DISPARU il y a 30 s → aucune fenêtre. Les onduleurs reprennent tout, les gardes doivent agir plus vite, pas moins (dépassement de 10 min > tolérance 4 min)", time: MORNING, 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 08:00:00', soc_percent: '60', evcs_current_a: '16', evcs_set_current_a: '16', evcs_command_current_a: '16', evcs_ac_power_w: '3680', house_ac_power_w: '4400', vebus_ac_out_l1_power_w: '4400', pv_power_w: '600', pv_power_10m_avg_w: '3900', pv_trend_w: '-2000', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800', battery_current_a: '-15', grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0', battery_soft_breach_at: '2026-08-08 08:50:00', acin1_last_changed_ts: String(MORNING_TS - 30), grid_switch_last_changed_ts: String(MORNING_TS - 30), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "stop_conditions_lost", decision: "stop", desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, { id: 'etablissement-pas-de-demarrage', label: "Réseau fermé il y a 30 s, rien en charge : on ne démarre pas non plus sur des mesures d'établissement", 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', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800', acin1_last_changed_ts: String(MORNING_TS - 30), grid_switch_last_changed_ts: String(MORNING_TS - 30), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "idle_wait_conditions", decision: "hold", desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, { id: 'etablissement-demarrage-hors-fenetre', label: 'CONTREFACTUEL : mêmes mesures, transition vieille de 400 s → le démarrage solaire a bien lieu', 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', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '1800', acin1_last_changed_ts: String(MORNING_TS - 400), grid_switch_last_changed_ts: String(MORNING_TS - 400), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "start_min_current", decision: "start", desiredCharging: true, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, // ======= SÉQUENCE D'OUVERTURE DU CONTACTEUR (31/08/2026) ======= // Le plafond majoré (28 A en BOOST) n'est légitime que tant que le réseau est // là. C'est l'hystérésis qui ouvre, une minute après que la demande réseau de // l'EVCS a disparu : l'EVCS doit donc baisser AVANT, de lui-même. { id: 'ouverture-plafond-tombe-hors-demande-reseau', label: "Matin solaire, AC-IN encore présent mais l'EVCS ne demande plus le réseau : le plafond retombe à 20 A et la voiture est réduite IMMÉDIATEMENT", time: MORNING, 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 08:00:00', // BOOST EDF : plafond réseau 28 A, voiture effectivement à 28 A. grid_current_max_a: '28', current_max_auto_a: '22', evcs_current_a: '28', evcs_set_current_a: '28', evcs_command_current_a: '28', evcs_ac_power_w: '6440', house_ac_power_w: '8440', vebus_ac_out_l1_power_w: '8440', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '2000', soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200', battery_current_a: '10', // Cooldown de courant NON écoulé : la réduction ne doit pas l'attendre. last_current_change_at: '2026-08-08 08:59:50', acin1_last_changed_ts: String(MORNING_TS - 4000), grid_switch_last_changed_ts: String(MORNING_TS - 4000), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "charging_step_down_system_cap", decision: "step_down_fast", desiredCharging: true, currentA: 20, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, { id: 'ouverture-plafond-maintenu-en-demande-reseau', label: "CONTREFACTUEL : force_grid_now, l'EVCS demande le réseau → le plafond BOOST 28 A est conservé, aucune réduction", time: MORNING, 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 08:00:00', // BOOST EDF : plafond réseau 28 A, voiture effectivement à 28 A. grid_current_max_a: '28', current_max_auto_a: '22', evcs_current_a: '28', evcs_set_current_a: '28', evcs_command_current_a: '28', evcs_ac_power_w: '6440', house_ac_power_w: '8440', vebus_ac_out_l1_power_w: '8440', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '2000', victron_evcs_strategy: 'force_grid_now', soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200', battery_current_a: '10', last_current_change_at: '2026-08-08 08:59:50', acin1_last_changed_ts: String(MORNING_TS - 4000), grid_switch_last_changed_ts: String(MORNING_TS - 4000), }, expect: { profile: "morning_solar", sourceMode: "grid_forced", state: "charging_hold_grid", decision: "hold", desiredCharging: true, currentA: 28, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, { id: 'ouverture-reseau-disparu-reduction-immediate', label: 'Réseau DISPARU : plafond câblé 20 A, voiture encore à 28 A, cooldown non écoulé → réduction immédiate, sans attendre le compteur de confort', time: MORNING, 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 08:00:00', // BOOST EDF : plafond réseau 28 A, voiture effectivement à 28 A. grid_current_max_a: '28', current_max_auto_a: '22', evcs_current_a: '28', evcs_set_current_a: '28', evcs_command_current_a: '28', evcs_ac_power_w: '6440', house_ac_power_w: '8440', vebus_ac_out_l1_power_w: '8440', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '2000', grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0', soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200', battery_current_a: '10', last_current_change_at: '2026-08-08 08:59:50', acin1_last_changed_ts: String(MORNING_TS - 20), grid_switch_last_changed_ts: String(MORNING_TS - 20), }, expect: { profile: "morning_solar", sourceMode: "solar_only", state: "charging_step_down_system_cap", decision: "step_down_fast", desiredCharging: true, currentA: 20, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: "turn_off", }, }, // =================================================================== // CONSIGNE DE PUISSANCE RESEAU (01/09/2026) // Fermer le contacteur ne suffit pas : au-dessus du SOC minimal, l'ESS en // autoconsommation ne preleve que `ac_power_setpoint` et laisse la batterie // porter maison + voiture. Incident de la nuit du 01/09 : consigne a 50 W, // charge HC demarree puis arretee en 60 s par le garde batterie. // =================================================================== { id: 'consigne-session-hc-reseau-porte-la-charge', label: 'Nuit HC, reseau present, session reseau : la consigne monte au besoin mesure (maison hors EVCS + voiture au plafond)', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '50', allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', grid_setpoint_w: '50', }, expect: { sourceMode: 'grid_hc_allowed', gridSetpointW: 5525, }, }, { id: 'consigne-hors-session-retour-au-repos', label: 'CONTREFACTUEL — meme nuit sans session reseau : la consigne redescend a la valeur de repos', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '50', allow_grid_hc_night: 'off', grid_state: 'off', victron_acin1_available: '0', grid_setpoint_w: '4000', }, expect: { gridSetpointW: 700, }, }, { id: 'consigne-deja-genereuse-aucune-ecriture', label: 'Session reseau, consigne a 5600 W pour un besoin de 5295 W : deja suffisante, aucune reecriture (le bruit de la maison ne la fait pas courir)', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '50', allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', grid_setpoint_w: '5600', }, expect: { gridSetpointW: null, }, }, { id: 'consigne-insuffisante-corrigee-immediatement', label: "LE DEFAUT DE LA NUIT DU 01/09 — consigne 45 W sous le besoin : la batterie porterait la difference, correction sans attendre", time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '50', allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', grid_setpoint_w: '5250', }, expect: { sourceMode: 'grid_hc_allowed', gridSetpointW: 5525, }, }, { id: 'consigne-derive-externe-corrigee', label: 'Hors session mais consigne laissee a 4000 W par un tiers : corrigee au cycle suivant', time: MORNING, overrides: { soc_percent: '60', grid_state: 'off', victron_acin1_available: '0', grid_setpoint_w: '4000', }, expect: { gridSetpointW: 700, }, }, // ================= CREDIT SOLAIRE SUR LA CONSIGNE (11/09/2026) ================= // Releve reel : BOOST 28 A en plein soleil, consigne 8260 W, PV effondre a // 121 W, reseau 7697 W, batterie 0 A. La consigne evincait le solaire. { id: 'consigne-credit-solaire-descend', label: "Releve 11/09 17:02 — BOOST 28 A, 6 trackers brides, batterie neutre : la consigne DESCEND pour rendre la main au solaire", time: '2026-08-08T17:02:00+02:00', 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 16:30:00', victron_evcs_strategy: 'force_grid_now', grid_state: 'on', victron_acin1_available: '1', grid_current_max_a: '28', current_max_auto_a: '22', soc_percent: '96', battery_current_a: '0', pv_power_w: '121', pv_power_10m_avg_w: '150', pv_trend_w: '0', house_ac_power_w: '7695', vebus_ac_out_l1_power_w: '7695', evcs_ac_power_w: '5893', evcs_current_a: '28', grid_setpoint_w: '8260', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', }, expect: { sourceMode: 'grid_forced', gridSetpointW: 6799, }, }, { id: 'consigne-credit-trackers-a-fond-pas-de-descente', label: "CONTREFACTUEL — meme instant, mais les 6 trackers en mppt_active : plus rien a liberer, la consigne reste au plafond", time: '2026-08-08T17:02:00+02:00', 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 16:30:00', victron_evcs_strategy: 'force_grid_now', grid_state: 'on', victron_acin1_available: '1', grid_current_max_a: '28', current_max_auto_a: '22', soc_percent: '96', battery_current_a: '0', pv_power_w: '121', pv_power_10m_avg_w: '150', pv_trend_w: '0', house_ac_power_w: '7695', vebus_ac_out_l1_power_w: '7695', evcs_ac_power_w: '5893', evcs_current_a: '28', grid_setpoint_w: '8260', mppt_operation_modes: 'mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active', }, expect: { sourceMode: 'grid_forced', gridSetpointW: null, }, }, { id: 'consigne-credit-batterie-porte-remonte', label: "CONTREFACTUEL — trackers brides MAIS batterie en depassement souple (-60 A) : securite d'abord, la consigne remonte au plafond", time: '2026-08-08T17:02:00+02:00', 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 16:30:00', victron_evcs_strategy: 'force_grid_now', grid_state: 'on', victron_acin1_available: '1', grid_current_max_a: '28', current_max_auto_a: '22', soc_percent: '96', battery_current_a: '-60', pv_power_w: '121', pv_power_10m_avg_w: '150', pv_trend_w: '0', house_ac_power_w: '7695', vebus_ac_out_l1_power_w: '7695', evcs_ac_power_w: '5893', evcs_current_a: '28', grid_setpoint_w: '2000', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', }, expect: { sourceMode: 'grid_forced', gridSetpointW: 8465, }, }, { id: 'consigne-credit-nuit-trackers-eteints', label: "ANTI-OSCILLATEUR — nuit HC, session reseau, les 6 trackers `off` : rien a liberer, comportement de nuit STRICTEMENT inchange (§31, §57)", time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '50', allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', grid_setpoint_w: '5000', mppt_operation_modes: 'off,off,off,off,off,off', }, expect: { sourceMode: 'grid_hc_allowed', gridSetpointW: 5525, }, }, { id: 'consigne-credit-plancher-maison', label: "La descente s'arrete a la maison hors voiture : le reseau continue de la couvrir (garantie du 01/09 et du 04/09)", time: '2026-08-08T17:02:00+02:00', 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 16:30:00', victron_evcs_strategy: 'force_grid_now', grid_state: 'on', victron_acin1_available: '1', grid_current_max_a: '6', current_max_auto_a: '6', soc_percent: '96', battery_current_a: '0', pv_power_w: '4500', pv_power_10m_avg_w: '4500', pv_trend_w: '0', house_ac_power_w: '7695', vebus_ac_out_l1_power_w: '7695', evcs_ac_power_w: '5893', evcs_current_a: '28', grid_setpoint_w: '2000', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited,voltage_current_limited', }, expect: { sourceMode: 'grid_forced', gridSetpointW: 1802, }, }, { id: 'consigne-credit-converge-tient', label: "ANTI-OSCILLATEUR (11/09 17:47) — consigne deja descendue a 2241 W et les 6 trackers repasses mppt_active : on TIENT, on ne repousse pas au plafond", time: '2026-08-08T17:02:00+02:00', 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 16:30:00', victron_evcs_strategy: 'force_grid_now', grid_state: 'on', victron_acin1_available: '1', grid_current_max_a: '28', current_max_auto_a: '22', soc_percent: '92', battery_current_a: '36', pv_power_w: '7074', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '6895', vebus_ac_out_l1_power_w: '6895', evcs_ac_power_w: '6059', evcs_current_a: '28', grid_setpoint_w: '5500', mppt_operation_modes: 'mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active', }, expect: { sourceMode: 'grid_forced', gridSetpointW: null, }, }, { id: 'consigne-credit-plancher-nuage', label: "INCIDENT 11/09 18:00 — consigne descendue a 1281 W, voiture a 28 A : un nuage a fait chuter la PV de 4,6 kW et la batterie a pris 87 A (limite dure 70) -> hard stop. La consigne doit etre RELEVEE avant le nuage.", time: '2026-08-08T17:02:00+02:00', 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 16:30:00', victron_evcs_strategy: 'force_grid_now', grid_state: 'on', victron_acin1_available: '1', grid_current_max_a: '28', current_max_auto_a: '22', soc_percent: '96', battery_current_a: '-1', pv_power_w: '6058', pv_power_10m_avg_w: '5800', pv_trend_w: '0', house_ac_power_w: '7170', vebus_ac_out_l1_power_w: '7170', evcs_ac_power_w: '6133', evcs_current_a: '28', grid_setpoint_w: '1281', mppt_operation_modes: 'mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active', }, expect: { sourceMode: 'grid_forced', gridSetpointW: 4829, }, }, // ======== SESSION RESEAU DE NUIT SANS VEHICULE (12/09/2026) ======== // Nuit du 11-12/09, voiture rendue la veille : le bilan a reclame 92 % puis // 59 % de SOC toute la nuit, `reseau_necessaire` vrai a chaque calcul, et // rien ne s'est passe -- SOC fige a 20 %, consigne reseau a 700 W depuis // 18:36. La session exigeait un vehicule branche. { id: 'nuit-sans-vehicule-session-engage', label: "INCIDENT 12/09 04:00 — HC, AUCUN vehicule, SOC 20, le bilan demande 59,1 % : la session reseau doit s'engager pour la MAISON", time: '2026-08-08T04:00:00+02:00', overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', grid_state: 'on', victron_acin1_available: '1', soc_percent: '20', battery_current_a: '0', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', house_ac_power_w: '612', vebus_ac_out_l1_power_w: '612', evcs_ac_power_w: '0', grid_setpoint_w: '700', balance_required_soc: '59.1', balance_energy_to_cover_kwh: '3.13', balance_status: 'ok', balance_last_run: '08/08 03:58', // Le MANQUE, pose explicitement : sans lui le scenario heriterait du 0 de // la fixture, ce qui decrirait une batterie couverte alors qu'il declare // un SOC de 20 % pour un requis de 59,1 % — un etat impossible. // Valeur calculee avec la courbe mesuree, parc de reference (7 modules, // echelle 1) : 13 points sous le genou de 33 % a 0,097 = 1,261 kWh, puis // 26,1 points a 0,169 = 4,411 kWh, soit 5,672 kWh. Ce scenario est le seul // du corpus dont le deficit TRAVERSE le genou. balance_grid_energy_needed_kwh: '5.672', }, expect: { profile: 'night_hc', takeoverNeeded: true, takeoverHelperService: 'turn_on', sessionCommittedService: 'turn_on', // 18/09/2026 : 5437 -> 2380. Les 5437 valaient 612 de maison + 4825 // empruntes au plafond d'une borne ou aucun vehicule n'est branche. La // nouvelle valeur vaut 612 + le manque reel borne par DVCC (30 A, soit // 1538 W a la tension de la fixture), plus la marge d'ecriture d'un pas // de courant. ⚠️ Attendu calcule sur la FIXTURE et jamais sur la tension // du jour : le recalculer sur l'installation vivante rendrait la suite // rouge un matin ou la batterie est a 49 V sans que rien ne soit casse. gridSetpointW: 2380, }, }, { id: 'consigne-bilan-fin-de-fenetre-bornee-par-dvcc', label: "CONSIGNE 18/09 — meme manque a 07:10, 14 min de fenetre : c'est DVCC qui borne, pas le temps", time: '2026-08-08T07:10:00+02:00', overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', grid_state: 'on', victron_acin1_available: '1', soc_percent: '20', battery_current_a: '0', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', house_ac_power_w: '612', vebus_ac_out_l1_power_w: '612', evcs_ac_power_w: '0', grid_setpoint_w: '700', balance_required_soc: '59.1', balance_energy_to_cover_kwh: '3.13', balance_status: 'ok', balance_last_run: '08/08 07:08', balance_grid_energy_needed_kwh: '5.672', }, expect: { profile: 'night_hc', // ⚠️ LE SCENARIO QUI PROTEGE CONTRE LA REGRESSION LA PLUS GRAVE de cette // conception. 5,672 kWh a combler en 14 minutes font 17 kW : avec un // simple plancher d'horizon, la consigne saturait sur GRID_SETPOINT_MAX_W // (10 000 W), soit PIRE que les 5437 du plafond emprunte qu'on remplace. // Le plafond DVCC ramene le terme a 1538 W, et la consigne a la meme // valeur qu'a 04:00 — parce que ce qui borne n'est pas le temps qui // reste, c'est ce que la batterie a le droit de prendre. // Le plafond BMS n'aurait rien borne : 250 A a 49 V font 12,2 kW, // au-dessus des 10 000 de l'ecretage. gridSetpointW: 2380, }, }, { id: 'consigne-bilan-max-la-voiture-domine', label: 'CONTREFACTUEL 18/09 — meme nuit, meme manque, mais VEHICULE BRANCHE : le max retient la voiture, pas la somme', time: '2026-08-08T04:00:00+02:00', overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', grid_state: 'on', victron_acin1_available: '1', soc_percent: '20', battery_current_a: '0', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', house_ac_power_w: '612', vebus_ac_out_l1_power_w: '612', evcs_ac_power_w: '0', grid_setpoint_w: '700', balance_required_soc: '59.1', balance_energy_to_cover_kwh: '3.13', balance_status: 'ok', balance_last_run: '08/08 03:58', balance_grid_energy_needed_kwh: '5.672', }, expect: { profile: 'night_hc', // Terme voiture 4825 W contre terme batterie 1538 W : le `max` retient la // voiture, et la consigne vaut ce qu'elle vaut aujourd'hui. Avec une // ADDITION elle monterait de 1538 W de plus — une autorisation en plus sur // le chemin DOMINANT (85 nuits sur 88 ont un vehicule branche) pour // corriger un chemin emprunte zero fois. C'est ce scenario qui epingle le // choix du max, et il doit rester a la valeur historique. gridSetpointW: 5437, }, }, { // ======== LA FENETRE DE MIDI EST UNE FENETRE D'HEURES CREUSES (18/09) ======== // L'engagement de session etait gate sur `night_hc` seul : la charge HC // manuelle demandee par le proprietaire ne pouvait rien faire a midi. Le // plancher relevait bien la cible du bilan, mais aucune session ne // s'engageait — donc pas de contacteur, pas de consigne, et un plancher du // soir releve pour rien. id: 'hc-midi-bilan-reclame-engage-la-session', label: 'MIDI 18/09 — journee sombre, le bilan reclame 85 % pour un SOC de 40 : la session HC de midi doit s engager', time: MIDDAY, overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', soc_percent: '40', battery_current_a: '-8', pv_power_w: '200', pv_power_10m_avg_w: '200', pv_trend_w: '0', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', balance_required_soc: '85', balance_energy_to_cover_kwh: '7.6', balance_grid_energy_needed_kwh: '7.605', balance_status: 'ok', balance_last_run: '08/08 12:50', }, expect: { profile: 'midday_hc_or_solar', sessionCommittedService: 'turn_on', takeoverNeeded: true, takeoverHelperService: 'turn_on', }, }, { id: 'hc-midi-soleil-aucun-engagement', label: 'CONTREFACTUEL — meme midi en plein soleil : le bilan ne reclame rien (22 %), aucune session malgre un SOC sous le palier du matin', time: MIDDAY, overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', soc_percent: '30', battery_current_a: '60', pv_power_w: '7000', pv_power_10m_avg_w: '6800', pv_trend_w: '0', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', // Le croisement PV est passe : le bilan retombe au plancher dur. balance_required_soc: '22', balance_energy_to_cover_kwh: '0', balance_grid_energy_needed_kwh: '0', balance_status: 'ok', balance_last_run: '08/08 12:50', }, expect: { profile: 'midday_hc_or_solar', // ⚠️ LE SCENARIO QUI JUSTIFIE LE CHOIX DU CRITERE. A midi on compare le SOC // au SOC REQUIS du bilan (22), et non a `batteryMorningTargetPct` qui // contient le palier de prevision — celui-ci vaudrait 35 par beau temps, // donc ce SOC de 30 % engagerait une session reseau EN PLEIN SOLEIL, // c'est-a-dire la substitution qu'on a mis des semaines a eteindre. takeoverNeeded: false, }, }, // ======== JEEP BRANCHEE A MIDI, DEPART 15:30 (28/09/2026) ======== // « JEEP branchee engage le reseau en HC midi si l'analyse de production le // justifie » : le BILAN fait l'analyse et publie un verdict, le controleur le // relaie sous les memes gardes (statut, fraicheur) que la cible. Ici la // batterie n'a rien a reclamer (SOC 45 > 30) : seul le verdict voiture engage. { id: 'hc-midi-jeep-verdict-bilan-engage-la-session', label: 'MIDI 28/09 — JEEP branchee, le bilan juge le solaire insuffisant avant 15:30 : la session HC de midi s engage', time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '45', battery_current_a: '10', pv_power_w: '3000', pv_power_10m_avg_w: '3000', pv_trend_w: '0', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', balance_required_soc: '30', balance_energy_to_cover_kwh: '0', balance_grid_energy_needed_kwh: '0', balance_status: 'ok', balance_last_run: '08/08 12:50', balance_vehicle_grid_needed: 'on', }, expect: { profile: 'midday_hc_or_solar', sessionCommittedService: 'turn_on', takeoverNeeded: true }, }, { id: 'hc-midi-jeep-sans-verdict-aucun-engagement', label: 'CONTREFACTUEL — meme midi, le bilan juge le solaire suffisant : aucune session', time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '45', battery_current_a: '10', pv_power_w: '3000', pv_power_10m_avg_w: '3000', pv_trend_w: '0', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', balance_required_soc: '30', balance_energy_to_cover_kwh: '0', balance_grid_energy_needed_kwh: '0', balance_status: 'ok', balance_last_run: '08/08 12:50', balance_vehicle_grid_needed: 'off', }, expect: { profile: 'midday_hc_or_solar', takeoverNeeded: false }, }, { id: 'hc-midi-jeep-verdict-voiture-debranchee', label: 'CONTREFACTUEL — verdict encore a ON mais voiture debranchee : aucune session', time: MIDDAY, overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', soc_percent: '45', battery_current_a: '10', pv_power_w: '3000', pv_power_10m_avg_w: '3000', pv_trend_w: '0', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', balance_required_soc: '30', balance_energy_to_cover_kwh: '0', balance_grid_energy_needed_kwh: '0', balance_status: 'ok', balance_last_run: '08/08 12:50', balance_vehicle_grid_needed: 'on', }, expect: { profile: 'midday_hc_or_solar', takeoverNeeded: false }, }, { id: 'hc-midi-jeep-verdict-bilan-perime', label: 'CONTREFACTUEL — verdict a ON mais bilan vieux de 2 h : on ne relaie pas un verdict perime', time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', soc_percent: '45', battery_current_a: '10', pv_power_w: '3000', pv_power_10m_avg_w: '3000', pv_trend_w: '0', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', balance_required_soc: '30', balance_energy_to_cover_kwh: '0', balance_grid_energy_needed_kwh: '0', balance_status: 'ok', balance_last_run: '08/08 11:00', balance_vehicle_grid_needed: 'on', }, expect: { profile: 'midday_hc_or_solar', takeoverNeeded: false }, }, { id: 'hc-midi-session-engagee-voiture-sur-reseau', label: 'MIDI — session engagee, contacteur ferme, plein soleil : la voiture passe sur le reseau (batterie et voiture ensemble)', time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', grid_state: 'on', victron_acin1_available: '1', night_grid_session_committed_prev: 'on', evcs_device_connected: 'on', soc_percent: '45', battery_current_a: '10', pv_power_w: '6000', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', balance_required_soc: '30', balance_energy_to_cover_kwh: '0', balance_grid_energy_needed_kwh: '0', balance_status: 'ok', balance_last_run: '08/08 12:50', balance_vehicle_grid_needed: 'on', }, expect: { profile: 'midday_hc_or_solar', sourceMode: 'grid_hc_allowed', takeoverNeeded: true }, }, { id: 'consigne-bilan-plancher-nuage-exclut-la-batterie', label: 'CONSIGNE 18/09 — midi, soleil bride, manque batterie, PAS de voiture : le terme batterie ne doit PAS remonter le plancher nuage', time: MIDDAY, overrides: { // ⚠️ LA FENETRE CREUSE N'EST PAS FORCEMENT LA NUIT. Celle de midi // (12:24-14:24) admet simultanement ce qu'on croyait incompatible : du // soleil fort, des chargeurs brides, et un manque batterie. C'est la // seule configuration qui distingue un plancher nuage correct d'un // plancher pollue par le terme batterie — et le discriminant n'est pas // l'heure, c'est un DVCC HAUT. A 30 A le terme batterie plafonnerait sous // le budget de surtension et les deux variantes rendraient la meme chose. // // Forme reelle : c'est l'episode du 17/09 — force_grid pose a midi, // soleil, aucune voiture — avec en plus un manque batterie et l'ACM en // boost de journee (180 A, son profil de 07:30). victron_evcs_strategy: 'force_grid_now', evcs_vehicle_connected: 'off', evcs_device_connected: 'on', grid_state: 'on', victron_acin1_available: '1', soc_percent: '35', battery_current_a: '20', pv_power_w: '7000', pv_power_10m_avg_w: '6500', pv_trend_w: '0', mppt_operation_modes: 'voltage_current_limited,voltage_current_limited,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', evcs_ac_power_w: '0', grid_setpoint_w: '8000', dvcc_max_charge_current_a: '180', // 35 % -> 90 % entierement au-dessus du genou : 55 points x 0,169 = 9,295 kWh. balance_required_soc: '90', balance_grid_energy_needed_kwh: '9.295', balance_energy_to_cover_kwh: '9.295', balance_status: 'ok', balance_last_run: '08/08 12:30', }, expect: { // Des chargeurs brides prouvent qu'il reste de la ressource : la consigne // DESCEND. Le plancher qui borne cette descente vaut maison + voiture, // soit 900 W ici. Si le terme batterie y entrait, le plancher remonterait // de plusieurs kW et BLOQUERAIT la descente — la consigne resterait haute // et evincerait le solaire, c'est-a-dire le defaut du 11/09 reintroduit // par la porte de derriere. Ce scenario ne teste donc pas une bizarrerie // de forme, il teste le retour d'un incident connu. gridSetpointW: 4219, }, }, { id: 'nuit-sans-vehicule-batterie-couverte', label: "CONTREFACTUEL — meme nuit sans vehicule mais SOC 70 au-dessus des 59,1 % requis : aucune session, la batterie passe la nuit seule", time: '2026-08-08T04:00:00+02:00', overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', grid_state: 'on', victron_acin1_available: '1', soc_percent: '70', battery_current_a: '-12', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', house_ac_power_w: '612', vebus_ac_out_l1_power_w: '612', evcs_ac_power_w: '0', grid_setpoint_w: '700', balance_required_soc: '59.1', balance_energy_to_cover_kwh: '3.13', balance_status: 'ok', balance_last_run: '08/08 03:58', }, expect: { profile: 'night_hc', takeoverNeeded: false, sessionCommittedService: null, }, }, { id: 'nuit-sans-vehicule-hors-fenetre', label: "CONTREFACTUEL — meme deficit mais a 23:00, HORS fenetre HC : aucune session. Le relachement reste la fin de fenetre, donc monotone.", time: '2026-08-08T23:00:00+02:00', overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', grid_state: 'on', victron_acin1_available: '1', soc_percent: '20', battery_current_a: '-12', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', house_ac_power_w: '612', vebus_ac_out_l1_power_w: '612', evcs_ac_power_w: '0', grid_setpoint_w: '700', balance_required_soc: '92.2', balance_energy_to_cover_kwh: '6.33', balance_status: 'ok', balance_last_run: '08/08 22:58', }, expect: { takeoverNeeded: false, sessionCommittedService: null, }, }, { id: 'nuit-sans-vehicule-bilan-perime', label: "CONTREFACTUEL — HC sans vehicule, SOC 20, mais bilan vieux de 2 h : comportement historique, aucune session", time: '2026-08-08T04:00:00+02:00', overrides: { evcs_vehicle_connected: 'off', evcs_device_connected: 'on', grid_state: 'on', victron_acin1_available: '1', soc_percent: '20', battery_current_a: '0', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', house_ac_power_w: '612', vebus_ac_out_l1_power_w: '612', evcs_ac_power_w: '0', grid_setpoint_w: '700', balance_required_soc: '59.1', balance_energy_to_cover_kwh: '3.13', balance_status: 'ok', balance_last_run: '08/08 02:00', }, expect: { takeoverNeeded: false, sessionCommittedService: null, }, }, // ======== MISE A L'ECHELLE DU PARC (12/09/2026) ======== // Les bandes de politique batterie sont en amperes absolus, decrivant le parc // de 4 modules. Ajouter 4 US5000 (soit 12 equivalents US2000C) doit les // mettre a l'echelle, sinon le nouveau parc reste bride au niveau de l'ancien. { id: 'parc-double-decharge-toleree', label: "Parc porte a 12 equivalents (4 US2000C + 4 US5000), BMS declarant 300 A : les -120 A qui arretaient tout a 4 modules deviennent tolerables", time: AFTERNOON, 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 14:00:00', soc_percent: '94', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3600', pv_power_w: '1000', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '9000', vebus_ac_out_l1_power_w: '9000', battery_current_a: '-120', modules_en_service: '12', bms_max_discharge_current_a: '300', bms_max_charge_current_a: '300', }, expect: { profile: 'outside_window', state: 'charging_step_down_system_cap', }, }, { id: 'parc-double-plafond-bms', label: "CONTREFACTUEL — parc declare a 12 mais le BMS n'autorise que 100 A (cellule faible, cablage) : le plafond BMS prime, -120 A reste un arret dur", time: AFTERNOON, 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 14:00:00', soc_percent: '94', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3600', pv_power_w: '1000', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '9000', vebus_ac_out_l1_power_w: '9000', battery_current_a: '-120', modules_en_service: '12', bms_max_discharge_current_a: '100', bms_max_charge_current_a: '100', }, expect: { profile: 'outside_window', state: 'battery_current_hard_stop', decision: 'stop_hard', }, }, { id: 'parc-non-declare-comportement-inchange', label: "CONTREFACTUEL — aucun parc declare, aucune limite BMS lisible : echelle 1, plafond inactif, comportement d'avant ce chantier", time: AFTERNOON, 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 14:00:00', soc_percent: '94', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3600', pv_power_w: '1000', pv_power_10m_avg_w: '6000', pv_trend_w: '0', house_ac_power_w: '9000', vebus_ac_out_l1_power_w: '9000', battery_current_a: '-120', }, expect: { profile: 'outside_window', state: 'battery_current_hard_stop', decision: 'stop_hard', }, }, // ===================================================== // P12 — CHARGE NUIT SUR LA BATTERIE (15/09/2026) // ===================================================== // Idee du proprietaire : plutot qu'acheter du reseau la nuit pour charger la // voiture, lui donner le solaire DEJA STOCKE, et laisser ainsi de la place // dans la batterie pour la production du lendemain. Releve de sa nuit du 14 // au 15/09 : 8 kWh achetes au reseau, batterie encore a 69 % a 5 h du matin, // et une prevision de 38,7 kWh le lendemain dont 10,5 auraient ete ecretes. // // Le mode `battery_assist_night` existait pour exactement ca et etait MORT // depuis toujours : il exigeait `batteryCurrentA >= 0`, impossible la nuit // puisque la maison tire sur la batterie. P12 remplace ce test de COURANT par // un test d'ENERGIE — rester au-dessus de `required_soc_prudent`, que le // bilan publie deja. Aucune constante nouvelle. { id: 'p12-charge-nuit-sur-batterie', label: 'Nuit HC, P12 ON, SOC 69 bien au-dessus du plancher 38,7 -> la voiture puise dans la batterie', time: '2026-09-15T02:30:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '69', allow_grid_hc_night: 'on', night_battery_charge_enabled: 'on', night_battery_done_prev: 'off', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-16', evcs_charging_switch: 'off', balance_status: 'ok', balance_last_run: '15/09 02:25', balance_required_soc: '36.8', balance_required_soc_prudent: '38.7', balance_energy_to_cover_kwh: '3.18', }, expect: { profile: 'night_hc', sourceMode: 'battery_assist_night', // ⚠️ Verifier que la voiture CHARGE REELLEMENT, pas seulement que le mode // est choisi. Sans ces trois lignes, neutraliser la condition de demarrage // dans BATTERY_CLOUD_LOCKS ne cassait aucun scenario — constate par // mutation le 15/09. state: 'start_battery_assist_night_min_current', decision: 'start', desiredCharging: true, currentA: 6, takeoverNeeded: false, edfOnAction: false, }, }, // CONTREFACTUEL — surplus epuise : la bascule vers le reseau se fait TOUTE // SEULE. C'est la soupape decrite par le proprietaire, sans regle horaire. { id: 'p12-surplus-epuise-le-reseau-prend-le-relais', label: 'Nuit HC, P12 ON mais SOC 37 sous le plancher 38,7 -> P7 reprend, EDF se ferme', time: '2026-09-15T02:30:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '37', allow_grid_hc_night: 'on', night_battery_charge_enabled: 'on', night_battery_done_prev: 'off', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-16', evcs_charging_switch: 'off', balance_status: 'ok', balance_last_run: '15/09 02:25', balance_required_soc: '36.8', balance_required_soc_prudent: '38.7', balance_energy_to_cover_kwh: '3.18', }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', takeoverNeeded: true, edfOnAction: true, }, }, // ⚠️ LE SCENARIO ANTI-OSCILLATEUR, le plus important des quatre. // Voiture arretee, le plancher descend PLUS VITE que le SOC (-3,7 pt/h // contre -2,6) : le surplus reapparaitrait tout seul. Ici le SOC est // repasse AU-DESSUS du plancher, mais le verrou est pose — pas de reprise. { id: 'p12-verrou-pas-de-reprise-du-surplus', label: 'Nuit HC, P12 ON, SOC 45 repasse au-dessus du plancher MAIS verrou pose -> pas de reprise', time: '2026-09-15T04:00:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '45', allow_grid_hc_night: 'on', night_battery_charge_enabled: 'on', night_battery_done_prev: 'on', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-16', evcs_charging_switch: 'off', balance_status: 'ok', balance_last_run: '15/09 03:55', balance_required_soc: '36.8', balance_required_soc_prudent: '38.7', balance_energy_to_cover_kwh: '3.18', }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', takeoverNeeded: true, }, }, // CONTREFACTUEL du toggle : a off, exactement le comportement d'avant. { id: 'p12-toggle-off-comportement-historique', label: 'Nuit HC, memes conditions mais toggle P12 OFF -> P7 comme avant', time: '2026-09-15T02:30:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '69', allow_grid_hc_night: 'on', night_battery_charge_enabled: 'off', night_battery_done_prev: 'off', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-16', evcs_charging_switch: 'off', balance_status: 'ok', balance_last_run: '15/09 02:25', balance_required_soc: '36.8', balance_required_soc_prudent: '38.7', balance_energy_to_cover_kwh: '3.18', }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', takeoverNeeded: true, edfOnAction: true, }, }, // ⚠️ GARDE DE FRAICHEUR. Le plancher vient du bilan ; s'il est perime, il ne // decrit plus la nuit en cours et vider la batterie dessus serait aveugle. // Ajoute apres qu'une mutation eut montre que rien ne testait cette garde. { id: 'p12-bilan-perime-pas-de-charge-batterie', label: 'Nuit HC, P12 ON, SOC 69 mais bilan vieux de 3 h -> P7 comme avant, pas de puisage batterie', time: '2026-09-15T02:30:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '69', allow_grid_hc_night: 'on', night_battery_charge_enabled: 'on', night_battery_done_prev: 'off', grid_state: 'off', victron_acin1_available: '0', battery_current_a: '-16', evcs_charging_switch: 'off', balance_status: 'ok', balance_last_run: '14/09 23:25', balance_required_soc: '36.8', balance_required_soc_prudent: '38.7', balance_energy_to_cover_kwh: '3.18', }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', takeoverNeeded: true, edfOnAction: true, }, }, // ⚠️ P12 DOIT GAGNER LE PREMIER CYCLE DE LA NUIT, OU IL NE GAGNE PAS. // // Decouvert en essai reel le 15/09 a 05:30. P7 s'etait engage a 05:13, avant // que le proprietaire n'active P12 : le helper `night_grid_takeover_needed` // etait donc deja ON, et le verrou anti-creneaux de juillet (P2) tenait le // reseau — `nightGridHcPreferred` passe bien a false grace a P12, mais // `nightTakeoverLatched` maintient `nightGridTakeoverNeeded` de son cote. // // Et c'est le comportement SUR : basculer reseau -> batterie en pleine // session, c'est exactement le creneau que ce verrou existe pour empecher. // Aucun des cinq scenarios P12 ne le montrait — ils partent tous d'une nuit // vierge. Celui-ci epingle la contrainte pour qu'un changement futur soit // delibere. { id: 'p12-verrou-p2-tient-si-le-reseau-est-deja-engage', label: 'Nuit HC, P7 deja engage (takeover ON) puis P12 active -> le verrou anti-creneaux tient, pas de bascule', time: '2026-09-15T05:30:00+02:00', overrides: { ...VEHICLE_NIGHT, soc_percent: '69', allow_grid_hc_night: 'on', night_battery_charge_enabled: 'on', night_battery_done_prev: 'off', // LE point du scenario : la reprise reseau est deja engagee. night_takeover_prev: 'on', grid_state: 'on', victron_acin1_available: '1', battery_current_a: '1', evcs_charging_switch: 'on', balance_status: 'ok', balance_last_run: '15/09 05:25', balance_required_soc: '36.8', balance_required_soc_prudent: '36.2', balance_energy_to_cover_kwh: '3.18', }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', takeoverNeeded: true, }, }, // ===================================================== // BUDGET VOITURE DE LA NUIT (30/09/2026, decisions du proprietaire) // ===================================================== // « Definir combien la voiture peut prendre sans utiliser EDF, et brider la // charge a cette valeur ou autoriser une charge complete avec EDF. » Par // defaut « sans EDF » ; « complete » = budget batterie puis EDF. Budget fige // a l'ouverture avec l'index du compteur de la borne ; arret au compteur. // Releve de reference : 30/09 01:25, SOC 57, requis 39,2, prudent 44,7. { id: 'budget-fige-a-l-ouverture', label: 'Nuit HC, sans EDF, budget 3,7 kWh publie : fige avec l index 4000, la voiture part sur la batterie', time: '2026-09-30T01:25:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 01:20', night_charge_mode: 'sans_edf', night_budget_prev: '', evcs_total_energy_kwh: '4000' }, expect: { profile: 'night_hc', sourceMode: 'battery_assist_night', state: 'start_battery_assist_night_min_current', desiredCharging: true, nightBudgetWrite: '3.7|4000', takeoverNeeded: false, edfOnAction: false }, }, { id: 'budget-pas-atteint-la-charge-continue', label: 'Budget 3,7, 2 kWh delivres, SOC 52 au-dessus du prudent : la voiture continue sur la batterie', time: '2026-09-30T02:10:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 02:05', ...EN_CHARGE_BATTERIE, soc_percent: '52', night_charge_mode: 'sans_edf', night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4002' }, expect: { sourceMode: 'battery_assist_night', desiredCharging: true, nightBudgetWrite: null, takeoverNeeded: false, edfOnAction: false }, }, { id: 'budget-atteint-sans-edf-la-voiture-attend', label: 'Budget 3,7 atteint (3,75 delivres), SOC 50 encore au-dessus du prudent, sans EDF : arret, pas de reseau', time: '2026-09-30T02:30:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 02:25', ...EN_CHARGE_BATTERIE, soc_percent: '50', night_charge_mode: 'sans_edf', night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4003.75' }, expect: { sourceMode: 'blocked', desiredCharging: false, takeoverNeeded: false, edfOnAction: false }, }, { id: 'budget-atteint-complete-edf-finit', label: 'CONTREFACTUEL — meme nuit en charge complete : EDF prend le relais en HC', time: '2026-09-30T02:30:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 02:25', ...EN_CHARGE_BATTERIE, soc_percent: '50', night_charge_mode: 'complete', night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4003.75' }, expect: { sourceMode: 'grid_hc_allowed', takeoverNeeded: true, edfOnAction: true }, }, { id: 'budget-sans-edf-plancher-dur-edf-protege', label: 'Sans EDF mais SOC 20 sous le plancher dur : EDF se ferme quand meme pour la batterie', time: '2026-09-30T04:00:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 03:55', soc_percent: '20', night_charge_mode: 'sans_edf', night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4003.75' }, // 04/10 (option A du proprietaire) : EDF se ferme pour la BATTERIE, la // voiture reste bloquee « sans EDF » (avant : grid_forced, voiture sur EDF). expect: { sourceMode: 'blocked', desiredCharging: false, edfOnAction: true, takeoverNeeded: true }, }, { id: 'budget-sans-edf-reprise-reseau-voiture-bloquee', label: 'Nuit du 03-04/10 rejouee : budget 1,41 atteint, SOC 39 sous la cible 39,5 a 02:57 : EDF ferme pour la batterie, la JEEP ne prend PAS le reseau', time: '2026-10-04T02:57:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '04/10 02:55', soc_percent: '39', balance_required_soc: '39.5', balance_required_soc_prudent: '40.4', night_charge_mode: 'sans_edf', night_budget_prev: '1.41|4038.8', evcs_total_energy_kwh: '4040.2' }, expect: { sourceMode: 'blocked', desiredCharging: false, edfOnAction: true, takeoverNeeded: true }, }, { id: 'budget-bilan-perime-sans-edf-rien', label: 'Sans EDF, bilan vieux de 3 h : aucun budget fige, la voiture attend, pas de reseau', time: '2026-09-30T01:25:00+02:00', overrides: { ...BUDGET_NUIT, night_charge_mode: 'sans_edf', balance_last_run: '29/09 22:20', night_budget_prev: '', evcs_total_energy_kwh: '4000' }, expect: { sourceMode: 'blocked', desiredCharging: false, nightBudgetWrite: null, takeoverNeeded: false, edfOnAction: false }, }, { id: 'p12-verrou-pose-quand-le-budget-est-atteint', label: 'Budget atteint en session : le verrou P12 est ecrit (une seule passe sur batterie)', time: '2026-09-30T02:30:00+02:00', overrides: { ...BUDGET_NUIT, ...EN_CHARGE_BATTERIE, balance_last_run: '30/09 02:25', soc_percent: '50', night_charge_mode: 'sans_edf', night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4003.75' }, expect: { nightBatteryDoneService: 'turn_on' }, }, { id: 'p12-verrou-leve-en-fin-de-session', label: '08:00, verrou encore pose : il est leve (sinon la charge sur batterie mourait pour toutes les nuits suivantes)', time: '2026-09-30T08:00:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 07:55', night_battery_done_prev: 'on' }, expect: { nightBatteryDoneService: 'turn_off' }, }, // Jours de depart du matin (01/10/2026) : releve reel du jeudi 01/10 06:00, // JEEP pleine a 95 %, SOC 37 pour une cible de 35, depart JEEP 06:30. La marge // de 30 min declenchait la « recharge urgente avant depart » (0 kWh manquant) // et fermait EDF. Le jeudi, la JEEP ne part pas. { id: 'depart-matin-jour-de-depart-rien-ne-manque', label: 'Jour de depart, SOC 37 au-dessus de la cible du bilan (28,6) : il ne manque rien, aucune urgence a 06:00 (EDF ferme pour rien les 21, 22, 25, 27/09 et 01/10)', time: '2026-10-01T06:00:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '01/10 05:55', soc_percent: '37', balance_required_soc: '28.6', balance_required_soc_prudent: '29.7', evcs_vehicle: 'JEEP', departure_time_jeep: '06:30:00', departure_day_ok: 'on', night_charge_mode: 'sans_edf', night_budget_prev: '5.93|4010', evcs_total_energy_kwh: '4015' }, expect: { edfOnAction: false, takeoverNeeded: false }, }, { id: 'depart-matin-jour-de-depart-batterie-sous-la-cible', label: 'Jour de depart, SOC 25 sous la cible du bilan (28,6) : il manque 1,1 kWh, l urgence engage le reseau avant 06:30', time: '2026-10-01T06:00:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '01/10 05:55', soc_percent: '25', balance_required_soc: '28.6', balance_required_soc_prudent: '29.7', evcs_vehicle: 'JEEP', departure_time_jeep: '06:30:00', departure_day_ok: 'on', night_charge_mode: 'sans_edf', night_budget_prev: '5.93|4010', evcs_total_energy_kwh: '4015' }, // « sans EDF » : l'urgence ferme EDF pour la batterie, la voiture reste bloquee (04/10). expect: { sourceMode: 'blocked', desiredCharging: false, edfOnAction: true }, }, { id: 'depart-matin-pas-un-jour-de-depart', label: 'Jeudi : la JEEP ne part pas, aucune urgence de depart, EDF reste ouvert', time: '2026-10-01T06:00:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '01/10 05:55', soc_percent: '37', balance_required_soc: '28.6', balance_required_soc_prudent: '29.7', evcs_vehicle: 'JEEP', departure_time_jeep: '06:30:00', departure_day_ok: 'off', night_charge_mode: 'sans_edf', night_budget_prev: '5.93|4010', evcs_total_energy_kwh: '4015' }, expect: { edfOnAction: false, takeoverNeeded: false }, }, { id: 'budget-efface-en-fin-de-session', label: 'Fin de fenetre HC (08:00) : le budget fige est efface', time: '2026-09-30T08:00:00+02:00', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 07:55', night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4003.75' }, expect: { nightBudgetWrite: '' }, }, // =================================================================== // BOOST 28 A du 18/09/2026 — le verrou survivait a sa cause (HANDOFF) // Releve reel : clic BOOST 13:08:50, contacteur ferme 13:09:53, garde // batterie a -32,4 A a 13:09:30 (23 s AVANT l'arrivee d'EDF), verrou // 20 min reseau present. Trois volets, un scenario chacun + contrefactuel. // =================================================================== { id: 'boost-attente-reseau-courant-mini', label: "Session active, force_grid demande, reseau PAS ENCORE la → on retombe au courant MINI (le 13 A tenu sur la batterie est le defaut du 18/09)", time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on', // Charge solaire en cours a 13 A, min_run PAS ecoule (start recent). last_start_at: '2026-08-08 12:58:30', evcs_current_a: '13', evcs_set_current_a: '13', evcs_command_current_a: '13', evcs_ac_power_w: '2990', victron_evcs_strategy: 'force_grid_now', // Le contacteur n'est PAS encore ferme : c'est la fenetre de l'incident. grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0', soc_percent: '55', pv_power_w: '1200', pv_power_10m_avg_w: '1400', house_ac_power_w: '3900', vebus_ac_out_l1_power_w: '3900', // Batterie sollicitee mais SOUS le garde dur : la charge continue. battery_current_a: '-20', }, expect: { sourceMode: 'grid_forced', state: 'hold_min_run_wait_grid_takeover', decision: 'hold', desiredCharging: true, currentA: 6, }, }, { id: 'boost-garde-sans-reseau-emet-no-grid', label: "Le garde batterie mord RESEAU ABSENT → blockReason 'grid_battery_guard_no_grid' (et pas le verrou plein)", time: MIDDAY, 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 12:58:30', // 07/10 : deja AU COURANT MINI (6 A) — au-dessus, la voiture est d'abord // bridee a 6 A (scenarios `garde-reseau-bride-a-6a-*`), l'arret ne vient // qu'ensuite. evcs_current_a: '6', evcs_set_current_a: '6', evcs_command_current_a: '6', evcs_ac_power_w: '1380', victron_evcs_strategy: 'force_grid_now', grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0', // Le releve reel du 13:09:30 : -32,4 A, sous le garde dur de sa bande. soc_percent: '55', pv_power_w: '300', pv_power_10m_avg_w: '400', house_ac_power_w: '3900', vebus_ac_out_l1_power_w: '3900', // ⚠️ Fenetre etroite VOULUE : le garde reseau (grid_battery_hard_a -20) // doit mordre SANS que le hard general de la bande 40-60 (~-20,9 dans ce // fixture) ne prenne la main — c'est exactement la hierarchie de // production du 18/09 (-32,4 A avec un parc 13/4 ou le hard general est // a -98 A). Le fixture n'etant pas a l'echelle du parc reel, on se place // entre les deux seuils DU FIXTURE. battery_current_a: '-20.5', }, expect: { state: 'stop_grid_battery_guard', decision: 'stop', desiredCharging: false, blockReason: 'grid_battery_guard_no_grid', }, }, { id: 'garde-reseau-bride-a-6a-reseau-absent', label: "07/10 : meme garde depasse a 13 A, reseau absent → voiture bridee a 6 A, ni arret ni verrou", time: MIDDAY, 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 12:58:30', evcs_current_a: '13', evcs_set_current_a: '13', evcs_command_current_a: '13', evcs_ac_power_w: '2990', victron_evcs_strategy: 'force_grid_now', grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0', soc_percent: '55', pv_power_w: '300', pv_power_10m_avg_w: '400', house_ac_power_w: '3900', vebus_ac_out_l1_power_w: '3900', battery_current_a: '-20.5', }, expect: { state: 'charging_cut_to_min_grid_guard', decision: 'step_down', desiredCharging: true, currentA: 6, }, }, { id: 'garde-reseau-bride-a-6a-pointe-maison-0610', label: "06/10 13:46 rejoue : force_grid_now a 20 A, reseau present, pointe de cuisine → voiture bridee a 6 A au lieu d'un arret + 20 min de verrou", time: MIDDAY, 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 12:30:00', evcs_current_a: '20', evcs_set_current_a: '20', evcs_command_current_a: '20', evcs_ac_power_w: '3470', victron_evcs_strategy: 'force_grid_now', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '3970', acin1_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 1800), grid_switch_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 1800), soc_percent: '55', pv_power_w: '1240', pv_power_10m_avg_w: '1500', house_ac_power_w: '7100', vebus_ac_out_l1_power_w: '7100', // Meme fenetre etroite que ci-dessus (garde reseau -20, hard general ~-20,9 // dans ce fixture) : en production -41,9 A pour un hard general a -98 A. battery_current_a: '-20.5', }, expect: { state: 'charging_cut_to_min_grid_guard', decision: 'step_down', desiredCharging: true, currentA: 6, }, }, { id: 'boost-verrou-no-grid-leve-a-l-arrivee-du-reseau', label: "Verrou '_no_grid' pose, le reseau ARRIVE → le verrou se leve, la charge repart (le 18/09 elle restait bloquee 20 min)", time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', evcs_charging_switch: 'off', control_latch: 'off', charge_permission_now: 'off', victron_evcs_strategy: 'force_grid_now', // Le verrou du releve reel : pose il y a 30 s, 20 min devant lui. last_block_reason: 'grid_battery_guard_no_grid', blocked_until: '2026-08-08 13:19:30', last_stop_at: '2026-08-08 12:59:30', prev_surplus_state: 'stop_grid_battery_guard', // Le contacteur s'est ferme 23 s apres le stop : reseau PRESENT. grid_state: 'on', victron_acin1_available: '1', grid_power_w: '900', soc_percent: '55', pv_power_w: '1200', pv_power_10m_avg_w: '1400', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', battery_current_a: '2', acin1_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400), grid_switch_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400), }, expect: { sourceMode: 'grid_forced', state: 'start_grid_forced_min_current', decision: 'start', desiredCharging: true, }, }, { id: 'boost-verrou-plein-tient-reseau-present', label: "CONTREFACTUEL anti-oscillateur : verrou 'grid_battery_guard' (garde mordu RESEAU PRESENT, incident du 01/09) → il tient ses 20 min malgre le reseau", time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', evcs_charging_switch: 'off', control_latch: 'off', charge_permission_now: 'off', victron_evcs_strategy: 'force_grid_now', last_block_reason: 'grid_battery_guard', blocked_until: '2026-08-08 13:19:30', last_stop_at: '2026-08-08 12:59:30', prev_surplus_state: 'stop_grid_battery_guard', grid_state: 'on', victron_acin1_available: '1', grid_power_w: '900', soc_percent: '55', pv_power_w: '1200', pv_power_10m_avg_w: '1400', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', battery_current_a: '2', acin1_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400), grid_switch_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400), }, expect: { state: 'wait_reason_lock', decision: 'hold', desiredCharging: false, blockReason: 'grid_battery_guard', }, }, { id: 'boost-verrou-no-grid-tient-reseau-toujours-absent', // NOTE : ce n'est pas le verrou qui protege ici mais la branche anterieure // wait_grid_source (source reseau demandee, reseau absent => arret). La // clause de pertinence '_no_grid && !gridAvailable' dans BCL est donc une // ceinture DEFENSIVE (elle couvre un futur reordonnancement de branches) ; // la propriete observable epinglee est : AUCUNE tentative sur la batterie. label: "Verrou '_no_grid', reseau toujours absent → arret en attente du reseau, aucun retry sur la batterie", time: MIDDAY, overrides: { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', evcs_charging_switch: 'off', control_latch: 'off', charge_permission_now: 'off', victron_evcs_strategy: 'force_grid_now', last_block_reason: 'grid_battery_guard_no_grid', blocked_until: '2026-08-08 13:19:30', last_stop_at: '2026-08-08 12:59:30', prev_surplus_state: 'stop_grid_battery_guard', grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0', soc_percent: '55', pv_power_w: '300', pv_power_10m_avg_w: '400', house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900', battery_current_a: '-4', }, expect: { state: 'wait_grid_source', desiredCharging: false, blockReason: 'grid_unavailable', }, }, ];