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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
This commit is contained in:
Tallagrand Thierry
2026-09-24 15:30:11 +02:00
parent a08820d827
commit ec5f43ecff

View File

@@ -76,23 +76,30 @@ export async function texteRapport(client, jour = new Date()) {
AND ts >= $1 AND ts < $2 ORDER BY ts`, [debut, fin]); AND ts >= $1 AND ts < $2 ORDER BY ts`, [debut, fin]);
const fermetures = bascules.filter((b) => b.apres === 'on'); 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( const { rows: [e] } = await q(
`WITH mn AS (SELECT generate_series($1::timestamptz, $2::timestamptz, interval '1 minute') t), `WITH b AS (SELECT $1::timestamptz d, LEAST($2::timestamptz, now()) f),
j AS (SELECT mn.t, g AS (SELECT valeur v, LEAST(EXTRACT(EPOCH FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) dt
(SELECT valeur FROM mesure m WHERE m.cle = 'sensor.victron_grid_power_l1' FROM mesure, b WHERE cle = 'sensor.victron_grid_power_l1' AND ts >= d AND ts < f),
AND m.ts <= mn.t ORDER BY m.ts DESC LIMIT 1) g, ev AS (SELECT valeur v, LEAST(EXTRACT(EPOCH FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) dt
(SELECT valeur FROM mesure m WHERE m.cle = 'sensor.victron_evcs_ac_power' FROM mesure, b WHERE cle = 'sensor.victron_evcs_ac_power' AND ts >= d AND ts < f),
AND m.ts <= mn.t ORDER BY m.ts DESC LIMIT 1) v, ba AS (SELECT valeur v, LEAST(EXTRACT(EPOCH FROM (lead(ts) OVER (ORDER BY ts) - ts)), 300) dt
(SELECT valeur FROM mesure m WHERE m.cle = 'sensor.victron_battery_power' FROM mesure, b WHERE cle = 'sensor.victron_battery_power' AND ts >= d AND ts < f)
AND m.ts <= mn.t ORDER BY m.ts DESC LIMIT 1) b SELECT (SELECT coalesce(sum(GREATEST(v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM g) AS importe,
FROM mn) (SELECT coalesce(sum(GREATEST(-v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM g) AS injecte,
SELECT coalesce(sum(g) FILTER (WHERE g > 0), 0) / 60000.0 AS importe, (SELECT coalesce(sum(GREATEST(v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM ev) AS voiture,
coalesce(-sum(g) FILTER (WHERE g < 0), 0) / 60000.0 AS injecte, (SELECT coalesce(sum(GREATEST(v,0)*dt) FILTER (WHERE dt IS NOT NULL),0)/3600000.0 FROM ba) AS batterie_chargee,
coalesce(sum(v) FILTER (WHERE v > 0), 0) / 60000.0 AS voiture, (SELECT count(*) FROM g WHERE v < 0) AS points_negatifs,
coalesce(sum(b) FILTER (WHERE b > 0), 0) / 60000.0 AS batterie_chargee, (SELECT min(v) FROM g) AS pire_injection`, [debut, fin]);
count(*) FILTER (WHERE g < 0) AS points_negatifs,
min(g) AS pire_injection
FROM j`, [debut, fin]);
if (!fermetures.length) { if (!fermetures.length) {
dire('Le contacteur est reste ouvert toute la nuit : aucune energie achetee au reseau.'); dire('Le contacteur est reste ouvert toute la nuit : aucune energie achetee au reseau.');