Simplify and rationalize the hourly report template
This commit is contained in:
@@ -11,7 +11,7 @@ func TestTemplateLookup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Template() error = %v", err)
|
||||
}
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "## Summary", "## Hourly Forecast", "## Weather Story"} {
|
||||
for _, want := range []string{"# {{ .Report.Title }}", "**Updated:**", "## Current Conditions", "## Hourly Forecast", "## Forecast Discussion"} {
|
||||
if !strings.Contains(source, want) {
|
||||
t.Fatalf("template missing %q:\n%s", want, source)
|
||||
}
|
||||
@@ -38,10 +38,10 @@ func TestSchemaLookup(t *testing.T) {
|
||||
if schema.AdditionalProperties {
|
||||
t.Fatal("additionalProperties = true, want false")
|
||||
}
|
||||
if strings.Join(schema.Required, ",") != "summary,timing,impacts" {
|
||||
t.Fatalf("required = %#v, want summary/timing/impacts", schema.Required)
|
||||
if strings.Join(schema.Required, ",") != "summary,forecast_discussion" {
|
||||
t.Fatalf("required = %#v, want summary/forecast_discussion", schema.Required)
|
||||
}
|
||||
for _, field := range []string{"summary", "timing", "impacts", "confidence"} {
|
||||
for _, field := range []string{"summary", "forecast_discussion", "precipitation_timing", "confidence"} {
|
||||
property, ok := schema.Properties[field].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("schema property %q missing or invalid", field)
|
||||
@@ -58,34 +58,36 @@ func TestRenderHourly(t *testing.T) {
|
||||
Title: "Hourly Report",
|
||||
LocationName: "Brentwood",
|
||||
ValidPeriodLabel: "May 29, 8:30 AM to 2:30 PM",
|
||||
GeneratedAtLabel: "May 29, 8:30 AM",
|
||||
GeneratedAtLabel: "Saturday, June 14, 2026 at 9:14 AM",
|
||||
},
|
||||
GeneratedText: testGeneratedText{
|
||||
Summary: "Storm chances increase through late morning.",
|
||||
Timing: "The main window is 10 AM to noon.",
|
||||
Impacts: "Brief downpours may slow travel.",
|
||||
Confidence: "Medium confidence in timing.",
|
||||
Summary: "Storm chances increase through late morning.",
|
||||
ForecastDiscussion: "A front will keep the region unsettled.",
|
||||
PrecipitationTiming: "A cold front is moving into the region.",
|
||||
Confidence: "Medium confidence in timing.",
|
||||
},
|
||||
Modules: testModules{
|
||||
CurrentConditions: &testCurrentConditions{
|
||||
ConditionText: "Partly cloudy",
|
||||
ConditionTextLower: "partly cloudy",
|
||||
TemperatureF: floatPtr(74),
|
||||
ApparentTemperatureF: floatPtr(76),
|
||||
RelativeHumidityPercent: floatPtr(71),
|
||||
WindDirection: "S",
|
||||
WindDirectionText: "south",
|
||||
WindSpeedMph: floatPtr(8),
|
||||
},
|
||||
HourlyForecast: &testHourlyForecast{
|
||||
Periods: []testHourlyPeriod{
|
||||
{PeriodBegins: "9 AM", TextDescription: "Cloudy", TemperatureF: floatPtr(74), ProbabilityOfPrecipitationPercent: floatPtr(30), WindDirection: "S", WindSpeedMph: floatPtr(8)},
|
||||
{PeriodBegins: "10 AM", TextDescription: "Showers", TemperatureF: floatPtr(75), ProbabilityOfPrecipitationPercent: floatPtr(70), WindDirection: "S", WindSpeedMph: floatPtr(10)},
|
||||
{HourLabel: "9:00 AM", TextDescription: "Cloudy", TextDescriptionLower: "cloudy", TemperatureF: floatPtr(74), ProbabilityOfPrecipitationPercent: floatPtr(19), WindDirection: "S", WindSpeedMph: floatPtr(8)},
|
||||
{HourLabel: "10:00 AM", TextDescription: "Showers", TextDescriptionLower: "showers", TemperatureF: floatPtr(75), ProbabilityOfPrecipitationPercent: floatPtr(70), MentionPrecipitation: true, WindDirection: "S", WindSpeedMph: floatPtr(10)},
|
||||
},
|
||||
},
|
||||
PrecipTiming: &testPrecipTiming{
|
||||
MaxPopPercent: intPtr(70),
|
||||
MaxPopTime: "10 AM",
|
||||
PrecipitationWindows: []testPrecipWindow{
|
||||
{PeriodBegins: "10 AM", PeriodEnds: "12 PM", MaxPopPercent: intPtr(70), MaxPopTime: "10 AM"},
|
||||
{PeriodBegins: "10 AM", PeriodBeginsHourLabel: "10:00 AM", PeriodEnds: "12 PM", PeriodEndsHourLabel: "12:00 PM", MaxPopPercent: intPtr(70), MaxPopTime: "10 AM", MaxPopHourLabel: "10:00 AM"},
|
||||
},
|
||||
},
|
||||
AlertDigest: &testAlertDigest{
|
||||
@@ -114,34 +116,71 @@ func TestRenderHourly(t *testing.T) {
|
||||
text := string(rendered)
|
||||
for _, want := range []string{
|
||||
"# Hourly Report",
|
||||
"Valid: May 29, 8:30 AM to 2:30 PM",
|
||||
"**Updated:** Saturday, June 14, 2026 at 9:14 AM",
|
||||
"Storm chances increase through late morning.",
|
||||
"## Confidence",
|
||||
"- 10 AM: Showers; 75 F; 70% precipitation; wind S 10 mph",
|
||||
"- Flood Watch: Flood Watch until 2:30 PM (Moderate)",
|
||||
"Short-term discussion favors increasing rain coverage.",
|
||||
"Currently, it is 74°F and partly cloudy. It feels like 76°F, with a relative humidity of 71% and winds from the south at 8 mph.",
|
||||
"## Active Alerts",
|
||||
"- **Flood Watch**: Flood Watch until 2:30 PM",
|
||||
"- **9:00 AM:** 74°F and cloudy.",
|
||||
"- **10:00 AM:** 75°F and showers. Probability of precipitation is 70%.",
|
||||
"- **10:00 AM** to **12:00 PM**: Precipitation is expected during this period. The peak precipitation chance is 70% at 10:00 AM.",
|
||||
"A cold front is moving into the region.",
|
||||
"A front will keep the region unsettled.",
|
||||
} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Fatalf("rendered template missing %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
if strings.Contains(text, "19%") || strings.Contains(text, "wind S") || strings.Contains(text, "## Confidence") {
|
||||
t.Fatalf("rendered template included omitted details:\n%s", text)
|
||||
}
|
||||
assertOrderedText(t, text, []string{
|
||||
"# Hourly Report",
|
||||
"## Summary",
|
||||
"## Timing",
|
||||
"## Impacts",
|
||||
"## Confidence",
|
||||
"## Current Conditions",
|
||||
"## Active Alerts",
|
||||
"## Hourly Forecast",
|
||||
"## Precipitation Timing",
|
||||
"## Alerts",
|
||||
"## SPC Outlooks",
|
||||
"## Forecast Discussion",
|
||||
"## SPC Discussion",
|
||||
"## Weather Story",
|
||||
})
|
||||
}
|
||||
|
||||
func TestRenderHourlyOmitsConditionalSectionsForClearWeather(t *testing.T) {
|
||||
rendered, err := Render("hourly", testRenderContext{
|
||||
Report: testReportContext{
|
||||
Title: "Hourly Report",
|
||||
GeneratedAtLabel: "Saturday, June 14, 2026 at 9:14 AM",
|
||||
},
|
||||
GeneratedText: testGeneratedText{
|
||||
Summary: "Dry weather is expected through the next several hours.",
|
||||
ForecastDiscussion: "Quiet conditions should persist through midday.",
|
||||
},
|
||||
Modules: testModules{
|
||||
CurrentConditions: &testCurrentConditions{
|
||||
ConditionTextLower: "cloudy",
|
||||
TemperatureF: floatPtr(72),
|
||||
},
|
||||
HourlyForecast: &testHourlyForecast{
|
||||
Periods: []testHourlyPeriod{
|
||||
{HourLabel: "9:00 AM", TextDescriptionLower: "mostly cloudy", TemperatureF: floatPtr(71), ProbabilityOfPrecipitationPercent: floatPtr(10)},
|
||||
},
|
||||
},
|
||||
AlertDigest: &testAlertDigest{},
|
||||
PrecipTiming: &testPrecipTiming{
|
||||
MaxPopPercent: intPtr(10),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Render() error = %v", err)
|
||||
}
|
||||
text := string(rendered)
|
||||
for _, unwanted := range []string{"## Active Alerts", "## Precipitation Timing", "Probability of precipitation is 10%", "wind"} {
|
||||
if strings.Contains(text, unwanted) {
|
||||
t.Fatalf("clear render includes %q:\n%s", unwanted, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownAssetsReturnActionableErrors(t *testing.T) {
|
||||
if _, err := Template("daily"); err == nil || !strings.Contains(err.Error(), `unknown report template "daily"`) {
|
||||
t.Fatalf("Template() error = %v, want unknown template", err)
|
||||
@@ -178,10 +217,10 @@ type testReportContext struct {
|
||||
}
|
||||
|
||||
type testGeneratedText struct {
|
||||
Summary string
|
||||
Timing string
|
||||
Impacts string
|
||||
Confidence string
|
||||
Summary string
|
||||
ForecastDiscussion string
|
||||
PrecipitationTiming string
|
||||
Confidence string
|
||||
}
|
||||
|
||||
type testModules struct {
|
||||
@@ -197,11 +236,13 @@ type testModules struct {
|
||||
|
||||
type testCurrentConditions struct {
|
||||
ConditionText string
|
||||
ConditionTextLower string
|
||||
TemperatureF *float64
|
||||
ApparentTemperatureF *float64
|
||||
RelativeHumidityPercent *float64
|
||||
WindSpeedMph *float64
|
||||
WindDirection string
|
||||
WindDirectionText string
|
||||
}
|
||||
|
||||
type testHourlyForecast struct {
|
||||
@@ -209,14 +250,17 @@ type testHourlyForecast struct {
|
||||
}
|
||||
|
||||
type testHourlyPeriod struct {
|
||||
HourLabel string
|
||||
PeriodBegins string
|
||||
Name string
|
||||
TextDescription string
|
||||
TextDescriptionLower string
|
||||
TemperatureF *float64
|
||||
WindSpeedMph *float64
|
||||
WindGustMph *float64
|
||||
WindDirection string
|
||||
ProbabilityOfPrecipitationPercent *float64
|
||||
MentionPrecipitation bool
|
||||
}
|
||||
|
||||
type testPrecipTiming struct {
|
||||
@@ -227,10 +271,13 @@ type testPrecipTiming struct {
|
||||
}
|
||||
|
||||
type testPrecipWindow struct {
|
||||
PeriodBegins string
|
||||
PeriodEnds string
|
||||
MaxPopPercent *int
|
||||
MaxPopTime string
|
||||
PeriodBegins string
|
||||
PeriodBeginsHourLabel string
|
||||
PeriodEnds string
|
||||
PeriodEndsHourLabel string
|
||||
MaxPopPercent *int
|
||||
MaxPopTime string
|
||||
MaxPopHourLabel string
|
||||
}
|
||||
|
||||
type testAlertDigest struct {
|
||||
|
||||
Reference in New Issue
Block a user