Files
weatherfeeder/internal/sources/nws/weatherstories.go

117 lines
3.0 KiB
Go

package nws
import (
"context"
"encoding/json"
"strings"
"time"
"gitea.maximumdirect.net/ejr/feedkit/config"
"gitea.maximumdirect.net/ejr/feedkit/event"
fksources "gitea.maximumdirect.net/ejr/feedkit/sources"
nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws"
"gitea.maximumdirect.net/ejr/weatherfeeder/standards"
)
// WeatherStoriesSource polls an NWS weatherstories endpoint and emits a RAW weather story Event.
//
// Output schema:
// - standards.SchemaRawNWSWeatherStoriesV1
type WeatherStoriesSource struct {
http *fksources.HTTPSource
}
func NewWeatherStoriesSource(cfg config.SourceConfig) (*WeatherStoriesSource, error) {
hs, err := fksources.NewHTTPSource(DriverWeatherStories, cfg, "application/geo+json, application/json")
if err != nil {
return nil, err
}
return &WeatherStoriesSource{http: hs}, nil
}
func (s *WeatherStoriesSource) Name() string { return s.http.Name }
func (s *WeatherStoriesSource) Kinds() []event.Kind {
return []event.Kind{event.Kind(standards.KindWeatherStory)}
}
func (s *WeatherStoriesSource) Poll(ctx context.Context) ([]event.Event, error) {
raw, meta, changed, err := s.fetchRaw(ctx)
if err != nil {
return nil, err
}
if !changed {
return nil, nil
}
var effectiveAt *time.Time
switch {
case !meta.ParsedLatestUpdateTime.IsZero():
t := meta.ParsedLatestUpdateTime.UTC()
effectiveAt = &t
case !meta.ParsedLatestStartTime.IsZero():
t := meta.ParsedLatestStartTime.UTC()
effectiveAt = &t
}
emittedAt := time.Now().UTC()
eventID := fksources.DefaultEventID("", s.http.Name, effectiveAt, emittedAt)
return fksources.SingleEvent(
event.Kind(standards.KindWeatherStory),
s.http.Name,
standards.SchemaRawNWSWeatherStoriesV1,
eventID,
emittedAt,
effectiveAt,
raw,
)
}
type weatherStoriesMeta struct {
Stories []struct {
StartTime string `json:"startTime"`
UpdateTime string `json:"updateTime"`
} `json:"stories"`
ParsedLatestUpdateTime time.Time `json:"-"`
ParsedLatestStartTime time.Time `json:"-"`
}
func (s *WeatherStoriesSource) fetchRaw(ctx context.Context) (json.RawMessage, weatherStoriesMeta, bool, error) {
raw, changed, err := s.http.FetchJSONIfChanged(ctx)
if err != nil {
return nil, weatherStoriesMeta{}, false, err
}
if !changed {
return nil, weatherStoriesMeta{}, false, nil
}
var meta weatherStoriesMeta
if err := json.Unmarshal(raw, &meta); err != nil {
return raw, weatherStoriesMeta{}, true, nil
}
for _, story := range meta.Stories {
if ts := strings.TrimSpace(story.UpdateTime); ts != "" {
if t, err := nwscommon.ParseTime(ts); err == nil {
t = t.UTC()
if meta.ParsedLatestUpdateTime.IsZero() || t.After(meta.ParsedLatestUpdateTime) {
meta.ParsedLatestUpdateTime = t
}
}
}
if ts := strings.TrimSpace(story.StartTime); ts != "" {
if t, err := nwscommon.ParseTime(ts); err == nil {
t = t.UTC()
if meta.ParsedLatestStartTime.IsZero() || t.After(meta.ParsedLatestStartTime) {
meta.ParsedLatestStartTime = t
}
}
}
}
return raw, meta, true, nil
}