2025-11-05 16:47:38 +08:00

85 lines
2.4 KiB
Go

package data
import (
"context"
"database/sql"
"math"
"time"
)
type WindowAgg struct {
Ta sql.NullFloat64
Ua sql.NullFloat64
Pa sql.NullFloat64
Sm sql.NullFloat64
Dm sql.NullFloat64
}
// WindowAverages computes averages on rs485_weather_data over [start,end) window.
// Filters values < 0.001 as invalid. Wind direction averaged vectorially.
func WindowAverages(ctx context.Context, stationID string, start, end time.Time) (WindowAgg, error) {
const q = `
SELECT
AVG(temperature) AS ta,
AVG(humidity) AS ua,
AVG(pressure) AS pa,
AVG(CASE WHEN wind_speed >= 0.001 THEN wind_speed END) AS sm,
DEGREES(
ATAN2(
AVG(CASE WHEN wind_speed >= 0.001 THEN SIN(RADIANS(wind_direction)) END),
AVG(CASE WHEN wind_speed >= 0.001 THEN COS(RADIANS(wind_direction)) END)
)
) AS dm
FROM rs485_weather_data
WHERE station_id = $1 AND timestamp >= $2 AND timestamp < $3`
var agg WindowAgg
err := DB().QueryRowContext(ctx, q, stationID, start, end).Scan(
&agg.Ta, &agg.Ua, &agg.Pa, &agg.Sm, &agg.Dm,
)
if err != nil {
return WindowAgg{}, err
}
// Normalize Dm to [0,360)
if agg.Dm.Valid {
v := agg.Dm.Float64
if v < 0 {
v += 360.0
}
// handle NaN from no data
if math.IsNaN(v) {
agg.Dm.Valid = false
} else {
agg.Dm.Float64 = v
}
}
return agg, nil
}
// DailyRainSinceMidnight computes current_day_rain = max(0, latest - baselineAtMidnight).
// If baseline is null, returns 0.
func DailyRainSinceMidnight(ctx context.Context, stationID string, now time.Time) (float64, error) {
// Midnight in Asia/Shanghai
loc, _ := time.LoadLocation("Asia/Shanghai")
if loc == nil {
loc = time.FixedZone("CST", 8*3600)
}
dayStart := time.Date(now.In(loc).Year(), now.In(loc).Month(), now.In(loc).Day(), 0, 0, 0, 0, loc)
var baseline sql.NullFloat64
const qBase = `SELECT rainfall FROM rs485_weather_data WHERE station_id=$1 AND timestamp <= $2 ORDER BY timestamp DESC LIMIT 1`
_ = DB().QueryRowContext(ctx, qBase, stationID, dayStart).Scan(&baseline)
var current sql.NullFloat64
const qCur = `SELECT rainfall FROM rs485_weather_data WHERE station_id=$1 ORDER BY timestamp DESC LIMIT 1`
_ = DB().QueryRowContext(ctx, qCur, stationID).Scan(&current)
if !current.Valid || !baseline.Valid {
return 0, nil
}
v := current.Float64 - baseline.Float64
if v < 0 {
v = 0
}
return v, nil
}