V 1.6.0 fix: Tagesregen per MAX (kumulierter Tageszähler, Reset um Mitternacht)

Wochenwerte als Summe täglicher Maxima; /weather/stats mit Subquery über tägliche Maxima.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 17:31:07 +02:00
parent 4f89db49b6
commit 9c2855fa98
5 changed files with 41 additions and 27 deletions
+31 -17
View File
@@ -270,11 +270,17 @@ async def get_weather_statistics(
AVG(pressure) as avg_pressure, AVG(pressure) as avg_pressure,
AVG(wind_speed * 1.60934) as avg_wind_speed, AVG(wind_speed * 1.60934) as avg_wind_speed,
MAX(wind_gust * 1.60934) as max_wind_gust, MAX(wind_gust * 1.60934) as max_wind_gust,
SUM(rain) as total_rain, (SELECT COALESCE(SUM(daily_max), 0)
FROM (
SELECT MAX(rain) as daily_max
FROM weather_data d2
WHERE d2.datetime >= NOW() - make_interval(hours => %s)
GROUP BY DATE(d2.datetime)
) sub) as total_rain,
COUNT(*) as data_points COUNT(*) as data_points
FROM weather_data FROM weather_data
WHERE datetime >= NOW() - make_interval(hours => %s) WHERE datetime >= NOW() - make_interval(hours => %s)
""", (hours,)) """, (hours, hours))
result = cursor.fetchone() result = cursor.fetchone()
if not result or result['data_points'] == 0: if not result or result['data_points'] == 0:
@@ -300,7 +306,7 @@ async def get_daily_statistics(
AVG(pressure) as avg_pressure, AVG(pressure) as avg_pressure,
AVG(wind_speed * 1.60934) as avg_wind_speed, AVG(wind_speed * 1.60934) as avg_wind_speed,
MAX(wind_gust * 1.60934) as max_wind_gust, MAX(wind_gust * 1.60934) as max_wind_gust,
SUM(rain) as total_rain, MAX(rain) as total_rain,
COUNT(*) as data_points COUNT(*) as data_points
FROM weather_data FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s) WHERE datetime >= NOW() - make_interval(days => %s)
@@ -387,7 +393,7 @@ async def get_hourly_aggregated_data(
AVG(wind_speed * 1.60934) as wind_speed, AVG(wind_speed * 1.60934) as wind_speed,
MAX(wind_gust * 1.60934) as wind_gust, MAX(wind_gust * 1.60934) as wind_gust,
AVG(wind_dir) as wind_dir, AVG(wind_dir) as wind_dir,
AVG(rain) as rain, MAX(rain) as rain,
AVG(rain_rate) as rain_rate, AVG(rain_rate) as rain_rate,
MAX(received_at) as received_at MAX(received_at) as received_at
FROM weather_data FROM weather_data
@@ -429,7 +435,7 @@ async def get_daily_aggregated_data(
MAX(wind_gust * 1.60934)::float as wind_gust, MAX(wind_gust * 1.60934)::float as wind_gust,
(array_agg(datetime ORDER BY wind_gust DESC NULLS LAST))[1] as max_wind_gust_time, (array_agg(datetime ORDER BY wind_gust DESC NULLS LAST))[1] as max_wind_gust_time,
AVG(wind_dir)::float as wind_dir, AVG(wind_dir)::float as wind_dir,
SUM(rain)::float as total_rain MAX(rain)::float as total_rain
FROM weather_data FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s) WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime) GROUP BY date_trunc('day', datetime)
@@ -469,7 +475,7 @@ async def get_daily_with_minmax_data(
MAX(wind_gust * 1.60934)::float as wind_gust, MAX(wind_gust * 1.60934)::float as wind_gust,
(array_agg(datetime ORDER BY wind_gust DESC NULLS LAST))[1] as max_wind_gust_time, (array_agg(datetime ORDER BY wind_gust DESC NULLS LAST))[1] as max_wind_gust_time,
AVG(wind_dir)::float as wind_dir, AVG(wind_dir)::float as wind_dir,
SUM(rain)::float as total_rain MAX(rain)::float as total_rain
FROM weather_data FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s) WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime) GROUP BY date_trunc('day', datetime)
@@ -490,7 +496,7 @@ async def get_daily_rain_data(
cursor.execute(""" cursor.execute("""
SELECT SELECT
date_trunc('day', datetime) as date, date_trunc('day', datetime) as date,
SUM(rain) as total_rain MAX(rain) as total_rain
FROM weather_data FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s) WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime) GROUP BY date_trunc('day', datetime)
@@ -512,20 +518,28 @@ async def get_weekly_rain_data(
if days >= 365: if days >= 365:
cursor.execute(""" cursor.execute("""
SELECT SELECT
date_trunc('week', datetime) as week_start, date_trunc('week', day) as week_start,
SUM(rain) as total_rain SUM(daily_rain) as total_rain
FROM weather_data FROM (
GROUP BY date_trunc('week', datetime) SELECT DATE(datetime) as day, MAX(rain) as daily_rain
FROM weather_data
GROUP BY DATE(datetime)
) sub
GROUP BY date_trunc('week', day)
ORDER BY week_start ASC ORDER BY week_start ASC
""") """)
else: else:
cursor.execute(""" cursor.execute("""
SELECT SELECT
date_trunc('week', datetime) as week_start, date_trunc('week', day) as week_start,
SUM(rain) as total_rain SUM(daily_rain) as total_rain
FROM weather_data FROM (
WHERE datetime >= NOW() - make_interval(days => %s) SELECT DATE(datetime) as day, MAX(rain) as daily_rain
GROUP BY date_trunc('week', datetime) FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY DATE(datetime)
) sub
GROUP BY date_trunc('week', day)
ORDER BY week_start ASC ORDER BY week_start ASC
""", (days,)) """, (days,))
results = cursor.fetchall() results = cursor.fetchall()
@@ -596,7 +610,7 @@ async def get_daily_aggregated_range(
MAX(wind_gust * 1.60934)::float as wind_gust, MAX(wind_gust * 1.60934)::float as wind_gust,
(array_agg(datetime ORDER BY wind_gust DESC NULLS LAST))[1] as max_wind_gust_time, (array_agg(datetime ORDER BY wind_gust DESC NULLS LAST))[1] as max_wind_gust_time,
AVG(wind_dir)::float as wind_dir, AVG(wind_dir)::float as wind_dir,
SUM(rain)::float as total_rain MAX(rain)::float as total_rain
FROM weather_data FROM weather_data
WHERE datetime BETWEEN %s AND %s WHERE datetime BETWEEN %s AND %s
GROUP BY date_trunc('day', datetime) GROUP BY date_trunc('day', datetime)
-2
View File
@@ -1,5 +1,3 @@
version: '3.8'
services: services:
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "wetterstation-frontend", "name": "wetterstation-frontend",
"private": true, "private": true,
"version": "1.5.9", "version": "1.6.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+2 -2
View File
@@ -26,8 +26,8 @@ function buildUrls(timeRange) {
const days = timeRange.days || 1 const days = timeRange.days || 1
const path = days >= 7 ? 'daily-aggregated-range' : 'hourly-aggregated-range' const path = days >= 7 ? 'daily-aggregated-range' : 'hourly-aggregated-range'
return { return {
weatherUrl: `${API_BASE}/weather/${path}?start=${start}&end=${end}`, weatherUrl: `${API_BASE}/weather/${path}?start=${start}&end=${end}`,
rainUrl: null, // TODO: Regen-Aggregation fuer Range implementieren rainUrl: days < 7 ? `${API_BASE}/weather/daily-aggregated-range?start=${start}&end=${end}` : null,
needsCurrent: true, needsCurrent: true,
} }
} }
+7 -5
View File
@@ -171,14 +171,14 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
// Spezieller Suffix für Regen // Spezieller Suffix für Regen
const rainSuffix = useMemo(() => { const rainSuffix = useMemo(() => {
if (typeof timeRange === 'object' && timeRange.type === 'custom') { if (typeof timeRange === 'object' && timeRange.type === 'custom') {
const days = timeRange.days || 1 return ' (pro Tag)'
return days >= 7 ? ' (pro Tag)' : ''
} }
switch (timeRange) { switch (timeRange) {
case '7d': case '7d':
case '30d': case '30d':
case '365d':
return ' (pro Tag)' return ' (pro Tag)'
case '365d':
return ' (pro Woche)'
default: default:
return '' return ''
} }
@@ -639,11 +639,13 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
} }
}] }]
} else if (typeof timeRange === 'object' && timeRange.type === 'custom') { } else if (typeof timeRange === 'object' && timeRange.type === 'custom') {
// Custom range: tägliche Summen aus sortedData (total_rain ist im daily-aggregated-range enthalten) // Custom range: tägliche Summen — bei kurzen Ranges (<7d) aus rainData (extra Fetch),
// bei langen Ranges aus sortedData (daily-aggregated-range enthält total_rain)
yAxisTitle = 'Regen (mm pro Tag)' yAxisTitle = 'Regen (mm pro Tag)'
const rainSource = rainData.length > 0 ? rainData : sortedData
series = [{ series = [{
name: 'Regen', name: 'Regen',
data: sortedData data: rainSource
.filter(item => item.total_rain != null && item.total_rain > 0) .filter(item => item.total_rain != null && item.total_rain > 0)
.map(item => [new Date(item.datetime).getTime(), item.total_rain]), .map(item => [new Date(item.datetime).getTime(), item.total_rain]),
color: 'rgb(54, 162, 235)', color: 'rgb(54, 162, 235)',