Run report generation through module snapshots
This commit is contained in:
@@ -16,9 +16,10 @@ weatherreporter generate weekend
|
||||
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00
|
||||
```
|
||||
|
||||
Each command resolves a report period, fetches a Weather API bundle, builds a
|
||||
briefing, builds a prompt input data package, runs `scriptorium render`, runs
|
||||
`scriptorium run`, and writes managed artifacts under the configured workspace.
|
||||
Each command resolves a report period, fetches a Weather API bundle, builds
|
||||
module snapshots, builds a YAML prompt input data package, runs
|
||||
`scriptorium render`, runs `scriptorium run`, and writes managed artifacts
|
||||
under the configured workspace.
|
||||
When distributor notification is enabled, weatherreporter uploads the managed
|
||||
Markdown report after `scriptorium run` succeeds and final metadata is saved.
|
||||
`--out PATH` writes an extra Markdown copy for the current generated report; it
|
||||
|
||||
@@ -79,6 +79,11 @@ type ModuleSnapshotRequest struct {
|
||||
Resolved report.Resolved
|
||||
}
|
||||
|
||||
type ReportFacts struct {
|
||||
Collected facts.CollectedFacts
|
||||
Derived facts.DerivedFacts
|
||||
}
|
||||
|
||||
type ReportRequest struct {
|
||||
Config config.Config
|
||||
Resolved report.Resolved
|
||||
@@ -494,26 +499,33 @@ func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
briefingPackage, err := BuildBriefing(BriefingRequest{
|
||||
reportFacts, err := BuildReportFacts(ModuleSnapshotRequest{
|
||||
Config: req.Config,
|
||||
Resolved: req.Resolved,
|
||||
}, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
moduleSnapshot, err := BuildModuleSnapshot(ModuleSnapshotRequest{
|
||||
moduleSnapshot, err := BuildModuleSnapshotFromFacts(ModuleSnapshotRequest{
|
||||
Config: req.Config,
|
||||
Resolved: req.Resolved,
|
||||
}, bundle)
|
||||
}, reportFacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
briefingPath, err := store.SaveBriefing(ctx, req.Resolved, briefingPackage)
|
||||
moduleSnapshotPath, err := store.SaveModuleSnapshot(ctx, req.Resolved, moduleSnapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
moduleSnapshotPath, err := store.SaveModuleSnapshot(ctx, req.Resolved, moduleSnapshot)
|
||||
briefingPackage, err := BuildBriefingFromFacts(BriefingRequest{
|
||||
Config: req.Config,
|
||||
Resolved: req.Resolved,
|
||||
}, reportFacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
briefingPath, err := store.SaveBriefing(ctx, req.Resolved, briefingPackage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -522,7 +534,8 @@ func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata := state.BuildMetadata(req.Resolved, briefingPackage, state.ArtifactPaths{
|
||||
briefingMetadata := briefing.BuildMetadata(briefingBuildContext(req.Config, req.Resolved, reportFacts.Collected))
|
||||
metadata := state.BuildMetadataFromBriefingMetadata(req.Resolved, briefingMetadata, state.ArtifactPaths{
|
||||
Briefing: briefingPath,
|
||||
ModuleSnapshot: moduleSnapshotPath,
|
||||
Metadata: paths.Metadata,
|
||||
@@ -834,38 +847,56 @@ func distributorUploadFiles(sourcePath string, bundlePaths []string) []distribut
|
||||
}
|
||||
|
||||
func BuildBriefing(req BriefingRequest, bundle *weatherdata.Bundle) (briefing.Package, error) {
|
||||
collected := facts.BuildCollected(bundle)
|
||||
derived, err := buildDerivedFacts(req.Config, req.Resolved, collected)
|
||||
reportFacts, err := BuildReportFacts(ModuleSnapshotRequest{
|
||||
Config: req.Config,
|
||||
Resolved: req.Resolved,
|
||||
}, bundle)
|
||||
if err != nil {
|
||||
return briefing.Package{}, err
|
||||
}
|
||||
buildContext := briefing.BuildContext{
|
||||
Resolved: req.Resolved,
|
||||
Bundle: collected.Bundle(),
|
||||
Units: req.Config.WeatherAPI.Units,
|
||||
Timezone: req.Config.WeatherAPI.Timezone,
|
||||
Location: briefingLocation(req.Config),
|
||||
}
|
||||
return BuildBriefingFromFacts(req, reportFacts)
|
||||
}
|
||||
|
||||
func BuildBriefingFromFacts(req BriefingRequest, reportFacts ReportFacts) (briefing.Package, error) {
|
||||
buildContext := briefingBuildContext(req.Config, req.Resolved, reportFacts.Collected)
|
||||
switch req.Resolved.Definition.ID {
|
||||
case report.DailyToday, report.DailyTomorrow:
|
||||
return briefing.BuildDaily(buildContext, derived.FirstDailySummary())
|
||||
return briefing.BuildDaily(buildContext, reportFacts.Derived.FirstDailySummary())
|
||||
case report.ThreeDay, report.Weekend:
|
||||
if req.Resolved.Definition.ID == report.Weekend {
|
||||
return briefing.BuildWeekend(buildContext, derived.DailySummaries)
|
||||
return briefing.BuildWeekend(buildContext, reportFacts.Derived.DailySummaries)
|
||||
}
|
||||
return briefing.BuildThreeDay(buildContext, derived.DailySummaries)
|
||||
return briefing.BuildThreeDay(buildContext, reportFacts.Derived.DailySummaries)
|
||||
case report.Storm:
|
||||
return briefing.BuildStorm(buildContext, derived)
|
||||
return briefing.BuildStorm(buildContext, reportFacts.Derived)
|
||||
default:
|
||||
return briefing.Package{}, fmt.Errorf("briefing is not implemented for report %q", req.Resolved.Definition.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func BuildModuleSnapshot(req ModuleSnapshotRequest, bundle *weatherdata.Bundle) (module.Snapshot, error) {
|
||||
reportFacts, err := BuildReportFacts(req, bundle)
|
||||
if err != nil {
|
||||
return module.Snapshot{}, err
|
||||
}
|
||||
return BuildModuleSnapshotFromFacts(req, reportFacts)
|
||||
}
|
||||
|
||||
func BuildReportFacts(req ModuleSnapshotRequest, bundle *weatherdata.Bundle) (ReportFacts, error) {
|
||||
collected := facts.BuildCollected(bundle)
|
||||
derived, err := buildDerivedFacts(req.Config, req.Resolved, collected)
|
||||
if err != nil {
|
||||
return module.Snapshot{}, err
|
||||
return ReportFacts{}, err
|
||||
}
|
||||
return ReportFacts{
|
||||
Collected: collected,
|
||||
Derived: derived,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BuildModuleSnapshotFromFacts(req ModuleSnapshotRequest, reportFacts ReportFacts) (module.Snapshot, error) {
|
||||
if !req.Resolved.ValidPeriod.IsValid() {
|
||||
return module.Snapshot{}, fmt.Errorf("resolved valid period is required")
|
||||
}
|
||||
registry, err := briefing.DefaultModuleRegistry()
|
||||
if err != nil {
|
||||
@@ -873,8 +904,8 @@ func BuildModuleSnapshot(req ModuleSnapshotRequest, bundle *weatherdata.Bundle)
|
||||
}
|
||||
moduleContext := briefing.ModuleContext{
|
||||
Resolved: req.Resolved,
|
||||
Collected: collected,
|
||||
Derived: derived,
|
||||
Collected: reportFacts.Collected,
|
||||
Derived: reportFacts.Derived,
|
||||
Units: req.Config.WeatherAPI.Units,
|
||||
Timezone: req.Config.WeatherAPI.Timezone,
|
||||
Location: briefingLocation(req.Config),
|
||||
@@ -900,6 +931,16 @@ func BuildModuleSnapshot(req ModuleSnapshotRequest, bundle *weatherdata.Bundle)
|
||||
return module.NewSnapshot(outputs)
|
||||
}
|
||||
|
||||
func briefingBuildContext(cfg config.Config, resolved report.Resolved, collected facts.CollectedFacts) briefing.BuildContext {
|
||||
return briefing.BuildContext{
|
||||
Resolved: resolved,
|
||||
Bundle: collected.Bundle(),
|
||||
Units: cfg.WeatherAPI.Units,
|
||||
Timezone: cfg.WeatherAPI.Timezone,
|
||||
Location: briefingLocation(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
func promptMetadata(metadata state.Metadata) promptinput.Metadata {
|
||||
return promptinput.Metadata{
|
||||
RunID: metadata.RunID,
|
||||
|
||||
@@ -166,12 +166,18 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
|
||||
runBody: "# Daily Report\n\nRain this morning.\n",
|
||||
}
|
||||
outputPath := filepath.Join(t.TempDir(), "daily.md")
|
||||
filesystemStore, err := state.NewFilesystemStore(cfg.Workspace)
|
||||
if err != nil {
|
||||
t.Fatalf("NewFilesystemStore() error = %v", err)
|
||||
}
|
||||
store := &recordingStore{Store: filesystemStore}
|
||||
|
||||
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||
Config: cfg,
|
||||
Resolved: resolved,
|
||||
OutputPath: outputPath,
|
||||
Renderer: renderer,
|
||||
Store: store,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateReport() error = %v", err)
|
||||
@@ -195,6 +201,9 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
|
||||
if renderer.runRequest.OutputPath != result.ReportPath {
|
||||
t.Fatalf("run OutputPath = %q, want managed report path %q", renderer.runRequest.OutputPath, result.ReportPath)
|
||||
}
|
||||
if got, want := strings.Join(store.calls, ","), "module_snapshot,briefing,data_package,preflight,metadata,prepare_report,metadata"; !strings.HasPrefix(got, want) {
|
||||
t.Fatalf("store calls = %v, want prefix %s", store.calls, want)
|
||||
}
|
||||
assertPathsExist(t, result.BriefingPath, result.ModuleSnapshotPath, result.DataPackagePath, result.PreflightPath, result.ReportPath, result.MetadataPath, outputPath)
|
||||
snapshotData, err := os.ReadFile(result.ModuleSnapshotPath)
|
||||
if err != nil {
|
||||
@@ -1536,6 +1545,41 @@ type recordingRenderer struct {
|
||||
runBody string
|
||||
}
|
||||
|
||||
type recordingStore struct {
|
||||
state.Store
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveBriefing(ctx context.Context, resolved report.Resolved, pkg briefing.Package) (string, error) {
|
||||
s.calls = append(s.calls, "briefing")
|
||||
return s.Store.SaveBriefing(ctx, resolved, pkg)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveModuleSnapshot(ctx context.Context, resolved report.Resolved, snapshot module.Snapshot) (string, error) {
|
||||
s.calls = append(s.calls, "module_snapshot")
|
||||
return s.Store.SaveModuleSnapshot(ctx, resolved, snapshot)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveDataPackage(ctx context.Context, resolved report.Resolved, pkg promptinput.Package) (string, error) {
|
||||
s.calls = append(s.calls, "data_package")
|
||||
return s.Store.SaveDataPackage(ctx, resolved, pkg)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SavePreflight(ctx context.Context, resolved report.Resolved, artifact state.PreflightArtifact) (string, error) {
|
||||
s.calls = append(s.calls, "preflight")
|
||||
return s.Store.SavePreflight(ctx, resolved, artifact)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *recordingStore) SaveMetadata(ctx context.Context, metadata state.Metadata) (string, error) {
|
||||
s.calls = append(s.calls, "metadata")
|
||||
return s.Store.SaveMetadata(ctx, metadata)
|
||||
}
|
||||
|
||||
func successfulRenderer(body string) *recordingRenderer {
|
||||
return &recordingRenderer{
|
||||
renderResult: &scriptorium.RenderResult{ExitCode: 0},
|
||||
|
||||
@@ -35,22 +35,26 @@ type Metadata struct {
|
||||
}
|
||||
|
||||
func BuildMetadata(resolved report.Resolved, briefingPackage briefing.Package, paths ArtifactPaths) Metadata {
|
||||
return BuildMetadataFromBriefingMetadata(resolved, briefingPackage.Metadata, paths)
|
||||
}
|
||||
|
||||
func BuildMetadataFromBriefingMetadata(resolved report.Resolved, briefingMetadata briefing.Metadata, paths ArtifactPaths) Metadata {
|
||||
metadata := resolved.Metadata()
|
||||
return Metadata{
|
||||
SchemaVersion: MetadataSchemaVersion,
|
||||
RunID: metadata.RunID,
|
||||
MetadataPath: paths.Metadata,
|
||||
ReportID: metadata.ReportID,
|
||||
Variant: briefingPackage.Metadata.Variant,
|
||||
Variant: briefingMetadata.Variant,
|
||||
PromptID: metadata.PromptID,
|
||||
GeneratedAt: metadata.GeneratedAt,
|
||||
Timezone: metadata.Timezone,
|
||||
ValidPeriod: metadata.ValidPeriod,
|
||||
Location: copyLocation(briefingPackage.Metadata.Location),
|
||||
SourceLocationID: briefingPackage.Metadata.SourceLocationID,
|
||||
SourceLocation: briefingPackage.Metadata.SourceLocation,
|
||||
Sources: briefingPackage.Metadata.Sources,
|
||||
SourceWarnings: briefingPackage.Metadata.SourceWarnings,
|
||||
Location: copyLocation(briefingMetadata.Location),
|
||||
SourceLocationID: briefingMetadata.SourceLocationID,
|
||||
SourceLocation: briefingMetadata.SourceLocation,
|
||||
Sources: briefingMetadata.Sources,
|
||||
SourceWarnings: briefingMetadata.SourceWarnings,
|
||||
BriefingPath: paths.Briefing,
|
||||
ModuleSnapshotPath: paths.ModuleSnapshot,
|
||||
DataPackagePath: paths.DataPackage,
|
||||
|
||||
Reference in New Issue
Block a user