diff --git a/docs/internal/prompt-input.md b/docs/internal/prompt-input.md index 18f128a..70e7f66 100644 --- a/docs/internal/prompt-input.md +++ b/docs/internal/prompt-input.md @@ -17,7 +17,9 @@ Inputs: Outputs: - `promptinput.Package` containing schema version, RunID, report metadata, - briefing content, Recent Changes, and source warnings + briefing content, Recent Changes, and source warnings. Briefing content + includes discussion key messages and short/long-term AFD narratives when the + Weather API provides them. - optional JSON file written by `promptinput.Save` ## Boundaries diff --git a/internal/adapters/weatherapi/client_test.go b/internal/adapters/weatherapi/client_test.go index fc3151d..8cd77c9 100644 --- a/internal/adapters/weatherapi/client_test.go +++ b/internal/adapters/weatherapi/client_test.go @@ -43,6 +43,12 @@ func TestFetchBundleFromFixtures(t *testing.T) { if bundle.Discussion == nil || len(bundle.Discussion.KeyMessages) != 2 { t.Fatalf("Discussion = %#v, want key messages", bundle.Discussion) } + if bundle.Discussion.ShortTerm == nil || bundle.Discussion.ShortTerm.Narrative != "A weak boundary may trigger isolated showers." { + t.Fatalf("Discussion.ShortTerm = %#v, want short-term AFD narrative", bundle.Discussion.ShortTerm) + } + if bundle.Discussion.LongTerm == nil || bundle.Discussion.LongTerm.Narrative != "Warmer temperatures and periodic rain chances continue into the weekend." { + t.Fatalf("Discussion.LongTerm = %#v, want long-term AFD narrative", bundle.Discussion.LongTerm) + } if len(bundle.Sources) != 8 { t.Fatalf("Sources length = %d, want 8", len(bundle.Sources)) } diff --git a/internal/adapters/weatherapi/testdata/discussion.json b/internal/adapters/weatherapi/testdata/discussion.json index c72769e..67375c2 100644 --- a/internal/adapters/weatherapi/testdata/discussion.json +++ b/internal/adapters/weatherapi/testdata/discussion.json @@ -11,6 +11,10 @@ "shortTerm": { "title": "Short Term", "narrative": "A weak boundary may trigger isolated showers." + }, + "longTerm": { + "title": "Long Term", + "narrative": "Warmer temperatures and periodic rain chances continue into the weekend." } } } diff --git a/internal/app/app_test.go b/internal/app/app_test.go index b571358..ad5a765 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -33,7 +33,7 @@ func TestFetchAndSaveBundle(t *testing.T) { case "/alerts/active": _, _ = w.Write([]byte(`{"data":{"alerts":[]}}`)) case "/discussion": - _, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":[]}}`)) + _, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":[],"shortTerm":{"title":"Short Term","narrative":"Short-term AFD narrative for saved bundle."},"longTerm":{"title":"Long Term","narrative":"Long-term AFD narrative for saved bundle."}}}`)) default: http.NotFound(w, r) } @@ -195,6 +195,9 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) { if !strings.Contains(string(data), `"recentChanges"`) || !strings.Contains(string(data), `data_package.v1`) { t.Fatalf("data package missing expected content:\n%s", string(data)) } + if !strings.Contains(string(data), "Short-term AFD narrative for generated report.") || !strings.Contains(string(data), "Long-term AFD narrative for generated report.") { + t.Fatalf("data package missing AFD short/long-term discussion:\n%s", string(data)) + } preflight, err := os.ReadFile(result.PreflightPath) if err != nil { t.Fatalf("read preflight: %v", err) @@ -881,7 +884,7 @@ func dailyBundleServer(t *testing.T) *httptest.Server { case "/alerts/active": _, _ = w.Write([]byte(`{"data":{"alerts":[{"event":"Flood Watch","effective":"2026-05-29T05:00:00-05:00","expires":"2026-05-29T09:00:00-05:00"}]}}`)) case "/discussion": - _, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":["Storms are most likely during the morning."]}}`)) + _, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":["Storms are most likely during the morning."],"shortTerm":{"title":"Short Term","narrative":"Short-term AFD narrative for generated report."},"longTerm":{"title":"Long Term","narrative":"Long-term AFD narrative for generated report."}}}`)) default: http.NotFound(w, r) } diff --git a/internal/briefing/daily_test.go b/internal/briefing/daily_test.go index 8d89ad0..efaf80d 100644 --- a/internal/briefing/daily_test.go +++ b/internal/briefing/daily_test.go @@ -67,6 +67,12 @@ func TestDailyBriefingFromRepresentativeFixture(t *testing.T) { if len(pkg.Daily.Discussion.KeyMessages) != 1 { t.Fatalf("Discussion key messages length = %d, want 1", len(pkg.Daily.Discussion.KeyMessages)) } + if pkg.Daily.Discussion.ShortTerm != "Morning showers taper as a weak boundary shifts east." { + t.Fatalf("Discussion.ShortTerm = %q, want short-term AFD narrative", pkg.Daily.Discussion.ShortTerm) + } + if pkg.Daily.Discussion.LongTerm != "Warmer and more humid conditions return with periodic rain chances." { + t.Fatalf("Discussion.LongTerm = %q, want long-term AFD narrative", pkg.Daily.Discussion.LongTerm) + } if pkg.Daily.OutdoorWindows.Best == nil || pkg.Daily.OutdoorWindows.Worst == nil { t.Fatalf("OutdoorWindows = %#v, want best and worst", pkg.Daily.OutdoorWindows) } diff --git a/internal/briefing/storm_test.go b/internal/briefing/storm_test.go index b71a248..9935528 100644 --- a/internal/briefing/storm_test.go +++ b/internal/briefing/storm_test.go @@ -44,7 +44,12 @@ func TestStormBriefingWithActiveAlert(t *testing.T) { Alerts: &forecast.AlertRun{Alerts: []json.RawMessage{ json.RawMessage(`{"event":"Severe Thunderstorm Warning","headline":"Severe storms near Testville","severity":"Severe","effective":"2026-05-29T06:30:00-05:00","expires":"2026-05-29T08:30:00-05:00"}`), }}, - Discussion: &forecast.Discussion{Product: "discussion", KeyMessages: []string{"Storms may intensify quickly."}}, + Discussion: &forecast.Discussion{ + Product: "discussion", + KeyMessages: []string{"Storms may intensify quickly."}, + ShortTerm: &forecast.DiscussionSection{Narrative: "Short-term storm coverage peaks this morning."}, + LongTerm: &forecast.DiscussionSection{Narrative: "Long-term pattern stays unsettled after the event."}, + }, WeatherStory: &forecast.WeatherStory{Raw: json.RawMessage(`{"headline":"Storm risk"}`)}, Sources: []forecast.Source{{Name: "hourly", FetchedAt: time.Now()}}, } @@ -75,6 +80,12 @@ func TestStormBriefingWithActiveAlert(t *testing.T) { if pkg.Storm.WeatherStory == nil { t.Fatal("WeatherStory = nil, want available story context") } + if pkg.Storm.Discussion.ShortTerm != "Short-term storm coverage peaks this morning." { + t.Fatalf("Discussion.ShortTerm = %q, want short-term AFD narrative", pkg.Storm.Discussion.ShortTerm) + } + if pkg.Storm.Discussion.LongTerm != "Long-term pattern stays unsettled after the event." { + t.Fatalf("Discussion.LongTerm = %q, want long-term AFD narrative", pkg.Storm.Discussion.LongTerm) + } if len(pkg.Storm.WhatToWatchNext) == 0 { t.Fatal("WhatToWatchNext length = 0, want watch inputs") } diff --git a/internal/briefing/three_day_test.go b/internal/briefing/three_day_test.go index 3d24eb4..ce267f0 100644 --- a/internal/briefing/three_day_test.go +++ b/internal/briefing/three_day_test.go @@ -43,7 +43,12 @@ func TestThreeDayBriefingBuildsOutlookDays(t *testing.T) { }, }, AlertOverlaps: []forecast.AlertOverlap{{Event: "Flood Watch"}}, - Discussion: &forecast.Discussion{Product: "discussion", KeyMessages: []string{"Unsettled stretch."}}, + Discussion: &forecast.Discussion{ + Product: "discussion", + KeyMessages: []string{"Unsettled stretch."}, + ShortTerm: &forecast.DiscussionSection{Narrative: "Short-term rain chances remain focused today."}, + LongTerm: &forecast.DiscussionSection{Narrative: "Long-term warmth builds into the weekend."}, + }, }, { Date: "2026-05-30", @@ -80,4 +85,10 @@ func TestThreeDayBriefingBuildsOutlookDays(t *testing.T) { if len(pkg.ThreeDay.RelevantAlerts) != 1 { t.Fatalf("RelevantAlerts length = %d, want 1", len(pkg.ThreeDay.RelevantAlerts)) } + if pkg.ThreeDay.Discussion.ShortTerm != "Short-term rain chances remain focused today." { + t.Fatalf("Discussion.ShortTerm = %q, want short-term AFD narrative", pkg.ThreeDay.Discussion.ShortTerm) + } + if pkg.ThreeDay.Discussion.LongTerm != "Long-term warmth builds into the weekend." { + t.Fatalf("Discussion.LongTerm = %q, want long-term AFD narrative", pkg.ThreeDay.Discussion.LongTerm) + } } diff --git a/internal/briefing/weekend_test.go b/internal/briefing/weekend_test.go index 4b2b6ee..2f8d48c 100644 --- a/internal/briefing/weekend_test.go +++ b/internal/briefing/weekend_test.go @@ -54,7 +54,12 @@ func TestWeekendBriefingBuildsPlanningInputs(t *testing.T) { }, }, AlertOverlaps: []forecast.AlertOverlap{{Event: "Flood Watch"}}, - Discussion: &forecast.Discussion{Product: "discussion", KeyMessages: []string{"Timing may shift."}}, + Discussion: &forecast.Discussion{ + Product: "discussion", + KeyMessages: []string{"Timing may shift."}, + ShortTerm: &forecast.DiscussionSection{Narrative: "Short-term showers exit before the weekend."}, + LongTerm: &forecast.DiscussionSection{Narrative: "Long-term weekend rain timing remains uncertain."}, + }, }, } @@ -85,4 +90,10 @@ func TestWeekendBriefingBuildsPlanningInputs(t *testing.T) { if len(pkg.Weekend.Planning.UncertaintyInputs) == 0 { t.Fatal("UncertaintyInputs length = 0, want discussion context") } + if pkg.Weekend.Discussion.ShortTerm != "Short-term showers exit before the weekend." { + t.Fatalf("Discussion.ShortTerm = %q, want short-term AFD narrative", pkg.Weekend.Discussion.ShortTerm) + } + if pkg.Weekend.Discussion.LongTerm != "Long-term weekend rain timing remains uncertain." { + t.Fatalf("Discussion.LongTerm = %q, want long-term AFD narrative", pkg.Weekend.Discussion.LongTerm) + } } diff --git a/internal/forecast/testdata/daily_bundle.json b/internal/forecast/testdata/daily_bundle.json index a5ef672..0e1e496 100644 --- a/internal/forecast/testdata/daily_bundle.json +++ b/internal/forecast/testdata/daily_bundle.json @@ -54,7 +54,15 @@ "issuedAt": "2026-05-29T09:25:00-05:00", "keyMessages": [ "Storms are most likely during the morning." - ] + ], + "shortTerm": { + "title": "Short Term", + "narrative": "Morning showers taper as a weak boundary shifts east." + }, + "longTerm": { + "title": "Long Term", + "narrative": "Warmer and more humid conditions return with periodic rain chances." + } }, "sources": [ {