Added new, focused forecast discussion endpoints for key messages, short term, and long term discussions
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
This commit is contained in:
@@ -8,6 +8,33 @@ import (
|
||||
"gitea.maximumdirect.net/ejr/weatherfeeder/model"
|
||||
)
|
||||
|
||||
type DiscussionKeyMessagesPayload struct {
|
||||
OfficeID string `json:"officeId,omitempty" xml:"officeId,omitempty"`
|
||||
OfficeName string `json:"officeName,omitempty" xml:"officeName,omitempty"`
|
||||
Product model.ForecastDiscussionProduct `json:"product" xml:"product"`
|
||||
IssuedAt time.Time `json:"issuedAt" xml:"issuedAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty" xml:"updatedAt,omitempty"`
|
||||
KeyMessages []string `json:"keyMessages" xml:"keyMessages"`
|
||||
}
|
||||
|
||||
type DiscussionShortTermPayload struct {
|
||||
OfficeID string `json:"officeId,omitempty" xml:"officeId,omitempty"`
|
||||
OfficeName string `json:"officeName,omitempty" xml:"officeName,omitempty"`
|
||||
Product model.ForecastDiscussionProduct `json:"product" xml:"product"`
|
||||
IssuedAt time.Time `json:"issuedAt" xml:"issuedAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty" xml:"updatedAt,omitempty"`
|
||||
ShortTerm *model.WeatherForecastDiscussionSection `json:"shortTerm,omitempty" xml:"shortTerm,omitempty"`
|
||||
}
|
||||
|
||||
type DiscussionLongTermPayload struct {
|
||||
OfficeID string `json:"officeId,omitempty" xml:"officeId,omitempty"`
|
||||
OfficeName string `json:"officeName,omitempty" xml:"officeName,omitempty"`
|
||||
Product model.ForecastDiscussionProduct `json:"product" xml:"product"`
|
||||
IssuedAt time.Time `json:"issuedAt" xml:"issuedAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty" xml:"updatedAt,omitempty"`
|
||||
LongTerm *model.WeatherForecastDiscussionSection `json:"longTerm,omitempty" xml:"longTerm,omitempty"`
|
||||
}
|
||||
|
||||
func DiscussionPayload(run *model.WeatherForecastDiscussion, _ Units, tz *time.Location) any {
|
||||
if run == nil {
|
||||
return nil
|
||||
@@ -26,6 +53,51 @@ func DiscussionPayload(run *model.WeatherForecastDiscussion, _ Units, tz *time.L
|
||||
return &out
|
||||
}
|
||||
|
||||
func DiscussionKeyMessagesOnlyPayload(run *model.WeatherForecastDiscussion, _ Units, tz *time.Location) any {
|
||||
if run == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return DiscussionKeyMessagesPayload{
|
||||
OfficeID: run.OfficeID,
|
||||
OfficeName: run.OfficeName,
|
||||
Product: run.Product,
|
||||
IssuedAt: inLocationTime(run.IssuedAt, tz),
|
||||
UpdatedAt: inLocationTimePtr(run.UpdatedAt, tz),
|
||||
KeyMessages: append([]string{}, run.KeyMessages...),
|
||||
}
|
||||
}
|
||||
|
||||
func DiscussionShortTermOnlyPayload(run *model.WeatherForecastDiscussion, _ Units, tz *time.Location) any {
|
||||
if run == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return DiscussionShortTermPayload{
|
||||
OfficeID: run.OfficeID,
|
||||
OfficeName: run.OfficeName,
|
||||
Product: run.Product,
|
||||
IssuedAt: inLocationTime(run.IssuedAt, tz),
|
||||
UpdatedAt: inLocationTimePtr(run.UpdatedAt, tz),
|
||||
ShortTerm: copyDiscussionSection(run.ShortTerm, tz),
|
||||
}
|
||||
}
|
||||
|
||||
func DiscussionLongTermOnlyPayload(run *model.WeatherForecastDiscussion, _ Units, tz *time.Location) any {
|
||||
if run == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return DiscussionLongTermPayload{
|
||||
OfficeID: run.OfficeID,
|
||||
OfficeName: run.OfficeName,
|
||||
Product: run.Product,
|
||||
IssuedAt: inLocationTime(run.IssuedAt, tz),
|
||||
UpdatedAt: inLocationTimePtr(run.UpdatedAt, tz),
|
||||
LongTerm: copyDiscussionSection(run.LongTerm, tz),
|
||||
}
|
||||
}
|
||||
|
||||
func copyDiscussionSection(in *model.WeatherForecastDiscussionSection, tz *time.Location) *model.WeatherForecastDiscussionSection {
|
||||
if in == nil {
|
||||
return nil
|
||||
|
||||
@@ -209,6 +209,90 @@ func TestDiscussionPayloadTimezoneConversionAndCopySemantics(t *testing.T) {
|
||||
assertOffsetSeconds(t, run.IssuedAt, 0)
|
||||
}
|
||||
|
||||
func TestDiscussionFocusedPayloads(t *testing.T) {
|
||||
loc := time.FixedZone("UTC-05:00", -5*60*60)
|
||||
issuedAt := time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC)
|
||||
updatedAt := issuedAt.Add(30 * time.Minute)
|
||||
shortIssuedAt := issuedAt.Add(-15 * time.Minute)
|
||||
longIssuedAt := issuedAt.Add(15 * time.Minute)
|
||||
run := &model.WeatherForecastDiscussion{
|
||||
OfficeID: "LSX",
|
||||
OfficeName: "National Weather Service Saint Louis MO",
|
||||
Product: model.ForecastDiscussionProductAFD,
|
||||
IssuedAt: issuedAt,
|
||||
UpdatedAt: &updatedAt,
|
||||
KeyMessages: nil,
|
||||
ShortTerm: &model.WeatherForecastDiscussionSection{Qualifier: "(Tonight)", IssuedAt: &shortIssuedAt, Text: "Short term text"},
|
||||
LongTerm: &model.WeatherForecastDiscussionSection{Qualifier: "(Tomorrow)", IssuedAt: &longIssuedAt, Text: "Long term text"},
|
||||
}
|
||||
|
||||
keyMessagesPayload := DiscussionKeyMessagesOnlyPayload(run, UnitsMetric, loc)
|
||||
keyMessages, ok := keyMessagesPayload.(DiscussionKeyMessagesPayload)
|
||||
if !ok {
|
||||
t.Fatalf("expected DiscussionKeyMessagesPayload, got %T", keyMessagesPayload)
|
||||
}
|
||||
if keyMessages.KeyMessages == nil {
|
||||
t.Fatalf("expected keyMessages slice to be non-nil")
|
||||
}
|
||||
if len(keyMessages.KeyMessages) != 0 {
|
||||
t.Fatalf("expected empty keyMessages slice, got %#v", keyMessages.KeyMessages)
|
||||
}
|
||||
assertOffsetSeconds(t, keyMessages.IssuedAt, -5*60*60)
|
||||
assertOffsetSeconds(t, *keyMessages.UpdatedAt, -5*60*60)
|
||||
|
||||
shortPayload := DiscussionShortTermOnlyPayload(run, UnitsMetric, loc)
|
||||
shortTerm, ok := shortPayload.(DiscussionShortTermPayload)
|
||||
if !ok {
|
||||
t.Fatalf("expected DiscussionShortTermPayload, got %T", shortPayload)
|
||||
}
|
||||
if shortTerm.ShortTerm == nil {
|
||||
t.Fatalf("expected shortTerm section")
|
||||
}
|
||||
if shortTerm.ShortTerm == run.ShortTerm {
|
||||
t.Fatalf("expected shortTerm section copy")
|
||||
}
|
||||
assertOffsetSeconds(t, *shortTerm.ShortTerm.IssuedAt, -5*60*60)
|
||||
|
||||
longPayload := DiscussionLongTermOnlyPayload(run, UnitsMetric, loc)
|
||||
longTerm, ok := longPayload.(DiscussionLongTermPayload)
|
||||
if !ok {
|
||||
t.Fatalf("expected DiscussionLongTermPayload, got %T", longPayload)
|
||||
}
|
||||
if longTerm.LongTerm == nil {
|
||||
t.Fatalf("expected longTerm section")
|
||||
}
|
||||
if longTerm.LongTerm == run.LongTerm {
|
||||
t.Fatalf("expected longTerm section copy")
|
||||
}
|
||||
assertOffsetSeconds(t, *longTerm.LongTerm.IssuedAt, -5*60*60)
|
||||
}
|
||||
|
||||
func TestDiscussionFocusedPayloadsPreserveNilSections(t *testing.T) {
|
||||
run := &model.WeatherForecastDiscussion{
|
||||
OfficeID: "LSX",
|
||||
Product: model.ForecastDiscussionProductAFD,
|
||||
IssuedAt: time.Date(2026, 7, 10, 12, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
shortPayload := DiscussionShortTermOnlyPayload(run, UnitsMetric, nil)
|
||||
shortTerm, ok := shortPayload.(DiscussionShortTermPayload)
|
||||
if !ok {
|
||||
t.Fatalf("expected DiscussionShortTermPayload, got %T", shortPayload)
|
||||
}
|
||||
if shortTerm.ShortTerm != nil {
|
||||
t.Fatalf("expected nil shortTerm, got %+v", shortTerm.ShortTerm)
|
||||
}
|
||||
|
||||
longPayload := DiscussionLongTermOnlyPayload(run, UnitsMetric, nil)
|
||||
longTerm, ok := longPayload.(DiscussionLongTermPayload)
|
||||
if !ok {
|
||||
t.Fatalf("expected DiscussionLongTermPayload, got %T", longPayload)
|
||||
}
|
||||
if longTerm.LongTerm != nil {
|
||||
t.Fatalf("expected nil longTerm, got %+v", longTerm.LongTerm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricCopyAndNilHandling(t *testing.T) {
|
||||
obs := &model.WeatherObservation{
|
||||
TemperatureC: float64Ptr(20.6),
|
||||
@@ -239,6 +323,15 @@ func TestMetricCopyAndNilHandling(t *testing.T) {
|
||||
if DiscussionPayload(nil, UnitsUS, nil) != nil {
|
||||
t.Fatalf("expected nil discussion input to return nil payload")
|
||||
}
|
||||
if DiscussionKeyMessagesOnlyPayload(nil, UnitsUS, nil) != nil {
|
||||
t.Fatalf("expected nil key messages discussion input to return nil payload")
|
||||
}
|
||||
if DiscussionShortTermOnlyPayload(nil, UnitsUS, nil) != nil {
|
||||
t.Fatalf("expected nil short term discussion input to return nil payload")
|
||||
}
|
||||
if DiscussionLongTermOnlyPayload(nil, UnitsUS, nil) != nil {
|
||||
t.Fatalf("expected nil long term discussion input to return nil payload")
|
||||
}
|
||||
if CurrentConditionsPayload(nil, UnitsUS, 0) != nil {
|
||||
t.Fatalf("expected nil current conditions input to return nil payload")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user