From 71a71e40d7bcad54fcadf83df3092d134d6cc4b9 Mon Sep 17 00:00:00 2001 From: Tallagrand Thierry Date: Tue, 29 Sep 2026 00:00:09 +0200 Subject: [PATCH] HA Bilan energetique : nuage soiree/nuit (ApexCharts), points publies dans sensor.evcs_nuit_maison_prevue_kwh Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_011uJ47NxgfpGnuuMr3f2n7Z --- dashboards/bilan_energetique.yaml | 75 +++++++++++++++++++++++++++++++ tools/historise-evcs.mjs | 14 +++--- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/dashboards/bilan_energetique.yaml b/dashboards/bilan_energetique.yaml index 7a14d1e..9ebe6e6 100644 --- a/dashboards/bilan_energetique.yaml +++ b/dashboards/bilan_energetique.yaml @@ -247,6 +247,81 @@ views: pleine à cette heure : au-delà de 25 %, la production maximale observée est minorée et le bilan le signale.* + # Nuage soiree / nuit (28/09/2026) : meme donnees que Grafana « La nuit », + # publiees par le service d'historisation dans sensor.evcs_nuit_maison_prevue_kwh + # (attribut `nuage` = [soiree, nuit, voiture 0/1]). Graphique « ligne » a trait + # nul + marqueurs = nuage de points ; axe X numerique (kWh), pas le temps. + - type: custom:apexcharts-card + header: + show: true + title: Soirée contre nuit — la soirée annonce la nuit + show_states: false + graph_span: 1d + apex_config: + chart: + height: 300 + zoom: {enabled: false} + xaxis: + type: numeric + min: 2 + max: 10.5 + tickAmount: 8 + decimalsInFloat: 1 + title: {text: 'Soirée 18:00-21:30 (kWh, voiture exclue)'} + yaxis: + - min: 7 + max: 16 + decimalsInFloat: 0 + title: {text: 'Nuit 21:30-09:00 (kWh)'} + markers: + size: [6, 6, 0, 11] + tooltip: + x: + formatter: 'EVAL:function(v) { return "soirée " + v.toFixed(2) + " kWh"; }' + legend: + show: true + series: + - entity: sensor.evcs_nuit_maison_prevue_kwh + name: Nuit sans voiture + type: line + stroke_width: 0 + color: '#4caf50' + data_generator: | + return (entity.attributes.nuage || []).filter(p => !p[2]) + .map(p => [p[0], p[1]]).sort((a, b) => a[0] - b[0]); + - entity: sensor.evcs_nuit_maison_prevue_kwh + name: Nuit avec voiture + type: line + stroke_width: 0 + color: '#2196f3' + data_generator: | + return (entity.attributes.nuage || []).filter(p => p[2]) + .map(p => [p[0], p[1]]).sort((a, b) => a[0] - b[0]); + - entity: sensor.evcs_nuit_maison_prevue_kwh + name: Régression + type: line + stroke_width: 2 + color: '#ff9800' + data_generator: | + const a = entity.attributes.a, b = entity.attributes.b, n = entity.attributes.nuage || []; + if (a === null || b === null || !n.length) return []; + const xs = n.map(p => p[0]), lo = Math.min(...xs), hi = Math.max(...xs); + return [[lo, a + b * lo], [hi, a + b * hi]]; + - entity: sensor.evcs_nuit_maison_prevue_kwh + name: Ce soir (prévu) + type: line + stroke_width: 0 + color: '#f44336' + data_generator: | + const s = entity.attributes.soiree_kwh, v = parseFloat(entity.state); + return (s !== null && s !== undefined && !isNaN(v)) ? [[s, v]] : []; + + - type: markdown + content: | + {% set s = 'sensor.evcs_nuit_maison_prevue_kwh' %} + {% if is_number(states(s)) %}**Cette nuit** : soirée {{ state_attr(s, 'soiree_kwh') }} kWh → nuit prévue **{{ states(s) }} kWh** (moyenne {{ state_attr(s, 'nuit_moyenne_kwh') }}), soit **×{{ state_attr(s, 'facteur') }}** sur le profil du bilan.{% else %}Hors de la fenêtre 21:30 → 09:00 : pas de prévision de nuit en cours.{% endif %} + Modèle sur {{ state_attr(s, 'nuits') }} nuits : nuit = {{ state_attr(s, 'a') }} + {{ state_attr(s, 'b') }} × soirée (r² {{ state_attr(s, 'r2') }}). + - type: markdown content: | ### ⚠️ Taille du parc — en équivalents US2000C diff --git a/tools/historise-evcs.mjs b/tools/historise-evcs.mjs index d1cfce0..f02c5e3 100644 --- a/tools/historise-evcs.mjs +++ b/tools/historise-evcs.mjs @@ -2015,19 +2015,23 @@ async function publierNuitMaison(client) { if (DRY || !client) return 0; const maintenant = new Date(); const cadre = nuitMaisonPrevue([], NaN, maintenant); - let res = cadre; + // Les nuits closes sont lues A CHAQUE fois, meme le jour : elles alimentent + // aussi le nuage du tableau HA (attribut `nuage`, [soiree, nuit, voiture 0/1]). + const nuits = (await client.query(NUITS_SQL + + '\nSELECT soiree_kwh AS soiree, maison_nuit_kwh AS nuit, voiture_kwh AS voiture FROM nuits ORDER BY j')).rows; + let soiree = NaN; if (cadre.etat !== 'hors_fenetre') { - const nuits = (await client.query(NUITS_SQL + '\nSELECT soiree_kwh AS soiree, maison_nuit_kwh AS nuit FROM nuits')).rows; const debut = cadre.attributs.debut_ms; const soir = (await client.query(SOIREE_SQL, [debut - 3.5 * 3600000, debut])).rows[0]; // Une soiree sans mesure (service arrete) ne vaut pas zero kWh. - const soiree = soir && Number(soir.points) > 0 ? Number(soir.kwh) : NaN; - res = nuitMaisonPrevue(nuits, soiree, maintenant); + soiree = soir && Number(soir.points) > 0 ? Number(soir.kwh) : NaN; } + const res = nuitMaisonPrevue(nuits, soiree, maintenant); + const nuage = nuits.map((n) => [Number(n.soiree), Number(n.nuit), Number(n.voiture) >= 0.5 ? 1 : 0]); await publierCapteur('sensor.evcs_nuit_maison_prevue_kwh', res.etat, { friendly_name: 'EVCS — nuit de la maison prevue par la soiree', icon: 'mdi:chart-scatter-plot', unit_of_measurement: typeof res.etat === 'number' ? 'kWh' : undefined, ...res.attributs, - calcule_a: maintenant.toISOString(), + nuage, calcule_a: maintenant.toISOString(), }); return 1; }