// Scenarios golden pour le node "Compute Charge Targets" // (workflow "Victron - Auto Charge Manager V1.2", id cTeeHy4ePiVeELLh). // // Ce workflow decide ce que la BATTERIE a le droit de prendre : les limites // DVCC en courant et en tension. Il gouverne donc le haut de la reserve — c'est // lui qui plafonnait le parc a 93 % de SOC jusqu'au 06/09 — et il n'avait aucun // test : il n'etait meme pas suivi dans le depot. // // Le fixture est un snapshot REEL, capture le 06/09/2026 en rendant le gabarit // du node "HA Snapshot via Template" contre Home Assistant. Ne pas le remplacer // par des valeurs inventees : c'est la lecon du 24/08, ou le fixture de // l'hysteresis portait les valeurs de REPLI du code (22/35) au lieu des seuils // de production (20/30), ce qui masquait un bug de bande. // // ⚠️ ORDRE DES BRANCHES, a garder en tete en lisant les attendus : // 1. automatisation desactivee // 2. secours SOC < 20 hors fenetres HC // 3. takeover EVCS nuit (recover / hold) // 4. hiver + HC nuit // 5. ete + HC nuit // 6. HC midi (rattrapage, seulement si SOC < 85 ET forecast < 8 ET PV < 1500) // 7. fenetre jour (boost / mid / topoff / hold selon le SOC) // 8. hors fenetre // puis l'override se superpose a tout. const ETE = '2026-07-15'; const HIVER = '2026-01-15'; const h = (jour, hhmm) => `${jour}T${hhmm}:00+02:00`; const hHiver = (hhmm) => `${HIVER}T${hhmm}:00+01:00`; // Le parc a quatre modules du 06/09 : le BMS autorise 53,2 V, et nos profils // restent tous en dessous. const parcNeuf = { soc_percent: '60', battery_voltage_v: '51.0', battery_current_a: '20' }; export const scenarios = [ // ---------------------------------------------------------------- gardes { id: 'acm-automatisation-desactivee', label: "Toggle d'automatisation OFF : les reglages en place sont conserves tels quels", time: h(ETE, '11:00'), overrides: { ...parcNeuf, auto_enabled: 'off', current_max_charge_current_a: '77', current_max_charge_voltage_v: '50.9' }, expect: { mode: 'automation_disabled', profil: 'manual_keep_current', courantA: 77, tensionV: 50.9, publieCourant: false, publieTension: false }, }, { id: 'acm-secours-soc-critique', label: 'SOC 18 % hors fenetres HC : secours a 60 A / 50,4 V et alerte critique', time: h(ETE, '10:00'), overrides: { ...parcNeuf, soc_percent: '18' }, expect: { mode: 'emergency_rescue', profil: 'emergency_rescue', courantA: 60, tensionV: 50.4, alerte: true }, }, // ------------------------------------------------------- takeover de nuit { id: 'acm-takeover-recharge-vers-la-cible', label: 'Takeover EVCS, EDF ferme, SOC 30 sous la cible 50 : recharge a 30 A', time: h(ETE, '02:30'), overrides: { ...parcNeuf, soc_percent: '30', night_takeover: 'on', edf_switch: 'on', night_target_soc: '50' }, expect: { mode: 'evcs_takeover_recover', profil: 'night_slow', courantA: 30, tensionV: 50.1 }, }, // COURANT CALCULE (07/10/2026) : energie a reprendre / temps restant, borne // par l'abonnement 12 kVA moins la maison. Releve du 07/10 a midi : SOC 31, // cible 44,1, 3,91 kWh a reprendre, maison 5,3 kW (BOOST + repas). { id: 'acm-takeover-midi-courant-calcule', label: 'RELEVE 07/10 12:30 : 3,91 kWh en 1,9 h -> 50 A (au lieu de 30 A fixes, qui ne donnaient que 2,8 kWh)', time: h(ETE, '12:30'), overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on', night_target_soc: '44.1', grid_energy_needed_kwh: '3.91', house_ac_power_w: '5300', grid_subscription_va: '12000', battery_voltage_v: '51' }, expect: { mode: 'evcs_takeover_recover', courantA: 50 }, }, { id: 'acm-takeover-midi-borne-par-l-abonnement', label: 'Meme midi, maison a 9,5 kW : il ne reste que 1,3 kW sous les 90 % des 12 kVA -> 25,5 A', time: h(ETE, '12:30'), overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on', night_target_soc: '44.1', grid_energy_needed_kwh: '3.91', house_ac_power_w: '9500', grid_subscription_va: '12000', battery_voltage_v: '51' }, expect: { mode: 'evcs_takeover_recover', courantA: 25.5 }, }, { id: 'acm-takeover-nuit-petit-besoin-plancher-10a', label: 'Nuit 01:30, 1 kWh a reprendre en 5,9 h : 3,8 A calcules -> plancher 10 A (la batterie est menagee)', time: h(ETE, '01:30'), overrides: { ...parcNeuf, soc_percent: '40', night_takeover: 'on', edf_switch: 'on', night_target_soc: '44', grid_energy_needed_kwh: '1', house_ac_power_w: '700', grid_subscription_va: '12000', battery_voltage_v: '51' }, expect: { mode: 'evcs_takeover_recover', courantA: 10 }, }, { id: 'acm-takeover-maintien-a-la-cible', label: 'RELEVE 06/09 05:06 : cible atteinte, la batterie est tenue a 2 A pendant que le reseau porte la maison', time: h(ETE, '05:06'), overrides: { ...parcNeuf, soc_percent: '55', night_takeover: 'on', edf_switch: 'on', night_target_soc: '49.4' }, expect: { mode: 'evcs_takeover_hold', profil: 'takeover_hold', courantA: 2, tensionV: 50.1 }, }, { id: 'acm-takeover-cible-plafonnee-a-95', label: 'GARDE : une cible aberrante de 120 % est ramenee a 95, donc SOC 96 tient deja', time: h(ETE, '03:00'), overrides: { ...parcNeuf, soc_percent: '96', night_takeover: 'on', edf_switch: 'on', night_target_soc: '120' }, expect: { mode: 'evcs_takeover_hold', courantA: 2 }, }, { id: 'acm-takeover-sans-edf-ne-mord-pas', label: "CONTREFACTUEL : takeover demande mais contacteur OUVERT — on ne bride pas la batterie sur du reseau absent", time: h(ETE, '03:00'), overrides: { ...parcNeuf, soc_percent: '30', night_takeover: 'on', edf_switch: 'off', night_target_soc: '50' }, expect: { mode: 'summer_night_idle' }, }, // ------------------------------------------------------------ nuit HC { id: 'acm-hiver-nuit-forecast-sombre', label: 'Hiver, HC nuit, demain sombre (5 kWh) : on remonte vers 80 %', time: hHiver('03:00'), overrides: { ...parcNeuf, soc_percent: '50', today_remaining_kwh: '0', tomorrow_kwh: '5' }, expect: { mode: 'winter_night_charge_window', profil: 'night_slow', courantA: 30 }, }, { id: 'acm-hiver-nuit-forecast-ensoleille', label: 'PAIRE : meme nuit d hiver mais demain ensoleille (30 kWh), cible 40 % deja depassee', time: hHiver('03:00'), overrides: { ...parcNeuf, soc_percent: '50', today_remaining_kwh: '0', tomorrow_kwh: '30' }, expect: { mode: 'winter_night_idle' }, }, { id: 'acm-ete-nuit-secours', label: 'Ete, HC nuit, SOC 20 sous le seuil de 22 : secours', time: h(ETE, '03:00'), overrides: { ...parcNeuf, soc_percent: '20' }, expect: { mode: 'summer_night_rescue_window', profil: 'night_slow' }, }, { id: 'acm-ete-nuit-repos', label: 'PAIRE : meme nuit d ete a SOC 50, aucun besoin reseau', time: h(ETE, '03:00'), overrides: { ...parcNeuf, soc_percent: '50' }, expect: { mode: 'summer_night_idle' }, }, // ------------------------------------------------------------ midi HC { id: 'acm-midi-rattrapage', label: 'HC midi, SOC 60, journee sombre (5 kWh) et PV a 800 W : rattrapage reseau a 30 A', time: h(ETE, '13:00'), overrides: { ...parcNeuf, soc_percent: '60', today_remaining_kwh: '5', pv_power_w: '800' }, expect: { mode: 'midday_catchup_window', profil: 'midday_grid_catchup', courantA: 30, tensionV: 50.8 }, }, { id: 'acm-midi-pas-de-rattrapage-si-le-soleil-revient', label: 'CONTREFACTUEL : meme midi mais PV a 2000 W — le rattrapage ne mord plus, on repasse en profil jour', time: h(ETE, '13:00'), overrides: { ...parcNeuf, soc_percent: '60', today_remaining_kwh: '5', pv_power_w: '2000' }, expect: { mode: 'day_pv_boost', profil: 'pv_boost' }, }, // ------------------------------------------------------------ jour { // 13/09/2026 — 130 -> 150 A. Mesure de 10:39 a 12:50 : la DVCC etait a // 130 A et la batterie prenait 114 a 127 A, soit 88 a 98 % du plafond, // avec une tension plate a 50,6 V (donc aucune absorption : le courant ne // retombait pas de lui-meme, il butait). Le BMS declare 250 A et 53,2 V, // les 135 cellules tiennent dans 2 mV, aucun module ne bloque la charge. // Le 130 A venait de l'ANCIEN parc, sorti le 12/09. 150 A est un palier, // pas une cible : on regarde ce qu'il libere avant d'aller plus loin. id: 'acm-jour-boost', // 14/09/2026 — 150 -> 180 A. Neuf releves consecutifs a 11:45, pas de 12 s : // courant 148-150 A COLLE au plafond de 150, tension 50,38-50,41 V pour une // consigne de 51,5 (donc 1,1 V de marge, aucune absorption : ce n'est PAS la // tension qui limite), ecart entre cellules 2 mV, zero alarme, et 4 a 6 // chargeurs solaires sur 6 qui declarent se retenir. Le bilan bouclait au // watt pres — batterie 7,56 kW + maison 0,75 pour 8,4 kW produits, signature // de l'ecretage. C'etait donc NOTRE plafond, pas le materiel. // // ⚠️ Le parc n'est PAS celui que le Cerbo declare : quatre US5000 et cinq // US2000, soit TREIZE equivalents 2000, dont le Cerbo ne voit que les quatre // US5000 (d'ou « 4 modules » et « 400 Ah »). Les 250 A qu'il autorise sont // calcules sur quatre modules et s'appliquent a treize equivalents : a 180 A // cela fait moins de 14 A par equivalent. Corrobore par la mesure d'energie, // 0,312 kWh par point de SOC quand treize equivalents en predisent 0,325. label: 'Jour, SOC 60 sous 80 : capture PV maximale, 180 A a 51,5 V (releve du 14/09 : la batterie butait sur 150)', time: h(ETE, '11:00'), overrides: { ...parcNeuf, soc_percent: '60', pv_power_w: '4000' }, expect: { mode: 'day_pv_boost', profil: 'pv_boost', courantA: 250, tensionV: 51.5 }, }, { id: 'acm-jour-mid', // 13/09/2026 — 110 -> 130 A, meme raison que pv_boost : le 110 A venait de // l'ANCIEN parc. A 12:49, SOC 82, la batterie prenait encore 127 A juste // avant que l'ACM ne la ramene a son plafond. La table reste decroissante : // 150 / 130 / 80 / 30. // 14/09/2026 — 130 -> 160 A, dans le meme mouvement que pv_boost : la table // doit rester DECROISSANTE, un palier haut ne peut pas depasser celui d'en // dessous. Table : 180 / 160 / 80 / 30. label: 'Jour, SOC 85 entre 80 et 90 : 160 A a 51,5 V', time: h(ETE, '11:00'), overrides: { ...parcNeuf, soc_percent: '85', pv_power_w: '4000', battery_voltage_v: '50.5' }, expect: { mode: 'day_pv_mid', profil: 'pv_mid', courantA: 160, tensionV: 51.5 }, }, { id: 'acm-jour-topoff-52v4', label: "LE CORRECTIF DU 06/09 : SOC 93 dans la tranche 90-95, la tension monte a 52,8 V (parc assaini, top-off releve 20/09) et le parc reprend de la charge", time: h(ETE, '15:00'), overrides: { ...parcNeuf, soc_percent: '93', pv_power_w: '4000', battery_voltage_v: '51.1' }, expect: { mode: 'day_pv_topoff', profil: 'pv_topoff', courantA: 80, tensionV: 52.8 }, }, { id: 'acm-jour-hold-au-dessus-de-95', label: "GARDE : au-dessus de 95 % on redescend a 50,6 V — on atteint le haut, on n'y sejourne pas", time: h(ETE, '15:00'), overrides: { ...parcNeuf, soc_percent: '96', pv_power_w: '4000' }, expect: { mode: 'day_pv_hold', profil: 'pv_hold', courantA: 30, tensionV: 50.6 }, }, { id: 'acm-hors-fenetre', label: 'Hors fenetre utile (21 h en ete) : profil de maintien', time: h(ETE, '21:00'), overrides: { ...parcNeuf, soc_percent: '70' }, expect: { mode: 'off_window_idle', profil: 'pv_hold', courantA: 30, tensionV: 50.6 }, }, // ------------------------------------------------------------ override { id: 'acm-override-manuel-actif', label: 'Override manuel valide : les valeurs saisies prennent la main sur le profil calcule', time: h(ETE, '11:00'), overrides: { ...parcNeuf, soc_percent: '60', override_enabled: 'on', override_profile: 'manual', override_current_a: '12', override_voltage_v: '50.0', override_until: '2026-07-15 23:00:00' }, expect: { overrideActif: true, profil: 'override_manual', courantA: 12, tensionV: 50 }, }, { id: 'acm-override-expire-sans-effet', label: "CONTREFACTUEL : meme override mais l'echeance est passee — le profil calcule reprend", time: h(ETE, '11:00'), overrides: { ...parcNeuf, soc_percent: '60', override_enabled: 'on', override_profile: 'manual', override_current_a: '12', override_voltage_v: '50.0', override_until: '2026-07-15 09:00:00' }, expect: { overrideActif: false, mode: 'day_pv_boost', courantA: 250 }, }, // ------------------------------------------------------------ publication { id: 'acm-pas-de-publication-si-identique', label: "GARDE : cible egale a ce qui est deja regle — on n'ecrit ni le courant ni la tension", time: h(ETE, '21:00'), overrides: { ...parcNeuf, soc_percent: '70', current_max_charge_current_a: '30', current_max_charge_voltage_v: '50.6' }, expect: { publieCourant: false, publieTension: false }, }, ];