Files
EVCS/tests/golden-acm/scenarios.mjs
Tallagrand Thierry 7b2e8e74d9 Rec 2 : top-off ACM 52,4 -> 52,8 V sur le parc assaini
Le 52,4 V avait ete pose le 06/09 pour menager la cellule 12 faible du pack
ANCIEN (§57). Ce pack est sorti. Cellules du pack 2 a 3-6 mV, la plus haute a
3356 mV, loin des 3550 mV ou le BMS reduit le courant. On releve le top-off
pour atteindre une charge plus pleine et RECALIBRER la jauge SOC Pylontech (qui
derive tant qu'on n'atteint jamais 100 %). 52,8 V = 3,52 V/cellule, sous les
53,2 V autorises par le BMS.

⚠️ Cellules du pack 1 (US5000) pas encore sous surveillance continue (2e cable
en commande). Proprietaire a autorise explicitement malgre le caveat.

patch rejouable tools/patch-20260920-topoff-52v8.mjs. golden-acm 20/20, attendu
acm-jour-topoff mis a jour (52,4->52,8), mutation : retour a 52,4 -> 1 echec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
2026-09-20 13:47:16 +02:00

235 lines
11 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' },
expect: { mode: 'evcs_takeover_recover', profil: 'night_slow',
courantA: 30, tensionV: 50.1 },
},
{
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: 180, 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: 180 },
},
// ------------------------------------------------------------ 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 },
},
];