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 }