Files
EVCS/tests/golden/scenarios.mjs
Tallagrand Thierry 9bc4af0ea1 Fixes A/B/C/D incident rebond EDF nuit 07-08/08 + GRAFCET doc
- A: état collant night_recovery_done_idle + bande cible−2 (anti-rebond takeover)
- B: snapshot relit night_session_start_soc/at (fin des réécritures 30s)
- C: hystérésis grid — turn_off_vehicle_unplugged (coupure immédiate, bypass min_on)
- D: evcs_status_raw==3 (charged) = fin de charge immédiate
- golden 19→23 scénarios (2 attendus mis à jour, 4 nouveaux)
- MANUEL_COMPLET §11.6 GRAFCET cycle nuit, HANDOFF §17, vision-notes incident
2026-08-08 14:20:21 +02:00

529 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// R3 — Scénarios golden EVCS. Chaque scénario = instant simulé + overrides du
// snapshot HA réel (tests/golden/fixture-snapshot.json) + résumé attendu.
// Les attendus sont FIGÉS après vérification manuelle : un diff = régression
// (ou évolution volontaire → mettre à jour l'attendu dans le même commit).
//
// Fenêtres (helpers réels) : night_hc 01:24-07:24 | morning 07:30-11:00 |
// midday 12:24-14:24 | evening 17:00-20:30 | sinon outside_window.
// Seuils : hard floor nuit 22 | cible matin forecast fort 35 (tomorrow_kwh 57) |
// soc_start_min 80 | morning_soc_min 35 | midday_soc_min 75.
const NIGHT = '2026-08-08T01:30:00+02:00';
const MORNING = '2026-08-08T09:00:00+02:00';
const MIDDAY = '2026-08-08T13:00:00+02:00';
const AFTERNOON = '2026-08-08T15:30:00+02:00';
const EVENING = '2026-08-08T18:00:00+02:00';
const VEHICLE_NIGHT = {
evcs_vehicle_connected: 'on',
evcs_device_connected: 'on',
pv_power_w: '0',
pv_power_10m_avg_w: '0',
pv_trend_w: '0',
house_ac_power_w: '700',
vebus_ac_out_l1_power_w: '700',
battery_current_a: '-14',
};
export const scenarios = [
// ================= NUIT HC (P7 + historique) =================
{
id: 'night-hc-p7-toggle-on',
label: 'Nuit HC, VW branchée, toggle HC nuit ON, SOC 53, EDF OFF → demande réseau (P7)',
time: NIGHT,
overrides: { ...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'on' },
expect: {
profile: 'night_hc',
sourceMode: 'grid_hc_allowed',
state: 'night_wait_grid_takeover',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: true,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-hc-p7-edf-on-start',
label: 'Nuit HC, cycle suivant : EDF ON, batterie tenue +2A → démarrage charge HC',
time: NIGHT,
overrides: {
...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
},
expect: {
profile: 'night_hc',
sourceMode: 'grid_hc_allowed',
state: 'start_grid_hc_min_current',
decision: 'start',
desiredCharging: true,
currentA: 6,
takeoverNeeded: true,
edfOnAction: false,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-hc-toggle-off-historique',
label: 'Nuit HC, toggle OFF → comportement historique battery_assist_night (idle)',
time: NIGHT,
overrides: { ...VEHICLE_NIGHT, soc_percent: '53', allow_grid_hc_night: 'off' },
expect: {
profile: 'night_hc',
sourceMode: 'battery_assist_night',
state: 'idle_wait_conditions',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-soc-sous-cible',
label: 'Nuit HC, SOC 30 < cible matin 35 → grid_forced',
time: NIGHT,
overrides: { ...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on' },
expect: {
profile: 'night_hc',
sourceMode: 'grid_forced',
state: 'night_wait_grid_takeover',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: true,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-hard-floor',
label: 'Nuit HC, SOC 20 ≤ hard floor 22 → hard stop protection batterie + demande réseau',
time: NIGHT,
overrides: { ...VEHICLE_NIGHT, soc_percent: '20', allow_grid_hc_night: 'on' },
expect: {
profile: 'night_hc',
sourceMode: 'grid_forced',
state: 'battery_current_hard_stop',
decision: 'stop_hard',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: true,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-sans-vehicule',
label: 'Nuit HC sans véhicule → bloqué, takeover relâché',
time: NIGHT,
overrides: { ...VEHICLE_NIGHT, evcs_vehicle_connected: 'off', soc_percent: '53' },
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'vehicle_not_connected',
decision: 'stop_not_connected',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
// ================= FIN DE CHARGE NUIT (P8) =================
{
id: 'night-vw-pleine-transition',
label: 'Nuit HC 04:00, cycle après vehicle_full_or_paused, SOC ≥ cible → recovery done, takeover relâché',
time: '2026-08-08T04:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '60', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'vehicle_full_or_paused',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-vw-pleine-autoentretien',
label: 'Nuit HC 05:00, veille auto-entretenue SOC ≥ cible, EDF déjà coupé → reste en recovery done',
time: '2026-08-08T05:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '55', allow_grid_hc_night: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_session_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-vw-pleine-batterie-basse-edf-maintenu',
label: 'Nuit HC 05:00, VW pleine MAIS SOC 30 ≤ cible 35 → takeover maintenu (recharge batterie), EVCS en veille',
time: '2026-08-08T05:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '30', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_session_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_session_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: false,
takeoverHelperService: 'turn_on',
},
},
// ===== FIX A/D 08/08/2026 : anti-rebond EDF post-charge (incident nuit 07-08/08) =====
{
id: 'night-recovery-atteinte-soc-cible',
label: 'Nuit HC 04:15, VW pleine, SOC 35 == cible (le cas du rebond) → recovery done, takeover coupé une fois pour toutes',
time: '2026-08-08T04:15:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '35', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_session_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-recovery-done-bande-collante',
label: 'Nuit HC 04:45, recovery done, SOC 34 dans la bande (cible−2 < 34 < 35) → PAS de ré-engagement (anti-rebond)',
time: '2026-08-08T04:45:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '34', allow_grid_hc_night: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'night-recovery-rearm-sous-bande',
label: 'Nuit HC 05:30, recovery done mais SOC 32 ≤ cible−2 → ré-engagement takeover (recharge batterie)',
time: '2026-08-08T05:30:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '32', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
prev_surplus_state: 'night_recovery_done_idle',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_session_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: true,
edfOnAction: false,
takeoverHelperService: 'turn_on',
},
},
{
id: 'night-vw-statut-charged',
label: 'Nuit HC 03:00, statut EVCS brut 3 (charged), sans état VDF préalable → fin de charge immédiate (Fix D)',
time: '2026-08-08T03:00:00+02:00',
overrides: {
...VEHICLE_NIGHT, soc_percent: '60', allow_grid_hc_night: 'on',
grid_state: 'on', victron_acin1_available: '1',
battery_current_a: '2', night_takeover_prev: 'on',
evcs_charging_switch: 'off',
evcs_status_raw: '3',
prev_surplus_state: '',
},
expect: {
profile: 'night_hc',
sourceMode: 'blocked',
state: 'night_recovery_done_idle',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
// ================= JOUR SOLAIRE =================
{
id: 'morning-solaire-fort',
label: 'Matin, PV 4,5 kW, SOC 60, batterie +10A → charge solaire',
time: MORNING,
overrides: {
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200',
pv_trend_w: '100', battery_current_a: '10',
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
},
expect: {
profile: 'morning_solar',
sourceMode: 'solar_only',
state: 'start_min_current',
decision: 'start',
desiredCharging: true,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'morning-nuageux',
label: 'Matin nuageux, PV 300 W, SOC 40 → pas de démarrage',
time: MORNING,
overrides: {
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
soc_percent: '40', pv_power_w: '300', pv_power_10m_avg_w: '250',
pv_trend_w: '-50', battery_current_a: '-3',
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
},
expect: {
profile: 'morning_solar',
sourceMode: 'blocked',
state: 'idle_wait_source',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'midday-abondance-p6',
label: 'Midi abondance P6 : PV avg 4,5 kW + batterie +25A, SOC 66 < plancher 75 → démarre quand même',
time: MIDDAY,
overrides: {
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
soc_percent: '66', pv_power_w: '5000', pv_power_10m_avg_w: '4500',
pv_trend_w: '200', battery_current_a: '25',
house_ac_power_w: '1200', vebus_ac_out_l1_power_w: '1200',
},
expect: {
profile: 'midday_hc_or_solar',
sourceMode: 'solar_only',
state: 'start_min_current',
decision: 'start',
desiredCharging: true,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'midday-hc-garde-soc',
label: 'Midi sans PV, SOC 50 > 35, HC midi autorisé + EDF ON → grid proposé mais gardé (soc>35)',
time: MIDDAY,
overrides: {
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
soc_percent: '50', pv_power_w: '100', pv_power_10m_avg_w: '100',
pv_trend_w: '0', battery_current_a: '-4',
grid_state: 'on', victron_acin1_available: '1',
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
},
expect: {
profile: 'midday_hc_or_solar',
sourceMode: 'grid_hc_allowed',
state: 'idle_wait_conditions',
decision: 'hold',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'apresmidi-provocation-p3',
label: 'Après-midi hors fenêtre, SOC 95, PV bridé 300 W (< hold 500) → provocation PV (solar_potential)',
time: AFTERNOON,
overrides: {
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
soc_percent: '95', pv_power_w: '300', pv_power_10m_avg_w: '300',
pv_trend_w: '0', battery_current_a: '2',
house_ac_power_w: '600', vebus_ac_out_l1_power_w: '600',
},
expect: {
profile: 'outside_window',
sourceMode: 'solar_potential',
state: 'start_forecast_solar_potential',
decision: 'start',
desiredCharging: true,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
// ================= STRATÉGIES MANUELLES =================
{
id: 'force-grid-jour',
label: 'force_grid_now l\'après-midi, EDF ON, SOC 60 → charge réseau forcée',
time: AFTERNOON,
overrides: {
victron_evcs_strategy: 'force_grid_now',
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
soc_percent: '60', pv_power_w: '500', pv_power_10m_avg_w: '500',
pv_trend_w: '0', battery_current_a: '2',
grid_state: 'on', victron_acin1_available: '1',
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
},
expect: {
profile: 'outside_window',
sourceMode: 'grid_forced',
state: 'start_grid_forced_min_current',
decision: 'start',
desiredCharging: true,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'force-charge-nuit',
label: 'force_charge_now la nuit (toggle HC ON) → démarrage manuel local (non-régression P7)',
time: NIGHT,
overrides: {
...VEHICLE_NIGHT, victron_evcs_strategy: 'force_charge_now',
soc_percent: '60', allow_grid_hc_night: 'on',
},
expect: {
profile: 'night_hc',
sourceMode: 'solar_potential',
state: 'start_manual_local_min_current',
decision: 'start',
desiredCharging: true,
currentA: 6,
takeoverNeeded: true,
edfOnAction: false,
takeoverHelperService: 'turn_on',
},
},
{
id: 'strategie-stop',
label: 'Stratégie stop en journée → tout bloqué',
time: AFTERNOON,
overrides: {
victron_evcs_strategy: 'stop',
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
soc_percent: '80', pv_power_w: '3000', pv_power_10m_avg_w: '3000',
battery_current_a: '10',
},
expect: {
profile: 'outside_window',
sourceMode: 'blocked',
state: 'stopped_user',
decision: 'stop_stop_mode',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
// ================= SÉCURITÉ =================
{
id: 'securite-overload',
label: 'Charge solaire en cours + surcharge VE.Bus → protection (stop/step-down)',
time: EVENING,
overrides: {
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on',
last_start_at: '2026-08-08 17:30:00',
soc_percent: '90', pv_power_w: '3500', pv_power_10m_avg_w: '3400',
battery_current_a: '5', evcs_current_a: '13', evcs_set_current_a: '13',
vebus_overload: 'on', vebus_l1_overload: 'on',
house_ac_power_w: '4800', vebus_ac_out_l1_power_w: '4800',
},
expect: {
profile: 'evening_high_soc',
sourceMode: 'solar_only',
state: 'vebus_overload_stop_min_current',
decision: 'stop',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'vehicule-debranche-jour',
label: 'Après-midi véhicule débranché → stop_not_connected (état de repos)',
time: AFTERNOON,
overrides: {
evcs_vehicle_connected: 'off', evcs_device_connected: 'on',
soc_percent: '93', pv_power_w: '1800', pv_power_10m_avg_w: '1800',
battery_current_a: '3',
},
expect: {
profile: 'outside_window',
sourceMode: 'solar_only',
state: 'vehicle_not_connected',
decision: 'stop_not_connected',
desiredCharging: false,
currentA: 6,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
];