All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
108 lines
3.0 KiB
Go
108 lines
3.0 KiB
Go
package nws
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"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/model"
|
|
"gitea.maximumdirect.net/ejr/weatherfeeder/standards"
|
|
)
|
|
|
|
// WeatherStoriesNormalizer converts:
|
|
//
|
|
// standards.SchemaRawNWSWeatherStoriesV1 -> standards.SchemaWeatherStoryV1
|
|
//
|
|
// It maps the NWS /weatherstories JSON response into a canonical story snapshot.
|
|
type WeatherStoriesNormalizer struct{}
|
|
|
|
func (WeatherStoriesNormalizer) Match(e event.Event) bool {
|
|
return strings.TrimSpace(e.Schema) == standards.SchemaRawNWSWeatherStoriesV1
|
|
}
|
|
|
|
func (WeatherStoriesNormalizer) Normalize(ctx context.Context, in event.Event) (*event.Event, error) {
|
|
_ = ctx
|
|
|
|
fallbackAsOf := in.EmittedAt.UTC()
|
|
if in.EffectiveAt != nil && !in.EffectiveAt.IsZero() {
|
|
fallbackAsOf = in.EffectiveAt.UTC()
|
|
}
|
|
|
|
return normcommon.NormalizeJSON(
|
|
in,
|
|
"nws weatherstories",
|
|
standards.SchemaWeatherStoryV1,
|
|
func(parsed nwsWeatherStoriesResponse) (model.WeatherStoryRun, time.Time, error) {
|
|
return buildWeatherStories(parsed, fallbackAsOf)
|
|
},
|
|
)
|
|
}
|
|
|
|
func buildWeatherStories(parsed nwsWeatherStoriesResponse, fallbackAsOf time.Time) (model.WeatherStoryRun, time.Time, error) {
|
|
stories := make([]model.WeatherStory, 0, len(parsed.Stories))
|
|
|
|
var officeID string
|
|
var asOf time.Time
|
|
for i, raw := range parsed.Stories {
|
|
startTime, err := parseRequiredNWSTime(raw.StartTime, fmt.Sprintf("stories[%d].startTime", i))
|
|
if err != nil {
|
|
return model.WeatherStoryRun{}, time.Time{}, err
|
|
}
|
|
endTime, err := parseRequiredNWSTime(raw.EndTime, fmt.Sprintf("stories[%d].endTime", i))
|
|
if err != nil {
|
|
return model.WeatherStoryRun{}, time.Time{}, err
|
|
}
|
|
updatedAt, err := parseRequiredNWSTime(raw.UpdateTime, fmt.Sprintf("stories[%d].updateTime", i))
|
|
if err != nil {
|
|
return model.WeatherStoryRun{}, time.Time{}, err
|
|
}
|
|
|
|
storyOfficeID := strings.TrimSpace(raw.OfficeID)
|
|
if officeID == "" && storyOfficeID != "" {
|
|
officeID = storyOfficeID
|
|
}
|
|
if asOf.IsZero() || updatedAt.After(asOf) {
|
|
asOf = updatedAt
|
|
}
|
|
|
|
stories = append(stories, model.WeatherStory{
|
|
OfficeID: storyOfficeID,
|
|
StartTime: startTime,
|
|
EndTime: endTime,
|
|
UpdatedAt: updatedAt,
|
|
Title: strings.TrimSpace(raw.Title),
|
|
Description: strings.TrimSpace(raw.Description),
|
|
AltText: strings.TrimSpace(raw.AltText),
|
|
Priority: raw.Priority,
|
|
Order: raw.Order,
|
|
DownloadURL: strings.TrimSpace(raw.Download),
|
|
})
|
|
}
|
|
|
|
if asOf.IsZero() {
|
|
asOf = fallbackAsOf.UTC()
|
|
}
|
|
|
|
run := model.WeatherStoryRun{
|
|
OfficeID: officeID,
|
|
AsOf: asOf,
|
|
Stories: stories,
|
|
}
|
|
return run, asOf, nil
|
|
}
|
|
|
|
func parseRequiredNWSTime(raw, field string) (time.Time, error) {
|
|
if strings.TrimSpace(raw) == "" {
|
|
return time.Time{}, fmt.Errorf("%s is required", field)
|
|
}
|
|
t, err := nwscommon.ParseTime(raw)
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("%s: %w", field, err)
|
|
}
|
|
return t.UTC(), nil
|
|
}
|