Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f464592c56 | |||
| 123e8ff763 |
@@ -51,10 +51,11 @@ func main() {
|
||||
sinkReg.Register("nats", func(cfg config.SinkConfig) (fksinks.Sink, error) {
|
||||
return fksinks.NewNATSSinkFromConfig(cfg)
|
||||
})
|
||||
|
||||
// --- Build sources into scheduler jobs ---
|
||||
var jobs []fkscheduler.Job
|
||||
for i, sc := range cfg.Sources {
|
||||
src, err := srcReg.Build(sc)
|
||||
in, err := srcReg.BuildInput(sc) // may be polling or streaming
|
||||
if err != nil {
|
||||
log.Fatalf("build source failed (sources[%d] name=%q driver=%q): %v", i, sc.Name, sc.Driver, err)
|
||||
}
|
||||
@@ -65,16 +66,25 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("invalid kind in config (sources[%d] name=%q kind=%q): %v", i, sc.Name, sc.Kind, err)
|
||||
}
|
||||
if src.Kind() != expectedKind {
|
||||
if in.Kind() != expectedKind {
|
||||
log.Fatalf(
|
||||
"source kind mismatch (sources[%d] name=%q driver=%q): config kind=%q but driver emits kind=%q",
|
||||
i, sc.Name, sc.Driver, expectedKind, src.Kind(),
|
||||
i, sc.Name, sc.Driver, expectedKind, in.Kind(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a polling source, every is required.
|
||||
if _, ok := in.(fksources.Source); ok && sc.Every.Duration <= 0 {
|
||||
log.Fatalf(
|
||||
"polling source missing/invalid interval (sources[%d] name=%q driver=%q): sources[].every must be > 0",
|
||||
i, sc.Name, sc.Driver,
|
||||
)
|
||||
}
|
||||
|
||||
// For stream sources, Every is ignored; it is fine if omitted/zero.
|
||||
jobs = append(jobs, fkscheduler.Job{
|
||||
Source: src,
|
||||
Source: in,
|
||||
Every: sc.Every.Duration,
|
||||
})
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -2,7 +2,7 @@ module gitea.maximumdirect.net/ejr/weatherfeeder
|
||||
|
||||
go 1.25
|
||||
|
||||
require gitea.maximumdirect.net/ejr/feedkit v0.4.1
|
||||
require gitea.maximumdirect.net/ejr/feedkit v0.5.0
|
||||
|
||||
require (
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
||||
gitea.maximumdirect.net/ejr/feedkit v0.4.1 h1:mMFtPCBKp2LXV3euPH21WzjHku/HHx31KQqYW+w1aqU=
|
||||
gitea.maximumdirect.net/ejr/feedkit v0.4.1/go.mod h1:wYtA10GouvSe7L/8e1UEC+tqcp32HJofExIo1k+Wjls=
|
||||
gitea.maximumdirect.net/ejr/feedkit v0.5.0 h1:T4pRTo9Tj/o7TbZYUbp8UE7cQVLmIucUrYmD6G8E8ZQ=
|
||||
gitea.maximumdirect.net/ejr/feedkit v0.5.0/go.mod h1:wYtA10GouvSe7L/8e1UEC+tqcp32HJofExIo1k+Wjls=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk=
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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/<provider>.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user