normalizers/openweather: extract shared helpers into common.go
Refactor OpenWeather normalizers to improve structure and reuse by moving provider-specific helper functions out of observation.go and into a new common.go. This keeps observation.go focused on schema matching and domain mapping, preserves the “one normalizer per file” convention, and establishes a clear home for helpers that will be shared by future OpenWeather forecast and alert normalizers. No functional behavior changes; this is a pure internal refactor.
This commit is contained in:
71
internal/normalizers/openweather/common.go
Normal file
71
internal/normalizers/openweather/common.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// FILE: ./internal/normalizers/openweather/common.go
|
||||||
|
package openweather
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file holds provider-specific helpers that are shared across multiple
|
||||||
|
// OpenWeather normalizers (observations today; forecasts/alerts later).
|
||||||
|
// Keeping these out of observation.go helps preserve the "one normalizer per file"
|
||||||
|
// convention while avoiding duplication.
|
||||||
|
|
||||||
|
// primaryCondition returns the "primary" weather condition from OpenWeather's
|
||||||
|
// weather array. Per OpenWeather conventions, element [0] is treated as primary.
|
||||||
|
func primaryCondition(list []owmWeather) (id int, desc string, icon string) {
|
||||||
|
if len(list) == 0 {
|
||||||
|
return 0, "", ""
|
||||||
|
}
|
||||||
|
w := list[0]
|
||||||
|
return w.ID, strings.TrimSpace(w.Description), strings.TrimSpace(w.Icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inferIsDay determines day/night using the best available upstream signals.
|
||||||
|
//
|
||||||
|
// Priority:
|
||||||
|
// 1. The OpenWeather icon suffix ("d" / "n") when present.
|
||||||
|
// 2. Sunrise/sunset bounds (unix seconds), if provided.
|
||||||
|
// 3. Unknown (nil) when no reliable signal is present.
|
||||||
|
func inferIsDay(icon string, dt, sunrise, sunset int64) *bool {
|
||||||
|
// Prefer icon suffix.
|
||||||
|
icon = strings.TrimSpace(icon)
|
||||||
|
if icon != "" {
|
||||||
|
last := icon[len(icon)-1]
|
||||||
|
switch last {
|
||||||
|
case 'd':
|
||||||
|
v := true
|
||||||
|
return &v
|
||||||
|
case 'n':
|
||||||
|
v := false
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to sunrise/sunset bounds if provided.
|
||||||
|
if dt > 0 && sunrise > 0 && sunset > 0 {
|
||||||
|
v := dt >= sunrise && dt < sunset
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openWeatherIconURL builds the standard OpenWeather icon URL for the given icon code.
|
||||||
|
func openWeatherIconURL(icon string) string {
|
||||||
|
icon = strings.TrimSpace(icon)
|
||||||
|
if icon == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("https://openweathermap.org/img/wn/%s@2x.png", icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
// openWeatherStationID returns a stable station identifier for the given response.
|
||||||
|
// Prefer the OpenWeather city ID when present; otherwise, fall back to coordinates.
|
||||||
|
func openWeatherStationID(parsed owmResponse) string {
|
||||||
|
if parsed.ID != 0 {
|
||||||
|
return fmt.Sprintf("OPENWEATHER(%d)", parsed.ID)
|
||||||
|
}
|
||||||
|
// Fallback: synthesize from coordinates.
|
||||||
|
return fmt.Sprintf("OPENWEATHER(%.5f,%.5f)", parsed.Coord.Lat, parsed.Coord.Lon)
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package openweather
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -132,51 +131,3 @@ func buildObservation(parsed owmResponse) (model.WeatherObservation, time.Time,
|
|||||||
|
|
||||||
return obs, ts, nil
|
return obs, ts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func primaryCondition(list []owmWeather) (id int, desc string, icon string) {
|
|
||||||
if len(list) == 0 {
|
|
||||||
return 0, "", ""
|
|
||||||
}
|
|
||||||
w := list[0]
|
|
||||||
return w.ID, strings.TrimSpace(w.Description), strings.TrimSpace(w.Icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
func inferIsDay(icon string, dt, sunrise, sunset int64) *bool {
|
|
||||||
// Prefer icon suffix.
|
|
||||||
icon = strings.TrimSpace(icon)
|
|
||||||
if icon != "" {
|
|
||||||
last := icon[len(icon)-1]
|
|
||||||
switch last {
|
|
||||||
case 'd':
|
|
||||||
v := true
|
|
||||||
return &v
|
|
||||||
case 'n':
|
|
||||||
v := false
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to sunrise/sunset bounds if provided.
|
|
||||||
if dt > 0 && sunrise > 0 && sunset > 0 {
|
|
||||||
v := dt >= sunrise && dt < sunset
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func openWeatherIconURL(icon string) string {
|
|
||||||
icon = strings.TrimSpace(icon)
|
|
||||||
if icon == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("https://openweathermap.org/img/wn/%s@2x.png", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
func openWeatherStationID(parsed owmResponse) string {
|
|
||||||
if parsed.ID != 0 {
|
|
||||||
return fmt.Sprintf("OPENWEATHER(%d)", parsed.ID)
|
|
||||||
}
|
|
||||||
// Fallback: synthesize from coordinates.
|
|
||||||
return fmt.Sprintf("OPENWEATHER(%.5f,%.5f)", parsed.Coord.Lat, parsed.Coord.Lon)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user