WITH pts AS ( SELECT cle, ts, valeur, least(coalesce(lead(ts) OVER (PARTITION BY cle ORDER BY ts), now()), ts + interval '1 hour') AS fin FROM mesure WHERE cle IN ('sensor.victron_consumption_power_l1', 'sensor.victron_evcs_ac_power') AND ts > now() - interval '22 days' ), heures AS ( SELECT h AS h0, h + interval '1 hour' AS h1 FROM generate_series(date_trunc('hour', now() - interval '20 days'), date_trunc('hour', now()), interval '1 hour') h ), e AS ( SELECT h.h0, least(h.h1, now()) AS h1, sum(p.valeur * extract(epoch FROM least(p.fin, h.h1) - greatest(p.ts, h.h0)) * CASE WHEN p.cle = 'sensor.victron_evcs_ac_power' THEN -1 ELSE 1 END) / 3.6e6 AS kwh FROM heures h JOIN pts p ON p.fin > h.h0 AND p.ts < h.h1 GROUP BY 1, 2 ), cadre AS ( SELECT *, extract(hour FROM h0 AT TIME ZONE 'Europe/Paris')::int AS heure, ((extract(hour FROM h0 AT TIME ZONE 'Europe/Paris')::int + 6) % 24) AS rang FROM e ) SELECT lpad(heure::text, 2, '0') || ' h' AS "Heure", round(percentile_cont(0.5) WITHIN GROUP (ORDER BY kwh) FILTER (WHERE h1 - h0 = interval '1 hour' AND h0 < date_trunc('hour', now()) - interval '18 hours')::numeric, 2) AS "Mediane 20 nuits", round(percentile_cont(0.8) WITHIN GROUP (ORDER BY kwh) FILTER (WHERE h1 - h0 = interval '1 hour' AND h0 < date_trunc('hour', now()) - interval '18 hours')::numeric, 2) AS "8 nuits sur 10 sous", round((max(kwh) FILTER (WHERE h0 >= (CASE WHEN extract(hour FROM now() AT TIME ZONE 'Europe/Paris') >= 18 THEN date_trunc('day', now() AT TIME ZONE 'Europe/Paris') + interval '18 hours' ELSE date_trunc('day', now() AT TIME ZONE 'Europe/Paris') - interval '6 hours' END) AT TIME ZONE 'Europe/Paris'))::numeric, 2) AS "Cette nuit" FROM cadre GROUP BY heure, rang ORDER BY rang