From 123e8ff76367b919297fca4b0793467e8827ac2c Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 8 Feb 2026 09:15:07 -0600 Subject: [PATCH] Moved the standards package out of internal/ so it can be imported by downstream consumers. --- internal/normalizers/nws/alerts.go | 2 +- internal/normalizers/nws/forecast.go | 2 +- internal/normalizers/nws/observation.go | 2 +- internal/normalizers/openmeteo/forecast.go | 2 +- internal/normalizers/openmeteo/observation.go | 2 +- .../normalizers/openweather/observation.go | 2 +- internal/sources/nws/alerts.go | 2 +- internal/sources/nws/forecast.go | 2 +- internal/sources/nws/observation.go | 2 +- internal/sources/openmeteo/forecast.go | 2 +- internal/sources/openmeteo/observation.go | 2 +- internal/sources/openweather/observation.go | 2 +- internal/standards/wmo_categories.go | 127 ------------------ {internal/standards => standards}/doc.go | 2 +- {internal/standards => standards}/schema.go | 0 {internal/standards => standards}/wmo.go | 124 +++++++++++++++++ 16 files changed, 137 insertions(+), 140 deletions(-) delete mode 100644 internal/standards/wmo_categories.go rename {internal/standards => standards}/doc.go (94%) rename {internal/standards => standards}/schema.go (100%) rename {internal/standards => standards}/wmo.go (57%) diff --git a/internal/normalizers/nws/alerts.go b/internal/normalizers/nws/alerts.go index 85d573c..e727b56 100644 --- a/internal/normalizers/nws/alerts.go +++ b/internal/normalizers/nws/alerts.go @@ -11,8 +11,8 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" normcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/normalizers/common" nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" "gitea.maximumdirect.net/ejr/weatherfeeder/model" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // AlertsNormalizer converts: diff --git a/internal/normalizers/nws/forecast.go b/internal/normalizers/nws/forecast.go index f7fb806..b76e8c2 100644 --- a/internal/normalizers/nws/forecast.go +++ b/internal/normalizers/nws/forecast.go @@ -10,8 +10,8 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" normcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/normalizers/common" nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" "gitea.maximumdirect.net/ejr/weatherfeeder/model" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ForecastNormalizer converts: diff --git a/internal/normalizers/nws/observation.go b/internal/normalizers/nws/observation.go index 3512476..19ddd25 100644 --- a/internal/normalizers/nws/observation.go +++ b/internal/normalizers/nws/observation.go @@ -10,8 +10,8 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" normcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/normalizers/common" nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" "gitea.maximumdirect.net/ejr/weatherfeeder/model" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ObservationNormalizer converts: diff --git a/internal/normalizers/openmeteo/forecast.go b/internal/normalizers/openmeteo/forecast.go index ce0358f..ed51c66 100644 --- a/internal/normalizers/openmeteo/forecast.go +++ b/internal/normalizers/openmeteo/forecast.go @@ -9,8 +9,8 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" normcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/normalizers/common" omcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/openmeteo" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" "gitea.maximumdirect.net/ejr/weatherfeeder/model" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ForecastNormalizer converts: diff --git a/internal/normalizers/openmeteo/observation.go b/internal/normalizers/openmeteo/observation.go index d79468e..99ee403 100644 --- a/internal/normalizers/openmeteo/observation.go +++ b/internal/normalizers/openmeteo/observation.go @@ -10,8 +10,8 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" normcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/normalizers/common" omcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/openmeteo" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" "gitea.maximumdirect.net/ejr/weatherfeeder/model" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ObservationNormalizer converts: diff --git a/internal/normalizers/openweather/observation.go b/internal/normalizers/openweather/observation.go index 2a6bcb4..68ecb40 100644 --- a/internal/normalizers/openweather/observation.go +++ b/internal/normalizers/openweather/observation.go @@ -8,8 +8,8 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" normcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/normalizers/common" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" "gitea.maximumdirect.net/ejr/weatherfeeder/model" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ObservationNormalizer converts: diff --git a/internal/sources/nws/alerts.go b/internal/sources/nws/alerts.go index 30fe007..403c5d8 100644 --- a/internal/sources/nws/alerts.go +++ b/internal/sources/nws/alerts.go @@ -11,7 +11,7 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // AlertsSource polls an NWS alerts endpoint and emits a RAW alerts Event. diff --git a/internal/sources/nws/forecast.go b/internal/sources/nws/forecast.go index 0572416..bba6f12 100644 --- a/internal/sources/nws/forecast.go +++ b/internal/sources/nws/forecast.go @@ -11,7 +11,7 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ForecastSource polls an NWS forecast endpoint (narrative or hourly) and emits a RAW forecast Event. diff --git a/internal/sources/nws/observation.go b/internal/sources/nws/observation.go index 740421f..25b0b68 100644 --- a/internal/sources/nws/observation.go +++ b/internal/sources/nws/observation.go @@ -11,7 +11,7 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ObservationSource polls an NWS station observation endpoint and emits a RAW observation Event. diff --git a/internal/sources/openmeteo/forecast.go b/internal/sources/openmeteo/forecast.go index 346a89c..39d240f 100644 --- a/internal/sources/openmeteo/forecast.go +++ b/internal/sources/openmeteo/forecast.go @@ -10,7 +10,7 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/openmeteo" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ForecastSource polls an Open-Meteo hourly forecast endpoint and emits one RAW Forecast Event. diff --git a/internal/sources/openmeteo/observation.go b/internal/sources/openmeteo/observation.go index 268e7de..ceea4ff 100644 --- a/internal/sources/openmeteo/observation.go +++ b/internal/sources/openmeteo/observation.go @@ -10,7 +10,7 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/openmeteo" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) // ObservationSource polls an Open-Meteo endpoint and emits one RAW Observation Event. diff --git a/internal/sources/openweather/observation.go b/internal/sources/openweather/observation.go index 8de2c03..9fecbd1 100644 --- a/internal/sources/openweather/observation.go +++ b/internal/sources/openweather/observation.go @@ -11,7 +11,7 @@ import ( "gitea.maximumdirect.net/ejr/feedkit/event" owcommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/openweather" "gitea.maximumdirect.net/ejr/weatherfeeder/internal/sources/common" - "gitea.maximumdirect.net/ejr/weatherfeeder/internal/standards" + "gitea.maximumdirect.net/ejr/weatherfeeder/standards" ) type ObservationSource struct { diff --git a/internal/standards/wmo_categories.go b/internal/standards/wmo_categories.go deleted file mode 100644 index b3297da..0000000 --- a/internal/standards/wmo_categories.go +++ /dev/null @@ -1,127 +0,0 @@ -package standards - -import "gitea.maximumdirect.net/ejr/weatherfeeder/model" - -// This file provides small, shared helper functions for reasoning about WMO codes. -// These are intentionally "coarse" categories that are useful for business logic, -// dashboards, and alerting decisions. -// -// Example uses: -// - jogging suitability: precipitation? thunderstorm? freezing precip? -// - quick glance: "is it cloudy?" "is there any precip?" -// - downstream normalizers / aggregators - -func IsThunderstorm(code model.WMOCode) bool { - switch code { - case 95, 96, 99: - return true - default: - return false - } -} - -func IsHail(code model.WMOCode) bool { - switch code { - case 96, 99: - return true - default: - return false - } -} - -func IsFog(code model.WMOCode) bool { - switch code { - case 45, 48: - return true - default: - return false - } -} - -// IsPrecipitation returns true if the code represents any precipitation -// (drizzle, rain, snow, showers, etc.). -func IsPrecipitation(code model.WMOCode) bool { - switch code { - // Drizzle - case 51, 53, 55, 56, 57: - return true - - // Rain - case 61, 63, 65, 66, 67: - return true - - // Snow - case 71, 73, 75, 77: - return true - - // Showers - case 80, 81, 82, 85, 86: - return true - - // Thunderstorm (often includes rain/hail) - case 95, 96, 99: - return true - - default: - return false - } -} - -func IsRainFamily(code model.WMOCode) bool { - switch code { - // Drizzle + freezing drizzle - case 51, 53, 55, 56, 57: - return true - - // Rain + freezing rain - case 61, 63, 65, 66, 67: - return true - - // Rain showers - case 80, 81, 82: - return true - - // Thunderstorm often implies rain - case 95, 96, 99: - return true - - default: - return false - } -} - -func IsSnowFamily(code model.WMOCode) bool { - switch code { - // Snow and related - case 71, 73, 75, 77: - return true - - // Snow showers - case 85, 86: - return true - - default: - return false - } -} - -// IsFreezingPrecip returns true if the code represents freezing drizzle/rain. -func IsFreezingPrecip(code model.WMOCode) bool { - switch code { - case 56, 57, 66, 67: - return true - default: - return false - } -} - -// IsSkyOnly returns true for codes that represent "sky condition only" -// (clear/mostly/partly/cloudy) rather than fog/precip/etc. -func IsSkyOnly(code model.WMOCode) bool { - switch code { - case 0, 1, 2, 3: - return true - default: - return false - } -} diff --git a/internal/standards/doc.go b/standards/doc.go similarity index 94% rename from internal/standards/doc.go rename to standards/doc.go index 54729f3..10022c3 100644 --- a/internal/standards/doc.go +++ b/standards/doc.go @@ -15,7 +15,7 @@ // ----------------------- // For readability and stability, canonical payloads (weather.* schemas) should not emit // noisy floating-point representations. weatherfeeder enforces this by rounding float -// values in canonical payloads to 2 digits after the decimal point at normalization +// values in canonical payloads to 4 digits after the decimal point at normalization // finalization time. // // Provider-specific decoding helpers and quirks live in internal/providers/. diff --git a/internal/standards/schema.go b/standards/schema.go similarity index 100% rename from internal/standards/schema.go rename to standards/schema.go diff --git a/internal/standards/wmo.go b/standards/wmo.go similarity index 57% rename from internal/standards/wmo.go rename to standards/wmo.go index e1d0e76..b4f4232 100644 --- a/internal/standards/wmo.go +++ b/standards/wmo.go @@ -1,5 +1,14 @@ package standards +// This file provides small, shared helper functions for reasoning about WMO codes. +// These are intentionally "coarse" categories that are useful for business logic, +// dashboards, and alerting decisions. +// +// Example uses: +// - jogging suitability: precipitation? thunderstorm? freezing precip? +// - quick glance: "is it cloudy?" "is there any precip?" +// - downstream normalizers / aggregators + import ( "fmt" @@ -100,3 +109,118 @@ func IsKnownWMO(code model.WMOCode) bool { _, ok := WMODescriptions[code] return ok } + +func IsThunderstorm(code model.WMOCode) bool { + switch code { + case 95, 96, 99: + return true + default: + return false + } +} + +func IsHail(code model.WMOCode) bool { + switch code { + case 96, 99: + return true + default: + return false + } +} + +func IsFog(code model.WMOCode) bool { + switch code { + case 45, 48: + return true + default: + return false + } +} + +// IsPrecipitation returns true if the code represents any precipitation +// (drizzle, rain, snow, showers, etc.). +func IsPrecipitation(code model.WMOCode) bool { + switch code { + // Drizzle + case 51, 53, 55, 56, 57: + return true + + // Rain + case 61, 63, 65, 66, 67: + return true + + // Snow + case 71, 73, 75, 77: + return true + + // Showers + case 80, 81, 82, 85, 86: + return true + + // Thunderstorm (often includes rain/hail) + case 95, 96, 99: + return true + + default: + return false + } +} + +func IsRainFamily(code model.WMOCode) bool { + switch code { + // Drizzle + freezing drizzle + case 51, 53, 55, 56, 57: + return true + + // Rain + freezing rain + case 61, 63, 65, 66, 67: + return true + + // Rain showers + case 80, 81, 82: + return true + + // Thunderstorm often implies rain + case 95, 96, 99: + return true + + default: + return false + } +} + +func IsSnowFamily(code model.WMOCode) bool { + switch code { + // Snow and related + case 71, 73, 75, 77: + return true + + // Snow showers + case 85, 86: + return true + + default: + return false + } +} + +// IsFreezingPrecip returns true if the code represents freezing drizzle/rain. +func IsFreezingPrecip(code model.WMOCode) bool { + switch code { + case 56, 57, 66, 67: + return true + default: + return false + } +} + +// IsSkyOnly returns true for codes that represent "sky condition only" +// (clear/mostly/partly/cloudy) rather than fog/precip/etc. +func IsSkyOnly(code model.WMOCode) bool { + switch code { + case 0, 1, 2, 3: + return true + default: + return false + } +}