Filter outlook discussions with retained outlooks

This commit is contained in:
2026-06-12 12:58:28 +00:00
parent 82dd304f10
commit fa1b2cb390
2 changed files with 106 additions and 2 deletions

View File

@@ -85,6 +85,7 @@ func (s *Service) LatestConvectiveOutlook(ctx context.Context, filter OutlookFil
}
}
out.Outlooks = outlooks
out.Discussions = filterOutlookDiscussions(out.Discussions, out.Outlooks)
return out, nil
}
@@ -123,6 +124,12 @@ func cloneOutlookRun(run *model.WeatherOutlookRun) *model.WeatherOutlookRun {
out.Outlooks[i] = cloneOutlook(run.Outlooks[i])
}
}
if run.Discussions != nil {
out.Discussions = make([]model.WeatherOutlookDiscussion, len(run.Discussions))
for i := range run.Discussions {
out.Discussions[i] = cloneOutlookDiscussion(run.Discussions[i])
}
}
return &out
}
@@ -135,6 +142,31 @@ func cloneOutlook(outlook model.WeatherOutlook) model.WeatherOutlook {
return out
}
func cloneOutlookDiscussion(discussion model.WeatherOutlookDiscussion) model.WeatherOutlookDiscussion {
out := discussion
out.UpdatedAt = copyTime(discussion.UpdatedAt)
return out
}
func filterOutlookDiscussions(discussions []model.WeatherOutlookDiscussion, outlooks []model.WeatherOutlook) []model.WeatherOutlookDiscussion {
if len(outlooks) == 0 {
return []model.WeatherOutlookDiscussion{}
}
retainedDays := make(map[int]struct{}, len(outlooks))
for _, outlook := range outlooks {
retainedDays[outlook.Day] = struct{}{}
}
out := discussions[:0]
for _, discussion := range discussions {
if _, ok := retainedDays[discussion.Day]; ok {
out = append(out, discussion)
}
}
return out
}
func copyFloat64(value *float64) *float64 {
if value == nil {
return nil

View File

@@ -5,6 +5,7 @@ package app
import (
"context"
"errors"
"strconv"
"testing"
"time"
@@ -131,6 +132,7 @@ func TestServiceDelegatesLatestConvectiveOutlookRun(t *testing.T) {
if run == nil || run.LocationID != "stl" {
t.Fatalf("unexpected outlook run: %+v", run)
}
assertDiscussionDays(t, run, []int{1, 2})
}
func TestServiceLatestConvectiveOutlookNoData(t *testing.T) {
@@ -208,6 +210,7 @@ func TestServiceLatestConvectiveOutlookFiltersByDay(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
assertOutlookIDs(t, run, []string{"day-2"})
assertDiscussionDays(t, run, []int{2})
}
func TestServiceLatestConvectiveOutlookFiltersByOutlookType(t *testing.T) {
@@ -219,6 +222,7 @@ func TestServiceLatestConvectiveOutlookFiltersByOutlookType(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
assertOutlookIDs(t, run, []string{"tor-1"})
assertDiscussionDays(t, run, []int{1})
}
func TestServiceLatestConvectiveOutlookFiltersByContainsLocation(t *testing.T) {
@@ -230,7 +234,21 @@ func TestServiceLatestConvectiveOutlookFiltersByContainsLocation(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertOutlookIDs(t, run, []string{"cat-1", "tor-1"})
assertOutlookIDs(t, run, []string{"cat-1", "tor-1", "day-2"})
assertDiscussionDays(t, run, []int{1, 2})
}
func TestServiceLatestConvectiveOutlookContainsLocationFalseReturnsEmptyRun(t *testing.T) {
containsLocation := false
repo := &fakeRepository{outlookRun: testOutlookRun()}
svc := NewService(repo)
run, err := svc.LatestConvectiveOutlook(context.Background(), OutlookFilter{ContainsLocation: &containsLocation})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertOutlookIDs(t, run, nil)
assertDiscussionDays(t, run, nil)
}
func TestServiceLatestConvectiveOutlookFiltersByActiveAt(t *testing.T) {
@@ -243,6 +261,7 @@ func TestServiceLatestConvectiveOutlookFiltersByActiveAt(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
assertOutlookIDs(t, run, []string{"cat-1", "tor-1"})
assertDiscussionDays(t, run, []int{1})
}
func TestServiceLatestConvectiveOutlookCombinesFilters(t *testing.T) {
@@ -262,6 +281,7 @@ func TestServiceLatestConvectiveOutlookCombinesFilters(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
assertOutlookIDs(t, run, []string{"cat-1"})
assertDiscussionDays(t, run, []int{1})
}
func TestServiceLatestConvectiveOutlookActiveAtBoundary(t *testing.T) {
@@ -277,12 +297,14 @@ func TestServiceLatestConvectiveOutlookActiveAtBoundary(t *testing.T) {
t.Fatalf("unexpected validFrom error: %v", err)
}
assertOutlookIDs(t, fromRun, []string{"cat-1"})
assertDiscussionDays(t, fromRun, []int{1})
toRun, err := svc.LatestConvectiveOutlook(context.Background(), OutlookFilter{ActiveAt: &validTo})
if err != nil {
t.Fatalf("unexpected validTo error: %v", err)
}
assertOutlookIDs(t, toRun, nil)
assertDiscussionDays(t, toRun, nil)
}
func TestServiceLatestConvectiveOutlookKeepsRunMetadataWithEmptyOutlooks(t *testing.T) {
@@ -306,6 +328,12 @@ func TestServiceLatestConvectiveOutlookKeepsRunMetadataWithEmptyOutlooks(t *test
if len(run.Outlooks) != 0 {
t.Fatalf("expected no outlooks, got %+v", run.Outlooks)
}
if run.Discussions == nil {
t.Fatal("expected empty discussions slice, got nil")
}
if len(run.Discussions) != 0 {
t.Fatalf("expected no discussions, got %+v", run.Discussions)
}
}
func TestServiceLatestConvectiveOutlookDoesNotMutateRepositoryRun(t *testing.T) {
@@ -325,9 +353,12 @@ func TestServiceLatestConvectiveOutlookDoesNotMutateRepositoryRun(t *testing.T)
*run.Longitude = -99
*run.IssuedAt = testTime(99)
*run.Outlooks[0].SeverityRank = 99
*run.Discussions[0].UpdatedAt = testTime(98)
run.Outlooks[0].Geometry[0] = '{'
run.Outlooks[0].ID = "changed"
run.Discussions[0].Headline = "changed"
run.Outlooks = run.Outlooks[:1]
run.Discussions = run.Discussions[:1]
if *original.Latitude != 38.62 {
t.Fatalf("expected original latitude unchanged, got %v", *original.Latitude)
@@ -350,6 +381,15 @@ func TestServiceLatestConvectiveOutlookDoesNotMutateRepositoryRun(t *testing.T)
if len(original.Outlooks) != 3 {
t.Fatalf("expected original outlook slice unchanged, got %d entries", len(original.Outlooks))
}
if original.Discussions[0].UpdatedAt == nil || !original.Discussions[0].UpdatedAt.Equal(testTime(10)) {
t.Fatalf("expected original discussion updatedAt unchanged, got %v", original.Discussions[0].UpdatedAt)
}
if original.Discussions[0].Headline != "Day 1 headline" {
t.Fatalf("expected original discussion headline unchanged, got %q", original.Discussions[0].Headline)
}
if len(original.Discussions) != 3 {
t.Fatalf("expected original discussion slice unchanged, got %d entries", len(original.Discussions))
}
}
func TestServicePropagatesErrors(t *testing.T) {
@@ -376,8 +416,25 @@ func testOutlookRun() *model.WeatherOutlookRun {
Outlooks: []model.WeatherOutlook{
testOutlook("cat-1", 1, "categorical", true, testTime(12), testTime(18), 5, `["cat"]`),
testOutlook("tor-1", 1, "tornado", true, testTime(13), testTime(19), 7, `["tor"]`),
testOutlook("day-2", 2, "wind", false, testTime(18), testTime(24), 2, `["wind"]`),
testOutlook("day-2", 2, "wind", true, testTime(18), testTime(24), 2, `["wind"]`),
},
Discussions: []model.WeatherOutlookDiscussion{
testOutlookDiscussion(1),
testOutlookDiscussion(2),
testOutlookDiscussion(3),
},
}
}
func testOutlookDiscussion(day int) model.WeatherOutlookDiscussion {
updatedAt := testTime(9 + day)
dayText := strconv.Itoa(day)
return model.WeatherOutlookDiscussion{
Day: day,
Headline: "Day " + dayText + " headline",
Summary: "Day " + dayText + " summary",
Discussion: "Day " + dayText + " discussion",
UpdatedAt: &updatedAt,
}
}
@@ -421,3 +478,18 @@ func assertOutlookIDs(t *testing.T, run *model.WeatherOutlookRun, want []string)
}
}
}
func assertDiscussionDays(t *testing.T, run *model.WeatherOutlookRun, want []int) {
t.Helper()
if run == nil {
t.Fatal("expected outlook run")
}
if len(run.Discussions) != len(want) {
t.Fatalf("expected discussion days %v, got %+v", want, run.Discussions)
}
for i := range want {
if run.Discussions[i].Day != want[i] {
t.Fatalf("expected discussion days %v, got %+v", want, run.Discussions)
}
}
}