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 }