Centralize generated text template catalog
This commit is contained in:
@@ -21,7 +21,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptinput"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/reporttemplate"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/state"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
@@ -654,6 +653,11 @@ type generatedReportRequest struct {
|
||||
}
|
||||
|
||||
func generateTextTemplateReport(ctx context.Context, req generatedReportRequest) (*ReportResult, error) {
|
||||
handler, err := generatedtext.LookupDefinition(req.Resolved.Definition)
|
||||
if err != nil {
|
||||
return nil, generatedReportError(req.Resolved, req.metadata.RunID, "lookup generated text catalog", err)
|
||||
}
|
||||
|
||||
structuredResult, runErr := req.renderer.StructuredRun(ctx, scriptorium.StructuredRunRequest{
|
||||
PromptID: req.Resolved.Definition.PromptID,
|
||||
DataPackagePath: req.dataPackagePath,
|
||||
@@ -680,7 +684,7 @@ func generateTextTemplateReport(ctx context.Context, req generatedReportRequest)
|
||||
if err != nil {
|
||||
return nil, generatedReportError(req.Resolved, req.metadata.RunID, "load raw generated text", err)
|
||||
}
|
||||
generatedText, normalizedGeneratedText, err := validateGeneratedText(req.Resolved.Definition, rawGeneratedText)
|
||||
generatedText, normalizedGeneratedText, err := handler.Validate(rawGeneratedText)
|
||||
if err != nil {
|
||||
return nil, generatedReportError(req.Resolved, req.metadata.RunID, "validate generated text", err)
|
||||
}
|
||||
@@ -694,7 +698,7 @@ func generateTextTemplateReport(ctx context.Context, req generatedReportRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
renderContext, err := buildRenderContext(req.Resolved.Definition, req.briefingMetadata, req.moduleSnapshot, req.reportFacts, generatedText)
|
||||
renderContext, err := handler.BuildRenderContext(req.briefingMetadata, req.moduleSnapshot, req.reportFacts.Collected, req.reportFacts.Derived, generatedText)
|
||||
if err != nil {
|
||||
return nil, generatedReportError(req.Resolved, req.metadata.RunID, "build render context", err)
|
||||
}
|
||||
@@ -708,7 +712,7 @@ func generateTextTemplateReport(ctx context.Context, req generatedReportRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rendered, err := reporttemplate.Render(req.Resolved.Definition.TemplateID, renderContext)
|
||||
rendered, err := handler.Render(renderContext)
|
||||
if err != nil {
|
||||
return nil, generatedReportError(req.Resolved, req.metadata.RunID, "render template", err)
|
||||
}
|
||||
@@ -1113,36 +1117,6 @@ func preflightArtifact(result *scriptorium.RenderResult) state.PreflightArtifact
|
||||
}
|
||||
}
|
||||
|
||||
func validateGeneratedText(definition report.Definition, data []byte) (any, []byte, error) {
|
||||
switch definition.GeneratedTextSchemaID {
|
||||
case "hourly":
|
||||
return generatedtext.ValidateHourly(data)
|
||||
case "tomorrow":
|
||||
return generatedtext.ValidateTomorrow(data)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("generated text schema %q is not supported for report %q", definition.GeneratedTextSchemaID, definition.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func buildRenderContext(definition report.Definition, metadata briefing.Metadata, snapshot module.Snapshot, reportFacts ReportFacts, generated any) (any, error) {
|
||||
switch definition.TemplateID {
|
||||
case "hourly":
|
||||
hourly, ok := generated.(generatedtext.Hourly)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("report template %q requires hourly generated text for report %q", definition.TemplateID, definition.ID)
|
||||
}
|
||||
return generatedtext.BuildHourlyRenderContext(metadata, snapshot, hourly, reportFacts.Collected, reportFacts.Derived)
|
||||
case "tomorrow":
|
||||
tomorrow, ok := generated.(generatedtext.Tomorrow)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("report template %q requires tomorrow generated text for report %q", definition.TemplateID, definition.ID)
|
||||
}
|
||||
return generatedtext.BuildTomorrowRenderContext(metadata, snapshot, tomorrow, reportFacts.Collected, reportFacts.Derived)
|
||||
default:
|
||||
return nil, fmt.Errorf("report template %q is not supported for report %q", definition.TemplateID, definition.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func generatedReportError(resolved report.Resolved, runID string, operation string, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/scriptorium"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/generatedtext"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptinput"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
@@ -820,7 +819,7 @@ func TestGenerateHourlyReportPreservesRawTextOnValidationFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateHourlyReportRejectsUnsupportedTemplateBeforeRenderContext(t *testing.T) {
|
||||
func TestGenerateHourlyReportRejectsUnsupportedTemplateBeforeStructuredRun(t *testing.T) {
|
||||
cfg, resolved, store, notifier, outputPath := hourlyGeneratedTextFixture(t)
|
||||
resolved.Definition.TemplateID = "missing-template"
|
||||
renderer := &recordingRenderer{
|
||||
@@ -837,77 +836,21 @@ func TestGenerateHourlyReportRejectsUnsupportedTemplateBeforeRenderContext(t *te
|
||||
Store: store,
|
||||
Notifier: notifier,
|
||||
})
|
||||
assertGeneratedReportError(t, err, resolved, "build render context")
|
||||
assertGeneratedReportError(t, err, resolved, "lookup generated text catalog")
|
||||
if renderer.structuredRunCalls != 0 || renderer.runCalls != 0 {
|
||||
t.Fatalf("calls structured=%d run=%d, want no generated text run", renderer.structuredRunCalls, renderer.runCalls)
|
||||
}
|
||||
assertNoGeneratedFailureSideEffects(t, notifier, outputPath)
|
||||
|
||||
paths := hourlyArtifactPaths(t, store, resolved)
|
||||
assertPathsExist(t, paths.Preflight, paths.Metadata, paths.GeneratedTextRaw, paths.GeneratedTextResult, paths.GeneratedText)
|
||||
assertPathsMissing(t, paths.RenderContext, paths.RenderedReport)
|
||||
assertPathsExist(t, paths.Preflight, paths.Metadata)
|
||||
assertPathsMissing(t, paths.GeneratedTextRaw, paths.GeneratedTextResult, paths.GeneratedText, paths.RenderContext, paths.RenderedReport)
|
||||
metadataData, readErr := os.ReadFile(paths.Metadata)
|
||||
if readErr != nil {
|
||||
t.Fatalf("read metadata: %v", readErr)
|
||||
}
|
||||
if !strings.Contains(string(metadataData), paths.GeneratedText) {
|
||||
t.Fatalf("metadata missing generated text link:\n%s", string(metadataData))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedTextValidationDispatchSupportsKnownSchemas(t *testing.T) {
|
||||
hourlyDefinition := report.DefaultRegistry().MustLookup(report.Hourly)
|
||||
hourly, normalized, err := validateGeneratedText(hourlyDefinition, []byte(`{
|
||||
"summary": " Storm chances increase. ",
|
||||
"forecast_discussion": " A front will keep the region unsettled. "
|
||||
}`))
|
||||
if err != nil {
|
||||
t.Fatalf("validateGeneratedText(hourly) error = %v", err)
|
||||
}
|
||||
if _, ok := hourly.(generatedtext.Hourly); !ok {
|
||||
t.Fatalf("hourly generated text type = %T, want generatedtext.Hourly", hourly)
|
||||
}
|
||||
if !strings.Contains(string(normalized), `"summary":"Storm chances increase."`) {
|
||||
t.Fatalf("hourly normalized text = %s, want trimmed summary", normalized)
|
||||
}
|
||||
|
||||
tomorrowDefinition := report.DefaultRegistry().MustLookup(report.Tomorrow)
|
||||
tomorrow, normalized, err := validateGeneratedText(tomorrowDefinition, []byte(`{
|
||||
"summary": " Storms become more likely tomorrow. ",
|
||||
"forecast_discussion": [" A front will keep showers in the forecast. ", ""]
|
||||
}`))
|
||||
if err != nil {
|
||||
t.Fatalf("validateGeneratedText(tomorrow) error = %v", err)
|
||||
}
|
||||
if _, ok := tomorrow.(generatedtext.Tomorrow); !ok {
|
||||
t.Fatalf("tomorrow generated text type = %T, want generatedtext.Tomorrow", tomorrow)
|
||||
}
|
||||
if !strings.Contains(string(normalized), `"forecast_discussion":["A front will keep showers in the forecast."]`) {
|
||||
t.Fatalf("tomorrow normalized text = %s, want trimmed discussion paragraph", normalized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
definition := report.DefaultRegistry().MustLookup(report.Hourly)
|
||||
|
||||
_, err := buildRenderContext(definition, briefing.Metadata{}, module.Snapshot{}, ReportFacts{}, generatedtext.Tomorrow{
|
||||
Summary: "Storms become more likely tomorrow.",
|
||||
ForecastDiscussion: []string{"A front will keep showers in the forecast."},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("buildRenderContext() error = nil, want type mismatch")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `requires hourly generated text`) {
|
||||
t.Fatalf("buildRenderContext() error = %v, want hourly generated text requirement", err)
|
||||
}
|
||||
|
||||
tomorrowDefinition := report.DefaultRegistry().MustLookup(report.Tomorrow)
|
||||
_, err = buildRenderContext(tomorrowDefinition, briefing.Metadata{}, module.Snapshot{}, ReportFacts{}, generatedtext.Hourly{
|
||||
Summary: "Storm chances increase.",
|
||||
ForecastDiscussion: "A front will keep showers in the forecast.",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("buildRenderContext() error = nil, want tomorrow type mismatch")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `requires tomorrow generated text`) {
|
||||
t.Fatalf("buildRenderContext() error = %v, want tomorrow generated text requirement", err)
|
||||
if !strings.Contains(string(metadataData), paths.GeneratedTextRaw) {
|
||||
t.Fatalf("metadata missing generated text artifact path:\n%s", string(metadataData))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user