// 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; const VEHICLE_NIGHT = { evcs_vehicle_connected: 'on', evcs_device_connected: 'on', pv_power_w: '0', pv_power_10m_avg_w: '0', pv_trend_w: '0', house_ac_power_w: '700', vebus_ac_out_l1_power_w: '700', battery_current_a: '-14', }; export const scenarios = [ // ================= NUIT HC (P7 + historique) ================= { id: 'night-hc-p7-toggle-on', label: 'Nuit HC, VW branchée, toggle HC nuit ON, SOC 53, EDF OFF → demande réseau (P7)', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'on' }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', state: 'night_wait_grid_takeover', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: true, edfOnAction: true, takeoverHelperService: 'turn_on', }, }, { id: 'night-hc-p7-edf-on-start', label: 'Nuit HC, cycle suivant : EDF ON, batterie tenue +2A → démarrage charge HC', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'on', grid_state: 'on', victron_acin1_available: '1', battery_current_a: '2', night_takeover_prev: 'on', }, expect: { profile: 'night_hc', sourceMode: 'grid_hc_allowed', state: 'start_grid_hc_min_current', decision: 'start', desiredCharging: true, currentA: 6, takeoverNeeded: true, edfOnAction: false, takeoverHelperService: 'turn_on', }, }, { id: 'night-hc-toggle-off-historique', label: 'Nuit HC, toggle OFF → comportement historique battery_assist_night (idle)', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'off' }, expect: { profile: 'night_hc', sourceMode: 'battery_assist_night', state: 'idle_wait_conditions', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, { id: 'night-soc-sous-cible', label: 'Nuit HC, SOC 30 < cible matin 35 → grid_forced', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on' }, expect: { profile: 'night_hc', sourceMode: 'grid_forced', state: 'night_wait_grid_takeover', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: true, edfOnAction: true, takeoverHelperService: 'turn_on', }, }, { id: 'night-hard-floor', label: 'Nuit HC, SOC 20 ≤ hard floor 22 → hard stop protection batterie + demande réseau', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '20', allow_grid_hc_night: 'on' }, expect: { profile: 'night_hc', sourceMode: 'grid_forced', state: 'battery_current_hard_stop', decision: 'stop_hard', desiredCharging: false, currentA: 6, takeoverNeeded: true, edfOnAction: true, takeoverHelperService: 'turn_on', }, }, { id: 'night-sans-vehicule', label: 'Nuit HC sans véhicule → bloqué, takeover relâché', time: NIGHT, overrides: { ...VEHICLE_NIGHT, evcs_vehicle_connected: 'off', soc_percent: '53' }, expect: { profile: 'night_hc', sourceMode: 'blocked', state: 'vehicle_not_connected', decision: 'stop_not_connected', desiredCharging: false, currentA: 6, takeoverNeeded: false, edfOnAction: false, takeoverHelperService: 'turn_off', }, }, // ===== ADVISOR IA — premier câblage réel (09/08/2026, HANDOFF §20) ===== // Référence : night-soc-sous-cible (SOC 30 < cible règle 35 → grid_forced). // Avec la cible advisor 22 APPLIQUÉE : SOC 30 ≥ 22 → grid_hc_allowed. { id: 'night-advisor-target-applique', label: 'Nuit HC, SOC 30, advisor cible 22 appliquée (toggle ON, ok, frais) → hc_allowed au lieu de 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_hc_allowed', state: 'night_wait_grid_takeover', decision: 'hold', desiredCharging: false, currentA: 6, takeoverNeeded: true, edfOnAction: true, takeoverHelperService: 'turn_on', }, }, { id: 'night-advisor-target-stale', label: 'Nuit HC, SOC 30, advisor cible 22 mais recommandation > 120 min → fallback règles (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: '07/08 20:00', }, 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-advisor-toggle-off', label: 'Nuit HC, SOC 30, advisor cible 22 mais toggle APPLIQUER OFF → règles inchangées (grid_forced)', time: NIGHT, overrides: { ...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on', advisor_apply_night_target: 'off', 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', }, }, // ===== P9 09/08/2026 : charge réseau avant départ (hors HC) + sélecteur véhicule ===== // VW départ 08:50 (helper existant), JEEP départ 06:30 (nouveau helper). // À 08:00 (morning_solar, PV nul) : VW → fenêtre départ active (50 min) ; // JEEP → départ passé depuis 90 min → rien. { 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', }, }, { 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. { 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', }, }, // ================= 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', }, }, { 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', }, }, { // SENS UNIQUE : le bilan ne doit jamais ABAISSER la cible. id: 'cible-bilan-ne-baisse-pas-la-cible', label: "Nuit HC, SOC 30, le bilan ne demande que 25 % → le palier 35 % reste maître → le réseau est maintenu", 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', state: 'night_session_done_idle', takeoverNeeded: true, takeoverHelperService: 'turn_on', }, }, { // 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', }, }, // ===== 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: 50, }, }, { 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: 50, }, }, ];