295 lines
15 KiB
JavaScript
295 lines
15 KiB
JavaScript
// 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' },
|
|
// 09/10 : tension de la charge solaire normale (51,5 V) des qu'un courant est calcule.
|
|
expect: { mode: 'evcs_takeover_recover', profil: 'night_slow',
|
|
courantA: 30, tensionV: 51.5 },
|
|
},
|
|
{
|
|
id: 'acm-takeover-midi-soc-58-tension-pas-bridee',
|
|
label: 'RELEVE 09/10 14:05 : SOC 58, 12,9 kWh a reprendre, maison 2,3 kW : 140 A ET 51,5 V (a 50,1 V la batterie plafonnait a 23 A)',
|
|
time: h(ETE, '14:05'),
|
|
overrides: { ...parcNeuf, soc_percent: '58', night_takeover: 'on', edf_switch: 'on',
|
|
night_target_soc: '90', grid_energy_needed_kwh: '12.9', house_ac_power_w: '2300',
|
|
grid_subscription_va: '12000', battery_voltage_v: '51', pv_power_w: '500' },
|
|
expect: { mode: 'evcs_takeover_recover', courantA: 140, tensionV: 51.5 },
|
|
},
|
|
// 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', pv_power_w: '1550' },
|
|
expect: { mode: 'evcs_takeover_recover', courantA: 50 },
|
|
},
|
|
{
|
|
id: 'acm-takeover-midi-borne-par-l-abonnement',
|
|
label: 'Meme midi, maison a 9,5 kW, pas de solaire : 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', pv_power_w: '0' },
|
|
expect: { mode: 'evcs_takeover_recover', courantA: 25.5 },
|
|
},
|
|
// CHARGE MAXIMALE (08/10/2026) : jour faible, le bilan vise 100 % a midi ; le
|
|
// courant n'est plus fixe par la cible mais par la physique.
|
|
{
|
|
id: 'acm-takeover-midi-jour-faible-charge-max-solaire-compte',
|
|
label: 'Jour faible 12:30, SOC 31 -> 100 % (20,7 kWh), maison 5,8 kW, PV 1,55 kW : (10 800 - 5 800 + 1 550) / 51 = 128,4 A',
|
|
time: h(ETE, '12:30'),
|
|
overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on',
|
|
night_target_soc: '100', grid_energy_needed_kwh: '20.7', house_ac_power_w: '5800',
|
|
grid_subscription_va: '12000', battery_voltage_v: '51', pv_power_w: '1550' },
|
|
expect: { mode: 'evcs_takeover_recover', courantA: 128.4 },
|
|
},
|
|
{
|
|
id: 'acm-takeover-midi-jour-faible-charge-max-chargeurs',
|
|
label: 'Meme midi, maison 2 kW : 202 A possibles, borne aux 140 A des deux MultiPlus',
|
|
time: h(ETE, '12:30'),
|
|
overrides: { ...parcNeuf, soc_percent: '31', night_takeover: 'on', edf_switch: 'on',
|
|
night_target_soc: '100', grid_energy_needed_kwh: '20.7', house_ac_power_w: '2000',
|
|
grid_subscription_va: '12000', battery_voltage_v: '51', pv_power_w: '1500' },
|
|
expect: { mode: 'evcs_takeover_recover', courantA: 140 },
|
|
},
|
|
{
|
|
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 },
|
|
},
|
|
];
|