Corrige l'inversion plancher de demarrage / plancher de maintien (EVCS)

Incident du 24/08 12:24-12:28 : bascule de profil vers midday_hc_or_solar
avec SOC 58 %. Plancher de demarrage = min(midday_soc_min 75, abondance P6
40) = 40 ; plancher de maintien = max(soc_stop_min 29, 75-15) = 60. Le
systeme avait donc le droit de DEMARRER ce qu'il n'avait pas le droit de
MAINTENIR : cycle limite a la periode de min_run (3 min) sur toute la bande
[40 %, 60 %], et ~2,6 kW de PV ecretes a chaque creux (PV 10,24 kW -> 7,64 kW
quand la borne s'arrete, ESS sans injection).

Deux volets indissociables dans BATTERY_CLOUD_LOCKS.js :
- effectiveSocHoldFloor = min(regle du profil, plancher de demarrage
  effectif). Sans abondance, comportement inchange.
- l'abondance P6 se prouve au demarrage (batterie >= +20 A) mais se maintient
  en charge (batterie >= 0) : sinon la charge du vehicule, qui detourne
  precisement le surplus, detruit sa propre condition d'autorisation.

Golden 39 -> 40. Preuve par mutation sur midday-inversion-start-hold : code
d'avant -> stop / "Conditions de maintien perdues", apres -> step_up 19 A.

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-24 12:46:18 +02:00
parent 522f47e821
commit 057e0f7d8f
5 changed files with 148 additions and 39 deletions

View File

@@ -1283,18 +1283,22 @@ Cible : session réseau unique dimensionnée jusqu'au croisement PV/conso,
arbitrée par le conseiller IA. NB : mesurer la vraie capacité PV impose de
couper EDF (sinon le MPPT est bridé et on ne voit rien).
**B — Oscillation charge EVCS en plein soleil** (24/08 ~11:58, PV 7,9 kW).
Ce n'est PAS une limite 5 kVA : aucun `ve_bus_overload` depuis le 20/08,
**B — Oscillation charge EVCS en plein soleil** (24/08 12:25 → 12:28).
Ce n'est PAS une limite 5 kVA (aucun `ve_bus_overload` depuis le 20/08,
`ac_loads_max_safe_w` = 9000, AC-out L1 à 5335 W sans overload, plafond calculé
~20 A. Causes réelles :
1. `battery_discharge_soft_a` = 0 A et `battery_neutral_band_a` = 0 A annulent
la tolérance de −12 A de la bande SOC 40-60 % (`profileBatteryDischargeFloorA
= max(profil, global)`) → bande morte nulle, toute pointe de conso casse
`solarHoldOk` ; avec `min_run_minutes` = 3 → cycle de 3 à 6 min.
2. En `outside_window` le plancher de démarrage est `soc_start_min` = 80 % pour
un SOC de 49 % : seul l'override d'abondance P6 autorise le départ, et il
exige `batteryCurrentA >= +20 A` — que la charge du véhicule fait justement
tomber. **L'action détruit sa propre condition d'autorisation.**
~20 A) et **pas non plus une bande morte batterie trop étroite** : le courant
batterie était à **+127 A** au moment de l'arrêt. Les deux atténuations que
j'avais d'abord proposées (`battery_discharge_soft_a` à −12 A,
`battery_neutral_band_a` à 3-5 A) n'avaient donc **aucun effet sur ce cas** —
l'utilisateur l'a signalé, à raison, avant que je les applique.
Cause réelle : **inversion du plancher de démarrage et du plancher de maintien**.
À 12:24:00 le profil bascule vers `midday_hc_or_solar` ; avec SOC 58 % le
plancher de démarrage vaut `min(midday_soc_min 75, abondance P6 40)` = **40**
et le plancher de maintien `max(soc_stop_min 29, 75-15)` = **60**. Le système a
le droit de DÉMARRER ce qu'il n'a pas le droit de MAINTENIR → cycle limite à la
période de `min_run_minutes` (3 min) sur toute la bande [40 %, 60 %], et ~2,6 kW
de PV écrêtés à chaque creux (PV 10,24 kW → 7,64 kW quand la borne s'arrête).
### Décisions de conception validées avec l'utilisateur
- La conso se **lit**, elle ne se devine pas ; filtrage numérique dynamique des
@@ -1309,4 +1313,28 @@ Ce n'est PAS une limite 5 kVA : aucun `ve_bus_overload` depuis le 20/08,
s'auto-limiter à l'aveugle sur un proxy.
---
*Document mis à jour le : 24/08/2026 (~12:20) — Fix G retiré, golden-hysteresis 7/7 (seuils réels 20/30), diagnostic des deux oscillateurs*
### Correctif déployé 24/08 12:45 — invariant maintien <= démarrage
`src/nodes/BATTERY_CLOUD_LOCKS.js`, deux volets indissociables :
1. `effectiveSocHoldFloor = min(règle du profil, plancher de démarrage effectif)`
— on ne peut plus être autorisé à démarrer ce qu'on n'est pas autorisé à
maintenir. Sans abondance, comportement inchangé.
2. L'abondance P6 se PROUVE au démarrage (`solarAbundanceStart` : pvAvg >= 4000
ET batterie >= +20 A) mais se MAINTIENT en charge (`solarAbundanceHold` :
pvAvg >= 4000 ET batterie >= 0). Sans ce dédoublement, la charge du véhicule
détourne le surplus, fait tomber le courant batterie sous +20 A et détruit sa
propre condition d'autorisation — l'oscillation reviendrait dès que le PV
n'est que moyennement fort.
Golden 39 → 40 : `midday-inversion-start-hold` (relevé VRM du 24/08 12:25 :
PV 10,24 kW, charges 3815 W dont EVCS 3502 W, batterie +127 A, SOC 58 %).
Preuve par mutation : code d'avant → `stop` / « Conditions de maintien perdues » ;
après → `charging_step_up_battery` à 19 A. Les 39 scénarios existants inchangés.
**Leçon transverse** : trois oscillateurs identifiés le même jour (Fix G,
abondance P6 auto-destructrice, inversion démarrage/maintien) — même pathologie
à chaque fois, des seuils empilés par couches successives, chacun raisonnable
isolément, dont personne ne vérifie la cohérence d'ensemble. D'où le chantier
du bilan énergétique partagé plutôt qu'un garde de plus.
---
*Document mis à jour le : 24/08/2026 (~12:50) — Fix G retiré (golden-hysteresis 7/7, seuils réels 20/30), inversion démarrage/maintien corrigée (golden 40/40), diagnostic des deux oscillateurs*

