Bilan : la nuit prepare jusqu'a la nuit suivante (la regle du 08/10 prenait le creux de midi pour le soir)

This commit is contained in:
Tallagrand Thierry
2026-10-09 01:41:08 +02:00
parent b7448b72c4
commit 7969318185
6 changed files with 80 additions and 41 deletions

View File

@@ -2230,5 +2230,21 @@ approximations successives ») ; cette nuit la VW a charge A FOND de 05:17 a 07:
budget 0 kWh … activer complete ») ; EDF HP **partiel** ; midi **toujours faux** (« consigne inferieure au budget 0 kWh … activer complete ») ; EDF HP **partiel** ; midi **toujours faux** (« consigne inferieure au
solaire »). 1 juste / 1 partiel / 1 faux, contre 0 juste avant. Pas encore un role actif. solaire »). 1 juste / 1 partiel / 1 faux, contre 0 juste avant. Pas encore un role actif.
### 09/10 01:35 — La regle « la nuit prepare le soir » etait muette : corrigee
- Nuit du 08-09/10 : VRM annonce **14,7 kWh** le 09/10 et la regle ne s'est pas declenchee. Cause (rejeu de l'entree
reelle de 01:30) : `nextDeficitWindow` prenait le creux de 12 h (apres un surplus a 11 h) pour « la traversee du
soir » -> 22,1 % requis. Defaut de ma version du 08/10.
- **Nouvelle definition** : de la fin de la fenetre de nuit au debut de la NUIT SUIVANTE (01:24), la batterie ne doit
jamais passer sous le plancher (profil habituel, solaire ecrete a 100 %, HC de midi NON comptee) ; plus petite
energie de fin de nuit qui le garantit (recherche dichotomique sur l'energie au-dessus du plancher) ; hors
d'atteinte -> le mieux possible sans gaspiller. Sorties `nightEvening.floorPct`, `shortfallKwh`. Raison : « Journee
faible annoncee : la nuit charge a x % (sinon il manquerait y kWh avant la nuit prochaine…) ».
- Tests bilan 92/92 : nouveau scenario sur l'ENTREE REELLE du 09/10 01:30 (`tests/energy-balance/fixtures/bilan-0910-0130.json`)
-> 50,4 % (27,1 + 7 / 0,30, verifie a la main) ; les deux rejeux du 27/09 reviennent a leurs valeurs d'avant le 08/10
(25,3 % ; requis 41,4 %) : avec 18-25 kWh la journee remonte assez la batterie au profil habituel. Mutations : regle
coupee -> 1 ; minimum du parcours ignore -> 1.
- Deploye 01:37 ; calcul reel 01:40 : plancher et requis **50,4 %** (SOC 55 %). La session de nuit s'engagera quand le
SOC passera sous 50,4 % (la comparaison se fait au SOC courant : la batterie descend d'abord a 50,4 % puis est tenue).
--- ---
*Document mis a jour le : 08/10/2026 (~08:00) — conseiller relecteur de la journee (revue calculee + pistes) ; nuit : voiture qui profite d'EDF ferme pour la batterie, nuit qui prepare le soir les jours annonces faibles ; 08/10/2026 (~00:15) — jour faible : charge maximale en HC de midi (bilan 100 %, ACM plafond + solaire) ; 07/10/2026 (~22:45) — reprise de 16 h verifiee, EDF en HP a 18:03, bilan : reprise du soir et prevision recalee sur la matinee ; bilan HC de midi, reprise du soir apres coupure en pleine chauffe, mesure ECS sur 10 min ; recharge reseau HC : courant calcule (12 kVA) et consigne non tenue ; HC de midi : AUTO ne chauffe pas (BOOST), alerte v2, Jeep (contact, relecture, zone), recharge batterie bridee a 30 A par l'ACM ; jour faible = EDF les deux fenetres HC pour le chauffe-eau, alerte BOOST, Victron Mobile refait ; 1re decision jour faible en reel, mesure ECS reparee, douches 06:00/20:00, Grafana chauffe-eau, tableaux ECS ; chauffe-eau en HC de nuit les jours faibles (decision du bilan, workflow ECS sous controle du depot, VRM, V2.3 desactive) ; garde batterie reseau : voiture bridee a 6 A au lieu d'un arret ; garde-fou concierge 2 h ; allers-retours brefs de la borne retrouves dans l'historique HA (echange VW -> JEEP de 20 s), production 14,5 kWh contre 22 prevus, defaut garde batterie en mode reseau a trancher ; voiture a midi : la batterie n'est plus comptee comme du solaire (EDF automatique les jours de depart si le surplus ne couvre pas) ; nuit 05->06 : JEEP chargee sur EDF en complete (6,32 kWh), budget 0 du a la prevision, Windy/ECMWF contre VRM, a comparer ce soir ; concierge aligne sur la preuve fin de trajet ; a verifier : nuit 05->06 et mardi faible, comparaison VRM/Forecast.Solar ; JEEP identifiee aussi par sa fin de trajet, prevision de la nuit 05->06 ; identification JEEP reussie en reel, correction humaine qui prime (refus supprime), session du 04/10 coupee ; EVCS Master en quatre onglets ; Jeep lue par Uconnect et identifiee seule, capacite JEEP 10 kWh, concierge variante (a) ; releve par photo (SPARK), hass-uconnect en attente ; ecart semaine/week-end mesure (pas de profil week-end la nuit) ; 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* *Document mis a jour le : 09/10/2026 (~01:45) — conseiller relecteur de la journee (revue calculee + pistes) ; nuit : voiture qui profite d'EDF ferme pour la batterie, nuit qui prepare le soir les jours annonces faibles ; 08/10/2026 (~00:15) — jour faible : charge maximale en HC de midi (bilan 100 %, ACM plafond + solaire) ; 07/10/2026 (~22:45) — reprise de 16 h verifiee, EDF en HP a 18:03, bilan : reprise du soir et prevision recalee sur la matinee ; bilan HC de midi, reprise du soir apres coupure en pleine chauffe, mesure ECS sur 10 min ; recharge reseau HC : courant calcule (12 kVA) et consigne non tenue ; HC de midi : AUTO ne chauffe pas (BOOST), alerte v2, Jeep (contact, relecture, zone), recharge batterie bridee a 30 A par l'ACM ; jour faible = EDF les deux fenetres HC pour le chauffe-eau, alerte BOOST, Victron Mobile refait ; 1re decision jour faible en reel, mesure ECS reparee, douches 06:00/20:00, Grafana chauffe-eau, tableaux ECS ; chauffe-eau en HC de nuit les jours faibles (decision du bilan, workflow ECS sous controle du depot, VRM, V2.3 desactive) ; garde batterie reseau : voiture bridee a 6 A au lieu d'un arret ; garde-fou concierge 2 h ; allers-retours brefs de la borne retrouves dans l'historique HA (echange VW -> JEEP de 20 s), production 14,5 kWh contre 22 prevus, defaut garde batterie en mode reseau a trancher ; voiture a midi : la batterie n'est plus comptee comme du solaire (EDF automatique les jours de depart si le surplus ne couvre pas) ; nuit 05->06 : JEEP chargee sur EDF en complete (6,32 kWh), budget 0 du a la prevision, Windy/ECMWF contre VRM, a comparer ce soir ; concierge aligne sur la preuve fin de trajet ; a verifier : nuit 05->06 et mardi faible, comparaison VRM/Forecast.Solar ; JEEP identifiee aussi par sa fin de trajet, prevision de la nuit 05->06 ; identification JEEP reussie en reel, correction humaine qui prime (refus supprime), session du 04/10 coupee ; EVCS Master en quatre onglets ; Jeep lue par Uconnect et identifiee seule, capacite JEEP 10 kWh, concierge variante (a) ; releve par photo (SPARK), hass-uconnect en attente ; ecart semaine/week-end mesure (pas de profil week-end la nuit) ; 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

@@ -473,42 +473,51 @@ const hcMaxCharge = hcMiddayWindow && ecsNuitAujourdhui;
// « tenir jusqu'a midi »). // « tenir jusqu'a midi »).
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
const hcNightWindow = hcReachActive && !hcMiddayWindow; const hcNightWindow = hcReachActive && !hcMiddayWindow;
// ⚠️ CORRIGE LE 09/10 : la premiere version (08/10) visait « le debut de la
// traversee du soir » = la premiere fenetre de deficit apres le premier surplus.
// Nuit du 08-09/10 (VRM 14,7 kWh) : un surplus a 11 h puis un creux a 12 h ->
// « traversee » a 12 h, 22 % requis, regle muette alors que la journee etait
// faible. L'objectif est maintenant sans ambiguite : de la fin de la fenetre de
// nuit au debut de la NUIT SUIVANTE (01:24), la batterie ne doit JAMAIS passer
// sous le plancher, au profil habituel, solaire ecrete a 100 %. La HC de midi
// n'est pas comptee (prudence : elle est bornee par la maison, et ce que la nuit
// charge au meme prix ne lui coute rien).
let nightEvening = { active: false }; let nightEvening = { active: false };
if (hcNightWindow) { if (hcNightWindow && Number.isFinite(nextNightHcStartMs) && nextNightHcStartMs > hcWindowEndMs) {
const ndEve = nextDeficitWindow(slotsP50); const capKwh = energyBetweenSoc(socFloorPct, 100);
if (ndEve.found && Number.isFinite(num(ndEve.startMs, NaN)) && ndEve.startMs > hcWindowEndMs) { // Energie minimale au-dessus du plancher sur le parcours, depuis e0 en fin de nuit.
const socAtEvening = (s0) => { const minAuDessus = (e0) => {
let soc = s0; let e = e0, mini = e0;
for (const sl of slotsP50) { for (const sl of slotsP50) {
const debut = num(sl.startMs, NaN); const debut = num(sl.startMs, NaN);
if (!Number.isFinite(debut)) continue; if (!Number.isFinite(debut)) continue;
const a = Math.max(debut, hcWindowEndMs); const a = Math.max(debut, hcWindowEndMs);
const b = Math.min(debut + 3600000, ndEve.startMs); const b = Math.min(debut + 3600000, nextNightHcStartMs);
if (b <= a) continue; if (b <= a) continue;
const net = sl.netKwh * (b - a) / 3600000; e = Math.min(capKwh, e + sl.netKwh * (b - a) / 3600000);
soc = net >= 0 ? Math.min(100, socAfterEnergy(soc, net)) if (e < mini) mini = e;
: Math.max(socFloorPct, socForEnergy(Math.max(0, energyBetweenSoc(0, soc) + net), 0));
} }
return soc; return mini;
}; };
const requis = ndEve.requiredSocAtStartPct;
const base = Math.max(socFloorPct, hcFloorReachPct); const base = Math.max(socFloorPct, hcFloorReachPct);
const objectif = Math.min(requis, socAtEvening(100)) - 0.05; const eBase = energyBetweenSoc(socFloorPct, base);
let floor = base; // Hors d'atteinte meme pleine : on vise le mieux possible, sans gaspiller.
if (socAtEvening(base) < objectif) { const objectif = Math.min(0, minAuDessus(capKwh)) - 0.01;
let lo = base, hi = 100; let e0 = eBase;
for (let k = 0; k < 30; k++) { const m = (lo + hi) / 2; if (socAtEvening(m) >= objectif) hi = m; else lo = m; } if (minAuDessus(eBase) < objectif) {
floor = hi; let lo = eBase, hi = capKwh;
for (let k = 0; k < 40; k++) { const m = (lo + hi) / 2; if (minAuDessus(m) >= objectif) hi = m; else lo = m; }
e0 = hi;
} }
const floor = socAfterEnergy(socFloorPct, e0);
nightEvening = { nightEvening = {
active: floor > base + 0.05, active: floor > base + 0.05,
floorPct: round1(floor), floorPct: round1(Math.min(100, floor)),
eveningRequiredPct: round1(requis), // Sans relever : energie qui manquerait sous le plancher avant la nuit suivante.
eveningProjectedPct: round1(socAtEvening(base)), shortfallKwh: round2(Math.max(0, -minAuDessus(eBase))),
eveningStartHour: ndEve.startHour, untilMs: nextNightHcStartMs,
}; };
} }
}
const hcFloorTypicalPct = hcMaxCharge ? 100 const hcFloorTypicalPct = hcMaxCharge ? 100
: (nightEvening.active ? Math.max(hcFloorReachPct, nightEvening.floorPct) : hcFloorReachPct); : (nightEvening.active ? Math.max(hcFloorReachPct, nightEvening.floorPct) : hcFloorReachPct);
const hcFloorPrudentPct = hcMaxCharge ? 100 : hcFloorPrudentReachPct; const hcFloorPrudentPct = hcMaxCharge ? 100 : hcFloorPrudentReachPct;

View File

@@ -162,7 +162,7 @@ const reason = balance.crossing.found
const hc = balance.hcReach || {}; const hc = balance.hcReach || {};
const ne = hc.nightEvening || {}; const ne = hc.nightEvening || {};
const hcText = hc.active && ne.active const hcText = hc.active && ne.active
? ` Journée faible annoncée : la nuit charge à ${ne.floorPct}% (batterie prévue à ${ne.eveningProjectedPct}% à ${ne.eveningStartHour} h pour ${ne.eveningRequiredPct}% requis ; tenir jusqu'à midi demandait ${hc.floorReachPct}%).` ? ` Journée faible annoncée : la nuit charge à ${ne.floorPct}% (sinon il manquerait ${ne.shortfallKwh} kWh avant la nuit prochaine ; tenir jusqu'à midi demandait ${hc.floorReachPct}%).`
: hc.active && hc.maxCharge : hc.active && hc.maxCharge
? ` Jour faible : charge maximale en HC de midi (tenir jusqu'à ${new Date(hc.nextHcStartMs).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' })} demandait ${hc.floorReachPct}%).` ? ` Jour faible : charge maximale en HC de midi (tenir jusqu'à ${new Date(hc.nextHcStartMs).toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' })} demandait ${hc.floorReachPct}%).`
: hc.active && hc.raisesRequirement : hc.active && hc.raisesRequirement

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
import fs from 'node:fs';
// Scénarios du nœud BALANCE. // Scénarios du nœud BALANCE.
// Le scénario central est un REJEU de la matinée du 24/08/2026 : la production // Le scénario central est un REJEU de la matinée du 24/08/2026 : la production
// horaire est celle réellement mesurée ce jour-là (heures 06-09 h non // horaire est celle réellement mesurée ce jour-là (heures 06-09 h non
@@ -421,8 +422,10 @@ export const balanceScenarios = [
// 59,6 % a 19 h pour 64,7 % requis -> +5,1 points (courbe plate, pas // 59,6 % a 19 h pour 64,7 % requis -> +5,1 points (courbe plate, pas
// d'ecretage) : 25,3 + 5,0 = 30,3 % en fin de nuit. Le 27/09 reel (22 kWh) // d'ecretage) : 25,3 + 5,0 = 30,3 % en fin de nuit. Le 27/09 reel (22 kWh)
// a vide la batterie a 01:00 : le sens est le bon. // a vide la batterie a 01:00 : le sens est le bon.
// 09/10 : la regle « la nuit prepare jusqu'a la nuit suivante » ne joue pas ici :
// au profil habituel, 25,1 kWh remontent assez la batterie pour passer 01:24.
expect: { usable: true, requiredSocPct: 40.2, prudentRequiredSocPct: 48.4, gridNeeded: false, expect: { usable: true, requiredSocPct: 40.2, prudentRequiredSocPct: 48.4, gridNeeded: false,
hcReachActive: true, nightEveningActive: true, hcFloorPct: 30.3, hcNightFloorPrudentPct: 48.4 }, hcReachActive: true, nightEveningActive: false, hcFloorPct: 25.3, hcNightFloorPrudentPct: 48.4 },
}, },
{ {
// Meme nuit, journee annoncee sombre (18 kWh, prevision VRM du 27) : le // Meme nuit, journee annoncee sombre (18 kWh, prevision VRM du 27) : le
@@ -432,7 +435,7 @@ export const balanceScenarios = [
id: 'bilan-hc-nuit-2709-journee-sombre-bloque-p12', id: 'bilan-hc-nuit-2709-journee-sombre-bloque-p12',
label: 'Nuit 26-27/09, journee sombre (18 kWh) : P12 plancher 72,1 % > SOC 72 → voiture sur le reseau HC', label: 'Nuit 26-27/09, journee sombre (18 kWh) : P12 plancher 72,1 % > SOC 72 → voiture sur le reseau HC',
snapshot: snap2709('2026-09-27T01:20:00+02:00', 72, NUIT_2709, SOMBRE), snapshot: snap2709('2026-09-27T01:20:00+02:00', 72, NUIT_2709, SOMBRE),
expect: { usable: true, requiredSocPct: 54, prudentRequiredSocPct: 72.1, gridNeeded: false, expect: { usable: true, requiredSocPct: 41.4, prudentRequiredSocPct: 72.1, gridNeeded: false,
hcReachActive: true, hcNightFloorPrudentPct: 72.1 }, hcReachActive: true, hcNightFloorPrudentPct: 72.1 },
}, },
{ {
@@ -602,9 +605,19 @@ export const balanceScenarios = [
label: 'Fenetre HC de NUIT, decision jour faible du jour : la nuit garde « tenir jusqu a midi »', label: 'Fenetre HC de NUIT, decision jour faible du jour : la nuit garde « tenir jusqu a midi »',
snapshot: Object.assign(snap2709('2026-09-27T01:20:00+02:00', 72, NUIT_2709), snapshot: Object.assign(snap2709('2026-09-27T01:20:00+02:00', 72, NUIT_2709),
{ ecs_nuit_jour: '2026-09-27|on|x' }), { ecs_nuit_jour: '2026-09-27|on|x' }),
// Pas de charge maximale la nuit ; la nuit prepare le soir (30,3 %, voir le expect: { usable: true, hcReachActive: true, hcMaxCharge: false, hcFloorPct: 25.3 },
// scenario du 27/09 ci-dessus). },
expect: { usable: true, hcReachActive: true, hcMaxCharge: false, nightEveningActive: true, hcFloorPct: 30.3 }, {
// ENTREE REELLE du 09/10 01:30 (PREPARE_BALANCE, VRM 14,7 kWh) : la version du
// 08/10 prenait le creux de 12 h (apres un surplus a 11 h) pour la « traversee du
// soir » et restait muette. Au profil habituel, sans relever, il manque 7 kWh
// sous le plancher avant 01:24 le 10/10 -> fin de nuit a 27,1 + 7 / 0,30 = 50,4 %
// (verifie a la main, courbe plate, pas d'ecretage).
node: 'BALANCE',
id: 'bilan-hc-nuit-0910-journee-faible-reelle',
label: 'Nuit du 08-09/10, journee faible (VRM 14,7 kWh) : la nuit charge a 50,4 % pour passer la soiree',
snapshot: JSON.parse(fs.readFileSync(new URL('./fixtures/bilan-0910-0130.json', import.meta.url), 'utf8')),
expect: { usable: true, hcReachActive: true, nightEveningActive: true, hcFloorPct: 50.4 },
}, },
{ {
node: 'BALANCE', node: 'BALANCE',

File diff suppressed because one or more lines are too long