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:
+31
-17
@@ -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)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)',
|
||||||
|
|||||||
Reference in New Issue
Block a user