Bilan : reprise du chauffe-eau du soir comptee les jours faibles, prevision de l'apres-midi recalee sur la matinee

This commit is contained in:
Tallagrand Thierry
2026-10-07 22:47:49 +02:00
parent 16d1f8ee2b
commit 36ceb41933
8 changed files with 136 additions and 7 deletions

View File

@@ -184,13 +184,61 @@ function nightScaled(profile, slot) {
return (profile[h] ?? 0) * (1 + (nightFactor - 1) * nightShare(slot.startMs));
}
// =====================================================================
// JOUR FAIBLE : CE QUE LA JOURNEE DIT DEJA (07/10/2026, proprietaire)
// =====================================================================
// Le 07/10 la HC de midi visait 44 % (« arriver a 01:24 au-dessus de 22 % »),
// et EDF a quand meme ete ferme en HEURES PLEINES a 18:03 (~8 kWh HP). Deux
// erreurs du calcul, corrigees ici :
// 1. La reprise du chauffe-eau de 16 h a 20 h (douche de 20 h, 2,1 kWh mesures
// le 07/10) n'etait pas comptee : les jours faibles (decision du bilan),
// `ecs_reprise_soir_kwh` est ajoute a la consommation de 16 h a 20 h du jour.
// 2. La prevision de l'apres-midi n'etait pas recalee sur la matinee : VRM
// annoncait ~9 kWh de 14 h a 19 h, il en est venu ~5, et la matinee le
// disait deja (3,1 kWh produits a 12:36 pour ~5 prevus). Quand le reel du
// jour est EN RETARD sur la prevision des heures ecoulees (au moins 2 kWh
// prevus, production complete), les heures restantes du jour sont
// multipliees par le meme rapport, borne a [0,4 ; 1]. Jamais a la hausse :
// une matinee meilleure que prevu ne promet rien pour l'apres-midi.
const ECS_SOIR_H0 = 16;
const ECS_SOIR_H1 = 20;
const nowMsSlots = num(input.nowMs, Date.now());
const jourParis = (ms) => new Date(ms).toLocaleDateString('sv-SE', { timeZone: 'Europe/Paris' });
const aujourdhuiParis = jourParis(nowMsSlots);
function slotIsToday(slot) {
return Number.isFinite(num(slot.startMs, NaN)) && jourParis(num(slot.startMs, 0)) === aujourdhuiParis;
}
const ecsNuitAujourdhui = typeof input.ecs_nuit_jour === 'string'
&& input.ecs_nuit_jour.startsWith(aujourdhuiParis + '|on');
const ecsRepriseSoirKwh = ecsNuitAujourdhui ? Math.max(0, num(input.ecs_reprise_soir_kwh, 0)) : 0;
let prodRecal = { factor: 1, active: false };
{
const prevu = typeof input.forecast_today_csv === 'string' ? input.forecast_today_csv.split(',').map(Number) : [];
const reel = num(input.production_jour_kwh, NaN);
if (prevu.length === 24 && prevu.every(Number.isFinite) && Number.isFinite(reel)
&& input.production_jour_statut === 'complet') {
const p = new Intl.DateTimeFormat('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit', hour12: false })
.format(new Date(nowMsSlots)).split(':').map(Number);
let ecoule = 0;
for (let h = 0; h < 24; h++) ecoule += h < p[0] ? prevu[h] : (h === p[0] ? prevu[h] * p[1] / 60 : 0);
if (ecoule >= 2) {
const r = reel / ecoule;
prodRecal = { factor: round2(Math.max(0.4, Math.min(1, r))), active: r < 1, ratio: round2(r),
realKwh: round2(reel), expectedSoFarKwh: round2(ecoule) };
}
}
}
const prodRecalFactor = prodRecal.factor;
function buildSlots(consumption, prudent = false) {
return horizon.slice(0, CFG.SEARCH_HOURS).map(slot => {
const hour = num(slot.hour, 0);
const productionKwh = Math.max(0, num(slot.expectedKwh, 0));
const consumptionKwh = prudent
const aujourdhui = slotIsToday(slot);
const productionKwh = Math.max(0, num(slot.expectedKwh, 0)) * (aujourdhui ? prodRecalFactor : 1);
const consumptionKwh = (prudent
? Math.max(consumption[hour] ?? 0, nightScaled(consumptionP50, slot))
: nightScaled(consumption, slot);
: nightScaled(consumption, slot))
+ (aujourdhui && hour >= ECS_SOIR_H0 && hour < ECS_SOIR_H1 ? ecsRepriseSoirKwh / (ECS_SOIR_H1 - ECS_SOIR_H0) : 0);
return {
startMs: slot.startMs,
hour,
@@ -800,6 +848,9 @@ return [{
// Chauffe-eau en HC de nuit les jours faibles (07/10).
ecsNight,
// Reprise du soir et prevision du jour recalee sur la matinee (07/10).
ecsReprise: { active: ecsRepriseSoirKwh > 0, kwh: round2(ecsRepriseSoirKwh) },
prodRecal,
// Nuit prevue par la soiree (28/09) : facteur applique au profil.
nightModel: {

View File

@@ -82,6 +82,9 @@
"night_hc_start_ms": (as_timestamp(ns.nd) * 1000) if ns.nd != '' else none,
"night_hc_end_ms": (as_timestamp(ns.nf) * 1000) if ns.nf != '' else none,
"ecs_nuit_jour": states("input_text.evcs_balance_ecs_nuit_jour"),
"ecs_reprise_soir_kwh": states("input_number.evcs_chauffe_eau_reprise_soir_kwh"),
"production_jour_kwh": states("sensor.evcs_production_jour_kwh"),
"production_jour_statut": state_attr("sensor.evcs_production_jour_kwh", "statut"),
"nowMs": as_timestamp(now()) * 1000,
"balance_enabled": states("input_boolean.evcs_balance_enabled"),

View File

@@ -83,6 +83,12 @@ return [{
night_hc_end_ms: snapshot.night_hc_end_ms,
// Decision du jour pour le chauffe-eau (07/10) : « AAAA-MM-JJ|on|raison ».
ecs_nuit_jour: snapshot.ecs_nuit_jour,
// 07/10 : reprise du chauffe-eau le soir (jour faible) et production reelle
// du jour pour recaler la prevision de l'apres-midi.
ecs_reprise_soir_kwh: snapshot.ecs_reprise_soir_kwh,
production_jour_kwh: snapshot.production_jour_kwh,
production_jour_statut: snapshot.production_jour_statut,
forecast_today_csv: snapshot.forecast_today_csv,
// Repris pour l'écriture des helpers, sans influencer le calcul.
resourceUsable: resource.usable,