Elle empruntait le plafond de la borne — `maison + physicalCapA x V` — qui n a aucune relation physique avec ce dont la batterie a besoin. Qu il tombe juste etait un accident : le 17/09 a 13:51, voiture partie depuis cinq minutes, consigne a 7032 W pour une maison a 933 W. Elle additionne desormais des besoins NOMMES : la maison toujours, la voiture seulement si un vehicule est branche, la batterie seulement dans une fenetre d heures creuses — recharger la batterie sur le reseau n a de sens economique que la. Hors fenetre le terme vaut zero, donc la division par un temps qui tend vers zero disparait par construction et non par garde. MAX ET NON ADDITION. La consigne est une AUTORISATION, pas une prediction : le fichier le dit deja — « la consigne dit ce que le reseau a le DROIT de servir, DVCC dit ce que la batterie a le droit de prendre ». Additionner deux autorisations independantes n est pas plus honnete, seulement plus permissif. Mesure : avec l addition, `consigne-session-engagee-couvre-la-maison` passe de 5595 a 6341 W — une autorisation en plus sur le chemin dominant (85 nuits sur 88 ont un vehicule) pour corriger un chemin emprunte zero fois en treize jours. LE TERME BATTERIE EST BORNE PAR DVCC, PAS PAR LE BMS NI PAR LE TEMPS. Le BMS declare 250 A, soit 12,2 kW : au-dessus de GRID_SETPOINT_MAX_W, il ne bornerait rien. Et un plancher d horizon ne borne que le diviseur : en fin de fenetre (14 min, manque 5,672 kWh) la division rend 17 kW, ecretes a 10 000, soit PIRE que les 5437 empruntes qu on remplace. Avec DVCC le terme vaut 1538 W et la consigne 2380 W. Corollaire : le plancher d horizon devient sans objet, il ne reste qu une garde d une minute contre la division par zero. Le manque vient de `evcs_balance_grid_energy_needed_kwh` et non de `energy_to_cover_kwh` : la premiere est le MANQUE, nulle des que la batterie couvre et convertie par la courbe mesuree par morceaux ; la seconde est l energie que la batterie devra FOURNIR et ne tombe jamais a zero tant qu il reste de la nuit — 11,96 kWh annonces le 17/09 a 20:00 avec un SOC de 91 %. Le plancher nuage suit la voiture et PAS la batterie : il couvre l exposition creee par le courant du vehicule ; une recharge batterie qui ralentit quand le solaire s effondre ne coupe personne. INVARIANT TENU PAR LA FORME : `maison + max(...)` ne peut jamais descendre sous la maison, quel que soit DVCC. La regle du 01/09 et du 04/09 est donc garantie par construction. Toute lecture manquante retombe sur la formule d avant. Golden 102 -> 104. `nuit-sans-vehicule-session-engage` passe de 5437 a 2380 et recoit le manque explicitement, sans quoi il heritait du zero de la fixture et decrivait un etat impossible (SOC 20 pour un requis de 59,1 avec batterie couverte). Deux scenarios ajoutes : la fin de fenetre bornee par DVCC — celui qui protege contre la regression de 10 kW — et le contrefactuel a vehicule branche qui epingle le choix du max. Mutations : plafond DVCC retire -> 2 echecs dont la fin de fenetre ; max remplace par une addition -> le contrefactuel seul ; bilan ignore -> les deux scenarios pilotes par le bilan. Les autres suites sont intactes (hysteresis 24, ACM 20, advisor, bilan 48). ⚠️ RIEN N EST POUSSE VERS n8n : la regle du depot impose la validation du proprietaire avant tout PUT. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
2725 lines
107 KiB
JavaScript
2725 lines
107 KiB
JavaScript
// R3 — Scénarios golden EVCS. Chaque scénario = instant simulé + overrides du
|
||
// snapshot HA réel (tests/golden/fixture-snapshot.json) + résumé attendu.
|
||
// Les attendus sont FIGÉS après vérification manuelle : un diff = régression
|
||
// (ou évolution volontaire → mettre à jour l'attendu dans le même commit).
|
||
//
|
||
// Fenêtres (helpers réels) : night_hc 01:24-07:24 | morning 07:30-11:00 |
|
||
// midday 12:24-14:24 | evening 17:00-20:30 | sinon outside_window.
|
||
// Seuils : hard floor nuit 22 | cible matin forecast fort 35 (tomorrow_kwh 57) |
|
||
// soc_start_min 80 | morning_soc_min 35 | midday_soc_min 75.
|
||
|
||
const NIGHT = '2026-08-08T01:30:00+02:00';
|
||
const MORNING = '2026-08-08T09:00:00+02:00';
|
||
const MIDDAY = '2026-08-08T13:00:00+02:00';
|
||
const AFTERNOON = '2026-08-08T15:30:00+02:00';
|
||
const EVENING = '2026-08-08T18:00:00+02:00';
|
||
|
||
// 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',
|
||
},
|
||
},
|
||
|
||
// ===== 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',
|
||
},
|
||
},
|
||
{
|
||
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',
|
||
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',
|
||
},
|
||
},
|
||
|
||
// ================= 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',
|
||
},
|
||
},
|
||
|
||
// ===== 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,
|
||
},
|
||
},
|
||
{
|
||
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,
|
||
},
|
||
},
|
||
];
|