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:
52
HANDOFF.md
52
HANDOFF.md
@@ -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*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user