Sequence d ouverture : baisser le courant avant que le reseau parte

Le plafond majore (28 A en BOOST) n est legitime que tant que le reseau alimente
AC-IN : sinon les deux MultiPlus portent seuls la voiture plus la maison, le
mecanisme du W-105. Personne ne baissait avant l ouverture -- c est l hysteresis
qui ouvre, l EVCS l apprenait apres coup -- et quand il l apprenait, la reduction
attendait le cooldown de courant et descendait d un pas a la fois.

Correctif sans poignee de main ni helper nouveau : le plafond majore exige que
l EVCS DEMANDE lui-meme le reseau. Sa demande tombe au cycle ou la fenetre se
ferme (30 s), l hysteresis ne peut ouvrir qu ensuite (override disparu PUIS
min_on, cycle 1 min) : l EVCS baisse toujours avant, mecaniquement.

Second volet : un plafond n est pas une cible qu on approche. Reduction
immediate et directe au plafond, sans cooldown.

Le calcul du plafond a ete deplace apres sourceMode dans WINDOWS_SOURCEV2 ; rien
entre les deux ne le lisait, verifie avant de bouger.

Helper a 20 A par defaut : comportement inchange. Le correctif ne mord que sur
le BOOST 28 A, le cas dangereux.

Golden 48 -> 51, chaque cas apparie a son contrefactuel. Mutations des deux
volets separement : 1 puis 2 echecs cibles, restauration 51/51.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
Tallagrand Thierry
2026-08-31 20:03:23 +02:00
parent f0479945c4
commit fc49e6c31a
5 changed files with 238 additions and 32 deletions

View File

