Budget de nuit borne au plancher prudent ; sans EDF : voiture bloquee meme quand EDF se ferme pour la batterie (session batterie seule)

This commit is contained in:
Tallagrand Thierry
2026-10-04 06:09:39 +02:00
parent 09ab5ce771
commit 43b650ee51
9 changed files with 96 additions and 20 deletions

View File

@@ -1628,5 +1628,40 @@ reseau + decharge - charge) : elle inclut la borne et les pertes de l'onduleur.
a lieu un jour : verifier le selecteur vehicule (§47) et que les deux integrales batterie a lieu un jour : verifier le selecteur vehicule (§47) et que les deux integrales batterie
repartent de leur derniere valeur (elles restaurent leur etat). repartent de leur derniere valeur (elles restaurent leur etat).
### 04/10 matin — Nuit du budget : la JEEP chargee a 100 % sur EDF, deux defauts de ma conception
**Ce qui s'est passe** (mode sans_edf) : budget fige a 01:25 = **1,41 kWh** (SOC 47) ; JEEP sur
batterie, **arret au budget a 02:31** (1,40 kWh) — l'arret au compteur MARCHE. Puis a 02:57 SOC
39 sous la cible du matin 39,5 : reprise reseau de nuit, EDF ferme (02:58, session unique
jusqu'a 07:24) ; l'exception du 30/09 (« EDF deja engage pour la batterie -> la voiture en
profite ») a relance la JEEP sur le reseau : **4,7 kWh jusqu'au plein (04:02)**. Import depuis
02:58 : 5,6 kWh (HC). JEEP debranchee 05:57, pleine (54 % + 1,4 + 4,7 kWh).
**Cause 1** : le budget (creux de la trajectoire p80 au-dessus de 22 %) ne respectait pas le
critere qui ferme EDF : a 01:20, requis 42,5 (p50) / 43,6 (prudent) pour SOC 47 -> marge
~1,0 kWh, budget 1,41. La voiture a mange toute la marge ; une maison conforme au profil a
suffi a toucher la cible. Deux calculs du bilan jamais confrontes.
**Cause 2** : l'exception de securite du 30/09 etait trop large — elle devait garantir que
« sans EDF » n'empeche jamais EDF de proteger la BATTERIE, elle laissait aussi la VOITURE
charger sur le reseau.
**Correctifs (accord du proprietaire, deployes 06:08)** :
1. `BALANCE.js` : budget = min(creux, energie au-dessus du SOC requis PRUDENT) — le plancher
meme de P12. Bilan 76/76 : soleil 4,21 -> **3,75** et SOC bas 0,61 -> **0,15** kWh, verifies
a la main ((57 - 44,5) x 0,30 et (45 - 44,5) x 0,30), mutation qui mord.
2. Option A : en sans_edf la voiture reste `blocked` meme quand EDF se ferme pour la batterie.
Piege rencontre : la fermeture d'EDF (`edf_general_ok_on`) et la consigne (`gridSessionActive`)
suivaient la source de la VOITURE — bloquer la voiture coupait aussi la batterie (golden
`budget-sans-edf-plancher-dur-edf-protege` et `depart-matin-…-sous-la-cible` tombes). Nouveau
drapeau `nightBatteryGridOnly` (session de nuit && voiture en attente sans EDF && takeover) :
EDF + session reseau pour la batterie seule (WINDOWS, passe par BATTERY_CLOUD_LOCKS,
HA_SERVICE_CALLS_OUTPUT ; raison « sans EDF : EDF ferme pour la batterie seule, la voiture
attend le soleil »). Golden **136/136** (+1 : la nuit du 03-04/10 rejouee), attendus des deux
scenarios passes a `blocked` + EDF ferme, deux mutations qui mordent (3 scenarios chacune).
golden-acm 20/20, hysteresis 24/24. Executions n8n en succes apres deploiement.
**Tableau Energie, defaut de mon import** : l'import des statistiques AVANT la premiere heure
compilee ne suffit pas — HA est reparti de zero a 01:00 (sauts -1 615 / -335 / -481 / -465 kWh
sur la journee du 04/10). Corrige par `recorder/adjust_sum_statistics` a partir de 23:00Z
(+1 614,90 / +335,00 / +481,29 / +466,00, decharge 01:00-02:00 = 1,50 kWh reels d'apres la
base) ; heures continues verifiees. ⚠️ Lecon : apres un import sur une entite neuve,
verifier la premiere heure compilee et corriger par ajustement.
--- ---
*Document mis a jour le : 04/10/2026 (~01:40) — tableau Energie complete (Sud/Ouest, batterie par integration, borne ; aucun redemarrage HA) ; kilometrage des vehicules (km/kWh, capacite, releves de reference, agent cable 15/15) ; a verifier le 04/10 au matin : nuit du budget JEEP ; concierge : vehicule non confirme demande, heure de saisie ; Grafana vehicules en tableau ; Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10* *Document mis a jour le : 04/10/2026 (~06:15) — nuit du budget : budget borne au plancher prudent, sans EDF = voiture bloquee meme EDF ferme (batterie seule) ; Energie : sauts corriges ; tableau Energie complete (Sud/Ouest, batterie par integration, borne ; aucun redemarrage HA) ; kilometrage des vehicules (km/kWh, capacite, releves de reference, agent cable 15/15) ; a verifier le 04/10 au matin : nuit du budget JEEP ; concierge : vehicule non confirme demande, heure de saisie ; Grafana vehicules en tableau ; Grafana vehicules : ZERO et Invite ajoutes, session 16:46 reetiquetee ZERO ; prevision SOC 10 h ~35 % ; concierge fiable : niveau note par le script, une ligne, dedoublonnage, simulation par drapeau ; agent : message incompris = question, bourse sur demande explicite (banc final 6/6) ; a verifier : nuit du samedi 03 au dimanche 04/10 (budget < besoin JEEP), cron orange-cloture lundi 05/10 apres 17:40, comparaison VRM / Forecast.Solar lundi 05/10*

View File

@@ -460,7 +460,17 @@ if (nightHcActive && Number.isFinite(socPct)) {
if (coupes[i + 1] >= depuis) creux = Math.min(creux, reserve); if (coupes[i + 1] >= depuis) creux = Math.min(creux, reserve);
} }
} }
nightVehicleBudgetKwh = Number.isFinite(creux) ? Math.max(0, creux) : null; // BORNE PAR LE PLANCHER PRUDENT (04/10/2026). Nuit du 03-04/10 : SOC 47,
// prudent 43,6 -> ~1,0 kWh de marge, mais le creux a donne 1,41 kWh. La
// voiture a pris toute la marge, la batterie a touche la cible du matin a
// 02:57 et la reprise reseau a ferme EDF. Le budget ne peut donc jamais
// depasser l'energie au-dessus du SOC requis PRUDENT, celui-la meme que la
// voiture n'a pas le droit de franchir en assistance batterie (P12) : les
// deux criteres sont desormais les memes par construction.
const margePrudente = Number.isFinite(prudent.requiredSocPct)
? (socPct > prudent.requiredSocPct ? energyBetweenSoc(prudent.requiredSocPct, socPct) : 0)
: 0;
nightVehicleBudgetKwh = Number.isFinite(creux) ? Math.max(0, Math.min(creux, margePrudente)) : null;
} }
// --------------------------------------------------------------------- // ---------------------------------------------------------------------

