85 lines
2.4 KiB
Go
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(¤t)
|
|
|
|
if !current.Valid || !baseline.Valid {
|
|
return 0, nil
|
|
}
|
|
v := current.Float64 - baseline.Float64
|
|
if v < 0 {
|
|
v = 0
|
|
}
|
|
return v, nil
|
|
}
|