Add daily generated text assets
This commit is contained in:
@@ -12,10 +12,12 @@ import (
|
||||
|
||||
const (
|
||||
schemaIDHourly = "hourly"
|
||||
schemaIDDaily = "daily"
|
||||
schemaIDToday = "today"
|
||||
schemaIDTomorrow = "tomorrow"
|
||||
|
||||
templateIDHourly = "hourly"
|
||||
templateIDDaily = "daily"
|
||||
templateIDToday = "today"
|
||||
templateIDTomorrow = "tomorrow"
|
||||
)
|
||||
@@ -46,6 +48,11 @@ var catalog = []catalogEntry{
|
||||
validate: validateHourly,
|
||||
renderContextBuilder: buildHourlyContext,
|
||||
},
|
||||
{
|
||||
schemaID: schemaIDDaily,
|
||||
templateID: templateIDDaily,
|
||||
validate: validateDaily,
|
||||
},
|
||||
{
|
||||
schemaID: schemaIDToday,
|
||||
templateID: templateIDToday,
|
||||
@@ -142,6 +149,10 @@ func validateHourly(data []byte) (any, []byte, error) {
|
||||
return ValidateHourly(data)
|
||||
}
|
||||
|
||||
func validateDaily(data []byte) (any, []byte, error) {
|
||||
return ValidateDaily(data)
|
||||
}
|
||||
|
||||
func validateToday(data []byte) (any, []byte, error) {
|
||||
return ValidateToday(data)
|
||||
}
|
||||
|
||||
@@ -119,6 +119,32 @@ func TestCatalogLookupSupportsTodayDefinition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogLookupSupportsDailyDefinitionAssets(t *testing.T) {
|
||||
definition := report.Definition{
|
||||
ID: report.ID("daily"),
|
||||
GenerationMode: report.GenerationModeGeneratedTextTemplate,
|
||||
GeneratedTextSchemaID: "daily",
|
||||
TemplateID: "daily",
|
||||
}
|
||||
handler, err := LookupDefinition(definition)
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(daily) error = %v", err)
|
||||
}
|
||||
if handler.SchemaID() != "daily" || handler.TemplateID() != "daily" {
|
||||
t.Fatalf("handler IDs = %q/%q, want daily/daily", handler.SchemaID(), handler.TemplateID())
|
||||
}
|
||||
if schema, err := handler.Schema(); err != nil {
|
||||
t.Fatalf("Schema() error = %v", err)
|
||||
} else if len(schema) == 0 {
|
||||
t.Fatal("Schema() returned empty asset")
|
||||
}
|
||||
if template, err := handler.Template(); err != nil {
|
||||
t.Fatalf("Template() error = %v", err)
|
||||
} else if !strings.Contains(template, "# {{ .Report.Title }}") {
|
||||
t.Fatalf("Template() = %q, want Daily template source", template)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogValidationDispatchSupportsKnownSchemas(t *testing.T) {
|
||||
hourlyHandler, err := LookupDefinition(report.DefaultRegistry().MustLookup(report.Hourly))
|
||||
if err != nil {
|
||||
@@ -178,6 +204,29 @@ func TestCatalogValidationDispatchSupportsKnownSchemas(t *testing.T) {
|
||||
if !strings.Contains(string(normalized), `"forecast_discussion":["A front will keep rain chances elevated."]`) {
|
||||
t.Fatalf("today normalized text = %s, want trimmed discussion paragraph", normalized)
|
||||
}
|
||||
|
||||
dailyHandler, err := LookupDefinition(report.Definition{
|
||||
ID: report.ID("daily"),
|
||||
GenerationMode: report.GenerationModeGeneratedTextTemplate,
|
||||
GeneratedTextSchemaID: "daily",
|
||||
TemplateID: "daily",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(daily) error = %v", err)
|
||||
}
|
||||
daily, normalized, err := dailyHandler.Validate([]byte(`{
|
||||
"summary": " Showers are possible during the selected day. ",
|
||||
"forecast_discussion": [" A front will keep rain chances in the forecast. ", ""]
|
||||
}`))
|
||||
if err != nil {
|
||||
t.Fatalf("Validate(daily) error = %v", err)
|
||||
}
|
||||
if _, ok := daily.(Daily); !ok {
|
||||
t.Fatalf("daily generated text type = %T, want generatedtext.Daily", daily)
|
||||
}
|
||||
if !strings.Contains(string(normalized), `"forecast_discussion":["A front will keep rain chances in the forecast."]`) {
|
||||
t.Fatalf("daily normalized text = %s, want trimmed discussion paragraph", normalized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
@@ -231,3 +280,25 @@ func TestCatalogBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
t.Fatalf("BuildRenderContext(today) error = %v, want today generated text requirement", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogBuildRenderContextRejectsDailyUntilContextExists(t *testing.T) {
|
||||
handler, err := LookupDefinition(report.Definition{
|
||||
ID: report.ID("daily"),
|
||||
GenerationMode: report.GenerationModeGeneratedTextTemplate,
|
||||
GeneratedTextSchemaID: "daily",
|
||||
TemplateID: "daily",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("LookupDefinition(daily) error = %v", err)
|
||||
}
|
||||
_, err = handler.BuildRenderContext(testTomorrowMetadata(), testTomorrowSnapshot(t), testCollected(), testTomorrowDerived(), Daily{
|
||||
Summary: "Showers are possible during the selected day.",
|
||||
ForecastDiscussion: []string{"A front will keep rain chances in the forecast."},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("BuildRenderContext(daily) error = nil, want missing builder")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `render-context builder is not registered for template "daily"`) {
|
||||
t.Fatalf("BuildRenderContext(daily) error = %v, want missing daily builder", err)
|
||||
}
|
||||
}
|
||||
|
||||
37
internal/generatedtext/daily.go
Normal file
37
internal/generatedtext/daily.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package generatedtext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Daily struct {
|
||||
Summary string `json:"summary"`
|
||||
ForecastDiscussion []string `json:"forecast_discussion"`
|
||||
PrecipitationTiming string `json:"precipitation_timing,omitempty"`
|
||||
Confidence string `json:"confidence,omitempty"`
|
||||
}
|
||||
|
||||
func ValidateDaily(data []byte) (Daily, []byte, error) {
|
||||
value, err := decodeGeneratedText[Daily](data, "daily")
|
||||
if err != nil {
|
||||
return Daily{}, nil, err
|
||||
}
|
||||
|
||||
value.Summary = strings.TrimSpace(value.Summary)
|
||||
value.PrecipitationTiming = strings.TrimSpace(value.PrecipitationTiming)
|
||||
value.Confidence = strings.TrimSpace(value.Confidence)
|
||||
value.ForecastDiscussion = trimNonEmpty(value.ForecastDiscussion)
|
||||
if value.Summary == "" {
|
||||
return Daily{}, nil, fmt.Errorf("daily generated text summary is required")
|
||||
}
|
||||
if len(value.ForecastDiscussion) == 0 {
|
||||
return Daily{}, nil, fmt.Errorf("daily generated text forecast discussion is required")
|
||||
}
|
||||
|
||||
normalized, err := normalizeGeneratedText(value, "daily")
|
||||
if err != nil {
|
||||
return Daily{}, nil, err
|
||||
}
|
||||
return value, normalized, nil
|
||||
}
|
||||
111
internal/generatedtext/daily_test.go
Normal file
111
internal/generatedtext/daily_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package generatedtext
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateDailyNormalizesJSON(t *testing.T) {
|
||||
value, normalized, err := ValidateDaily([]byte(`{
|
||||
"summary": " Showers are possible during the selected day. ",
|
||||
"forecast_discussion": [
|
||||
" A front will keep rain chances in the forecast. ",
|
||||
"",
|
||||
" Temperatures stay seasonable by afternoon. "
|
||||
],
|
||||
"precipitation_timing": " Rain is most likely during the afternoon. ",
|
||||
"confidence": " Medium "
|
||||
}`))
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateDaily() error = %v", err)
|
||||
}
|
||||
if value.Summary != "Showers are possible during the selected day." {
|
||||
t.Fatalf("Summary = %q, want trimmed summary", value.Summary)
|
||||
}
|
||||
if strings.Join(value.ForecastDiscussion, "|") != "A front will keep rain chances in the forecast.|Temperatures stay seasonable by afternoon." {
|
||||
t.Fatalf("ForecastDiscussion = %#v, want trimmed non-empty paragraphs", value.ForecastDiscussion)
|
||||
}
|
||||
if value.PrecipitationTiming != "Rain is most likely during the afternoon." {
|
||||
t.Fatalf("PrecipitationTiming = %q, want trimmed precipitation timing", value.PrecipitationTiming)
|
||||
}
|
||||
want := `{"summary":"Showers are possible during the selected day.","forecast_discussion":["A front will keep rain chances in the forecast.","Temperatures stay seasonable by afternoon."],"precipitation_timing":"Rain is most likely during the afternoon.","confidence":"Medium"}`
|
||||
if string(normalized) != want {
|
||||
t.Fatalf("normalized = %s, want %s", normalized, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDailyOmitsEmptyOptionalFields(t *testing.T) {
|
||||
_, normalized, err := ValidateDaily([]byte(`{
|
||||
"summary": "Showers are possible during the selected day.",
|
||||
"forecast_discussion": ["A front will keep rain chances in the forecast."],
|
||||
"precipitation_timing": " ",
|
||||
"confidence": " "
|
||||
}`))
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateDaily() error = %v", err)
|
||||
}
|
||||
want := `{"summary":"Showers are possible during the selected day.","forecast_discussion":["A front will keep rain chances in the forecast."]}`
|
||||
if string(normalized) != want {
|
||||
t.Fatalf("normalized = %s, want %s", normalized, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDailyRejectsInvalidInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "malformed",
|
||||
in: `{`,
|
||||
want: "decode daily generated text",
|
||||
},
|
||||
{
|
||||
name: "unknown field",
|
||||
in: `{"summary":"Showers are possible during the selected day.","forecast_discussion":["A front will keep rain chances in the forecast."],"extra":"value"}`,
|
||||
want: `unknown field "extra"`,
|
||||
},
|
||||
{
|
||||
name: "missing summary",
|
||||
in: `{"forecast_discussion":["A front will keep rain chances in the forecast."]}`,
|
||||
want: "summary is required",
|
||||
},
|
||||
{
|
||||
name: "blank summary",
|
||||
in: `{"summary":" ","forecast_discussion":["A front will keep rain chances in the forecast."]}`,
|
||||
want: "summary is required",
|
||||
},
|
||||
{
|
||||
name: "missing forecast discussion",
|
||||
in: `{"summary":"Showers are possible during the selected day."}`,
|
||||
want: "forecast discussion is required",
|
||||
},
|
||||
{
|
||||
name: "blank forecast discussion",
|
||||
in: `{"summary":"Showers are possible during the selected day.","forecast_discussion":[" ",""]}`,
|
||||
want: "forecast discussion is required",
|
||||
},
|
||||
{
|
||||
name: "forecast discussion wrong type",
|
||||
in: `{"summary":"Showers are possible during the selected day.","forecast_discussion":"A front will keep rain chances in the forecast."}`,
|
||||
want: "cannot unmarshal string",
|
||||
},
|
||||
{
|
||||
name: "multiple values",
|
||||
in: `{"summary":"Showers are possible during the selected day.","forecast_discussion":["A front will keep rain chances in the forecast."]} {}`,
|
||||
want: "multiple JSON values",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, _, err := ValidateDaily([]byte(test.in))
|
||||
if err == nil {
|
||||
t.Fatal("ValidateDaily() error = nil, want error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), test.want) {
|
||||
t.Fatalf("ValidateDaily() error = %v, want %q", err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user