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:
18
HANDOFF.md
18
HANDOFF.md
@@ -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*
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
1
tests/energy-balance/fixtures/bilan-0910-0130.json
Normal file
1
tests/energy-balance/fixtures/bilan-0910-0130.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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
Reference in New Issue
Block a user