Rename rolling report to hourly
This commit is contained in:
@@ -30,7 +30,7 @@ type ReportKind string
|
||||
const (
|
||||
ReportDaily ReportKind = "daily"
|
||||
ReportTomorrow ReportKind = "tomorrow"
|
||||
ReportNearTerm ReportKind = "near-term"
|
||||
ReportHourly ReportKind = "hourly"
|
||||
ReportThreeDay ReportKind = "three-day"
|
||||
ReportWeekend ReportKind = "weekend"
|
||||
ReportStorm ReportKind = "storm"
|
||||
@@ -387,8 +387,8 @@ func reportIDForCommand(kind ReportKind) (report.ID, error) {
|
||||
return report.DailyToday, nil
|
||||
case ReportTomorrow:
|
||||
return report.DailyTomorrow, nil
|
||||
case ReportNearTerm:
|
||||
return report.NearTerm, nil
|
||||
case ReportHourly:
|
||||
return report.Hourly, nil
|
||||
case ReportThreeDay:
|
||||
return report.ThreeDay, nil
|
||||
case ReportWeekend:
|
||||
|
||||
@@ -349,19 +349,19 @@ func TestGenerateReportOmitsSPCConvectiveDiscussionBelowThreshold(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
server := nearTermBundleServer(t)
|
||||
func TestGenerateHourlyReportWritesExpectedArtifacts(t *testing.T) {
|
||||
server := hourlyBundleServer(t)
|
||||
cfg := dailyTestConfig(t, server)
|
||||
cfg.Workspace.Root = t.TempDir()
|
||||
resolved, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg,
|
||||
Report: ReportNearTerm,
|
||||
Report: ReportHourly,
|
||||
}, mustParse("2026-05-29T08:30:00-05:00"))
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveGenerate() error = %v", err)
|
||||
}
|
||||
outputPath := filepath.Join(t.TempDir(), "near-term.md")
|
||||
renderer := successfulRenderer("# Near-Term Report\n")
|
||||
outputPath := filepath.Join(t.TempDir(), "hourly.md")
|
||||
renderer := successfulRenderer("# Hourly Report\n")
|
||||
|
||||
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||
Config: cfg,
|
||||
@@ -373,8 +373,8 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
t.Fatalf("GenerateReport() error = %v", err)
|
||||
}
|
||||
|
||||
if result.Metadata.ReportID != report.NearTerm || result.Metadata.PromptID != "weather.near_term_report" {
|
||||
t.Fatalf("metadata report/prompt = %q/%q, want near-term", result.Metadata.ReportID, result.Metadata.PromptID)
|
||||
if result.Metadata.ReportID != report.Hourly || result.Metadata.PromptID != "weather.hourly_report" {
|
||||
t.Fatalf("metadata report/prompt = %q/%q, want hourly", result.Metadata.ReportID, result.Metadata.PromptID)
|
||||
}
|
||||
if got := result.Metadata.ValidPeriod.Start.Format(time.RFC3339); got != "2026-05-29T08:30:00-05:00" {
|
||||
t.Fatalf("valid period start = %s, want rolling window start", got)
|
||||
@@ -382,14 +382,14 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
if got := result.Metadata.ValidPeriod.End.Format(time.RFC3339); got != "2026-05-29T14:30:00-05:00" {
|
||||
t.Fatalf("valid period end = %s, want six-hour rolling window end", got)
|
||||
}
|
||||
if renderer.renderRequest.PromptID != "weather.near_term_report" || renderer.runRequest.PromptID != "weather.near_term_report" {
|
||||
t.Fatalf("renderer prompt IDs = %q/%q, want near-term prompt", renderer.renderRequest.PromptID, renderer.runRequest.PromptID)
|
||||
if renderer.renderRequest.PromptID != "weather.hourly_report" || renderer.runRequest.PromptID != "weather.hourly_report" {
|
||||
t.Fatalf("renderer prompt IDs = %q/%q, want hourly prompt", renderer.renderRequest.PromptID, renderer.runRequest.PromptID)
|
||||
}
|
||||
if !strings.Contains(result.ReportPath, filepath.Join("reports", "near-term")) {
|
||||
t.Fatalf("ReportPath = %q, want near-term artifact group", result.ReportPath)
|
||||
if !strings.Contains(result.ReportPath, filepath.Join("reports", "hourly")) {
|
||||
t.Fatalf("ReportPath = %q, want hourly artifact group", result.ReportPath)
|
||||
}
|
||||
if !strings.Contains(result.DataPackagePath, filepath.Join("data-packages", "near-term", "2026-05-29")) {
|
||||
t.Fatalf("DataPackagePath = %q, want near-term artifact group", result.DataPackagePath)
|
||||
if !strings.Contains(result.DataPackagePath, filepath.Join("data-packages", "hourly", "2026-05-29")) {
|
||||
t.Fatalf("DataPackagePath = %q, want hourly artifact group", result.DataPackagePath)
|
||||
}
|
||||
assertPathsExist(t, result.ModuleSnapshotPath, result.DataPackagePath, result.PreflightPath, result.ReportPath, result.MetadataPath, outputPath)
|
||||
if result.OutputPath != outputPath {
|
||||
@@ -399,7 +399,7 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("read output copy: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(copiedReport), "# Near-Term Report") {
|
||||
if !strings.Contains(string(copiedReport), "# Hourly Report") {
|
||||
t.Fatalf("output copy missing rendered report:\n%s", string(copiedReport))
|
||||
}
|
||||
|
||||
@@ -423,11 +423,11 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
t.Fatalf("decode hourly forecast: %v", err)
|
||||
}
|
||||
if !ok || len(hourly.Periods) != 5 {
|
||||
t.Fatalf("hourly forecast = %#v, want five overlapping near-term periods", hourly)
|
||||
t.Fatalf("hourly forecast = %#v, want five overlapping hourly periods", hourly)
|
||||
}
|
||||
hourlyJSON := mustMarshalString(t, hourly)
|
||||
if !strings.Contains(hourlyJSON, "Showers entering the area") || !strings.Contains(hourlyJSON, "Heavy rain") {
|
||||
t.Fatalf("hourly forecast missing selected near-term periods:\n%s", hourlyJSON)
|
||||
t.Fatalf("hourly forecast missing selected hourly periods:\n%s", hourlyJSON)
|
||||
}
|
||||
if strings.Contains(hourlyJSON, "Before-window storms") || strings.Contains(hourlyJSON, "After-window rain") {
|
||||
t.Fatalf("hourly forecast contains periods outside valid window:\n%s", hourlyJSON)
|
||||
@@ -438,13 +438,13 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
t.Fatalf("decode precip timing: %v", err)
|
||||
}
|
||||
if !ok || precip.MaxPopPercent == nil || *precip.MaxPopPercent != 80 || len(precip.PrecipitationWindows) != 2 {
|
||||
t.Fatalf("precip timing = %#v, want near-term precipitation windows", precip)
|
||||
t.Fatalf("precip timing = %#v, want hourly precipitation windows", precip)
|
||||
}
|
||||
if precip.PrecipitationWindows[0].PeriodBegins != "2026-05-29 at 8:00 AM" || precip.PrecipitationWindows[0].PeriodEnds != "2026-05-29 at 9:00 AM" {
|
||||
t.Fatalf("first precip window = %#v, want first selected hour only", precip.PrecipitationWindows[0])
|
||||
}
|
||||
if precip.PrecipitationWindows[1].PeriodBegins != "2026-05-29 at 10:00 AM" || precip.PrecipitationWindows[1].PeriodEnds != "2026-05-29 at 12:00 PM" {
|
||||
t.Fatalf("second precip window = %#v, want late-morning near-term rain", precip.PrecipitationWindows[1])
|
||||
t.Fatalf("second precip window = %#v, want late-morning hourly rain", precip.PrecipitationWindows[1])
|
||||
}
|
||||
|
||||
alerts, ok, err := module.StanzaValue[briefing.AlertDigestModule](result.ModuleSnapshot, "alert_digest")
|
||||
@@ -452,7 +452,7 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
t.Fatalf("decode alert digest: %v", err)
|
||||
}
|
||||
if !ok || !alerts.Checked || alerts.ActiveCount != 3 || alerts.RelevantCount != 1 || len(alerts.Relevant) != 1 || alerts.Relevant[0].Event != "Flood Watch" {
|
||||
t.Fatalf("alert digest = %#v, want only near-term alert overlap relevant", alerts)
|
||||
t.Fatalf("alert digest = %#v, want only hourly alert overlap relevant", alerts)
|
||||
}
|
||||
|
||||
outlooks, ok, err := module.StanzaValue[briefing.SPCConvectiveOutlooksModule](result.ModuleSnapshot, "spc_convective_outlooks")
|
||||
@@ -460,13 +460,13 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
t.Fatalf("decode SPC outlooks: %v", err)
|
||||
}
|
||||
if !ok || !outlooks.Checked || outlooks.OutlookCount != 1 || len(outlooks.Outlooks) != 1 || outlooks.Outlooks[0].Label != "SLGT" {
|
||||
t.Fatalf("SPC outlooks = %#v, want one overlapping near-term outlook", outlooks)
|
||||
t.Fatalf("SPC outlooks = %#v, want one overlapping hourly outlook", outlooks)
|
||||
}
|
||||
discussion, ok, err := module.StanzaValue[briefing.SPCConvectiveDiscussionModule](result.ModuleSnapshot, "spc_convective_discussion")
|
||||
if err != nil {
|
||||
t.Fatalf("decode SPC discussion: %v", err)
|
||||
}
|
||||
if !ok || len(discussion.Discussions) != 1 || discussion.Discussions[0].Headline != "Near-term severe storms" {
|
||||
if !ok || len(discussion.Discussions) != 1 || discussion.Discussions[0].Headline != "hourly severe storms" {
|
||||
t.Fatalf("SPC discussion = %#v, want discussion for retained overlapping outlook", discussion)
|
||||
}
|
||||
|
||||
@@ -474,11 +474,11 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("decode AFD: %v", err)
|
||||
}
|
||||
if !ok || len(afd.KeyMessages) != 1 || afd.ShortTerm != "Short-term AFD narrative for near-term report." {
|
||||
if !ok || len(afd.KeyMessages) != 1 || afd.ShortTerm != "Short-term AFD narrative for hourly report." {
|
||||
t.Fatalf("AFD = %#v, want key messages and short term", afd)
|
||||
}
|
||||
if afd.Product != "" || afd.LongTerm != "" {
|
||||
t.Fatalf("AFD = %#v, want near-term defaults to omit product and long term", afd)
|
||||
t.Fatalf("AFD = %#v, want hourly defaults to omit product and long term", afd)
|
||||
}
|
||||
|
||||
if result.PriorSnapshot != nil || len(result.RecentChanges) != 0 || len(result.DataPackage.RecentChanges.Items) != 0 {
|
||||
@@ -487,8 +487,8 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
data := readDataPackageForTest(t, result)
|
||||
text := string(data)
|
||||
for _, want := range []string{
|
||||
"id: near_term",
|
||||
"prompt_id: weather.near_term_report",
|
||||
"id: hourly",
|
||||
"prompt_id: weather.hourly_report",
|
||||
"valid_period:",
|
||||
"recent_changes:",
|
||||
" items: []",
|
||||
@@ -505,9 +505,9 @@ func TestGenerateNearTermReportWritesExpectedArtifacts(t *testing.T) {
|
||||
t.Fatalf("data package missing %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
for _, omitted := range []string{"validPeriod:", "Before-window storms", "After-window rain", "Evening Advisory", "Day 2 outlook", "Long-term AFD narrative for near-term report."} {
|
||||
for _, omitted := range []string{"validPeriod:", "Before-window storms", "After-window rain", "Evening Advisory", "Day 2 outlook", "Long-term AFD narrative for hourly report."} {
|
||||
if strings.Contains(text, omitted) {
|
||||
t.Fatalf("data package contains %q, want near-term filtered/canonical output:\n%s", omitted, text)
|
||||
t.Fatalf("data package contains %q, want hourly filtered/canonical output:\n%s", omitted, text)
|
||||
}
|
||||
}
|
||||
riskIndex := strings.Index(text, " applicable_risk_products:")
|
||||
@@ -1364,10 +1364,10 @@ func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) {
|
||||
requestDate: time.Time{},
|
||||
},
|
||||
{
|
||||
name: "near-term",
|
||||
kind: ReportNearTerm,
|
||||
wantID: report.NearTerm,
|
||||
wantPrompt: "weather.near_term_report",
|
||||
name: "hourly",
|
||||
kind: ReportHourly,
|
||||
wantID: report.Hourly,
|
||||
wantPrompt: "weather.hourly_report",
|
||||
wantStart: "2026-05-29T08:00:00-05:00",
|
||||
wantEnd: "2026-05-29T14:00:00-05:00",
|
||||
},
|
||||
@@ -1488,7 +1488,7 @@ func dailyBundleServerWithConvectiveResponse(t *testing.T, convectiveResponse st
|
||||
return server
|
||||
}
|
||||
|
||||
func nearTermBundleServer(t *testing.T) *httptest.Server {
|
||||
func hourlyBundleServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
@@ -1503,11 +1503,11 @@ func nearTermBundleServer(t *testing.T) *httptest.Server {
|
||||
case "/alerts/active":
|
||||
_, _ = w.Write([]byte(`{"data":{"alerts":[{"event":"Expired Advisory","headline":"Ends at valid start","severity":"Minor","effective":"2026-05-29T06:00:00-05:00","expires":"2026-05-29T08:30:00-05:00"},{"event":"Flood Watch","headline":"Flooding possible","severity":"Moderate","effective":"2026-05-29T11:00:00-05:00","expires":"2026-05-29T15:00:00-05:00"},{"event":"Evening Advisory","headline":"Starts at valid end","severity":"Minor","effective":"2026-05-29T14:30:00-05:00","expires":"2026-05-29T18:00:00-05:00"}]}}`))
|
||||
case "/discussion":
|
||||
_, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T08:05:00-05:00","keyMessages":["Storms are most likely late this morning."],"shortTerm":{"qualifier":"(Short Term)","text":"Short-term AFD narrative for near-term report."},"longTerm":{"qualifier":"(Long Term)","text":"Long-term AFD narrative for near-term report."}}}`))
|
||||
_, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T08:05:00-05:00","keyMessages":["Storms are most likely late this morning."],"shortTerm":{"qualifier":"(Short Term)","text":"Short-term AFD narrative for hourly report."},"longTerm":{"qualifier":"(Long Term)","text":"Long-term AFD narrative for hourly report."}}}`))
|
||||
case "/weatherstories/latest":
|
||||
_, _ = w.Write([]byte(`{"data":{"officeId":"LSX","startTime":"2026-05-29T13:00:00Z","endTime":"2026-05-29T20:00:00Z","updatedAt":"2026-05-29T13:05:00Z","title":"Near-Term Storm Chances","description":"Scattered showers and thunderstorms are possible.","altText":"Weather story graphic with rain chances.","priority":true,"order":1,"downloadUrl":"https://api.weather.gov/offices/LSX/weatherstories/download/near-term"}}`))
|
||||
_, _ = w.Write([]byte(`{"data":{"officeId":"LSX","startTime":"2026-05-29T13:00:00Z","endTime":"2026-05-29T20:00:00Z","updatedAt":"2026-05-29T13:05:00Z","title":"Hourly Storm Chances","description":"Scattered showers and thunderstorms are possible.","altText":"Weather story graphic with rain chances.","priority":true,"order":1,"downloadUrl":"https://api.weather.gov/offices/LSX/weatherstories/download/hourly"}}`))
|
||||
case "/outlooks/convective":
|
||||
_, _ = w.Write([]byte(`{"data":{"locationId":"home","locationName":"Brentwood","asOf":"2026-05-29T13:30:00Z","issuedAt":"2026-05-29T13:00:00Z","outlooks":[{"id":"day1-near-term","day":1,"outlookType":"categorical","label":"SLGT","labelText":"Slight Risk","severityRank":3,"validFrom":"2026-05-29T10:00:00-05:00","validTo":"2026-05-29T16:00:00-05:00","issuedAt":"2026-05-29T08:00:00-05:00","containsLocation":true},{"id":"day2-outside","day":2,"outlookType":"categorical","label":"ENH","labelText":"Day 2 outlook","severityRank":4,"validFrom":"2026-05-30T10:00:00-05:00","validTo":"2026-05-30T16:00:00-05:00","issuedAt":"2026-05-29T08:00:00-05:00","containsLocation":true}],"discussions":[{"day":1,"headline":"Near-term severe storms","summary":"Scattered severe storms are possible.","discussion":"Damaging winds may occur during the near-term window.","updatedAt":"2026-05-29T08:15:00-05:00"},{"day":2,"headline":"Day 2 discussion","summary":"Later period risk.","discussion":"This day 2 discussion should not be retained.","updatedAt":"2026-05-29T08:20:00-05:00"}]}}`))
|
||||
_, _ = w.Write([]byte(`{"data":{"locationId":"home","locationName":"Brentwood","asOf":"2026-05-29T13:30:00Z","issuedAt":"2026-05-29T13:00:00Z","outlooks":[{"id":"day1-hourly","day":1,"outlookType":"categorical","label":"SLGT","labelText":"Slight Risk","severityRank":3,"validFrom":"2026-05-29T10:00:00-05:00","validTo":"2026-05-29T16:00:00-05:00","issuedAt":"2026-05-29T08:00:00-05:00","containsLocation":true},{"id":"day2-outside","day":2,"outlookType":"categorical","label":"ENH","labelText":"Day 2 outlook","severityRank":4,"validFrom":"2026-05-30T10:00:00-05:00","validTo":"2026-05-30T16:00:00-05:00","issuedAt":"2026-05-29T08:00:00-05:00","containsLocation":true}],"discussions":[{"day":1,"headline":"hourly severe storms","summary":"Scattered severe storms are possible.","discussion":"Damaging winds may occur during the hourly window.","updatedAt":"2026-05-29T08:15:00-05:00"},{"day":2,"headline":"Day 2 discussion","summary":"Later period risk.","discussion":"This day 2 discussion should not be retained.","updatedAt":"2026-05-29T08:20:00-05:00"}]}}`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user