View File

@@ -538,29 +538,51 @@ restant un filet de sécurité borné.
#### B. Oscillation de la charge EVCS en plein soleil
Observée le 24/08 vers 11:58-12:00 avec PV 7,7-7,9 kW : la borne alterne
`Waiting for start` / 0 A et `Charging` / 7 A. **Ce n'est pas une limite
5 kVA** — aucun `binary_sensor.victron_ve_bus_overload` depuis le 20/08,
`ac_loads_max_safe_w` = 9000 W, AC-out L1 mesuré à 5335 W sans overload (le
parallèle maître/esclave tient bien au-delà de 5 kVA), et le plafond calculé
par `WINDOWS_SOURCEV2` ressort à ~20 A. Deux causes réelles :
Observée le 24/08 entre 12:25 et 12:28 : la borne charge à 18 A / 3502 W avec
PV 10,24 kW et batterie à **+127 A**, puis s'arrête (0 A, `Waiting for start`)
— et la PV retombe à 7638 W, soit **~2,6 kW écrêtés** faute de preneur (ESS
sans injection, batterie déjà à son courant de charge maximal).
1. **Bande morte nulle.** `battery_discharge_soft_a` = 0 A et
`battery_neutral_band_a` = 0 A (réglés à 0 le 17/07/2026). Comme
`profileBatteryDischargeFloorA = max(seuil du profil, réglage global)`, la
tolérance de −12 A prévue pour la bande SOC 40-60 % est **annulée** : la
moindre pointe de conso (ballon, lave-vaisselle) fait passer le courant
batterie sous 0 A et casse `solarHoldOk`. Avec `min_run_minutes` = 3, la
période du cycle est de 3 à 6 minutes.
2. **Condition de démarrage auto-destructrice (P6).** En `outside_window`
(11:00-12:24) le plancher de démarrage est `soc_start_min` = 80 % alors que
le SOC est à 49 %. Le seul chemin autorisant le départ est l'override
d'abondance P6, qui exige `pvPowerAvg10m ≥ 4000 W` **et**
`batteryCurrentA ≥ +20 A`. Or brancher la voiture détourne le surplus et
fait tomber le courant batterie sous +20 A : l'action détruit sa propre
condition d'autorisation. Système bistable → oscillateur.
**Ce n'est pas une limite 5 kVA** : aucun `binary_sensor.victron_ve_bus_overload`
depuis le 20/08, `ac_loads_max_safe_w` = 9000 W, AC-out L1 mesuré à 5335 W sans
overload (le parallèle maître/esclave tient bien au-delà de 5 kVA), plafond
calculé par `WINDOWS_SOURCEV2` ~20 A. **Ce n'est pas non plus une bande morte
batterie trop étroite** : le courant était à +127 A au moment de l'arrêt, très
loin de tout seuil de décharge.
Direction retenue (même logique qu'en A) : lire la conso au lieu de la
**Cause réelle : inversion du plancher de démarrage et du plancher de maintien.**
À 12:24:00 le profil bascule `outside_window` → `midday_hc_or_solar`. Dans ce
profil, avec SOC 58 % :
| | Calcul | Valeur |
|---|---|---|
| Plancher de **démarrage** | `min(midday_soc_min 75, abondance P6 40)` | **40 %** |
| Plancher de **maintien** | `max(soc_stop_min 29, midday_soc_min 75 − 15)` | **60 %** |
`40 ≤ 58 < 60` : le système a le droit de **démarrer** ce qu'il n'a pas le
droit de **maintenir**. Cycle limite garanti à la période de `min_run_minutes`
(3 min) sur toute la bande [40 %, 60 %], traversée chaque jour vers midi.
L'origine est P6, qui abaissait le plancher de démarrage en cas d'abondance
sans toucher au plancher de maintien.
**Correctif déployé le 24/08 12:45** (`BATTERY_CLOUD_LOCKS.js`), deux volets :
1. **Invariant maintien ≤ démarrage** :
`effectiveSocHoldFloor = min(règle du profil, plancher de démarrage effectif)`.
Sans abondance, rien ne change (60 reste 60).
2. **L'abondance se prouve au démarrage, se maintient en charge.**
`solarAbundanceStart` exige toujours `pvAvg ≥ 4000 W` **et**
`batteryCurrentA ≥ +20 A` (preuve avant d'agir) ; `solarAbundanceHold`
n'exige que `pvAvg ≥ 4000 W` **et** `batteryCurrentA ≥ 0` (la batterie n'est
pas devenue fournisseuse). Sans cette distinction, la charge du véhicule —
qui détourne précisément le surplus — fait tomber le courant batterie sous
+20 A et **détruit sa propre condition d'autorisation** : système bistable.
Scénario golden `midday-inversion-start-hold` (relevé VRM du 24/08 12:25).
Preuve par mutation : sur le code d'avant → `stop` / « Conditions de maintien
perdues » ; après → `charging_step_up_battery` à 19 A. Golden 39 → 40.
Direction de fond (même logique qu'en A) : lire la conso au lieu de la
deviner, la filtrer numériquement (les pointes nocturnes sont improbables hors
charge EVCS), lisser le SOC pour les décisions de confort en gardant
l'instantané pour la sécurité, et exploiter les messages d'état réels du

View File

@@ -405,12 +405,25 @@ const profileSocStartFloor =
const PV_ABUNDANCE_AVG_MIN_W = 4000;
const ABUNDANCE_BATTERY_CHARGE_MIN_A = 20;
const ABUNDANCE_SOC_FLOOR_PCT = 40;
const solarAbundance =
// 24/08/2026 — L'abondance se PROUVE au démarrage (batterie qui monte franchement,
// >= +20 A, avant qu'on touche à quoi que ce soit) mais elle se MAINTIENT tant que
// le PV reste fort ET que la batterie n'est pas devenue fournisseuse. Sans cette
// distinction, la charge du véhicule — qui détourne précisément le surplus — fait
// tomber le courant batterie sous +20 A et détruit sa propre condition
// d'autorisation : système bistable, oscillation à la période de min_run.
const solarAbundanceStart =
parsed.pvPowerAvg10mW >= PV_ABUNDANCE_AVG_MIN_W &&
parsed.batteryCurrentA >= ABUNDANCE_BATTERY_CHARGE_MIN_A;
const effectiveSocStartFloor = solarAbundance
? Math.min(profileSocStartFloor, ABUNDANCE_SOC_FLOOR_PCT)
: profileSocStartFloor;
const solarAbundanceHold =
parsed.pvPowerAvg10mW >= PV_ABUNDANCE_AVG_MIN_W &&
parsed.batteryCurrentA >= 0;
const solarAbundance = solarAbundanceStart; // conservé pour la traçabilité/debug
const abundanceSocFloor = (abundant) =>
abundant ? Math.min(profileSocStartFloor, ABUNDANCE_SOC_FLOOR_PCT) : profileSocStartFloor;
const effectiveSocStartFloor = abundanceSocFloor(solarAbundanceStart);
const profileSocHoldFloor =
context.profile === 'morning_solar' ? Math.max(params.socStopMin, params.morningSocMin - 10) :
@@ -418,6 +431,19 @@ const profileSocHoldFloor =
context.profile === 'evening_high_soc' ? params.eveningSocStopMin :
params.socStopMin;
// INVARIANT (24/08/2026) : on ne doit JAMAIS avoir le droit de DÉMARRER une charge
// qu'on n'a pas le droit de MAINTENIR. Le plancher de maintien doit donc rester
// <= au plancher de démarrage effectif.
// Incident du 24/08 midi : bascule de profil vers midday_hc_or_solar à 12:24 avec
// SOC 58 % => plancher de démarrage 40 % (abondance P6) mais plancher de maintien
// max(29, 75-15) = 60 % => démarrage autorisé, maintien refusé, cycle limite de
// 3 min (min_run) sur toute la bande [40 %, 60 %], et ~2,6 kW de PV écrêtés à
// chaque creux faute de preneur (ESS sans injection).
const effectiveSocHoldFloor = Math.min(
profileSocHoldFloor,
abundanceSocFloor(solarAbundanceHold)
);
const profileSpecificDischargeFloorA =
context.profile === 'morning_solar' ? params.morningBatteryMaxDischargeA :
context.profile === 'midday_hc_or_solar' ? params.middayBatteryMaxDischargeA :
@@ -553,7 +579,7 @@ const solarStartOk =
const rawSolarHoldOk =
(context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') &&
commonSafetyHoldOk &&
parsed.soc >= profileSocHoldFloor &&
parsed.soc >= effectiveSocHoldFloor &&
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
(
parsed.pvPowerW >= params.pvHoldMinW ||
@@ -862,7 +888,9 @@ return [{
profileSocStartFloor,
effectiveSocStartFloor,
solarAbundance,
solarAbundanceHold,
profileSocHoldFloor,
effectiveSocHoldFloor,
profileBatteryDischargeFloorA,
batteryCurrentHardExceeded,
mustStopHard,

View File

@@ -777,6 +777,37 @@ export const scenarios = [
takeoverHelperService: 'turn_off',
},
},
{
id: 'midday-inversion-start-hold',
label: 'INVARIANT maintien<=demarrage : midi, SOC 58 dans la bande [40,60], charge 18 A en cours sous abondance → doit CONTINUER (incident 24/08 12:24-12:28)',
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 11:30:00', // min_run largement écoulé
// Relevé VRM du 24/08 12:25 : PV 10,24 kW, charges 3815 W (dont EVCS 3502 W),
// batterie +127 A. SOC 58 % : au-dessus du plancher de démarrage sous
// abondance (40 %) mais sous le plancher de maintien de la règle midi
// (max(29, 75-15) = 60 %). Avant correctif : arrêt, puis redémarrage au
// cycle suivant → cycle limite de 3 min et PV écrêtée.
soc_percent: '58', pv_power_w: '10240', pv_power_10m_avg_w: '9800',
pv_trend_w: '200', battery_current_a: '127',
evcs_current_a: '18', evcs_set_current_a: '18', evcs_command_current_a: '18',
evcs_ac_power_w: '3502',
house_ac_power_w: '3815', vebus_ac_out_l1_power_w: '3815',
},
expect: {
profile: 'midday_hc_or_solar',
sourceMode: 'solar_only',
state: 'charging_step_up_battery',
decision: 'step_up',
desiredCharging: true,
currentA: 19,
takeoverNeeded: false,
edfOnAction: false,
takeoverHelperService: 'turn_off',
},
},
{
id: 'midday-hc-garde-soc',
label: 'Midi sans PV, SOC 50 > 35, HC midi autorisé + EDF ON → grid proposé mais gardé (soc>35)',

File diff suppressed because one or more lines are too long