La consigne garde de quoi encaisser un nuage

Incident 11/09 18:00, voiture a 28 A : la descente avait amene la
consigne a 1281 W alors que le solaire portait tout (PV 6058 W, reseau
1163 W). Un nuage a fait chuter la PV de 4,6 kW en une minute ; le reseau
etant plafonne a 1281 W, toute la difference est tombee sur la batterie
-- 87 A pour une limite dure de 70 -> `battery_current_hard_stop`, charge
coupee, redemarrage a 6 A.

Sans injection la production peut s'effondrer de plusieurs kW en quelques
secondes, et une boucle a 30 s ne rattrape pas cela : le reseau doit etre
DEJA autorise a prendre le relais. Second plancher : la consigne ne
descend jamais au point ou la perte totale du solaire depasserait ce que
la batterie sait encaisser, budget pris sur la politique par bande de SOC
(§35) avec 20 % de marge sous la limite DURE -- c'est elle qui coupe.

Sur le releve de l'incident, le plancher vaut 4829 W : au moment du
nuage la batterie n'aurait pris que 28 A au lieu de 87.

Contrepartie assumee : a 28 A la descente est nettement moins profonde
qu'a 6 A. C'est proportionne, le courant de la voiture est ce qui cree
l'exposition -- et proteger le materiel prime sur les kilowattheures.

Golden 90 -> 91. Mutation : plancher retire -> 2 scenarios tombent ;
budget pris sur la limite souple au lieu de la dure -> 3 tombent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-11 18:06:31 +02:00
parent fa63bb756b
commit dd56c6b48b
3 changed files with 64 additions and 5 deletions

View File

@@ -402,8 +402,43 @@ const gridSetpointCurrentW = Number.isFinite(parsed.gridSetpointW)
// a notre demande, couvre la maison. On ne libere que la part VOITURE, qui est
// exactement celle qui evince le solaire. Le solaire pourrait souvent couvrir
// la maison en plus, mais l'affirmer demanderait ses propres scenarios.
// ⚠️ SECOND PLANCHER : LE NUAGE (ajoute 11/09 18:05, apres incident).
// Premiere version de la descente : consigne tombee a 1281 W alors que le
// solaire portait tout (PV 6058 W, reseau 1163 W, voiture a 28 A). Un nuage a
// fait chuter la PV de 4,6 kW en une minute ; le reseau etant plafonne a
// 1281 W, la difference est tombee entierement sur la batterie -- 87 A pour
// une limite dure de 70 A -> `battery_current_hard_stop`, charge coupee.
//
// Sans injection, la production peut s'effondrer de plusieurs kW en quelques
// secondes. Une boucle a 30 s ne peut pas rattraper cela : il faut que le
// reseau soit DEJA autorise a prendre le relais. La consigne ne descend donc
// jamais au point ou la perte totale du solaire depasserait ce que la batterie
// sait encaisser.
//
// Le budget vient de la politique batterie deja calibree par bande de SOC
// (§35), pas d'une constante nouvelle, et on garde 20 % de marge sous la
// limite DURE -- c'est elle qui coupe la charge, et une coupure est plus
// couteuse que quelques centaines de watts de solaire non captes.
//
// Contrepartie assumee, et elle est reelle : voiture a 28 A, la descente est
// nettement moins profonde qu'a 10 A. C'est proportionne -- c'est le courant
// de la voiture qui cree l'exposition. Et c'est la regle du proprietaire :
// protéger le materiel prime sur l'economie de kilowattheures.
// `derived.batteryVoltageRef` est deja la tension retenue en amont (mesure
// vebus, repli 51,2 V) : meme grandeur que celle qui sert a convertir le
// courant batterie en puissance dans BATTERY_CLOUD_LOCKS.
const batterySurgeBudgetW = Math.max(
0,
Math.abs(batteryPolicy.batteryDischargeHardDynamicA) * derived.batteryVoltageRef * 0.8
);
const gridSetpointLoadW =
derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef;
const gridSetpointCloudFloorW = Math.round(
Math.max(0, gridSetpointLoadW - batterySurgeBudgetW)
);
const gridSetpointHouseFloorW = clamp(
Math.round(derived.houseNonEvcsW),
Math.max(Math.round(derived.houseNonEvcsW), gridSetpointCloudFloorW),
params.gridSetpointIdleW,
GRID_SETPOINT_MAX_W
);

View File

@@ -2209,7 +2209,7 @@ export const scenarios = [
},
expect: {
sourceMode: 'grid_forced',
gridSetpointW: 5018,
gridSetpointW: 6799,
},
},
{
@@ -2288,7 +2288,7 @@ export const scenarios = [
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',
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',
@@ -2318,7 +2318,7 @@ export const scenarios = [
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: '2241',
grid_setpoint_w: '5500',
mppt_operation_modes: 'mppt_active,mppt_active,mppt_active,mppt_active,mppt_active,mppt_active',
},
expect: {
@@ -2326,4 +2326,28 @@ export const scenarios = [
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,
},
},
];

File diff suppressed because one or more lines are too long