Wire hourly generated text rendering
This commit is contained in:
@@ -349,9 +349,15 @@ func TestGenerateReportOmitsSPCConvectiveDiscussionBelowThreshold(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateHourlyReportModeNotImplemented(t *testing.T) {
|
||||
func TestGenerateHourlyReportUsesGeneratedTextTemplateWorkflow(t *testing.T) {
|
||||
server := hourlyBundleServer(t)
|
||||
cfg := config.Defaults()
|
||||
cfg.WeatherAPI.BaseURL = server.URL + "/"
|
||||
cfg.WeatherAPI.Timezone = "America/Chicago"
|
||||
cfg.Workspace.Root = t.TempDir()
|
||||
cfg.Location.ID = "home"
|
||||
cfg.Location.Name = "Brentwood"
|
||||
cfg.Location.Region = "MO"
|
||||
resolved, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg,
|
||||
Report: ReportHourly,
|
||||
@@ -362,14 +368,136 @@ func TestGenerateHourlyReportModeNotImplemented(t *testing.T) {
|
||||
if resolved.Definition.GenerationMode != report.GenerationModeGeneratedTextTemplate {
|
||||
t.Fatalf("GenerationMode = %q, want generated text template", resolved.Definition.GenerationMode)
|
||||
}
|
||||
filesystemStore, err := state.NewFilesystemStore(cfg.Workspace)
|
||||
if err != nil {
|
||||
t.Fatalf("NewFilesystemStore() error = %v", err)
|
||||
}
|
||||
store := &recordingStore{Store: filesystemStore}
|
||||
renderer := &recordingRenderer{
|
||||
renderResult: &scriptorium.RenderResult{
|
||||
Command: []string{"scriptorium", "render"},
|
||||
Stdout: `{"prepared":true}`,
|
||||
ExitCode: 0,
|
||||
},
|
||||
structuredRunResult: &scriptorium.StructuredRunResult{
|
||||
Command: []string{"scriptorium", "run"},
|
||||
Stderr: "wrote generated text",
|
||||
ExitCode: 0,
|
||||
},
|
||||
structuredRunBody: `{
|
||||
"summary": " Storm chances increase through late morning. ",
|
||||
"timing": "The main window is 10 AM to noon.",
|
||||
"impacts": "Brief downpours may slow travel.",
|
||||
"confidence": "Medium"
|
||||
}`,
|
||||
}
|
||||
|
||||
_, err = GenerateReport(context.Background(), ReportRequest{
|
||||
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||
Config: cfg,
|
||||
Resolved: resolved,
|
||||
Renderer: successfulRenderer("# Hourly Report\n"),
|
||||
Renderer: renderer,
|
||||
Store: store,
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), `generation mode "generated_text_template" is not implemented for report "hourly"`) {
|
||||
t.Fatalf("GenerateReport() error = %v, want generation mode not implemented", err)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateReport() error = %v", err)
|
||||
}
|
||||
|
||||
if renderer.renderCalls != 1 {
|
||||
t.Fatalf("render calls = %d, want 1", renderer.renderCalls)
|
||||
}
|
||||
if renderer.structuredRunCalls != 1 {
|
||||
t.Fatalf("structured run calls = %d, want 1", renderer.structuredRunCalls)
|
||||
}
|
||||
if renderer.runCalls != 0 {
|
||||
t.Fatalf("markdown run calls = %d, want none", renderer.runCalls)
|
||||
}
|
||||
if renderer.structuredRunRequest.OutputPath != result.GeneratedTextRawPath {
|
||||
t.Fatalf("structured run OutputPath = %q, want %q", renderer.structuredRunRequest.OutputPath, result.GeneratedTextRawPath)
|
||||
}
|
||||
if renderer.structuredRunRequest.DataPackagePath != result.DataPackagePath {
|
||||
t.Fatalf("structured run DataPackagePath = %q, want %q", renderer.structuredRunRequest.DataPackagePath, result.DataPackagePath)
|
||||
}
|
||||
if got, want := strings.Join(store.calls, ","), "module_snapshot,data_package,preflight,metadata,generated_text_result,metadata,generated_text,render_context,prepare_report,metadata"; got != want {
|
||||
t.Fatalf("store calls = %v, want %s", store.calls, want)
|
||||
}
|
||||
|
||||
assertPathsExist(t,
|
||||
result.ModuleSnapshotPath,
|
||||
result.DataPackagePath,
|
||||
result.PreflightPath,
|
||||
result.GeneratedTextRawPath,
|
||||
result.GeneratedTextResultPath,
|
||||
result.GeneratedTextPath,
|
||||
result.RenderContextPath,
|
||||
result.ReportPath,
|
||||
result.MetadataPath,
|
||||
)
|
||||
raw, err := os.ReadFile(result.GeneratedTextRawPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read raw generated text: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(raw), `"summary": " Storm chances increase through late morning. "`) {
|
||||
t.Fatalf("raw generated text was not preserved:\n%s", string(raw))
|
||||
}
|
||||
normalized, err := os.ReadFile(result.GeneratedTextPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read validated generated text: %v", err)
|
||||
}
|
||||
if string(normalized) != `{"summary":"Storm chances increase through late morning.","timing":"The main window is 10 AM to noon.","impacts":"Brief downpours may slow travel.","confidence":"Medium"}` {
|
||||
t.Fatalf("validated generated text = %s, want normalized JSON", normalized)
|
||||
}
|
||||
renderContext, err := os.ReadFile(result.RenderContextPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read render context: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(renderContext), `"ReportTitle": "Hourly Report"`) || !strings.Contains(string(renderContext), `"LocationName": "Brentwood, MO"`) {
|
||||
t.Fatalf("render context missing deterministic fields:\n%s", string(renderContext))
|
||||
}
|
||||
reportData, err := os.ReadFile(result.ReportPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
reportText := string(reportData)
|
||||
for _, want := range []string{
|
||||
"# Hourly Report",
|
||||
"Storm chances increase through late morning.",
|
||||
"The main window is 10 AM to noon.",
|
||||
"Flood Watch: Flooding possible (Moderate)",
|
||||
"Short-term AFD narrative for hourly report.",
|
||||
"Hourly Storm Chances - Scattered showers and thunderstorms are possible.",
|
||||
} {
|
||||
if !strings.Contains(reportText, want) {
|
||||
t.Fatalf("rendered hourly report missing %q:\n%s", want, reportText)
|
||||
}
|
||||
}
|
||||
if result.OutputPath != result.ReportPath {
|
||||
t.Fatalf("OutputPath = %q, want managed report path %q", result.OutputPath, result.ReportPath)
|
||||
}
|
||||
if result.StructuredRunResult == nil || result.StructuredRunResult.OutputPath != result.GeneratedTextRawPath {
|
||||
t.Fatalf("StructuredRunResult = %#v, want captured structured run result", result.StructuredRunResult)
|
||||
}
|
||||
if result.RunResult != nil {
|
||||
t.Fatalf("RunResult = %#v, want nil for generated-text template workflow", result.RunResult)
|
||||
}
|
||||
if len(result.RecentChanges) != 0 {
|
||||
t.Fatalf("RecentChanges = %#v, want none for hourly report", result.RecentChanges)
|
||||
}
|
||||
if result.Metadata.GeneratedTextSchemaID != "hourly" ||
|
||||
result.Metadata.GeneratedTextRawPath != result.GeneratedTextRawPath ||
|
||||
result.Metadata.GeneratedTextResultPath != result.GeneratedTextResultPath ||
|
||||
result.Metadata.GeneratedTextPath != result.GeneratedTextPath ||
|
||||
result.Metadata.RenderContextPath != result.RenderContextPath ||
|
||||
result.Metadata.RenderedReportPath != result.ReportPath {
|
||||
t.Fatalf("metadata generated-text links = %#v, want saved artifact links", result.Metadata)
|
||||
}
|
||||
metadataData, err := os.ReadFile(result.MetadataPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read metadata: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(metadataData), `"generatedTextSchemaId": "hourly"`) ||
|
||||
!strings.Contains(string(metadataData), result.GeneratedTextRawPath) ||
|
||||
!strings.Contains(string(metadataData), result.RenderContextPath) {
|
||||
t.Fatalf("metadata JSON missing generated-text links:\n%s", string(metadataData))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1715,15 +1843,20 @@ func appBriefingMetadata(resolved report.Resolved) briefing.Metadata {
|
||||
}
|
||||
|
||||
type recordingRenderer struct {
|
||||
renderCalls int
|
||||
runCalls int
|
||||
renderRequest scriptorium.RenderRequest
|
||||
runRequest scriptorium.RunRequest
|
||||
renderResult *scriptorium.RenderResult
|
||||
runResult *scriptorium.RunResult
|
||||
err error
|
||||
runErr error
|
||||
runBody string
|
||||
renderCalls int
|
||||
runCalls int
|
||||
structuredRunCalls int
|
||||
renderRequest scriptorium.RenderRequest
|
||||
runRequest scriptorium.RunRequest
|
||||
structuredRunRequest scriptorium.StructuredRunRequest
|
||||
renderResult *scriptorium.RenderResult
|
||||
runResult *scriptorium.RunResult
|
||||
structuredRunResult *scriptorium.StructuredRunResult
|
||||
err error
|
||||
runErr error
|
||||
structuredRunErr error
|
||||
runBody string
|
||||
structuredRunBody string
|
||||
}
|
||||
|
||||
type recordingStore struct {
|
||||
@@ -1746,6 +1879,26 @@ func (s *recordingStore) SavePreflight(ctx context.Context, resolved report.Reso
|
||||
return s.Store.SavePreflight(ctx, resolved, artifact)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveGeneratedTextRaw(ctx context.Context, resolved report.Resolved, data []byte) (string, error) {
|
||||
s.calls = append(s.calls, "generated_text_raw")
|
||||
return s.Store.SaveGeneratedTextRaw(ctx, resolved, data)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveGeneratedTextResult(ctx context.Context, resolved report.Resolved, value any) (string, error) {
|
||||
s.calls = append(s.calls, "generated_text_result")
|
||||
return s.Store.SaveGeneratedTextResult(ctx, resolved, value)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveGeneratedText(ctx context.Context, resolved report.Resolved, data []byte) (string, error) {
|
||||
s.calls = append(s.calls, "generated_text")
|
||||
return s.Store.SaveGeneratedText(ctx, resolved, data)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveRenderContext(ctx context.Context, resolved report.Resolved, value any) (string, error) {
|
||||
s.calls = append(s.calls, "render_context")
|
||||
return s.Store.SaveRenderContext(ctx, resolved, value)
|
||||
}
|
||||
|
||||
func (s *recordingStore) PrepareRenderedReport(ctx context.Context, resolved report.Resolved) (string, error) {
|
||||
s.calls = append(s.calls, "prepare_report")
|
||||
return s.Store.PrepareRenderedReport(ctx, resolved)
|
||||
@@ -1765,10 +1918,11 @@ func successfulRenderer(body string) *recordingRenderer {
|
||||
}
|
||||
|
||||
type selectiveRenderer struct {
|
||||
renderCalls int
|
||||
runCalls int
|
||||
failRenderPrompt string
|
||||
runBody string
|
||||
renderCalls int
|
||||
runCalls int
|
||||
structuredRunCalls int
|
||||
failRenderPrompt string
|
||||
runBody string
|
||||
}
|
||||
|
||||
type recordingNotifier struct {
|
||||
@@ -1826,6 +1980,11 @@ func (r *selectiveRenderer) Run(_ context.Context, req scriptorium.RunRequest) (
|
||||
return &scriptorium.RunResult{ExitCode: 0, OutputPath: req.OutputPath}, nil
|
||||
}
|
||||
|
||||
func (r *selectiveRenderer) StructuredRun(_ context.Context, req scriptorium.StructuredRunRequest) (*scriptorium.StructuredRunResult, error) {
|
||||
r.structuredRunCalls++
|
||||
return &scriptorium.StructuredRunResult{ExitCode: 0, OutputPath: req.OutputPath}, nil
|
||||
}
|
||||
|
||||
func (r *recordingRenderer) Render(_ context.Context, req scriptorium.RenderRequest) (*scriptorium.RenderResult, error) {
|
||||
r.renderCalls++
|
||||
r.renderRequest = req
|
||||
@@ -1845,3 +2004,17 @@ func (r *recordingRenderer) Run(_ context.Context, req scriptorium.RunRequest) (
|
||||
}
|
||||
return r.runResult, r.runErr
|
||||
}
|
||||
|
||||
func (r *recordingRenderer) StructuredRun(_ context.Context, req scriptorium.StructuredRunRequest) (*scriptorium.StructuredRunResult, error) {
|
||||
r.structuredRunCalls++
|
||||
r.structuredRunRequest = req
|
||||
if r.structuredRunBody != "" {
|
||||
if err := os.WriteFile(req.OutputPath, []byte(r.structuredRunBody), 0o600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.structuredRunResult != nil {
|
||||
r.structuredRunResult.OutputPath = req.OutputPath
|
||||
}
|
||||
return r.structuredRunResult, r.structuredRunErr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user