2026-05-29 17:06:39 +00:00
package app
import (
"context"
2026-05-29 17:32:57 +00:00
"errors"
2026-05-29 17:06:39 +00:00
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
2026-05-29 17:17:50 +00:00
"time"
2026-05-29 17:06:39 +00:00
2026-05-29 17:32:57 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/scriptorium"
2026-05-29 17:53:03 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
2026-05-29 17:06:39 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
2026-05-29 17:53:03 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
2026-05-29 17:17:50 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
2026-05-29 17:40:38 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/state"
2026-05-29 17:06:39 +00:00
)
func TestFetchAndSaveBundle ( t * testing . T ) {
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
switch r . URL . Path {
case "/observations" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "timestamp":"2026-05-29T14:00:00Z","conditionCode":3}} ` ) )
case "/conditions/current" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "conditionText":"Clear"}} ` ) )
case "/forecast/hourly" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "issuedAt":"2026-05-29T10:30:00-05:00","product":"hourly","periods":[ { "startTime":"2026-05-29T13:00:00-05:00","endTime":"2026-05-29T14:00:00-05:00"}]}} ` ) )
case "/forecast/narrative" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "issuedAt":"2026-05-29T10:30:00-05:00","product":"narrative","periods":[]}} ` ) )
case "/alerts/active" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "alerts":[]}} ` ) )
case "/discussion" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":[]}} ` ) )
default :
http . NotFound ( w , r )
}
} ) )
defer server . Close ( )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
path := filepath . Join ( t . TempDir ( ) , "bundle.json" )
bundle , err := FetchAndSaveBundle ( context . Background ( ) , FetchBundleRequest { Config : cfg , OutputPath : path } )
if err != nil {
t . Fatalf ( "FetchAndSaveBundle() error = %v" , err )
}
if bundle . Hourly == nil {
t . Fatal ( "Hourly = nil, want fetched bundle" )
}
data , err := os . ReadFile ( path )
if err != nil {
t . Fatalf ( "read saved bundle: %v" , err )
}
if ! strings . Contains ( string ( data ) , ` "product": "hourly" ` ) {
t . Fatalf ( "saved bundle missing hourly product:\n%s" , string ( data ) )
}
}
func TestFetchAndSaveBundleRequiresOutputPath ( t * testing . T ) {
_ , err := FetchAndSaveBundle ( context . Background ( ) , FetchBundleRequest { Config : config . Defaults ( ) } )
if err == nil {
t . Fatal ( "FetchAndSaveBundle() error = nil, want output path error" )
}
if ! strings . Contains ( err . Error ( ) , "output path" ) {
t . Fatalf ( "error = %q, want output path context" , err . Error ( ) )
}
}
2026-05-29 17:17:50 +00:00
2026-05-29 17:23:49 +00:00
func TestGenerateDailyBriefingWritesArtifact ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
path := filepath . Join ( t . TempDir ( ) , "daily.briefing.json" )
result , err := GenerateDailyBriefing ( context . Background ( ) , DailyBriefingRequest {
Config : cfg ,
Resolved : resolved ,
OutputPath : path ,
} )
if err != nil {
t . Fatalf ( "GenerateDailyBriefing() error = %v" , err )
}
if result . OutputPath != path {
t . Fatalf ( "OutputPath = %q, want %q" , result . OutputPath , path )
}
if result . Package . Daily == nil {
t . Fatal ( "Daily = nil" )
}
data , err := os . ReadFile ( path )
if err != nil {
t . Fatalf ( "read briefing artifact: %v" , err )
}
if ! strings . Contains ( string ( data ) , ` "schemaVersion" ` ) || ! strings . Contains ( string ( data ) , ` "daily" ` ) {
t . Fatalf ( "briefing artifact missing expected fields:\n%s" , string ( data ) )
}
}
func TestGenerateDailyBriefingDefaultPath ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
result , err := GenerateDailyBriefing ( context . Background ( ) , DailyBriefingRequest {
Config : cfg ,
Resolved : resolved ,
} )
if err != nil {
t . Fatalf ( "GenerateDailyBriefing() error = %v" , err )
}
2026-05-29 17:40:38 +00:00
if ! strings . HasSuffix ( result . OutputPath , filepath . Join ( "snapshots" , "daily" , "2026-05-29" , "20260529T100000.000000000Z_daily_today.briefing.json" ) ) {
2026-05-29 17:23:49 +00:00
t . Fatalf ( "OutputPath = %q, want deterministic daily briefing path" , result . OutputPath )
}
}
2026-05-29 17:47:42 +00:00
func TestGenerateDailyReportWritesReportAndPreflight ( t * testing . T ) {
2026-05-29 17:32:57 +00:00
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
renderer := & recordingRenderer {
2026-05-29 17:47:42 +00:00
renderResult : & scriptorium . RenderResult {
2026-05-29 17:32:57 +00:00
Command : [ ] string { "scriptorium" , "render" } ,
Stdout : ` { "prepared":true} ` ,
ExitCode : 0 ,
} ,
2026-05-29 17:47:42 +00:00
runResult : & scriptorium . RunResult {
Command : [ ] string { "scriptorium" , "run" } ,
Stderr : "wrote report" ,
ExitCode : 0 ,
OutputPath : "" ,
} ,
runBody : "# Daily Report\n\nRain this morning.\n" ,
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:47:42 +00:00
outputPath := filepath . Join ( t . TempDir ( ) , "daily.md" )
2026-05-29 17:32:57 +00:00
2026-05-29 17:47:42 +00:00
result , err := GenerateDailyReport ( context . Background ( ) , DailyReportRequest {
Config : cfg ,
Resolved : resolved ,
OutputPath : outputPath ,
Renderer : renderer ,
2026-05-29 17:32:57 +00:00
} )
if err != nil {
2026-05-29 17:47:42 +00:00
t . Fatalf ( "GenerateDailyReport() error = %v" , err )
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:47:42 +00:00
if renderer . renderCalls != 1 {
t . Fatalf ( "render calls = %d, want 1" , renderer . renderCalls )
}
if renderer . runCalls != 1 {
t . Fatalf ( "run calls = %d, want 1" , renderer . runCalls )
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:47:42 +00:00
if renderer . renderRequest . PromptID != "weather.daily_report" {
t . Fatalf ( "render PromptID = %q, want weather.daily_report" , renderer . renderRequest . PromptID )
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:47:42 +00:00
if renderer . renderRequest . DataPackagePath != result . DataPackagePath {
t . Fatalf ( "render DataPackagePath = %q, want managed path %q" , renderer . renderRequest . DataPackagePath , result . DataPackagePath )
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:47:42 +00:00
if renderer . runRequest . DataPackagePath != result . DataPackagePath {
t . Fatalf ( "run DataPackagePath = %q, want managed path %q" , renderer . runRequest . DataPackagePath , result . DataPackagePath )
}
if renderer . runRequest . OutputPath != result . ReportPath {
t . Fatalf ( "run OutputPath = %q, want managed report path %q" , renderer . runRequest . OutputPath , result . ReportPath )
}
for _ , path := range [ ] string { result . BriefingPath , result . DataPackagePath , result . PreflightPath , result . ReportPath , result . MetadataPath } {
2026-05-29 17:32:57 +00:00
if _ , err := os . Stat ( path ) ; err != nil {
t . Fatalf ( "expected artifact %q: %v" , path , err )
}
}
2026-05-29 17:47:42 +00:00
if _ , err := os . Stat ( outputPath ) ; err != nil {
t . Fatalf ( "expected requested report output %q: %v" , outputPath , err )
2026-05-29 17:40:38 +00:00
}
2026-05-29 17:47:42 +00:00
data , err := os . ReadFile ( result . DataPackagePath )
2026-05-29 17:32:57 +00:00
if err != nil {
t . Fatalf ( "read data package: %v" , err )
}
if ! strings . Contains ( string ( data ) , ` "recentChanges" ` ) || ! strings . Contains ( string ( data ) , ` data_package.v1 ` ) {
t . Fatalf ( "data package missing expected content:\n%s" , string ( data ) )
}
preflight , err := os . ReadFile ( result . PreflightPath )
if err != nil {
t . Fatalf ( "read preflight: %v" , err )
}
if ! strings . Contains ( string ( preflight ) , ` prepared ` ) {
t . Fatalf ( "preflight output missing render stdout:\n%s" , string ( preflight ) )
}
2026-05-29 17:40:38 +00:00
if result . Metadata . RunID != resolved . Metadata ( ) . RunID {
t . Fatalf ( "metadata RunID = %q, want %q" , result . Metadata . RunID , resolved . Metadata ( ) . RunID )
}
if result . Metadata . BriefingPath != result . BriefingPath || result . Metadata . DataPackagePath != result . DataPackagePath {
t . Fatalf ( "metadata does not link artifact paths: %#v" , result . Metadata )
}
2026-05-29 17:47:42 +00:00
if result . Metadata . RenderedReportPath != result . ReportPath {
t . Fatalf ( "metadata rendered report path = %q, want %q" , result . Metadata . RenderedReportPath , result . ReportPath )
}
2026-05-29 17:53:03 +00:00
if len ( result . RecentChanges ) != 0 {
t . Fatalf ( "RecentChanges = %#v, want none without prior snapshot" , result . RecentChanges )
}
2026-05-29 17:47:42 +00:00
report , err := os . ReadFile ( outputPath )
if err != nil {
t . Fatalf ( "read report output: %v" , err )
}
if ! strings . Contains ( string ( report ) , "# Daily Report" ) {
t . Fatalf ( "report output missing markdown:\n%s" , string ( report ) )
}
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:47:42 +00:00
func TestGenerateDailyReportPersistsFailedPreflight ( t * testing . T ) {
2026-05-29 17:32:57 +00:00
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
renderer := & recordingRenderer {
2026-05-29 17:47:42 +00:00
renderResult : & scriptorium . RenderResult {
2026-05-29 17:32:57 +00:00
Command : [ ] string { "scriptorium" , "render" } ,
Stderr : "render failed" ,
ExitCode : 1 ,
} ,
err : errors . New ( "scriptorium render exited with code 1: render failed" ) ,
}
2026-05-29 17:47:42 +00:00
_ , err = GenerateDailyReport ( context . Background ( ) , DailyReportRequest {
2026-05-29 17:32:57 +00:00
Config : cfg ,
Resolved : resolved ,
Renderer : renderer ,
} )
if err == nil {
2026-05-29 17:47:42 +00:00
t . Fatal ( "GenerateDailyReport() error = nil, want render error" )
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:40:38 +00:00
store , err := state . NewFilesystemStore ( cfg . Workspace )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
paths , err := store . Paths ( resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
preflightPath := paths . Preflight
2026-05-29 17:32:57 +00:00
preflight , readErr := os . ReadFile ( preflightPath )
if readErr != nil {
t . Fatalf ( "read failed preflight: %v" , readErr )
}
if ! strings . Contains ( string ( preflight ) , ` "exitCode": 1 ` ) {
t . Fatalf ( "failed preflight was not persisted:\n%s" , string ( preflight ) )
}
2026-05-29 17:40:38 +00:00
if _ , err := os . Stat ( paths . Metadata ) ; err != nil {
t . Fatalf ( "expected metadata for failed preflight %q: %v" , paths . Metadata , err )
}
2026-05-29 17:47:42 +00:00
if renderer . runCalls != 0 {
t . Fatalf ( "run calls = %d, want none after failed preflight" , renderer . runCalls )
}
}
func TestGenerateDailyReportReturnsRunErrorAfterPreflight ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult {
Stderr : "validation failed" ,
ExitCode : 2 ,
} ,
runErr : errors . New ( "scriptorium run exited with code 2: validation failed" ) ,
runBody : "# Daily Report\n" ,
}
_ , err = GenerateDailyReport ( context . Background ( ) , DailyReportRequest {
Config : cfg ,
Resolved : resolved ,
Renderer : renderer ,
} )
if err == nil {
t . Fatal ( "GenerateDailyReport() error = nil, want run error" )
}
if renderer . renderCalls != 1 || renderer . runCalls != 1 {
t . Fatalf ( "calls render=%d run=%d, want one of each" , renderer . renderCalls , renderer . runCalls )
}
store , err := state . NewFilesystemStore ( cfg . Workspace )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
paths , err := store . Paths ( resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
if _ , err := os . Stat ( paths . Metadata ) ; err != nil {
t . Fatalf ( "expected metadata for failed run %q: %v" , paths . Metadata , err )
}
if _ , err := os . Stat ( paths . RenderedReport ) ; err != nil {
t . Fatalf ( "expected report from validation exit %q: %v" , paths . RenderedReport , err )
}
2026-05-29 17:32:57 +00:00
}
2026-05-29 17:53:03 +00:00
func TestGenerateDailyReportIncludesRecentChangesFromPriorSnapshot ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
store , err := state . NewFilesystemStore ( cfg . Workspace )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
priorResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T04:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(prior) error = %v" , err )
}
priorBriefing := priorDailyBriefing ( priorResolved )
priorBriefingPath , err := store . SaveBriefing ( context . Background ( ) , priorResolved , priorBriefing )
if err != nil {
t . Fatalf ( "SaveBriefing() error = %v" , err )
}
priorPaths , err := store . Paths ( priorResolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
_ , err = store . SaveMetadata ( context . Background ( ) , state . BuildMetadata ( priorResolved , priorBriefing , state . ArtifactPaths {
Briefing : priorBriefingPath ,
DataPackage : priorPaths . DataPackage ,
Preflight : priorPaths . Preflight ,
RenderedReport : priorPaths . RenderedReport ,
} ) )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
}
currentResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(current) error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# Daily Report\n" ,
}
result , err := GenerateDailyReport ( context . Background ( ) , DailyReportRequest {
Config : cfg ,
Resolved : currentResolved ,
Renderer : renderer ,
Store : store ,
} )
if err != nil {
t . Fatalf ( "GenerateDailyReport() error = %v" , err )
}
if len ( result . RecentChanges ) == 0 {
t . Fatal ( "RecentChanges length = 0, want changes from prior snapshot" )
}
data , err := os . ReadFile ( result . DataPackagePath )
if err != nil {
t . Fatalf ( "read data package: %v" , err )
}
if ! strings . Contains ( string ( data ) , "alert_added" ) || ! strings . Contains ( string ( data ) , "temperature_shift" ) {
t . Fatalf ( "data package missing recent changes:\n%s" , string ( data ) )
}
}
2026-05-29 18:00:27 +00:00
func TestGenerateTomorrowReportUsesTomorrowBriefingDate ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportTomorrow ,
} , mustParse ( "2026-05-29T18:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# Tomorrow Planning Brief\n" ,
}
result , err := GenerateDailyReport ( context . Background ( ) , DailyReportRequest {
Config : cfg ,
Resolved : resolved ,
Renderer : renderer ,
} )
if err != nil {
t . Fatalf ( "GenerateDailyReport() error = %v" , err )
}
if result . Briefing . Metadata . ReportID != report . DailyTomorrow || result . Briefing . Metadata . Variant != "tomorrow" {
t . Fatalf ( "metadata report/variant = %q/%q, want tomorrow" , result . Briefing . Metadata . ReportID , result . Briefing . Metadata . Variant )
}
if result . Briefing . Daily . ForecastSummaryDate != "2026-05-30" {
t . Fatalf ( "ForecastSummaryDate = %q, want 2026-05-30" , result . Briefing . Daily . ForecastSummaryDate )
}
if result . Briefing . Daily . Planning == nil {
t . Fatal ( "Planning = nil, want tomorrow planning inputs" )
}
if ! strings . Contains ( filepath . Base ( result . ReportPath ) , "daily_tomorrow" ) {
t . Fatalf ( "ReportPath = %q, want managed tomorrow report path" , result . ReportPath )
}
}
func TestTomorrowReportCanCompareAgainstPriorDailySnapshot ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
store , err := state . NewFilesystemStore ( cfg . Workspace )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
priorResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-30T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T17:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(prior) error = %v" , err )
}
priorBriefing := priorDailyBriefing ( priorResolved )
priorBriefingPath , err := store . SaveBriefing ( context . Background ( ) , priorResolved , priorBriefing )
if err != nil {
t . Fatalf ( "SaveBriefing() error = %v" , err )
}
priorPaths , err := store . Paths ( priorResolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
_ , err = store . SaveMetadata ( context . Background ( ) , state . BuildMetadata ( priorResolved , priorBriefing , state . ArtifactPaths {
Briefing : priorBriefingPath ,
DataPackage : priorPaths . DataPackage ,
Preflight : priorPaths . Preflight ,
RenderedReport : priorPaths . RenderedReport ,
} ) )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
}
currentResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportTomorrow ,
} , mustParse ( "2026-05-29T18:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(current) error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# Tomorrow Planning Brief\n" ,
}
result , err := GenerateDailyReport ( context . Background ( ) , DailyReportRequest {
Config : cfg ,
Resolved : currentResolved ,
Renderer : renderer ,
Store : store ,
} )
if err != nil {
t . Fatalf ( "GenerateDailyReport() error = %v" , err )
}
if result . PriorSnapshot == nil {
t . Fatal ( "PriorSnapshot = nil, want compatible prior daily snapshot" )
}
if len ( result . RecentChanges ) == 0 {
t . Fatal ( "RecentChanges length = 0, want changes from compatible prior daily snapshot" )
}
}
2026-05-29 18:08:50 +00:00
func TestGenerateThreeDayReportWritesReportAndRecentChanges ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
store , err := state . NewFilesystemStore ( cfg . Workspace )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
priorResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportThreeDay ,
} , mustParse ( "2026-05-29T04:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(prior) error = %v" , err )
}
priorBriefing := priorThreeDayBriefing ( priorResolved )
priorBriefingPath , err := store . SaveBriefing ( context . Background ( ) , priorResolved , priorBriefing )
if err != nil {
t . Fatalf ( "SaveBriefing() error = %v" , err )
}
priorPaths , err := store . Paths ( priorResolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
_ , err = store . SaveMetadata ( context . Background ( ) , state . BuildMetadata ( priorResolved , priorBriefing , state . ArtifactPaths {
Briefing : priorBriefingPath ,
DataPackage : priorPaths . DataPackage ,
Preflight : priorPaths . Preflight ,
RenderedReport : priorPaths . RenderedReport ,
} ) )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
}
currentResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportThreeDay ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(current) error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# 3-Day Outlook\n" ,
}
result , err := GenerateReport ( context . Background ( ) , ReportRequest {
Config : cfg ,
Resolved : currentResolved ,
Renderer : renderer ,
Store : store ,
} )
if err != nil {
t . Fatalf ( "GenerateReport() error = %v" , err )
}
if result . Briefing . ThreeDay == nil {
t . Fatal ( "ThreeDay = nil" )
}
if len ( result . Briefing . ThreeDay . Days ) != 3 {
t . Fatalf ( "ThreeDay days = %d, want 3" , len ( result . Briefing . ThreeDay . Days ) )
}
if renderer . renderRequest . PromptID != "weather.three_day_outlook" {
t . Fatalf ( "render PromptID = %q, want weather.three_day_outlook" , renderer . renderRequest . PromptID )
}
if result . PriorSnapshot == nil {
t . Fatal ( "PriorSnapshot = nil, want prior 3-day snapshot" )
}
if len ( result . RecentChanges ) == 0 {
t . Fatal ( "RecentChanges length = 0, want changes from prior 3-day snapshot" )
}
}
2026-05-29 18:16:10 +00:00
func TestGenerateWeekendReportWritesReportAndRecentChanges ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
store , err := state . NewFilesystemStore ( cfg . Workspace )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
priorResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportWeekend ,
} , mustParse ( "2026-05-29T04:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(prior) error = %v" , err )
}
priorBriefing := priorWeekendBriefing ( priorResolved )
priorBriefingPath , err := store . SaveBriefing ( context . Background ( ) , priorResolved , priorBriefing )
if err != nil {
t . Fatalf ( "SaveBriefing() error = %v" , err )
}
priorPaths , err := store . Paths ( priorResolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
_ , err = store . SaveMetadata ( context . Background ( ) , state . BuildMetadata ( priorResolved , priorBriefing , state . ArtifactPaths {
Briefing : priorBriefingPath ,
DataPackage : priorPaths . DataPackage ,
Preflight : priorPaths . Preflight ,
RenderedReport : priorPaths . RenderedReport ,
} ) )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
}
currentResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportWeekend ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(current) error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# Weekend Outlook\n" ,
}
result , err := GenerateReport ( context . Background ( ) , ReportRequest {
Config : cfg ,
Resolved : currentResolved ,
Renderer : renderer ,
Store : store ,
} )
if err != nil {
t . Fatalf ( "GenerateReport() error = %v" , err )
}
if result . Briefing . Weekend == nil {
t . Fatal ( "Weekend = nil" )
}
if len ( result . Briefing . Weekend . Days ) != 3 {
t . Fatalf ( "Weekend days = %d, want 3" , len ( result . Briefing . Weekend . Days ) )
}
if renderer . renderRequest . PromptID != "weather.weekend_outlook" {
t . Fatalf ( "render PromptID = %q, want weather.weekend_outlook" , renderer . renderRequest . PromptID )
}
if result . PriorSnapshot == nil {
t . Fatal ( "PriorSnapshot = nil, want prior weekend snapshot" )
}
if len ( result . RecentChanges ) == 0 {
t . Fatal ( "RecentChanges length = 0, want changes from prior weekend snapshot" )
}
}
2026-05-29 18:28:25 +00:00
func TestGenerateStormReportWritesReport ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportStorm ,
StormStart : mustParse ( "2026-05-29T06:00:00-05:00" ) ,
StormEnd : mustParse ( "2026-05-29T10:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# Storm Report\n" ,
}
outputPath := filepath . Join ( t . TempDir ( ) , "storm.md" )
result , err := GenerateReport ( context . Background ( ) , ReportRequest {
Config : cfg ,
Resolved : resolved ,
OutputPath : outputPath ,
Renderer : renderer ,
} )
if err != nil {
t . Fatalf ( "GenerateReport() error = %v" , err )
}
if result . Briefing . Storm == nil {
t . Fatal ( "Storm = nil" )
}
if renderer . renderRequest . PromptID != "weather.storm_report" {
t . Fatalf ( "render PromptID = %q, want weather.storm_report" , renderer . renderRequest . PromptID )
}
if len ( result . Briefing . Storm . HourlyPeriods ) == 0 || len ( result . Briefing . Storm . NarrativePeriods ) == 0 {
t . Fatalf ( "selected source periods hourly/narrative = %d/%d, want relevant periods" , len ( result . Briefing . Storm . HourlyPeriods ) , len ( result . Briefing . Storm . NarrativePeriods ) )
}
if _ , err := os . Stat ( outputPath ) ; err != nil {
t . Fatalf ( "expected requested report output %q: %v" , outputPath , err )
}
data , err := os . ReadFile ( result . DataPackagePath )
if err != nil {
t . Fatalf ( "read data package: %v" , err )
}
if ! strings . Contains ( string ( data ) , ` "storm" ` ) || ! strings . Contains ( string ( data ) , ` "weather.storm_report" ` ) {
t . Fatalf ( "data package missing storm content:\n%s" , string ( data ) )
}
}
2026-05-29 18:33:24 +00:00
func TestInspectGeneratedReportArtifacts ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# Daily Report\n" ,
}
result , err := GenerateReport ( context . Background ( ) , ReportRequest {
Config : cfg ,
Resolved : resolved ,
Renderer : renderer ,
} )
if err != nil {
t . Fatalf ( "GenerateReport() error = %v" , err )
}
records , err := InspectReports ( context . Background ( ) , InspectReportsRequest { Config : cfg , Limit : 1 } )
if err != nil {
t . Fatalf ( "InspectReports() error = %v" , err )
}
if len ( records ) != 1 || records [ 0 ] . RunID != result . Metadata . RunID {
t . Fatalf ( "records = %#v, want generated run" , records )
}
metadata , err := InspectMetadata ( context . Background ( ) , InspectRunRequest { Config : cfg , RunID : result . Metadata . RunID } )
if err != nil {
t . Fatalf ( "InspectMetadata() error = %v" , err )
}
if metadata . BriefingPath != result . BriefingPath || metadata . DataPackagePath != result . DataPackagePath {
t . Fatalf ( "metadata paths = %#v, want generated artifact paths" , metadata )
}
briefingPackage , err := InspectBriefing ( context . Background ( ) , InspectRunRequest { Config : cfg , RunID : result . Metadata . RunID } )
if err != nil {
t . Fatalf ( "InspectBriefing() error = %v" , err )
}
if briefingPackage . Metadata . RunID != result . Metadata . RunID {
t . Fatalf ( "briefing RunID = %q, want %q" , briefingPackage . Metadata . RunID , result . Metadata . RunID )
}
dataPackage , err := InspectDataPackage ( context . Background ( ) , InspectRunRequest { Config : cfg , RunID : result . Metadata . RunID } )
if err != nil {
t . Fatalf ( "InspectDataPackage() error = %v" , err )
}
if dataPackage . RunID != result . Metadata . RunID {
t . Fatalf ( "data package RunID = %q, want %q" , dataPackage . RunID , result . Metadata . RunID )
}
sources , err := InspectSources ( context . Background ( ) , InspectRunRequest { Config : cfg , RunID : result . Metadata . RunID } )
if err != nil {
t . Fatalf ( "InspectSources() error = %v" , err )
}
if len ( sources . Sources ) == 0 || len ( sources . Warnings ) == 0 {
t . Fatalf ( "sources = %#v, want provenance and warnings" , sources )
}
}
func TestInspectPriorSnapshot ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
store , err := state . NewFilesystemStore ( cfg . Workspace )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
priorResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T04:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(prior) error = %v" , err )
}
currentResolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportDaily ,
Date : mustParse ( "2026-05-29T12:00:00-05:00" ) ,
} , mustParse ( "2026-05-29T05:00:00-05:00" ) )
if err != nil {
t . Fatalf ( "ResolveGenerate(current) error = %v" , err )
}
renderer := & recordingRenderer {
renderResult : & scriptorium . RenderResult { ExitCode : 0 } ,
runResult : & scriptorium . RunResult { ExitCode : 0 } ,
runBody : "# Daily Report\n" ,
}
if _ , err := GenerateReport ( context . Background ( ) , ReportRequest { Config : cfg , Resolved : priorResolved , Renderer : renderer , Store : store } ) ; err != nil {
t . Fatalf ( "GenerateReport(prior) error = %v" , err )
}
current , err := GenerateReport ( context . Background ( ) , ReportRequest { Config : cfg , Resolved : currentResolved , Renderer : renderer , Store : store } )
if err != nil {
t . Fatalf ( "GenerateReport(current) error = %v" , err )
}
prior , err := InspectPriorSnapshot ( context . Background ( ) , InspectRunRequest { Config : cfg , RunID : current . Metadata . RunID } )
if err != nil {
t . Fatalf ( "InspectPriorSnapshot() error = %v" , err )
}
if prior == nil || prior . Metadata . RunID != priorResolved . Metadata ( ) . RunID {
t . Fatalf ( "prior = %#v, want previous generated run" , prior )
}
}
func TestInspectMissingMetadata ( t * testing . T ) {
cfg := config . Defaults ( )
cfg . Workspace . Root = t . TempDir ( )
_ , err := InspectMetadata ( context . Background ( ) , InspectRunRequest { Config : cfg , RunID : "missing" } )
if err == nil {
t . Fatal ( "InspectMetadata() error = nil, want missing metadata error" )
}
if ! strings . Contains ( err . Error ( ) , "metadata for run id" ) {
t . Fatalf ( "error = %q, want missing run id context" , err . Error ( ) )
}
}
2026-05-29 17:17:50 +00:00
func TestResolveGenerateMapsCommandToReportDefinition ( t * testing . T ) {
cfg := config . Defaults ( )
cfg . WeatherAPI . Timezone = "America/Chicago"
now := mustParse ( "2026-05-29T18:00:00-05:00" )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportTomorrow ,
} , now )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
if resolved . Definition . ID != report . DailyTomorrow {
t . Fatalf ( "ID = %q, want daily_tomorrow" , resolved . Definition . ID )
}
if resolved . Definition . PromptID != "weather.daily_report" {
t . Fatalf ( "PromptID = %q, want weather.daily_report" , resolved . Definition . PromptID )
}
if got := resolved . ValidPeriod . Start . Format ( "2006-01-02" ) ; got != "2026-05-30" {
t . Fatalf ( "valid start date = %s, want 2026-05-30" , got )
}
}
2026-05-29 17:23:49 +00:00
func dailyBundleServer ( t * testing . T ) * httptest . Server {
t . Helper ( )
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
switch r . URL . Path {
case "/observations" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "timestamp":"2026-05-29T14:00:00Z","conditionCode":3}} ` ) )
case "/conditions/current" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "conditionText":"Clear"}} ` ) )
case "/forecast/hourly" :
2026-05-29 18:00:27 +00:00
_ , _ = w . Write ( [ ] byte ( ` { "data": { "locationId":"test-grid","locationName":"Testville","issuedAt":"2026-05-29T10:30:00-05:00","product":"hourly","periods":[ { "startTime":"2026-05-29T06:00:00-05:00","endTime":"2026-05-29T07:00:00-05:00","textDescription":"Showers and thunderstorms","temperatureF":66,"probabilityOfPrecipitationPercent":80,"windGustMph":32}, { "startTime":"2026-05-30T06:00:00-05:00","endTime":"2026-05-30T07:00:00-05:00","textDescription":"Showers and thunderstorms","temperatureF":66,"probabilityOfPrecipitationPercent":80,"windGustMph":32}]}} ` ) )
2026-05-29 17:23:49 +00:00
case "/forecast/narrative" :
2026-05-29 18:00:27 +00:00
_ , _ = w . Write ( [ ] byte ( ` { "data": { "issuedAt":"2026-05-29T10:30:00-05:00","product":"narrative","periods":[ { "startTime":"2026-05-29T06:00:00-05:00","endTime":"2026-05-29T18:00:00-05:00","textDescription":"Morning storms, then partly sunny."}, { "startTime":"2026-05-30T06:00:00-05:00","endTime":"2026-05-30T18:00:00-05:00","textDescription":"Tomorrow starts stormy."}]}} ` ) )
2026-05-29 17:23:49 +00:00
case "/alerts/active" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "alerts":[ { "event":"Flood Watch","effective":"2026-05-29T05:00:00-05:00","expires":"2026-05-29T09:00:00-05:00"}]}} ` ) )
case "/discussion" :
_ , _ = w . Write ( [ ] byte ( ` { "data": { "product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":["Storms are most likely during the morning."]}} ` ) )
default :
http . NotFound ( w , r )
}
} ) )
t . Cleanup ( server . Close )
return server
}
2026-05-29 17:17:50 +00:00
func TestResolveGenerateStorm ( t * testing . T ) {
cfg := config . Defaults ( )
cfg . WeatherAPI . Timezone = "America/Chicago"
now := mustParse ( "2026-05-29T12:00:00-05:00" )
start := mustParse ( "2026-05-29T18:00:00-05:00" )
end := mustParse ( "2026-05-30T06:00:00-05:00" )
resolved , err := ResolveGenerate ( GenerateRequest {
Config : cfg ,
Report : ReportStorm ,
StormStart : start ,
StormEnd : end ,
} , now )
if err != nil {
t . Fatalf ( "ResolveGenerate() error = %v" , err )
}
if resolved . Definition . ID != report . Storm {
t . Fatalf ( "ID = %q, want storm" , resolved . Definition . ID )
}
if ! resolved . ValidPeriod . Start . Equal ( start ) || ! resolved . ValidPeriod . End . Equal ( end ) {
t . Fatalf ( "period = %#v, want storm window" , resolved . ValidPeriod )
}
}
func TestResolveBatchMorningSkipsWeekendOnSunday ( t * testing . T ) {
cfg := config . Defaults ( )
cfg . WeatherAPI . Timezone = "America/Chicago"
now := mustParse ( "2026-05-31T06:00:00-05:00" )
resolved , err := ResolveBatch ( BatchRequest { Config : cfg , Batch : BatchMorning } , now )
if err != nil {
t . Fatalf ( "ResolveBatch() error = %v" , err )
}
if len ( resolved ) != 2 {
t . Fatalf ( "resolved length = %d, want 2" , len ( resolved ) )
}
for _ , item := range resolved {
if item . Definition . ID == report . Weekend {
t . Fatal ( "morning batch included weekend on Sunday" )
}
}
}
2026-05-29 18:20:39 +00:00
func TestRunBatchContinuesAfterReportFailure ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
renderer := & selectiveRenderer {
failRenderPrompt : "weather.three_day_outlook" ,
runBody : "# Batch Report\n" ,
}
result , err := RunBatchDetailed ( context . Background ( ) , BatchRequest {
Config : cfg ,
Batch : BatchMorning ,
Now : mustParse ( "2026-05-29T05:00:00-05:00" ) ,
Renderer : renderer ,
} )
if err != nil {
t . Fatalf ( "RunBatchDetailed() error = %v" , err )
}
if result . Total != 3 || result . Succeeded != 2 || result . Failed != 1 {
t . Fatalf ( "summary total/succeeded/failed = %d/%d/%d, want 3/2/1" , result . Total , result . Succeeded , result . Failed )
}
if renderer . runCalls != 2 {
t . Fatalf ( "run calls = %d, want successful reports to continue" , renderer . runCalls )
}
var failedThreeDay bool
for _ , item := range result . Reports {
if item . ReportID == report . ThreeDay && item . Status == "failed" && strings . Contains ( item . Error , "render failed" ) {
failedThreeDay = true
}
if item . ReportID != report . ThreeDay && item . Status != "succeeded" {
t . Fatalf ( "report %s status = %s, want succeeded" , item . ReportID , item . Status )
}
}
if ! failedThreeDay {
t . Fatalf ( "reports = %#v, want failed 3-day item" , result . Reports )
}
}
func TestRunBatchUsesOutputDirectory ( t * testing . T ) {
server := dailyBundleServer ( t )
cfg := config . Defaults ( )
cfg . WeatherAPI . BaseURL = server . URL + "/"
cfg . WeatherAPI . Timezone = "America/Chicago"
cfg . Workspace . Root = t . TempDir ( )
outputDir := filepath . Join ( t . TempDir ( ) , "reports" )
result , err := RunBatchDetailed ( context . Background ( ) , BatchRequest {
Config : cfg ,
Batch : BatchEvening ,
Now : mustParse ( "2026-05-29T18:00:00-05:00" ) ,
OutputDir : outputDir ,
Renderer : & selectiveRenderer { runBody : "# Tomorrow\n" } ,
} )
if err != nil {
t . Fatalf ( "RunBatchDetailed() error = %v" , err )
}
if result . Failed != 0 || len ( result . Reports ) != 1 {
t . Fatalf ( "summary = %#v, want one successful report" , result )
}
want := filepath . Join ( outputDir , "tomorrow.md" )
if result . Reports [ 0 ] . OutputPath != want {
t . Fatalf ( "OutputPath = %q, want %q" , result . Reports [ 0 ] . OutputPath , want )
}
if _ , err := os . Stat ( want ) ; err != nil {
t . Fatalf ( "expected output copy %q: %v" , want , err )
}
}
2026-05-29 17:17:50 +00:00
func mustParse ( value string ) time . Time {
parsed , err := time . Parse ( time . RFC3339 , value )
if err != nil {
panic ( err )
}
return parsed
}
2026-05-29 17:32:57 +00:00
2026-05-29 17:53:03 +00:00
func priorDailyBriefing ( resolved report . Resolved ) briefing . Package {
low := 50.0
high := 58.0
precip := 10.0
return briefing . Package {
Metadata : briefing . Metadata {
SchemaVersion : briefing . SchemaVersion ,
RunID : resolved . Metadata ( ) . RunID ,
ReportID : resolved . Definition . ID ,
Variant : "today" ,
PromptID : resolved . Definition . PromptID ,
GeneratedAt : resolved . GeneratedAt ,
Units : "us" ,
Timezone : resolved . Timezone ,
ValidPeriod : resolved . ValidPeriod ,
} ,
Daily : & briefing . Daily {
BottomLine : briefing . BottomLine {
Temperature : forecast . Range { Min : & low , Max : & high } ,
MaxPrecipProbability : & forecast . TimedValue {
Value : precip ,
Time : resolved . ValidPeriod . Start . Add ( 6 * time . Hour ) ,
} ,
} ,
Dayparts : [ ] forecast . DaypartSummary {
{ Name : "morning" } ,
} ,
} ,
}
}
2026-05-29 18:08:50 +00:00
func priorThreeDayBriefing ( resolved report . Resolved ) briefing . Package {
high := 58.0
precip := 10.0
return briefing . Package {
Metadata : briefing . Metadata {
SchemaVersion : briefing . SchemaVersion ,
RunID : resolved . Metadata ( ) . RunID ,
ReportID : resolved . Definition . ID ,
PromptID : resolved . Definition . PromptID ,
GeneratedAt : resolved . GeneratedAt ,
Units : "us" ,
Timezone : resolved . Timezone ,
ValidPeriod : resolved . ValidPeriod ,
} ,
ThreeDay : & briefing . ThreeDay {
Days : [ ] briefing . OutlookDay {
{
Date : "2026-05-29" ,
Temperature : forecast . Range { Max : & high } ,
MaxPrecipitationProbability : & forecast . TimedValue {
Value : precip ,
Time : resolved . ValidPeriod . Start . Add ( 6 * time . Hour ) ,
} ,
} ,
} ,
} ,
}
}
2026-05-29 18:16:10 +00:00
func priorWeekendBriefing ( resolved report . Resolved ) briefing . Package {
high := 58.0
precip := 10.0
return briefing . Package {
Metadata : briefing . Metadata {
SchemaVersion : briefing . SchemaVersion ,
RunID : resolved . Metadata ( ) . RunID ,
ReportID : resolved . Definition . ID ,
PromptID : resolved . Definition . PromptID ,
GeneratedAt : resolved . GeneratedAt ,
Units : "us" ,
Timezone : resolved . Timezone ,
ValidPeriod : resolved . ValidPeriod ,
} ,
Weekend : & briefing . Weekend {
Days : [ ] briefing . OutlookDay {
{
Date : "2026-05-30" ,
Temperature : forecast . Range { Max : & high } ,
MaxPrecipitationProbability : & forecast . TimedValue {
Value : precip ,
Time : resolved . ValidPeriod . Start . Add ( 12 * time . Hour ) ,
} ,
} ,
} ,
} ,
}
}
2026-05-29 17:32:57 +00:00
type recordingRenderer struct {
2026-05-29 17:47:42 +00:00
renderCalls int
runCalls int
renderRequest scriptorium . RenderRequest
runRequest scriptorium . RunRequest
renderResult * scriptorium . RenderResult
runResult * scriptorium . RunResult
err error
runErr error
runBody string
2026-05-29 17:32:57 +00:00
}
2026-05-29 18:20:39 +00:00
type selectiveRenderer struct {
renderCalls int
runCalls int
failRenderPrompt string
runBody string
}
func ( r * selectiveRenderer ) Render ( _ context . Context , req scriptorium . RenderRequest ) ( * scriptorium . RenderResult , error ) {
r . renderCalls ++
if req . PromptID == r . failRenderPrompt {
return & scriptorium . RenderResult { ExitCode : 1 , Stderr : "render failed" } , errors . New ( "render failed" )
}
return & scriptorium . RenderResult { ExitCode : 0 } , nil
}
func ( r * selectiveRenderer ) Run ( _ context . Context , req scriptorium . RunRequest ) ( * scriptorium . RunResult , error ) {
r . runCalls ++
if r . runBody != "" {
if err := os . WriteFile ( req . OutputPath , [ ] byte ( r . runBody ) , 0 o600 ) ; err != nil {
return nil , err
}
}
return & scriptorium . RunResult { ExitCode : 0 , OutputPath : req . OutputPath } , nil
}
2026-05-29 17:32:57 +00:00
func ( r * recordingRenderer ) Render ( _ context . Context , req scriptorium . RenderRequest ) ( * scriptorium . RenderResult , error ) {
2026-05-29 17:47:42 +00:00
r . renderCalls ++
r . renderRequest = req
return r . renderResult , r . err
}
func ( r * recordingRenderer ) Run ( _ context . Context , req scriptorium . RunRequest ) ( * scriptorium . RunResult , error ) {
r . runCalls ++
r . runRequest = req
if r . runBody != "" {
if err := os . WriteFile ( req . OutputPath , [ ] byte ( r . runBody ) , 0 o600 ) ; err != nil {
return nil , err
}
}
if r . runResult != nil {
r . runResult . OutputPath = req . OutputPath
}
return r . runResult , r . runErr
2026-05-29 17:32:57 +00:00
}