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:
@@ -677,3 +677,70 @@ export function vrmVersCourbes(paires, maintenant = new Date()) {
|
||||
updated: `${p2(d.getDate())}/${p2(d.getMonth() + 1)} ${p2(d.getHours())}:${p2(d.getMinutes())}`,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// La nuit de la maison, prevue par la soiree (28/09/2026)
|
||||
// ---------------------------------------------------------------------
|
||||
// Demande du proprietaire : « si tu superposes 20 nuits tu dois trouver un
|
||||
// nuage de points ». Sur 24 nuits (04/09 -> 27/09), l'energie consommee par la
|
||||
// maison de 21:30 a 09:00 (voiture exclue) suit celle de la soiree 18:00-21:30 :
|
||||
// nuit = 6,98 + 0,74 x soiree, r2 = 0,40. Une soiree chargee (four) annonce une
|
||||
// nuit chargee ; le profil horaire MOYEN du bilan ne le voit pas.
|
||||
//
|
||||
// Ce qui est publie n'est PAS un kWh a substituer au bilan : c'est un FACTEUR,
|
||||
// nuit prevue / nuit moyenne des MEMES donnees. Le profil du bilan vient des
|
||||
// compteurs d'energie, la regression des puissances integrees : le rapport
|
||||
// annule l'ecart de methode, que la difference ne ferait qu'ajouter.
|
||||
//
|
||||
// Garde-fous : au moins 15 nuits et r2 >= 0,2, sinon le modele ne vaut rien et
|
||||
// on le dit (`indisponible`). Hors 21:30 -> 09:00, pas de soiree complete a
|
||||
// lire : `hors_fenetre`.
|
||||
export const NUIT_MIN_NUITS = 15;
|
||||
export const NUIT_MIN_R2 = 0.2;
|
||||
|
||||
function heureLocaleMs(jour, hhmm, reference) {
|
||||
// Meme calcul de decalage que minuitLocal, pour une heure de Paris donnee.
|
||||
const decalage = new Date(reference.toLocaleString('en-US', { timeZone: 'Europe/Paris' })) -
|
||||
new Date(reference.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||
return Date.parse(`${jour}T${hhmm}:00Z`) - decalage;
|
||||
}
|
||||
|
||||
export function nuitMaisonPrevue(nuits, soireeKwh, maintenant = new Date()) {
|
||||
const jourDe = (d) => new Intl.DateTimeFormat('en-CA', { timeZone: 'Europe/Paris' }).format(d);
|
||||
const hm = Number(new Intl.DateTimeFormat('en-GB', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
.format(maintenant).replace(':', ''));
|
||||
const aujourdhui = jourDe(maintenant);
|
||||
const hier = jourDe(new Date(maintenant.getTime() - 86400000));
|
||||
const demain = jourDe(new Date(maintenant.getTime() + 86400000));
|
||||
const fenetre = hm >= 2130 ? { soir: aujourdhui, matin: demain } : hm < 900 ? { soir: hier, matin: aujourdhui } : null;
|
||||
const pts = (nuits || []).map((x) => [Number(x.soiree), Number(x.nuit)]).filter(([a, b]) => Number.isFinite(a) && Number.isFinite(b));
|
||||
const n = pts.length;
|
||||
let modele = null;
|
||||
if (n >= 2) {
|
||||
const mx = pts.reduce((s, p) => s + p[0], 0) / n, my = pts.reduce((s, p) => s + p[1], 0) / n;
|
||||
let sxy = 0, sxx = 0, syy = 0;
|
||||
for (const [x, y] of pts) { sxy += (x - mx) * (y - my); sxx += (x - mx) ** 2; syy += (y - my) ** 2; }
|
||||
if (sxx > 0 && syy > 0) {
|
||||
const b = sxy / sxx;
|
||||
modele = { a: my - b * mx, b, r2: (sxy * sxy) / (sxx * syy), moyenne: my };
|
||||
}
|
||||
}
|
||||
const r2 = (v) => Math.round(v * 100) / 100;
|
||||
const attributs = {
|
||||
nuits: n, soiree_kwh: Number.isFinite(soireeKwh) ? r2(soireeKwh) : null,
|
||||
a: modele ? r2(modele.a) : null, b: modele ? Math.round(modele.b * 1000) / 1000 : null,
|
||||
r2: modele ? r2(modele.r2) : null, nuit_moyenne_kwh: modele ? r2(modele.moyenne) : null,
|
||||
debut_ms: fenetre ? heureLocaleMs(fenetre.soir, '21:30', maintenant) : null,
|
||||
fin_ms: fenetre ? heureLocaleMs(fenetre.matin, '09:00', maintenant) : null,
|
||||
};
|
||||
if (!fenetre) return { etat: 'hors_fenetre', attributs: { ...attributs, facteur: null, statut: 'hors_fenetre' } };
|
||||
if (!modele || n < NUIT_MIN_NUITS || modele.r2 < NUIT_MIN_R2 || !Number.isFinite(soireeKwh)) {
|
||||
return { etat: 'indisponible', attributs: { ...attributs, facteur: null, statut: 'indisponible',
|
||||
explication: !Number.isFinite(soireeKwh) ? 'soiree illisible'
|
||||
: n < NUIT_MIN_NUITS ? `seulement ${n} nuits (${NUIT_MIN_NUITS} requises)`
|
||||
: `modele trop faible (r2 ${modele ? r2(modele.r2) : '?'} < ${NUIT_MIN_R2})` } };
|
||||
}
|
||||
const prevue = modele.a + modele.b * soireeKwh;
|
||||
return { etat: r2(prevue), attributs: { ...attributs, facteur: Math.round((prevue / modele.moyenne) * 1000) / 1000,
|
||||
statut: 'ok', explication: `soiree ${r2(soireeKwh)} kWh -> nuit prevue ${r2(prevue)} kWh (moyenne ${r2(modele.moyenne)}, ${n} nuits, r2 ${r2(modele.r2)})` } };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user