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
|
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).
|
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).
|
**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,
|
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é
|
`ac_loads_max_safe_w` = 9000, AC-out L1 à 5335 W sans overload, plafond calculé
|
||||||
~20 A. Causes réelles :
|
~20 A) et **pas non plus une bande morte batterie trop étroite** : le courant
|
||||||
1. `battery_discharge_soft_a` = 0 A et `battery_neutral_band_a` = 0 A annulent
|
batterie était à **+127 A** au moment de l'arrêt. Les deux atténuations que
|
||||||
la tolérance de −12 A de la bande SOC 40-60 % (`profileBatteryDischargeFloorA
|
j'avais d'abord proposées (`battery_discharge_soft_a` à −12 A,
|
||||||
= max(profil, global)`) → bande morte nulle, toute pointe de conso casse
|
`battery_neutral_band_a` à 3-5 A) n'avaient donc **aucun effet sur ce cas** —
|
||||||
`solarHoldOk` ; avec `min_run_minutes` = 3 → cycle de 3 à 6 min.
|
l'utilisateur l'a signalé, à raison, avant que je les applique.
|
||||||
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
|
Cause réelle : **inversion du plancher de démarrage et du plancher de maintien**.
|
||||||
exige `batteryCurrentA >= +20 A` — que la charge du véhicule fait justement
|
À 12:24:00 le profil bascule vers `midday_hc_or_solar` ; avec SOC 58 % le
|
||||||
tomber. **L'action détruit sa propre condition d'autorisation.**
|
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
|
### Décisions de conception validées avec l'utilisateur
|
||||||
- La conso se **lit**, elle ne se devine pas ; filtrage numérique dynamique des
|
- 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.
|
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
|
#### 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
|
Observée le 24/08 entre 12:25 et 12:28 : la borne charge à 18 A / 3502 W avec
|
||||||
`Waiting for start` / 0 A et `Charging` / 7 A. **Ce n'est pas une limite
|
PV 10,24 kW et batterie à **+127 A**, puis s'arrête (0 A, `Waiting for start`)
|
||||||
5 kVA** — aucun `binary_sensor.victron_ve_bus_overload` depuis le 20/08,
|
— et la PV retombe à 7638 W, soit **~2,6 kW écrêtés** faute de preneur (ESS
|
||||||
`ac_loads_max_safe_w` = 9000 W, AC-out L1 mesuré à 5335 W sans overload (le
|
sans injection, batterie déjà à son courant de charge maximal).
|
||||||
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 :
|
|
||||||
|
|
||||||
1. **Bande morte nulle.** `battery_discharge_soft_a` = 0 A et
|
**Ce n'est pas une limite 5 kVA** : aucun `binary_sensor.victron_ve_bus_overload`
|
||||||
`battery_neutral_band_a` = 0 A (réglés à 0 le 17/07/2026). Comme
|
depuis le 20/08, `ac_loads_max_safe_w` = 9000 W, AC-out L1 mesuré à 5335 W sans
|
||||||
`profileBatteryDischargeFloorA = max(seuil du profil, réglage global)`, la
|
overload (le parallèle maître/esclave tient bien au-delà de 5 kVA), plafond
|
||||||
tolérance de −12 A prévue pour la bande SOC 40-60 % est **annulée** : la
|
calculé par `WINDOWS_SOURCEV2` ~20 A. **Ce n'est pas non plus une bande morte
|
||||||
moindre pointe de conso (ballon, lave-vaisselle) fait passer le courant
|
batterie trop étroite** : le courant était à +127 A au moment de l'arrêt, très
|
||||||
batterie sous 0 A et casse `solarHoldOk`. Avec `min_run_minutes` = 3, la
|
loin de tout seuil de décharge.
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
deviner, la filtrer numériquement (les pointes nocturnes sont improbables hors
|
||||||
charge EVCS), lisser le SOC pour les décisions de confort en gardant
|
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
|
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 PV_ABUNDANCE_AVG_MIN_W = 4000;
|
||||||
const ABUNDANCE_BATTERY_CHARGE_MIN_A = 20;
|
const ABUNDANCE_BATTERY_CHARGE_MIN_A = 20;
|
||||||
const ABUNDANCE_SOC_FLOOR_PCT = 40;
|
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.pvPowerAvg10mW >= PV_ABUNDANCE_AVG_MIN_W &&
|
||||||
parsed.batteryCurrentA >= ABUNDANCE_BATTERY_CHARGE_MIN_A;
|
parsed.batteryCurrentA >= ABUNDANCE_BATTERY_CHARGE_MIN_A;
|
||||||
const effectiveSocStartFloor = solarAbundance
|
const solarAbundanceHold =
|
||||||
? Math.min(profileSocStartFloor, ABUNDANCE_SOC_FLOOR_PCT)
|
parsed.pvPowerAvg10mW >= PV_ABUNDANCE_AVG_MIN_W &&
|
||||||
: profileSocStartFloor;
|
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 =
|
const profileSocHoldFloor =
|
||||||
context.profile === 'morning_solar' ? Math.max(params.socStopMin, params.morningSocMin - 10) :
|
context.profile === 'morning_solar' ? Math.max(params.socStopMin, params.morningSocMin - 10) :
|
||||||
@@ -418,6 +431,19 @@ const profileSocHoldFloor =
|
|||||||
context.profile === 'evening_high_soc' ? params.eveningSocStopMin :
|
context.profile === 'evening_high_soc' ? params.eveningSocStopMin :
|
||||||
params.socStopMin;
|
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 =
|
const profileSpecificDischargeFloorA =
|
||||||
context.profile === 'morning_solar' ? params.morningBatteryMaxDischargeA :
|
context.profile === 'morning_solar' ? params.morningBatteryMaxDischargeA :
|
||||||
context.profile === 'midday_hc_or_solar' ? params.middayBatteryMaxDischargeA :
|
context.profile === 'midday_hc_or_solar' ? params.middayBatteryMaxDischargeA :
|
||||||
@@ -553,7 +579,7 @@ const solarStartOk =
|
|||||||
const rawSolarHoldOk =
|
const rawSolarHoldOk =
|
||||||
(context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') &&
|
(context.sourceMode === 'solar_only' || context.sourceMode === 'solar_potential') &&
|
||||||
commonSafetyHoldOk &&
|
commonSafetyHoldOk &&
|
||||||
parsed.soc >= profileSocHoldFloor &&
|
parsed.soc >= effectiveSocHoldFloor &&
|
||||||
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
parsed.batteryCurrentA >= profileBatteryDischargeFloorA &&
|
||||||
(
|
(
|
||||||
parsed.pvPowerW >= params.pvHoldMinW ||
|
parsed.pvPowerW >= params.pvHoldMinW ||
|
||||||
@@ -862,7 +888,9 @@ return [{
|
|||||||
profileSocStartFloor,
|
profileSocStartFloor,
|
||||||
effectiveSocStartFloor,
|
effectiveSocStartFloor,
|
||||||
solarAbundance,
|
solarAbundance,
|
||||||
|
solarAbundanceHold,
|
||||||
profileSocHoldFloor,
|
profileSocHoldFloor,
|
||||||
|
effectiveSocHoldFloor,
|
||||||
profileBatteryDischargeFloorA,
|
profileBatteryDischargeFloorA,
|
||||||
batteryCurrentHardExceeded,
|
batteryCurrentHardExceeded,
|
||||||
mustStopHard,
|
mustStopHard,
|
||||||
|
|||||||
@@ -777,6 +777,37 @@ export const scenarios = [
|
|||||||
takeoverHelperService: 'turn_off',
|
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',
|
id: 'midday-hc-garde-soc',
|
||||||
label: 'Midi sans PV, SOC 50 > 35, HC midi autorisé + EDF ON → grid proposé mais gardé (soc>35)',
|
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