Route Tomorrow through generated text rendering
This commit is contained in:
@@ -566,6 +566,7 @@ func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, erro
|
||||
metadata: metadata,
|
||||
metadataPath: metadataPath,
|
||||
preflightPath: preflightPath,
|
||||
priorSnapshot: priorSnapshot,
|
||||
recentChanges: recentChanges,
|
||||
renderResult: renderResult,
|
||||
renderer: renderer,
|
||||
@@ -646,6 +647,7 @@ type generatedReportRequest struct {
|
||||
metadata state.Metadata
|
||||
metadataPath string
|
||||
preflightPath string
|
||||
priorSnapshot *state.PriorSnapshot
|
||||
recentChanges []changes.Change
|
||||
renderResult *scriptorium.RenderResult
|
||||
renderer Renderer
|
||||
@@ -755,6 +757,7 @@ func generateTextTemplateReport(ctx context.Context, req generatedReportRequest)
|
||||
NotificationPath: notificationPath,
|
||||
Metadata: req.metadata,
|
||||
MetadataPath: metadataPath,
|
||||
PriorSnapshot: req.priorSnapshot,
|
||||
RecentChanges: req.recentChanges,
|
||||
RenderResult: req.renderResult,
|
||||
StructuredRunResult: structuredResult,
|
||||
@@ -1129,6 +1132,12 @@ func buildRenderContext(definition report.Definition, metadata briefing.Metadata
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -813,7 +813,6 @@ func TestGeneratedTextValidationDispatchSupportsKnownSchemas(t *testing.T) {
|
||||
}
|
||||
|
||||
tomorrowDefinition := report.DefaultRegistry().MustLookup(report.Tomorrow)
|
||||
tomorrowDefinition.GeneratedTextSchemaID = "tomorrow"
|
||||
tomorrow, normalized, err := validateGeneratedText(tomorrowDefinition, []byte(`{
|
||||
"summary": " Storms become more likely tomorrow. ",
|
||||
"forecast_discussion": [" A front will keep showers in the forecast. ", ""]
|
||||
@@ -842,6 +841,18 @@ func TestBuildRenderContextRejectsMismatchedGeneratedText(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateReportDisabledNotificationDoesNotCallNotifier(t *testing.T) {
|
||||
@@ -1275,9 +1286,9 @@ func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) {
|
||||
t.Fatalf("ResolveGenerate() error = %v", err)
|
||||
}
|
||||
renderer := &recordingRenderer{
|
||||
renderResult: &scriptorium.RenderResult{ExitCode: 0},
|
||||
runResult: &scriptorium.RunResult{ExitCode: 0},
|
||||
runBody: "# Tomorrow Planning Brief\n",
|
||||
renderResult: &scriptorium.RenderResult{ExitCode: 0},
|
||||
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
|
||||
structuredRunBody: validTomorrowGeneratedTextJSON(),
|
||||
}
|
||||
|
||||
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||
@@ -1288,10 +1299,22 @@ func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateReport() error = %v", err)
|
||||
}
|
||||
if renderer.runCalls != 0 {
|
||||
t.Fatalf("markdown run calls = %d, want none", renderer.runCalls)
|
||||
}
|
||||
if renderer.structuredRunCalls != 1 {
|
||||
t.Fatalf("structured run calls = %d, want 1", renderer.structuredRunCalls)
|
||||
}
|
||||
if renderer.structuredRunRequest.OutputPath != result.GeneratedTextRawPath {
|
||||
t.Fatalf("structured run OutputPath = %q, want %q", renderer.structuredRunRequest.OutputPath, result.GeneratedTextRawPath)
|
||||
}
|
||||
|
||||
if result.Metadata.ReportID != report.Tomorrow || result.Metadata.Variant != "tomorrow" {
|
||||
t.Fatalf("metadata report/variant = %q/%q, want tomorrow", result.Metadata.ReportID, result.Metadata.Variant)
|
||||
}
|
||||
if result.Metadata.GeneratedTextSchemaID != "tomorrow" || result.Metadata.GeneratedTextPath != result.GeneratedTextPath || result.Metadata.RenderContextPath != result.RenderContextPath || result.Metadata.RenderedReportPath != result.ReportPath {
|
||||
t.Fatalf("metadata generated-text links = %#v, want tomorrow generated-text artifacts", result.Metadata)
|
||||
}
|
||||
dailySummary, ok, err := module.StanzaValue[map[string]any](result.ModuleSnapshot, "derived_daily_summary")
|
||||
if err != nil {
|
||||
t.Fatalf("decode daily summary: %v", err)
|
||||
@@ -1305,6 +1328,25 @@ func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) {
|
||||
if !strings.Contains(filepath.Base(result.ReportPath), "tomorrow") {
|
||||
t.Fatalf("ReportPath = %q, want managed tomorrow report path", result.ReportPath)
|
||||
}
|
||||
assertPathsExist(t, result.GeneratedTextRawPath, result.GeneratedTextResultPath, result.GeneratedTextPath, result.RenderContextPath, result.ReportPath)
|
||||
renderContext, err := os.ReadFile(result.RenderContextPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read render context: %v", err)
|
||||
}
|
||||
for _, want := range []string{`"Title": "Saturday's Weather"`, `"GeneratedText": {`, `"forecast_discussion": [`, `"Dayparts": [`} {
|
||||
if !strings.Contains(string(renderContext), want) {
|
||||
t.Fatalf("render context missing %q:\n%s", want, string(renderContext))
|
||||
}
|
||||
}
|
||||
reportData, err := os.ReadFile(result.ReportPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
for _, want := range []string{"# Saturday's Weather", "## Daypart Forecast", "## Forecast Discussion", "Tomorrow starts with showers before improving."} {
|
||||
if !strings.Contains(string(reportData), want) {
|
||||
t.Fatalf("tomorrow report missing %q:\n%s", want, string(reportData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomorrowReportCanCompareAgainstPriorTomorrowSnapshot(t *testing.T) {
|
||||
@@ -1334,9 +1376,9 @@ func TestTomorrowReportCanCompareAgainstPriorTomorrowSnapshot(t *testing.T) {
|
||||
t.Fatalf("ResolveGenerate(current) error = %v", err)
|
||||
}
|
||||
renderer := &recordingRenderer{
|
||||
renderResult: &scriptorium.RenderResult{ExitCode: 0},
|
||||
runResult: &scriptorium.RunResult{ExitCode: 0},
|
||||
runBody: "# Tomorrow Planning Brief\n",
|
||||
renderResult: &scriptorium.RenderResult{ExitCode: 0},
|
||||
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
|
||||
structuredRunBody: validTomorrowGeneratedTextJSON(),
|
||||
}
|
||||
|
||||
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||
@@ -1671,7 +1713,7 @@ func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) {
|
||||
name: "tomorrow",
|
||||
kind: ReportTomorrow,
|
||||
wantID: report.Tomorrow,
|
||||
wantPrompt: "weather.daily_report",
|
||||
wantPrompt: "weather.tomorrow_generated_text",
|
||||
wantStart: "2026-05-30T00:00:00-05:00",
|
||||
wantEnd: "2026-05-31T00:00:00-05:00",
|
||||
requestDate: time.Time{},
|
||||
@@ -2109,6 +2151,10 @@ func validHourlyGeneratedTextJSON() string {
|
||||
return `{"summary":"Storm chances increase through late morning.","forecast_discussion":"A front will keep the region unsettled.","precipitation_timing":"A cold front is moving into the region.","confidence":"Medium"}`
|
||||
}
|
||||
|
||||
func validTomorrowGeneratedTextJSON() string {
|
||||
return `{"summary":"Tomorrow starts with showers before improving.","forecast_discussion":["Morning showers should taper as drier air arrives.","Afternoon conditions trend quieter."],"precipitation_timing":"The best rain chance is during the morning."}`
|
||||
}
|
||||
|
||||
func generateDailyReportForTest(t *testing.T, cfg config.Config) *ReportResult {
|
||||
t.Helper()
|
||||
cfg.Workspace.Root = t.TempDir()
|
||||
@@ -2450,6 +2496,13 @@ func (r *selectiveRenderer) Run(_ context.Context, req scriptorium.RunRequest) (
|
||||
|
||||
func (r *selectiveRenderer) StructuredRun(_ context.Context, req scriptorium.StructuredRunRequest) (*scriptorium.StructuredRunResult, error) {
|
||||
r.structuredRunCalls++
|
||||
body := validHourlyGeneratedTextJSON()
|
||||
if req.PromptID == "weather.tomorrow_generated_text" {
|
||||
body = validTomorrowGeneratedTextJSON()
|
||||
}
|
||||
if err := os.WriteFile(req.OutputPath, []byte(body), 0o600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &scriptorium.StructuredRunResult{ExitCode: 0, OutputPath: req.OutputPath}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ func TestRunGenerateTomorrowWritesMarkdownReport(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(report), "# Daily Report") {
|
||||
if !strings.Contains(string(report), "# Saturday's Weather") {
|
||||
t.Fatalf("report output missing markdown:\n%s", string(report))
|
||||
}
|
||||
dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "tomorrow", "2026-05-30", "*.data_package.yaml")
|
||||
@@ -1176,13 +1176,31 @@ if [ "$1" = "render" ]; then
|
||||
fi
|
||||
if [ "$1" = "run" ]; then
|
||||
out=""
|
||||
prompt=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = "--out" ]; then
|
||||
shift
|
||||
out="$1"
|
||||
elif [ "$1" = "--prompt" ]; then
|
||||
shift
|
||||
prompt="$1"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
if [ "$prompt" = "weather.tomorrow_generated_text" ]; then
|
||||
cat > "$out" <<'JSON'
|
||||
{
|
||||
"summary": "Tomorrow starts with showers before improving.",
|
||||
"forecast_discussion": [
|
||||
"Morning showers should taper as drier air arrives.",
|
||||
"Afternoon conditions trend quieter."
|
||||
],
|
||||
"precipitation_timing": "The best rain chance is during the morning."
|
||||
}
|
||||
JSON
|
||||
printf 'wrote generated text\n' >&2
|
||||
exit 0
|
||||
fi
|
||||
printf '# Daily Report\n\nGenerated by fake scriptorium.\n' > "$out"
|
||||
printf 'wrote report\n' >&2
|
||||
exit 0
|
||||
@@ -1225,6 +1243,20 @@ if [ "$1" = "run" ]; then
|
||||
"precipitation_timing": "A cold front is moving into the region.",
|
||||
"confidence": "Medium"
|
||||
}
|
||||
JSON
|
||||
printf 'wrote generated text\n' >&2
|
||||
exit 0
|
||||
fi
|
||||
if [ "$prompt" = "weather.tomorrow_generated_text" ]; then
|
||||
cat > "$out" <<'JSON'
|
||||
{
|
||||
"summary": "Tomorrow starts with showers before improving.",
|
||||
"forecast_discussion": [
|
||||
"Morning showers should taper as drier air arrives.",
|
||||
"Afternoon conditions trend quieter."
|
||||
],
|
||||
"precipitation_timing": "The best rain chance is during the morning."
|
||||
}
|
||||
JSON
|
||||
printf 'wrote generated text\n' >&2
|
||||
exit 0
|
||||
|
||||
@@ -24,18 +24,20 @@ func dailyTodayDefinition() Definition {
|
||||
|
||||
func tomorrowDefinition() Definition {
|
||||
return Definition{
|
||||
ID: Tomorrow,
|
||||
Name: "Tomorrow Report",
|
||||
PromptID: "weather.daily_report",
|
||||
GenerationMode: GenerationModeScriptoriumMarkdown,
|
||||
ComparisonStrategy: CompareSameValidDate,
|
||||
ArtifactGroup: "tomorrow",
|
||||
BatchOutputName: "tomorrow.md",
|
||||
Generated: true,
|
||||
CompatiblePriorIDs: []ID{Tomorrow},
|
||||
Modules: tomorrowModules(),
|
||||
Evening: true,
|
||||
resolve: resolveTomorrow,
|
||||
ID: Tomorrow,
|
||||
Name: "Tomorrow Report",
|
||||
PromptID: "weather.tomorrow_generated_text",
|
||||
GenerationMode: GenerationModeGeneratedTextTemplate,
|
||||
TemplateID: "tomorrow",
|
||||
GeneratedTextSchemaID: "tomorrow",
|
||||
ComparisonStrategy: CompareSameValidDate,
|
||||
ArtifactGroup: "tomorrow",
|
||||
BatchOutputName: "tomorrow.md",
|
||||
Generated: true,
|
||||
CompatiblePriorIDs: []ID{Tomorrow},
|
||||
Modules: tomorrowModules(),
|
||||
Evening: true,
|
||||
resolve: resolveTomorrow,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,17 @@ func TestTomorrowValidPeriodFromEveningGeneration(t *testing.T) {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, resolved.ValidPeriod, "2026-05-30T00:00:00-05:00", "2026-05-31T00:00:00-05:00")
|
||||
if resolved.Definition.PromptID != "weather.daily_report" {
|
||||
t.Fatalf("PromptID = %q, want weather.daily_report", resolved.Definition.PromptID)
|
||||
if resolved.Definition.PromptID != "weather.tomorrow_generated_text" {
|
||||
t.Fatalf("PromptID = %q, want weather.tomorrow_generated_text", resolved.Definition.PromptID)
|
||||
}
|
||||
if resolved.Definition.GenerationMode != GenerationModeGeneratedTextTemplate {
|
||||
t.Fatalf("GenerationMode = %q, want generated_text_template", resolved.Definition.GenerationMode)
|
||||
}
|
||||
if resolved.Definition.TemplateID != "tomorrow" {
|
||||
t.Fatalf("TemplateID = %q, want tomorrow", resolved.Definition.TemplateID)
|
||||
}
|
||||
if resolved.Definition.GeneratedTextSchemaID != "tomorrow" {
|
||||
t.Fatalf("GeneratedTextSchemaID = %q, want tomorrow", resolved.Definition.GeneratedTextSchemaID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,15 +277,16 @@ func TestRegistryDefinitionsDeclareGenerationMetadata(t *testing.T) {
|
||||
if !definition.Generated {
|
||||
continue
|
||||
}
|
||||
if definition.ID == Hourly {
|
||||
if definition.ID == Hourly || definition.ID == Tomorrow {
|
||||
wantTemplate := string(definition.ID)
|
||||
if definition.GenerationMode != GenerationModeGeneratedTextTemplate {
|
||||
t.Fatalf("%s GenerationMode = %q, want %q", definition.ID, definition.GenerationMode, GenerationModeGeneratedTextTemplate)
|
||||
}
|
||||
if definition.TemplateID != "hourly" {
|
||||
t.Fatalf("%s TemplateID = %q, want hourly", definition.ID, definition.TemplateID)
|
||||
if definition.TemplateID != wantTemplate {
|
||||
t.Fatalf("%s TemplateID = %q, want %s", definition.ID, definition.TemplateID, wantTemplate)
|
||||
}
|
||||
if definition.GeneratedTextSchemaID != "hourly" {
|
||||
t.Fatalf("%s GeneratedTextSchemaID = %q, want hourly", definition.ID, definition.GeneratedTextSchemaID)
|
||||
if definition.GeneratedTextSchemaID != wantTemplate {
|
||||
t.Fatalf("%s GeneratedTextSchemaID = %q, want %s", definition.ID, definition.GeneratedTextSchemaID, wantTemplate)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user