En bascule vers le reseau, courant mini — et le verrou du garde meurt avec sa cause
Incident du 18/09 13:08-13:14 : clic BOOST 28 A, le controleur demande le reseau, l'hysteresis met ~1 min a fermer le contacteur, et pendant ce trou il MAINTIENT 13 A sur la batterie (hold_min_run_wait_grid_takeover). Le garde batterie mord a -32,4 A, 23 secondes AVANT l'arrivee d'EDF, et pose 20 min de verrou — que rien ne leve quand le reseau arrive. Resultat : contacteur ferme, import qui tourne, voiture a l'arret. Trois volets : 1. hold_min_run_wait_grid_takeover retombe au courant MINI — miroir de la sequence d'ouverture (§30) : on ne tient pas la puissance sur la source qu'on quitte. A 6 A le garde n'aurait jamais mordu. 2. Le stop encode si le reseau etait present : absent => blockReason 'grid_battery_guard_no_grid'. Present => verrou plein inchange (incident du 01/09, consigne insuffisante) — c'est ce qui interdit l'oscillateur stop/retry a reseau present. 3. BCL leve le verrou '_no_grid' des que le reseau arrive. Reseau toujours absent : la branche anterieure wait_grid_source protege deja (epingle). Golden 108 -> 113. Mutations en trois directions, chacune tombant sur un scenario different. Piege du fixture note en commentaire : ses seuils ne sont pas a l'echelle du parc reel (fenetre garde reseau / hard general etroite). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
@@ -247,6 +247,14 @@ const blockStillRelevant =
|
|||||||
)
|
)
|
||||||
: history.lastBlockReason === 'grid_battery_guard'
|
: history.lastBlockReason === 'grid_battery_guard'
|
||||||
? isGridSource
|
? isGridSource
|
||||||
|
// Garde batterie declenche RESEAU ABSENT (18/09) : sa cause est le
|
||||||
|
// trou entre la demande de reseau et la fermeture du contacteur.
|
||||||
|
// Le reseau arrive => la cause a disparu => le verrou se leve. Si la
|
||||||
|
// batterie est encore sollicitee au retry, le garde re-mord et pose
|
||||||
|
// cette fois 'grid_battery_guard' (reseau present) — verrou plein.
|
||||||
|
// Convergence en une tentative, meme patron que Fix H.
|
||||||
|
: history.lastBlockReason === 'grid_battery_guard_no_grid'
|
||||||
|
? (isGridSource && !parsed.gridAvailable)
|
||||||
: history.lastBlockReason === 'vebus_overload_persistent'
|
: history.lastBlockReason === 'vebus_overload_persistent'
|
||||||
? derived.anyVebusOverload
|
? derived.anyVebusOverload
|
||||||
: false;
|
: false;
|
||||||
|
|||||||
@@ -614,7 +614,14 @@ else if (cloud.sessionObservedActive) {
|
|||||||
reasonText: `Arrêt charge réseau: batterie trop sollicitée (${round1(parsed.batteryCurrentA)}A)`,
|
reasonText: `Arrêt charge réseau: batterie trop sollicitée (${round1(parsed.batteryCurrentA)}A)`,
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
mustStopFlag: true,
|
mustStopFlag: true,
|
||||||
blockReason: 'grid_battery_guard',
|
// Un verrou meurt avec sa cause (incident du 18/09 : BOOST demande le
|
||||||
|
// reseau, l'hysteresis met ~1 min a fermer, le garde mord 23 s AVANT
|
||||||
|
// l'arrivee d'EDF et posait 20 min de verrou reseau present). On encode
|
||||||
|
// ICI si le reseau etait la : absent => '_no_grid', et BCL levera le
|
||||||
|
// verrou des que le reseau arrive. Present => la cause est ailleurs
|
||||||
|
// (consigne insuffisante, incident du 01/09) et le verrou plein tient —
|
||||||
|
// c'est ce qui interdit l'oscillateur stop/retry a reseau present.
|
||||||
|
blockReason: parsed.gridAvailable ? 'grid_battery_guard' : 'grid_battery_guard_no_grid',
|
||||||
blockedUntilStamp: formatParisStampFromMs(nowMs + 20 * 60000),
|
blockedUntilStamp: formatParisStampFromMs(nowMs + 20 * 60000),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -803,8 +810,13 @@ else if (cloud.sessionObservedActive) {
|
|||||||
setOn({
|
setOn({
|
||||||
stateName: 'hold_min_run_wait_grid_takeover',
|
stateName: 'hold_min_run_wait_grid_takeover',
|
||||||
decisionName: 'hold',
|
decisionName: 'hold',
|
||||||
currentA: Math.min(derived.currentReferenceA, derived.physicalCapA),
|
// Miroir de la sequence d'ouverture (§30) : quand on bascule VERS le
|
||||||
reasonText: "Maintien jusqu'au mini ON en attente du réseau",
|
// reseau, on ne tient pas la puissance sur la source qu'on quitte.
|
||||||
|
// Tenir currentReferenceA (13 A le 18/09) pendant les ~80 s ou
|
||||||
|
// l'hysteresis n'a pas encore ferme le contacteur fait porter ~3 kW
|
||||||
|
// a la batterie et declenche le garde — courant MINI en attendant.
|
||||||
|
currentA: Math.min(params.currentMinA, derived.physicalCapA),
|
||||||
|
reasonText: "Attente du réseau au courant mini",
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
mustStopFlag: false,
|
mustStopFlag: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ function summarize(out) {
|
|||||||
decision: d.current_decision,
|
decision: d.current_decision,
|
||||||
desiredCharging: d.desired_charging,
|
desiredCharging: d.desired_charging,
|
||||||
currentA: d.desired_current_a,
|
currentA: d.desired_current_a,
|
||||||
|
blockReason: d.block_reason || null,
|
||||||
takeoverNeeded: !!c.night_grid_takeover_needed,
|
takeoverNeeded: !!c.night_grid_takeover_needed,
|
||||||
edfOnAction: !!calls.edf_general_ok_on?.execute,
|
edfOnAction: !!calls.edf_general_ok_on?.execute,
|
||||||
takeoverHelperService: calls.helper_night_grid_takeover_needed?.service || null,
|
takeoverHelperService: calls.helper_night_grid_takeover_needed?.service || null,
|
||||||
|
|||||||
@@ -2845,4 +2845,150 @@ export const scenarios = [
|
|||||||
takeoverNeeded: true,
|
takeoverNeeded: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// BOOST 28 A du 18/09/2026 — le verrou survivait a sa cause (HANDOFF)
|
||||||
|
// Releve reel : clic BOOST 13:08:50, contacteur ferme 13:09:53, garde
|
||||||
|
// batterie a -32,4 A a 13:09:30 (23 s AVANT l'arrivee d'EDF), verrou
|
||||||
|
// 20 min reseau present. Trois volets, un scenario chacun + contrefactuel.
|
||||||
|
// ===================================================================
|
||||||
|
{
|
||||||
|
id: 'boost-attente-reseau-courant-mini',
|
||||||
|
label: "Session active, force_grid demande, reseau PAS ENCORE la → on retombe au courant MINI (le 13 A tenu sur la batterie est le defaut du 18/09)",
|
||||||
|
time: MIDDAY,
|
||||||
|
overrides: {
|
||||||
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||||
|
evcs_charging_switch: 'on', control_latch: 'on', charge_permission_now: 'on',
|
||||||
|
// Charge solaire en cours a 13 A, min_run PAS ecoule (start recent).
|
||||||
|
last_start_at: '2026-08-08 12:58:30',
|
||||||
|
evcs_current_a: '13', evcs_set_current_a: '13', evcs_command_current_a: '13',
|
||||||
|
evcs_ac_power_w: '2990',
|
||||||
|
victron_evcs_strategy: 'force_grid_now',
|
||||||
|
// Le contacteur n'est PAS encore ferme : c'est la fenetre de l'incident.
|
||||||
|
grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0',
|
||||||
|
soc_percent: '55', pv_power_w: '1200', pv_power_10m_avg_w: '1400',
|
||||||
|
house_ac_power_w: '3900', vebus_ac_out_l1_power_w: '3900',
|
||||||
|
// Batterie sollicitee mais SOUS le garde dur : la charge continue.
|
||||||
|
battery_current_a: '-20',
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
sourceMode: 'grid_forced',
|
||||||
|
state: 'hold_min_run_wait_grid_takeover',
|
||||||
|
decision: 'hold',
|
||||||
|
desiredCharging: true,
|
||||||
|
currentA: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'boost-garde-sans-reseau-emet-no-grid',
|
||||||
|
label: "Le garde batterie mord RESEAU ABSENT → blockReason 'grid_battery_guard_no_grid' (et pas le verrou plein)",
|
||||||
|
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 12:58:30',
|
||||||
|
evcs_current_a: '13', evcs_set_current_a: '13', evcs_command_current_a: '13',
|
||||||
|
evcs_ac_power_w: '2990',
|
||||||
|
victron_evcs_strategy: 'force_grid_now',
|
||||||
|
grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0',
|
||||||
|
// Le releve reel du 13:09:30 : -32,4 A, sous le garde dur de sa bande.
|
||||||
|
soc_percent: '55', pv_power_w: '300', pv_power_10m_avg_w: '400',
|
||||||
|
house_ac_power_w: '3900', vebus_ac_out_l1_power_w: '3900',
|
||||||
|
// ⚠️ Fenetre etroite VOULUE : le garde reseau (grid_battery_hard_a -20)
|
||||||
|
// doit mordre SANS que le hard general de la bande 40-60 (~-20,9 dans ce
|
||||||
|
// fixture) ne prenne la main — c'est exactement la hierarchie de
|
||||||
|
// production du 18/09 (-32,4 A avec un parc 13/4 ou le hard general est
|
||||||
|
// a -98 A). Le fixture n'etant pas a l'echelle du parc reel, on se place
|
||||||
|
// entre les deux seuils DU FIXTURE.
|
||||||
|
battery_current_a: '-20.5',
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
state: 'stop_grid_battery_guard',
|
||||||
|
decision: 'stop',
|
||||||
|
desiredCharging: false,
|
||||||
|
blockReason: 'grid_battery_guard_no_grid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'boost-verrou-no-grid-leve-a-l-arrivee-du-reseau',
|
||||||
|
label: "Verrou '_no_grid' pose, le reseau ARRIVE → le verrou se leve, la charge repart (le 18/09 elle restait bloquee 20 min)",
|
||||||
|
time: MIDDAY,
|
||||||
|
overrides: {
|
||||||
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||||
|
evcs_charging_switch: 'off', control_latch: 'off', charge_permission_now: 'off',
|
||||||
|
victron_evcs_strategy: 'force_grid_now',
|
||||||
|
// Le verrou du releve reel : pose il y a 30 s, 20 min devant lui.
|
||||||
|
last_block_reason: 'grid_battery_guard_no_grid',
|
||||||
|
blocked_until: '2026-08-08 13:19:30',
|
||||||
|
last_stop_at: '2026-08-08 12:59:30',
|
||||||
|
prev_surplus_state: 'stop_grid_battery_guard',
|
||||||
|
// Le contacteur s'est ferme 23 s apres le stop : reseau PRESENT.
|
||||||
|
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '900',
|
||||||
|
soc_percent: '55', pv_power_w: '1200', pv_power_10m_avg_w: '1400',
|
||||||
|
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
||||||
|
battery_current_a: '2',
|
||||||
|
acin1_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400),
|
||||||
|
grid_switch_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400),
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
sourceMode: 'grid_forced',
|
||||||
|
state: 'start_grid_forced_min_current',
|
||||||
|
decision: 'start',
|
||||||
|
desiredCharging: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'boost-verrou-plein-tient-reseau-present',
|
||||||
|
label: "CONTREFACTUEL anti-oscillateur : verrou 'grid_battery_guard' (garde mordu RESEAU PRESENT, incident du 01/09) → il tient ses 20 min malgre le reseau",
|
||||||
|
time: MIDDAY,
|
||||||
|
overrides: {
|
||||||
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||||
|
evcs_charging_switch: 'off', control_latch: 'off', charge_permission_now: 'off',
|
||||||
|
victron_evcs_strategy: 'force_grid_now',
|
||||||
|
last_block_reason: 'grid_battery_guard',
|
||||||
|
blocked_until: '2026-08-08 13:19:30',
|
||||||
|
last_stop_at: '2026-08-08 12:59:30',
|
||||||
|
prev_surplus_state: 'stop_grid_battery_guard',
|
||||||
|
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '900',
|
||||||
|
soc_percent: '55', pv_power_w: '1200', pv_power_10m_avg_w: '1400',
|
||||||
|
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
||||||
|
battery_current_a: '2',
|
||||||
|
acin1_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400),
|
||||||
|
grid_switch_last_changed_ts: String(Date.parse(MIDDAY) / 1000 - 400),
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
state: 'wait_reason_lock',
|
||||||
|
decision: 'hold',
|
||||||
|
desiredCharging: false,
|
||||||
|
blockReason: 'grid_battery_guard',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'boost-verrou-no-grid-tient-reseau-toujours-absent',
|
||||||
|
// NOTE : ce n'est pas le verrou qui protege ici mais la branche anterieure
|
||||||
|
// wait_grid_source (source reseau demandee, reseau absent => arret). La
|
||||||
|
// clause de pertinence '_no_grid && !gridAvailable' dans BCL est donc une
|
||||||
|
// ceinture DEFENSIVE (elle couvre un futur reordonnancement de branches) ;
|
||||||
|
// la propriete observable epinglee est : AUCUNE tentative sur la batterie.
|
||||||
|
label: "Verrou '_no_grid', reseau toujours absent → arret en attente du reseau, aucun retry sur la batterie",
|
||||||
|
time: MIDDAY,
|
||||||
|
overrides: {
|
||||||
|
evcs_vehicle_connected: 'on', evcs_device_connected: 'on',
|
||||||
|
evcs_charging_switch: 'off', control_latch: 'off', charge_permission_now: 'off',
|
||||||
|
victron_evcs_strategy: 'force_grid_now',
|
||||||
|
last_block_reason: 'grid_battery_guard_no_grid',
|
||||||
|
blocked_until: '2026-08-08 13:19:30',
|
||||||
|
last_stop_at: '2026-08-08 12:59:30',
|
||||||
|
prev_surplus_state: 'stop_grid_battery_guard',
|
||||||
|
grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0',
|
||||||
|
soc_percent: '55', pv_power_w: '300', pv_power_10m_avg_w: '400',
|
||||||
|
house_ac_power_w: '900', vebus_ac_out_l1_power_w: '900',
|
||||||
|
battery_current_a: '-4',
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
state: 'wait_grid_source',
|
||||||
|
desiredCharging: false,
|
||||||
|
blockReason: 'grid_unavailable',
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user