@@ -1951,3 +1951,78 @@ restant verts puisqu'ils n'en dépendent pas. Restauration → 48/48.
--- ---
*Document mis à jour le : 31/08/2026 (~18:20) — fenêtre d'établissement déployée dans src/, golden 48/48, PUT n8n en attente de validation* *Document mis à jour le : 31/08/2026 (~18:20) — fenêtre d'établissement déployée dans src/, golden 48/48, PUT n8n en attente de validation*
## 30. Session du 31/08/2026 (suite) — Séquence d'ouverture du contacteur
### Le défaut, en deux morceaux
Le plafond de courant EVCS majoré (P5, `input_number.evcs_plafond_courant_edf_on`,
jusqu'à **28 A en BOOST**) n'est légitime que tant que le réseau alimente AC-IN :
hors réseau, les deux MultiPlus portent seuls la voiture **plus** la maison.
C'est le mécanisme du W-105 déjà consigné (§3, maintenance du 09/07).
1. **Personne ne baissait avant l'ouverture.** C'est l'hystérésis (cycle 1 min)
qui ouvre le contacteur ; le contrôleur EVCS ne l'apprenait qu'après coup, via
`parsed.acInAvailable`. Les deux automates n'ont aucune poignée de main.
2. **Et quand il l'apprenait, il ne baissait pas vite.** La branche
`charging_step_down_system_cap` de `DECISION_ENGINEV2` exigeait
`lifecycle.currentCooldownElapsed` — un compteur de **confort** — et ne
descendait que de `currentStepDownA` par cycle. De 28 A à 20 A : plusieurs
cooldowns. L'exposition ne se comptait donc pas en secondes mais en minutes.
### Le correctif
**Aucune poignée de main entre les deux automates, aucun helper nouveau.** Le
plafond majoré exige désormais que **l'EVCS demande lui-même le réseau** :
```js
const evcsGridCeilingAllowed =
parsed.acInAvailable &&
(sourceMode === 'grid_forced' || sourceMode === 'grid_hc_allowed' || forceGridRequested);
```
L'ordonnancement devient mécanique : la demande de l'EVCS tombe au cycle où la
fenêtre se ferme (30 s), alors que l'hystérésis ne peut ouvrir qu'**ensuite**
(sa branche `turn_off_recovered` exige justement que l'override ait disparu, puis
min_on écoulé, sur un cycle d'une minute). L'EVCS baisse donc toujours avant,
sans que rien ne soit coordonné.
⚠️ **Le calcul du plafond a été DÉPLACÉ après le calcul de `sourceMode`** dans
`WINDOWS_SOURCEV2` (juste avant `const operatingMode = profile;`). Vérifié avant
de bouger : `physicalCapA`, `canRunAtMinCurrent` et les quatre variables
intermédiaires n'étaient utilisés qu'à leur définition et dans l'export — rien
entre les deux ne les lisait. Ne pas les remonter.
Second volet, dans `DECISION_ENGINEV2` : **un plafond n'est pas une cible qu'on
approche, c'est une limite qu'on respecte.** La réduction ne dépend plus du
cooldown et descend directement au plafond (`decision: 'step_down_fast'`, valeur
déjà utilisée par la branche surcharge). Réduire est toujours sûr ; c'est la
montée qui doit être temporisée, et elle l'est.
### Portée réelle, et ce qui reste exposé
Avec le helper à sa valeur par défaut (20 A), **le comportement est strictement
inchangé** : le plafond valait déjà 20 A dans les deux branches. Le correctif ne
mord que sur le BOOST 28 A, c'est-à-dire exactement le cas dangereux.
Reste irréductible : une ouverture **manuelle** (EDF URGENCE) ou une perte
physique du réseau ne se laissent pas anticiper. L'exposition y passe de
plusieurs minutes à **≤ 30 s plus le temps de rampe de la borne**, ce qui est le
minimum atteignable sans signal matériel.
### Golden 48 → 51
| scénario | attendu |
|---|---|
| `ouverture-plafond-tombe-hors-demande-reseau` (AC-IN présent, BOOST 28, matin solaire, cooldown NON écoulé) | `charging_step_down_system_cap` → **20 A immédiatement** |
| `ouverture-plafond-maintenu-en-demande-reseau` (CONTREFACTUEL, `force_grid_now`) | `charging_hold_grid` **28 A**, BOOST préservé |
| `ouverture-reseau-disparu-reduction-immediate` (AC-IN absent, cooldown non écoulé) | réduction immédiate à 20 A |
Preuve par mutation, les deux volets séparément :
- plafond majoré reconditionné au seul `acInAvailable` → 1 échec ciblé ;
- réduction remise sous `currentCooldownElapsed` → 2 échecs ciblés ;
- restauration → 51/51.
### Suite
3. priorité solaire pour la voiture à batterie pleine (coût marginal nul :
6-7 trackers sur 7 bridés = ressource refusée) et interdiction de fermer le
contacteur pour charger une voiture en journée batterie pleine (~150 Wh).
---
*Document mis à jour le : 31/08/2026 (~19:00) — séquence d'ouverture corrigée, golden 51/51, PUT n8n en attente de validation*

View File

@@ -834,18 +834,20 @@ else if (cloud.sessionObservedActive) {
} }
} }
else if ((derived.currentReferenceA > derived.physicalCapA) && lifecycle.currentCooldownElapsed) { // SÉQUENCE D'OUVERTURE (31/08/2026) : un plafond n'est pas une cible qu'on
// approche, c'est une limite qu'on respecte. Cette réduction attendait le
// cooldown de courant — un compteur de confort — et ne descendait que d'un
// pas à la fois : de 28 A à 20 A elle prenait plusieurs cooldowns, pendant
// lesquels les onduleurs portaient un courant qu'ils n'ont plus le droit de
// porter (le plafond retombe à la disparition du réseau). Désormais :
// descente IMMÉDIATE et DIRECTE au plafond. Réduire est toujours sûr ; c'est
// la montée qui doit être temporisée, et elle l'est.
else if (derived.currentReferenceA > derived.physicalCapA) {
setOn({ setOn({
stateName: 'charging_step_down_system_cap', stateName: 'charging_step_down_system_cap',
decisionName: 'step_down', decisionName: 'step_down_fast',
currentA: Math.max( currentA: Math.max(params.currentMinA, derived.physicalCapA),
params.currentMinA, reasonText: `Réduction immédiate au plafond système ${derived.physicalCapA}A`,
Math.min(
derived.currentReferenceA - Math.max(params.currentStepDownA, 2),
derived.physicalCapA
)
),
reasonText: 'Réduction pour respecter la capacité système',
dynamic: true, dynamic: true,
mustStopFlag: false, mustStopFlag: false,
}); });

View File

@@ -117,25 +117,6 @@ const vebusNonEvcsW = Math.max(0, parsed.vebusAcOutPowerW - parsed.evcsAcPowerW)
const totalVebusSoftCeilingW = Math.max(6500, params.acLoadsMaxSafeW || 8500); const totalVebusSoftCeilingW = Math.max(6500, params.acLoadsMaxSafeW || 8500);
const totalVebusHardCeilingW = totalVebusSoftCeilingW + 400; const totalVebusHardCeilingW = totalVebusSoftCeilingW + 400;
// P5 : plafond EVCS configurable quand le réseau alimente réellement AC-IN
// (input_number.evcs_plafond_courant_edf_on). Hors réseau : plafond câblé prudent.
const evcsDedicatedCapBaseW = Math.max(0, cfg.evcsDedicatedMaxW - cfg.systemReserveW);
const safeEvcsByDedicatedCapW = parsed.acInAvailable
? Math.max(0, params.gridCurrentMaxA * acVoltageRef)
: evcsDedicatedCapBaseW;
const safeEvcsByVebusW = Math.max(0, totalVebusSoftCeilingW - vebusNonEvcsW - cfg.systemReserveW);
const safeEvcsCombinedW = Math.max(0, Math.min(safeEvcsByDedicatedCapW, safeEvcsByVebusW));
const systemCapCurrentA = Math.max(0, Math.floor(safeEvcsCombinedW / acVoltageRef));
const physicalCapA = clamp(
systemCapCurrentA,
0,
parsed.acInAvailable
? Math.max(params.currentMaxAutoA, params.gridCurrentMaxA)
: params.currentMaxAutoA
);
const canRunAtMinCurrent = physicalCapA >= params.currentMinA;
// ===================================================== // =====================================================
// FENETRE D'ETABLISSEMENT DU RESEAU (31/08/2026) // FENETRE D'ETABLISSEMENT DU RESEAU (31/08/2026)
// ===================================================== // =====================================================
@@ -779,6 +760,50 @@ if (dayVehicleFullIdle && profile !== 'night_hc') {
sourceMode = 'blocked'; sourceMode = 'blocked';
} }
// =====================================================
// PLAFOND DE COURANT EVCS — calculé APRÈS sourceMode (31/08/2026)
// =====================================================
// P5 : plafond majoré (input_number.evcs_plafond_courant_edf_on, jusqu'à 28 A
// en BOOST) quand le réseau alimente réellement AC-IN ; sinon plafond câblé
// prudent, celui que les deux MultiPlus peuvent porter SEULS.
//
// SÉQUENCE D'OUVERTURE (31/08/2026) — pourquoi ce bloc a été déplacé ici.
// Le plafond majoré n'est légitime que tant que le réseau est là. Or c'est
// l'hystérésis (cycle 1 min) qui ouvre le contacteur, et le contrôleur EVCS ne
// l'apprenait qu'après coup : pendant ce temps les deux onduleurs portaient
// seuls la voiture au courant majoré plus la maison — le mécanisme du W-105.
//
// Correctif sans poignée de main entre les deux automates : le plafond majoré
// exige désormais que l'EVCS DEMANDE lui-même le réseau. Sa demande tombe au
// cycle où la fenêtre se ferme (30 s), alors que l'hystérésis ne peut ouvrir
// qu'ensuite (override disparu ET min_on écoulé, cycle 1 min). L'EVCS baisse
// donc AVANT, mécaniquement, sans rien coordonner.
// Le calcul doit venir après sourceMode ; rien entre les deux ne l'utilisait.
const evcsGridCeilingAllowed =
parsed.acInAvailable &&
(
sourceMode === 'grid_forced' ||
sourceMode === 'grid_hc_allowed' ||
forceGridRequested
);
const evcsDedicatedCapBaseW = Math.max(0, cfg.evcsDedicatedMaxW - cfg.systemReserveW);
const safeEvcsByDedicatedCapW = evcsGridCeilingAllowed
? Math.max(0, params.gridCurrentMaxA * acVoltageRef)
: evcsDedicatedCapBaseW;
const safeEvcsByVebusW = Math.max(0, totalVebusSoftCeilingW - vebusNonEvcsW - cfg.systemReserveW);
const safeEvcsCombinedW = Math.max(0, Math.min(safeEvcsByDedicatedCapW, safeEvcsByVebusW));
const systemCapCurrentA = Math.max(0, Math.floor(safeEvcsCombinedW / acVoltageRef));
const physicalCapA = clamp(
systemCapCurrentA,
0,
evcsGridCeilingAllowed
? Math.max(params.currentMaxAutoA, params.gridCurrentMaxA)
: params.currentMaxAutoA
);
const canRunAtMinCurrent = physicalCapA >= params.currentMinA;
const operatingMode = profile; const operatingMode = profile;
// ===================================================== // =====================================================
@@ -827,6 +852,7 @@ return [{
systemCapCurrentA, systemCapCurrentA,
physicalCapA, physicalCapA,
canRunAtMinCurrent, canRunAtMinCurrent,
evcsGridCeilingAllowed,
gridSettling, gridSettling,
gridTransitionAgeSec: parsed.gridTransitionAgeSec, gridTransitionAgeSec: parsed.gridTransitionAgeSec,
endDayGuardSoc, endDayGuardSoc,

View File

@@ -1203,4 +1203,107 @@ export const scenarios = [
takeoverHelperService: "turn_off", takeoverHelperService: "turn_off",
}, },
}, },
// ======= SÉQUENCE D'OUVERTURE DU CONTACTEUR (31/08/2026) =======
// Le plafond majoré (28 A en BOOST) n'est légitime que tant que le réseau est
// là. C'est l'hystérésis qui ouvre, une minute après que la demande réseau de
// l'EVCS a disparu : l'EVCS doit donc baisser AVANT, de lui-même.
{
id: 'ouverture-plafond-tombe-hors-demande-reseau',
label: "Matin solaire, AC-IN encore présent mais l'EVCS ne demande plus le réseau : le plafond retombe à 20 A et la voiture est réduite IMMÉDIATEMENT",
time: MORNING,
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 08:00:00',
// BOOST EDF : plafond réseau 28 A, voiture effectivement à 28 A.
grid_current_max_a: '28', current_max_auto_a: '22',
evcs_current_a: '28', evcs_set_current_a: '28', evcs_command_current_a: '28',
evcs_ac_power_w: '6440',
house_ac_power_w: '8440', vebus_ac_out_l1_power_w: '8440',
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '2000',
soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200',
battery_current_a: '10',
// Cooldown de courant NON écoulé : la réduction ne doit pas l'attendre.
last_current_change_at: '2026-08-08 08:59:50',
acin1_last_changed_ts: String(MORNING_TS - 4000),
grid_switch_last_changed_ts: String(MORNING_TS - 4000),
},
expect: {
profile: "morning_solar",
sourceMode: "solar_only",
state: "charging_step_down_system_cap",
decision: "step_down_fast",
desiredCharging: true,
currentA: 20,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: "turn_off",
},
},
{
id: 'ouverture-plafond-maintenu-en-demande-reseau',
label: "CONTREFACTUEL : force_grid_now, l'EVCS demande le réseau → le plafond BOOST 28 A est conservé, aucune réduction",
time: MORNING,
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 08:00:00',
// BOOST EDF : plafond réseau 28 A, voiture effectivement à 28 A.
grid_current_max_a: '28', current_max_auto_a: '22',
evcs_current_a: '28', evcs_set_current_a: '28', evcs_command_current_a: '28',
evcs_ac_power_w: '6440',
house_ac_power_w: '8440', vebus_ac_out_l1_power_w: '8440',
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '2000',
victron_evcs_strategy: 'force_grid_now',
soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200',
battery_current_a: '10',
last_current_change_at: '2026-08-08 08:59:50',
acin1_last_changed_ts: String(MORNING_TS - 4000),
grid_switch_last_changed_ts: String(MORNING_TS - 4000),
},
expect: {
profile: "morning_solar",
sourceMode: "grid_forced",
state: "charging_hold_grid",
decision: "hold",
desiredCharging: true,
currentA: 28,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: "turn_off",
},
},
{
id: 'ouverture-reseau-disparu-reduction-immediate',
label: 'Réseau DISPARU : plafond câblé 20 A, voiture encore à 28 A, cooldown non écoulé → réduction immédiate, sans attendre le compteur de confort',
time: MORNING,
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 08:00:00',
// BOOST EDF : plafond réseau 28 A, voiture effectivement à 28 A.
grid_current_max_a: '28', current_max_auto_a: '22',
evcs_current_a: '28', evcs_set_current_a: '28', evcs_command_current_a: '28',
evcs_ac_power_w: '6440',
house_ac_power_w: '8440', vebus_ac_out_l1_power_w: '8440',
grid_state: 'on', victron_acin1_available: '1', grid_power_w: '2000',
grid_state: 'off', victron_acin1_available: '0', grid_power_w: '0',
soc_percent: '60', pv_power_w: '4500', pv_power_10m_avg_w: '4200',
battery_current_a: '10',
last_current_change_at: '2026-08-08 08:59:50',
acin1_last_changed_ts: String(MORNING_TS - 20),
grid_switch_last_changed_ts: String(MORNING_TS - 20),
},
expect: {
profile: "morning_solar",
sourceMode: "solar_only",
state: "charging_step_down_system_cap",
decision: "step_down_fast",
desiredCharging: true,
currentA: 20,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: "turn_off",
},
},
]; ];

File diff suppressed because one or more lines are too long