View File

@@ -1134,6 +1134,7 @@ return [{
nightBudgetToWrite: nightPrev.nightBudgetToWrite ?? null, nightBudgetToWrite: nightPrev.nightBudgetToWrite ?? null,
nightChargeMode: nightPrev.nightChargeMode || 'sans_edf', nightChargeMode: nightPrev.nightChargeMode || 'sans_edf',
nightCarWaitsNoGrid: !!nightPrev.nightCarWaitsNoGrid, nightCarWaitsNoGrid: !!nightPrev.nightCarWaitsNoGrid,
nightBatteryGridOnly: !!nightPrev.nightBatteryGridOnly,
maxBatteryDischargePoints, maxBatteryDischargePoints,
gridTakeoverTriggerPct, gridTakeoverTriggerPct,

View File

@@ -304,7 +304,8 @@ const reason = [...decision.reason];
if (night.nightBudgetKwh !== null && night.nightBudgetKwh !== undefined) { if (night.nightBudgetKwh !== null && night.nightBudgetKwh !== undefined) {
const f = (x) => String(Math.round(x * 100) / 100).replace('.', ','); const f = (x) => String(Math.round(x * 100) / 100).replace('.', ',');
reason.push(`Budget batterie nuit ${f(night.nightBudgetDeliveredKwh ?? 0)}/${f(night.nightBudgetKwh)} kWh` + reason.push(`Budget batterie nuit ${f(night.nightBudgetDeliveredKwh ?? 0)}/${f(night.nightBudgetKwh)} kWh` +
(night.nightCarWaitsNoGrid ? ' — atteint, sans EDF : la voiture attend le soleil' (night.nightBatteryGridOnly ? ' — sans EDF : EDF ferme pour la batterie seule, la voiture attend le soleil'
: night.nightCarWaitsNoGrid ? ' — atteint, sans EDF : la voiture attend le soleil'
: night.nightBudgetReached ? ' — atteint, EDF finit la charge' : '')); : night.nightBudgetReached ? ' — atteint, EDF finit la charge' : ''));
} else if (night.nightCarWaitsNoGrid) { } else if (night.nightCarWaitsNoGrid) {
reason.push('Pas de budget batterie exploitable, sans EDF : la voiture attend le soleil'); reason.push('Pas de budget batterie exploitable, sans EDF : la voiture attend le soleil');
@@ -365,7 +366,9 @@ const gridSessionActive =
( (
context.sourceMode === 'grid_forced' || context.sourceMode === 'grid_forced' ||
context.sourceMode === 'grid_hc_allowed' || context.sourceMode === 'grid_hc_allowed' ||
night.nightGridSessionCommitted === true night.nightGridSessionCommitted === true ||
// Batterie seule sur le reseau, voiture bloquee « sans EDF » (04/10).
night.nightBatteryGridOnly === true
); );
// ===================================================== // =====================================================
@@ -1471,7 +1474,8 @@ return [{
// ================================================= // =================================================
edf_general_ok_on: { edf_general_ok_on: {
execute: execute:
['grid_forced', 'grid_hc_allowed'].includes(context.sourceMode) && (['grid_forced', 'grid_hc_allowed'].includes(context.sourceMode) ||
night.nightBatteryGridOnly === true) &&
!parsed.gridAvailable, !parsed.gridAvailable,
domain: 'switch', domain: 'switch',
service: 'turn_on', service: 'turn_on',

View File

@@ -859,11 +859,14 @@ const nightPreferredSourceMode =
? 'blocked' ? 'blocked'
: vehicleChargeCompleted : vehicleChargeCompleted
? 'blocked' ? 'blocked'
// « Sans EDF » ne bloque la voiture que si EDF n'est PAS deja engage pour // « Sans EDF » : la voiture ne prend JAMAIS le reseau la nuit, MEME quand
// la batterie (plancher dur, cible du matin, session du bilan, verrou) : // EDF se ferme pour la batterie (plancher dur, cible du matin, session du
// proteger le materiel prime (regle du proprietaire, 05/09), et EDF deja // bilan, verrou). Decision du proprietaire le 04/10 (option A) : la nuit du
// ferme en HC, la voiture en profite au meme tarif. // 03-04/10, EDF ferme a 02:57 pour la batterie, l'ancienne exception a
: nightCarWaitsNoGrid && !nightGridTakeoverNeeded // laisse la JEEP prendre 4,7 kWh au reseau jusqu'au plein. La protection
// du materiel reste entiere : EDF se ferme quand meme (takeover), seule la
// voiture attend le soleil.
: nightCarWaitsNoGrid
? 'blocked' ? 'blocked'
: batteryAssistAllowed : batteryAssistAllowed
? 'battery_assist_night' ? 'battery_assist_night'
@@ -875,6 +878,16 @@ const nightPreferredSourceMode =
? 'grid_forced' ? 'grid_forced'
: 'grid_hc_allowed'; : 'grid_hc_allowed';
// « Sans EDF » ET EDF necessaire pour la BATTERIE (04/10/2026, option A du
// proprietaire) : la voiture reste bloquee, mais la batterie a sa session
// reseau. Sans ce drapeau, la fermeture d'EDF et la consigne de recharge, qui
// suivent la source de la VOITURE, seraient perdues avec elle (golden
// budget-sans-edf-plancher-dur-edf-protege, depart-matin-…-sous-la-cible).
const nightBatteryGridOnly =
nightSessionActive &&
nightCarWaitsNoGrid &&
nightGridTakeoverNeeded;
const batteryRecoveryAllowedNow = const batteryRecoveryAllowedNow =
nightSessionActive && nightSessionActive &&
nightGridTakeoverNeeded && nightGridTakeoverNeeded &&
@@ -1265,6 +1278,7 @@ return [{
nightBudgetToWrite, nightBudgetToWrite,
nightChargeMode: params.nightChargeMode, nightChargeMode: params.nightChargeMode,
nightCarWaitsNoGrid, nightCarWaitsNoGrid,
nightBatteryGridOnly,
nightGridSessionCommitted, nightGridSessionCommitted,
middayVehicleGridNeeded, middayVehicleGridNeeded,
nightGridSessionUncovered, nightGridSessionUncovered,

View File

@@ -610,16 +610,16 @@ export const balanceScenarios = [
{ {
node: 'BALANCE', node: 'BALANCE',
id: 'bilan-budget-voiture-nuit-soleil', id: 'bilan-budget-voiture-nuit-soleil',
label: '01:25, SOC 57, lendemain ensoleille : la voiture peut prendre 4,21 kWh sur la batterie', label: '01:25, SOC 57, lendemain ensoleille : budget borne par le plancher prudent 44,5 % -> (57 - 44,5) x 0,30 = 3,75 kWh (creux seul : 4,21, avant le 04/10)',
snapshot: soleil2809(Object.assign(snap2709('2026-09-28T01:25:00+02:00', 57, null), NUIT_HC_2809)), snapshot: soleil2809(Object.assign(snap2709('2026-09-28T01:25:00+02:00', 57, null), NUIT_HC_2809)),
expect: { usable: true, nightVehicleBudgetActive: true, nightVehicleBudgetKwh: 4.21 }, expect: { usable: true, nightVehicleBudgetActive: true, nightVehicleBudgetKwh: 3.75, prudentRequiredSocPct: 44.5 },
}, },
{ {
node: 'BALANCE', node: 'BALANCE',
id: 'bilan-budget-voiture-nuit-soc-bas', id: 'bilan-budget-voiture-nuit-soc-bas',
label: '01:25, SOC 45 : il ne reste que 0,61 kWh au-dessus du creux de 9 h', label: '01:25, SOC 45 : borne par le plancher prudent -> (45 - 44,5) x 0,30 = 0,15 kWh (creux seul : 0,61, avant le 04/10)',
snapshot: soleil2809(Object.assign(snap2709('2026-09-28T01:25:00+02:00', 45, null), NUIT_HC_2809)), snapshot: soleil2809(Object.assign(snap2709('2026-09-28T01:25:00+02:00', 45, null), NUIT_HC_2809)),
expect: { usable: true, nightVehicleBudgetActive: true, nightVehicleBudgetKwh: 0.61 }, expect: { usable: true, nightVehicleBudgetActive: true, nightVehicleBudgetKwh: 0.15, prudentRequiredSocPct: 44.5 },
}, },
{ {
node: 'BALANCE', node: 'BALANCE',

View File

@@ -3122,7 +3122,18 @@ export const scenarios = [
time: '2026-09-30T04:00:00+02:00', time: '2026-09-30T04:00:00+02:00',
overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 03:55', soc_percent: '20', night_charge_mode: 'sans_edf', overrides: { ...BUDGET_NUIT, balance_last_run: '30/09 03:55', soc_percent: '20', night_charge_mode: 'sans_edf',
night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4003.75' }, night_budget_prev: '3.7|4000', evcs_total_energy_kwh: '4003.75' },
expect: { sourceMode: 'grid_forced', edfOnAction: true }, // 04/10 (option A du proprietaire) : EDF se ferme pour la BATTERIE, la
// voiture reste bloquee « sans EDF » (avant : grid_forced, voiture sur EDF).
expect: { sourceMode: 'blocked', desiredCharging: false, edfOnAction: true, takeoverNeeded: true },
},
{
id: 'budget-sans-edf-reprise-reseau-voiture-bloquee',
label: 'Nuit du 03-04/10 rejouee : budget 1,41 atteint, SOC 39 sous la cible 39,5 a 02:57 : EDF ferme pour la batterie, la JEEP ne prend PAS le reseau',
time: '2026-10-04T02:57:00+02:00',
overrides: { ...BUDGET_NUIT, balance_last_run: '04/10 02:55', soc_percent: '39',
balance_required_soc: '39.5', balance_required_soc_prudent: '40.4',
night_charge_mode: 'sans_edf', night_budget_prev: '1.41|4038.8', evcs_total_energy_kwh: '4040.2' },
expect: { sourceMode: 'blocked', desiredCharging: false, edfOnAction: true, takeoverNeeded: true },
}, },
{ {
id: 'budget-bilan-perime-sans-edf-rien', id: 'budget-bilan-perime-sans-edf-rien',
@@ -3170,7 +3181,8 @@ export const scenarios = [
balance_required_soc: '28.6', balance_required_soc_prudent: '29.7', balance_required_soc: '28.6', balance_required_soc_prudent: '29.7',
evcs_vehicle: 'JEEP', departure_time_jeep: '06:30:00', departure_day_ok: 'on', evcs_vehicle: 'JEEP', departure_time_jeep: '06:30:00', departure_day_ok: 'on',
night_charge_mode: 'sans_edf', night_budget_prev: '5.93|4010', evcs_total_energy_kwh: '4015' }, night_charge_mode: 'sans_edf', night_budget_prev: '5.93|4010', evcs_total_energy_kwh: '4015' },
expect: { sourceMode: 'grid_forced', edfOnAction: true }, // « sans EDF » : l'urgence ferme EDF pour la batterie, la voiture reste bloquee (04/10).
expect: { sourceMode: 'blocked', desiredCharging: false, edfOnAction: true },
}, },
{ {
id: 'depart-matin-pas-un-jour-de-depart', id: 'depart-matin-pas-un-jour-de-depart',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long