Add filesystem state metadata baseline
This commit is contained in:
@@ -4,7 +4,6 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/scriptorium"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptinput"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/state"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
)
|
||||
|
||||
@@ -64,6 +64,7 @@ type DailyPreparationRequest struct {
|
||||
Resolved report.Resolved
|
||||
DataPackagePath string
|
||||
Renderer Renderer
|
||||
Store state.Store
|
||||
}
|
||||
|
||||
type DailyBriefingResult struct {
|
||||
@@ -77,6 +78,9 @@ type DailyPreparationResult struct {
|
||||
DataPackage promptinput.Package
|
||||
DataPackagePath string
|
||||
PreflightPath string
|
||||
Metadata state.Metadata
|
||||
MetadataPath string
|
||||
PriorSnapshot *state.PriorSnapshot
|
||||
RenderResult *scriptorium.RenderResult
|
||||
}
|
||||
|
||||
@@ -206,7 +210,15 @@ func GenerateDailyBriefing(ctx context.Context, req DailyBriefingRequest) (*Dail
|
||||
}
|
||||
outputPath := req.OutputPath
|
||||
if outputPath == "" {
|
||||
outputPath = defaultBriefingPath(req.Config, req.Resolved)
|
||||
store, err := defaultStore(req.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths, err := store.Paths(req.Resolved)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputPath = paths.Briefing
|
||||
}
|
||||
if err := briefing.Save(outputPath, pkg); err != nil {
|
||||
return nil, err
|
||||
@@ -215,6 +227,23 @@ func GenerateDailyBriefing(ctx context.Context, req DailyBriefingRequest) (*Dail
|
||||
}
|
||||
|
||||
func PrepareDailyReport(ctx context.Context, req DailyPreparationRequest) (*DailyPreparationResult, error) {
|
||||
store := req.Store
|
||||
if store == nil {
|
||||
defaultStore, err := defaultStore(req.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
store = defaultStore
|
||||
}
|
||||
paths, err := store.Paths(req.Resolved)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
priorSnapshot, err := store.FindPriorDailySnapshot(ctx, req.Resolved)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundle, err := FetchBundle(ctx, FetchBundleRequest{Config: req.Config})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -227,8 +256,8 @@ func PrepareDailyReport(ctx context.Context, req DailyPreparationRequest) (*Dail
|
||||
return nil, err
|
||||
}
|
||||
|
||||
briefingPath := defaultBriefingPath(req.Config, req.Resolved)
|
||||
if err := briefing.Save(briefingPath, briefingPackage); err != nil {
|
||||
briefingPath, err := store.SaveBriefing(ctx, req.Resolved, briefingPackage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -236,13 +265,15 @@ func PrepareDailyReport(ctx context.Context, req DailyPreparationRequest) (*Dail
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataPackagePath := req.DataPackagePath
|
||||
if dataPackagePath == "" {
|
||||
dataPackagePath = defaultDataPackagePath(req.Config, req.Resolved)
|
||||
}
|
||||
if err := promptinput.Save(dataPackagePath, dataPackage); err != nil {
|
||||
dataPackagePath, err := store.SaveDataPackage(ctx, req.Resolved, dataPackage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.DataPackagePath != "" && req.DataPackagePath != dataPackagePath {
|
||||
if err := promptinput.Save(req.DataPackagePath, dataPackage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
renderer := req.Renderer
|
||||
if renderer == nil {
|
||||
@@ -259,12 +290,25 @@ func PrepareDailyReport(ctx context.Context, req DailyPreparationRequest) (*Dail
|
||||
DataPackagePath: dataPackagePath,
|
||||
})
|
||||
|
||||
preflightPath := defaultPreflightPath(req.Config, req.Resolved)
|
||||
preflightPath := paths.Preflight
|
||||
if renderResult != nil {
|
||||
if err := scriptorium.SaveRenderResult(preflightPath, renderResult); err != nil {
|
||||
var err error
|
||||
preflightPath, err = store.SavePreflight(ctx, req.Resolved, renderResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
metadata := state.BuildMetadata(req.Resolved, briefingPackage, state.ArtifactPaths{
|
||||
Briefing: briefingPath,
|
||||
Metadata: paths.Metadata,
|
||||
DataPackage: dataPackagePath,
|
||||
Preflight: preflightPath,
|
||||
RenderedReport: paths.RenderedReport,
|
||||
})
|
||||
metadataPath, metadataErr := store.SaveMetadata(ctx, metadata)
|
||||
if metadataErr != nil {
|
||||
return nil, metadataErr
|
||||
}
|
||||
if renderErr != nil {
|
||||
return nil, renderErr
|
||||
}
|
||||
@@ -275,6 +319,9 @@ func PrepareDailyReport(ctx context.Context, req DailyPreparationRequest) (*Dail
|
||||
DataPackage: dataPackage,
|
||||
DataPackagePath: dataPackagePath,
|
||||
PreflightPath: preflightPath,
|
||||
Metadata: metadata,
|
||||
MetadataPath: metadataPath,
|
||||
PriorSnapshot: priorSnapshot,
|
||||
RenderResult: renderResult,
|
||||
}, nil
|
||||
}
|
||||
@@ -304,20 +351,6 @@ func BuildDailyBriefing(req DailyBriefingRequest, bundle *forecast.Bundle) (brie
|
||||
}, summary)
|
||||
}
|
||||
|
||||
func defaultBriefingPath(cfg config.Config, resolved report.Resolved) string {
|
||||
validDate := resolved.ValidPeriod.Start.Format("2006-01-02")
|
||||
filename := validDate + "." + string(resolved.Definition.ID) + ".briefing.json"
|
||||
return filepath.Join(cfg.Workspace.Root, cfg.Workspace.SnapshotsDir, "daily", validDate, filename)
|
||||
}
|
||||
|
||||
func defaultDataPackagePath(cfg config.Config, resolved report.Resolved) string {
|
||||
validDate := resolved.ValidPeriod.Start.Format("2006-01-02")
|
||||
filename := resolved.Metadata().RunID + ".data_package.json"
|
||||
return filepath.Join(cfg.Workspace.Root, cfg.Workspace.DataPackagesDir, "daily", validDate, filename)
|
||||
}
|
||||
|
||||
func defaultPreflightPath(cfg config.Config, resolved report.Resolved) string {
|
||||
validDate := resolved.ValidPeriod.Start.Format("2006-01-02")
|
||||
filename := resolved.Metadata().RunID + ".render.json"
|
||||
return filepath.Join(cfg.Workspace.Root, cfg.Workspace.PreflightDir, "daily", validDate, filename)
|
||||
func defaultStore(cfg config.Config) (*state.FilesystemStore, error) {
|
||||
return state.NewFilesystemStore(cfg.Workspace)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/scriptorium"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/state"
|
||||
)
|
||||
|
||||
func TestFetchAndSaveBundle(t *testing.T) {
|
||||
@@ -127,7 +128,7 @@ func TestGenerateDailyBriefingDefaultPath(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateDailyBriefing() error = %v", err)
|
||||
}
|
||||
if !strings.HasSuffix(result.OutputPath, filepath.Join("snapshots", "daily", "2026-05-29", "2026-05-29.daily_today.briefing.json")) {
|
||||
if !strings.HasSuffix(result.OutputPath, filepath.Join("snapshots", "daily", "2026-05-29", "20260529T100000.000000000Z_daily_today.briefing.json")) {
|
||||
t.Fatalf("OutputPath = %q, want deterministic daily briefing path", result.OutputPath)
|
||||
}
|
||||
}
|
||||
@@ -171,14 +172,17 @@ func TestPrepareDailyReportWritesDataPackageAndPreflight(t *testing.T) {
|
||||
if renderer.request.PromptID != "weather.daily_report" {
|
||||
t.Fatalf("PromptID = %q, want weather.daily_report", renderer.request.PromptID)
|
||||
}
|
||||
if renderer.request.DataPackagePath != dataPackagePath {
|
||||
t.Fatalf("DataPackagePath = %q, want %q", renderer.request.DataPackagePath, dataPackagePath)
|
||||
if renderer.request.DataPackagePath != result.DataPackagePath {
|
||||
t.Fatalf("DataPackagePath = %q, want managed path %q", renderer.request.DataPackagePath, result.DataPackagePath)
|
||||
}
|
||||
for _, path := range []string{result.BriefingPath, result.DataPackagePath, result.PreflightPath} {
|
||||
for _, path := range []string{result.BriefingPath, result.DataPackagePath, result.PreflightPath, result.MetadataPath} {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatalf("expected artifact %q: %v", path, err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(dataPackagePath); err != nil {
|
||||
t.Fatalf("expected requested data package copy %q: %v", dataPackagePath, err)
|
||||
}
|
||||
data, err := os.ReadFile(dataPackagePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read data package: %v", err)
|
||||
@@ -193,6 +197,12 @@ func TestPrepareDailyReportWritesDataPackageAndPreflight(t *testing.T) {
|
||||
if !strings.Contains(string(preflight), `prepared`) {
|
||||
t.Fatalf("preflight output missing render stdout:\n%s", string(preflight))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareDailyReportPersistsFailedPreflight(t *testing.T) {
|
||||
@@ -226,7 +236,15 @@ func TestPrepareDailyReportPersistsFailedPreflight(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("PrepareDailyReport() error = nil, want render error")
|
||||
}
|
||||
preflightPath := defaultPreflightPath(cfg, resolved)
|
||||
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
|
||||
preflight, readErr := os.ReadFile(preflightPath)
|
||||
if readErr != nil {
|
||||
t.Fatalf("read failed preflight: %v", readErr)
|
||||
@@ -234,6 +252,9 @@ func TestPrepareDailyReportPersistsFailedPreflight(t *testing.T) {
|
||||
if !strings.Contains(string(preflight), `"exitCode": 1`) {
|
||||
t.Fatalf("failed preflight was not persisted:\n%s", string(preflight))
|
||||
}
|
||||
if _, err := os.Stat(paths.Metadata); err != nil {
|
||||
t.Fatalf("expected metadata for failed preflight %q: %v", paths.Metadata, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user