From ec5f43ecff17846f7c37a340fc677fe214c7c575 Mon Sep 17 00:00:00 2001 From: Tallagrand Thierry Date: Thu, 24 Sep 2026 15:30:11 +0200 Subject: [PATCH] rapport-nuit : l'import etait extrapole sur les heures futures de la fenetre Signale par la session SPARK via les compteurs d'energie Victron : le rapport du 24/09 annoncait 4,2 kWh importes contre ~1,6 mesure. Cause reelle (verifiee en base) : le rapport tourne a 06:47 mais sa fenetre va jusqu'a 10:00 ; l'ancienne methode (grille generate_series 1 min + derniere valeur connue + sum/60000) reportait la derniere puissance connue sur les ~3 h futures -> import fantome. Ce n'est PAS la cadence d'echantillonnage (donnees eparses, 130 pts/961 min) et les compteurs d'energie ne sont pas dans la table mesure (seul grid_power_l1). Fix : integration ponderee par l'intervalle reel entre points (cap 300 s, jamais d'extrapolation apres le dernier echantillon) et fenetre plafonnee a now(). Applique aux 4 grandeurs integrees (importe/injecte/voiture/batterie_chargee). Verifie : 1,60 kWh sur fenetre complete (= compteurs), 1,19 en simulation 06:47. Tous les rapports depuis le 08/09 surestimaient ces kWh ; le reste (SOC, fermetures, vehicule, injection) etait juste. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj --- tools/rapport-nuit.mjs | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/tools/rapport-nuit.mjs b/tools/rapport-nuit.mjs index 0d13b0a..625d195 100644 --- a/tools/rapport-nuit.mjs +++ b/tools/rapport-nuit.mjs @@ -76,23 +76,30 @@ export async function texteRapport(client, jour = new Date()) { AND ts >= $1 AND ts < $2 ORDER BY ts`, [debut, fin]); const fermetures = bascules.filter((b) => b.apres === 'on'); + // Integration ponderee par l'intervalle REEL entre points (cap 300 s pour + // qu'un trou n'extrapole rien), et fenetre plafonnee a now(). Le rapport + // tourne a 06:47 alors que la fenetre va jusqu'a 10:00 : l'ancienne methode + // (grille generate_series 1 min + derniere valeur connue + sum/60000) + // reportait la derniere puissance connue sur les ~3 h FUTURES de la fenetre + // et surestimait l'import (3,8 kWh reconstitue au lieu de 1,6 mesure, nuit du + // 24/09, signale par la session SPARK via les compteurs d'energie Victron). + // NE JAMAIS revenir a sum(x)/60000 sur une grille qui depasse maintenant : + // elle integre du futur. La ponderation par lead(ts) n'a pas de point apres + // le dernier echantillon, donc elle n'extrapole jamais. const { rows: [e] } = await q( - `WITH mn AS (SELECT generate_series($1::timestamptz, $2::timestamptz, interval '1 minute') t), - j AS (SELECT mn.t, - (SELECT valeur FROM mesure m WHERE m.cle = 'sensor.victron_grid_power_l1' - AND m.ts <= mn.t ORDER BY m.ts DESC LIMIT 1) g, - (SELECT valeur FROM mesure m WHERE m.cle = 'sensor.victron_evcs_ac_power' - AND m.ts <= mn.t ORDER BY m.ts DESC LIMIT 1) v, - (SELECT valeur FROM mesure m WHERE m.cle = 'sensor.victron_battery_power' - AND m.ts <= mn.t ORDER BY m.ts DESC LIMIT 1) b - FROM mn) - SELECT coalesce(sum(g) FILTER (WHERE g > 0), 0) / 60000.0 AS importe, - coalesce(-sum(g) FILTER (WHERE g < 0), 0) / 60000.0 AS injecte, - coalesce(sum(v) FILTER (WHERE v > 0), 0) / 60000.0 AS voiture, - coalesce(sum(b) FILTER (WHERE b > 0), 0) / 60000.0 AS batterie_chargee, - count(*) FILTER (WHERE g < 0) AS points_negatifs, - min(g) AS pire_injection - FROM j`, [debut, fin]); + `WITH b AS (SELECT $1::timestamptz d, LEAST($2::timestamptz, now()) f), + g AS (SELECT valeur v, LEAST(EXTRACT(EPOCH FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) dt + FROM mesure, b WHERE cle = 'sensor.victron_grid_power_l1' AND ts >= d AND ts < f), + ev AS (SELECT valeur v, LEAST(EXTRACT(EPOCH FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) dt + FROM mesure, b WHERE cle = 'sensor.victron_evcs_ac_power' AND ts >= d AND ts < f), + ba AS (SELECT valeur v, LEAST(EXTRACT(EPOCH FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) dt + FROM mesure, b WHERE cle = 'sensor.victron_battery_power' AND ts >= d AND ts < f) + SELECT (SELECT coalesce(sum(GREATEST(v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM g) AS importe, + (SELECT coalesce(sum(GREATEST(-v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM g) AS injecte, + (SELECT coalesce(sum(GREATEST(v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM ev) AS voiture, + (SELECT coalesce(sum(GREATEST(v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM ba) AS batterie_chargee, + (SELECT count(*) FROM g WHERE v < 0) AS points_negatifs, + (SELECT min(v) FROM g) AS pire_injection`, [debut, fin]); if (!fermetures.length) { dire('Le contacteur est reste ouvert toute la nuit : aucune energie achetee au reseau.');