Bilan : la nuit prevue suit la soiree reelle (regression sur 24 nuits), nuage Grafana corrige
Le service publie le facteur nuit prevue / nuit moyenne (sensor.evcs_nuit_maison_prevue_kwh), le bilan l'applique a son profil de 21:30 a 09:00. Diagnostics 81/81, bilan 68/68. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z
This commit is contained in:
@@ -140,11 +140,57 @@ if (missing.length) {
|
||||
// - profil PRUDENT (p80) → planification : cible de nuit, dimensionnement
|
||||
// du stockage, choix qui engagent plusieurs heures.
|
||||
// ---------------------------------------------------------------------
|
||||
function buildSlots(consumption) {
|
||||
// ---------------------------------------------------------------------
|
||||
// LA NUIT PREVUE PAR LA SOIREE (28/09/2026, demande du proprietaire)
|
||||
//
|
||||
// Le profil p50 est une mediane HORAIRE : il ignore la soiree qui vient de se
|
||||
// passer. Or sur 24 nuits, la maison consomme d'autant plus de 21:30 a 09:00
|
||||
// que la soiree 18:00-21:30 a ete chargee (four) : nuit = 6,98 + 0,74 x soiree,
|
||||
// r2 = 0,40. Le service d'historisation publie le FACTEUR nuit prevue / nuit
|
||||
// moyenne (sensor.evcs_nuit_maison_prevue_kwh) ; on multiplie le profil par ce
|
||||
// facteur sur la seule fenetre 21:30 -> 09:00, au prorata des heures a cheval.
|
||||
// Le 28/09 : soiree 6,27 kWh -> facteur 1,09.
|
||||
//
|
||||
// Un FACTEUR et non des kWh : le profil vient des compteurs d'energie, la
|
||||
// regression des puissances integrees ; le rapport annule l'ecart de methode.
|
||||
// Dans les deux sens pour le profil TYPIQUE (c'est un estimateur, une soiree
|
||||
// legere allege la nuit) ; le PRUDENT ne descend jamais : max(p80, p50 recale).
|
||||
// Filet : facteur borne a [0,75 ; 1,45], les extremes des 24 nuits observees
|
||||
// (0,77 et 1,41 fois la moyenne) — hors de la, c'est une mesure fausse, pas une
|
||||
// nuit. Facteur absent, perime (> 30 min) ou hors fenetre : profil inchange.
|
||||
const NIGHT_FACTOR_MIN = 0.75;
|
||||
const NIGHT_FACTOR_MAX = 1.45;
|
||||
const nowMsNight = num(input.nowMs, Date.now());
|
||||
const nightStartMs = num(input.night_house_start_ms, NaN);
|
||||
const nightEndMs = num(input.night_house_end_ms, NaN);
|
||||
const nightFactorRaw = num(input.night_house_factor, NaN);
|
||||
const nightComputedMs = num(input.night_house_computed_ms, NaN);
|
||||
const nightModelActive =
|
||||
Number.isFinite(nightFactorRaw) && nightFactorRaw > 0 &&
|
||||
Number.isFinite(nightStartMs) && Number.isFinite(nightEndMs) && nightEndMs > nightStartMs &&
|
||||
nowMsNight >= nightStartMs && nowMsNight < nightEndMs &&
|
||||
Number.isFinite(nightComputedMs) && nowMsNight - nightComputedMs <= 30 * 60000;
|
||||
const nightFactor = nightModelActive
|
||||
? Math.min(NIGHT_FACTOR_MAX, Math.max(NIGHT_FACTOR_MIN, nightFactorRaw))
|
||||
: 1;
|
||||
function nightShare(startMs) {
|
||||
const debut = num(startMs, NaN);
|
||||
if (!nightModelActive || !Number.isFinite(debut)) return 0;
|
||||
const a = Math.max(debut, nightStartMs), b = Math.min(debut + 3600000, nightEndMs);
|
||||
return b > a ? (b - a) / 3600000 : 0;
|
||||
}
|
||||
function nightScaled(profile, slot) {
|
||||
const h = num(slot.hour, 0);
|
||||
return (profile[h] ?? 0) * (1 + (nightFactor - 1) * nightShare(slot.startMs));
|
||||
}
|
||||
|
||||
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 = consumption[hour] ?? 0;
|
||||
const consumptionKwh = prudent
|
||||
? Math.max(consumption[hour] ?? 0, nightScaled(consumptionP50, slot))
|
||||
: nightScaled(consumption, slot);
|
||||
return {
|
||||
startMs: slot.startMs,
|
||||
hour,
|
||||
@@ -341,7 +387,7 @@ function coverage(slots, hcFloorPct = 0) {
|
||||
}
|
||||
|
||||
const slotsP50 = buildSlots(consumptionP50);
|
||||
const slotsP80 = buildSlots(consumptionP80);
|
||||
const slotsP80 = buildSlots(consumptionP80, true);
|
||||
const hcFloorTypicalPct = hcReachFloorPct(slotsP50, nextHcStartMs);
|
||||
const hcFloorNightPrudentPct = hcNightReachActive ? hcReachFloorPct(slotsP80, nextNightHcStartMs) : 0;
|
||||
const hcFloorPrudentPct = Math.max(hcReachFloorPct(slotsP80, nextHcStartMs), hcFloorNightPrudentPct);
|
||||
@@ -613,6 +659,14 @@ return [{
|
||||
surplusAvailableKwh: round2(surplusAfterCrossingKwh),
|
||||
},
|
||||
|
||||
// Nuit prevue par la soiree (28/09) : facteur applique au profil.
|
||||
nightModel: {
|
||||
active: nightModelActive,
|
||||
factor: Math.round(nightFactor * 1000) / 1000,
|
||||
rawFactor: Number.isFinite(nightFactorRaw) ? nightFactorRaw : null,
|
||||
forecastKwh: num(input.night_house_forecast_kwh, NaN) || null,
|
||||
},
|
||||
|
||||
// Voiture branchee en HC, depart avant la prochaine HC (28/09).
|
||||
vehicleMidday: {
|
||||
active: vehicleRuleActive,
|
||||
|
||||
@@ -154,7 +154,11 @@ const hhmm = ms => new Date(ms).toLocaleTimeString('fr-FR', { timeZone: 'Europe/
|
||||
const vehText = vm.active
|
||||
? ` Voiture (départ ${hhmm(vm.departureMs)}) : ${vm.reachableKwh} kWh à donner, solaire ${vm.solarKwh} kWh → ${vm.gridNeeded ? 'réseau justifié' : 'solaire suffisant'}.`
|
||||
: '';
|
||||
setText('input_text.evcs_balance_reason', reason + hcText + vehText + degradedNote + ndText);
|
||||
const nm = balance.nightModel || {};
|
||||
const nightText = nm.active && Math.abs(nm.factor - 1) >= 0.005
|
||||
? ` Nuit recalée sur la soirée : conso ×${String(nm.factor).replace('.', ',')}${nm.forecastKwh ? ` (${String(nm.forecastKwh).replace('.', ',')} kWh prévus 21:30-09:00)` : ''}.`
|
||||
: '';
|
||||
setText('input_text.evcs_balance_reason', reason + nightText + hcText + vehText + degradedNote + ndText);
|
||||
|
||||
// Trace longue durée, pour comparer jour après jour le seuil calculé au seuil
|
||||
// fixe encore en vigueur — c'est cette comparaison qui décidera du câblage.
|
||||
|
||||
@@ -59,6 +59,11 @@
|
||||
{%- endif -%}
|
||||
{{ {
|
||||
"ts": now().isoformat(),
|
||||
"night_house_factor": state_attr("sensor.evcs_nuit_maison_prevue_kwh", "facteur"),
|
||||
"night_house_start_ms": state_attr("sensor.evcs_nuit_maison_prevue_kwh", "debut_ms"),
|
||||
"night_house_end_ms": state_attr("sensor.evcs_nuit_maison_prevue_kwh", "fin_ms"),
|
||||
"night_house_computed_ms": (as_timestamp(state_attr("sensor.evcs_nuit_maison_prevue_kwh", "calcule_a"), 0) * 1000),
|
||||
"night_house_forecast_kwh": states("sensor.evcs_nuit_maison_prevue_kwh"),
|
||||
"vehicle_connected": is_state("binary_sensor.victron_evcs_vehicle_connected", "on"),
|
||||
"vehicle_departure_ms": (as_timestamp(vh.dep) * 1000) if vh.dep != '' else none,
|
||||
"vehicle_level_pct": states("input_number.victron_evcs_vehicle_level_pct"),
|
||||
|
||||
@@ -60,6 +60,12 @@ return [{
|
||||
// Plancher de charge HC manuelle (18/09) : oublie ici jusqu'au 28/09, le
|
||||
// bouton n'avait donc jamais atteint le bilan.
|
||||
manual_hc_floor_percent: snapshot.manual_hc_floor_percent,
|
||||
// Nuit de la maison prevue par la soiree (28/09) : facteur sur le profil.
|
||||
night_house_factor: snapshot.night_house_factor,
|
||||
night_house_start_ms: snapshot.night_house_start_ms,
|
||||
night_house_end_ms: snapshot.night_house_end_ms,
|
||||
night_house_computed_ms: snapshot.night_house_computed_ms,
|
||||
night_house_forecast_kwh: snapshot.night_house_forecast_kwh,
|
||||
// Vehicule branche a midi (28/09, JEEP/VW/ZERO) : depart du jour et besoin saisi.
|
||||
vehicle_connected: snapshot.vehicle_connected,
|
||||
vehicle_departure_ms: snapshot.vehicle_departure_ms,
|
||||
|
||||
Reference in New Issue
Block a user