// 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) }