Persist module snapshots for generated reports
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/facts"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/fileutil"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptinput"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/state"
|
||||
@@ -73,6 +74,11 @@ type BriefingRequest struct {
|
||||
OutputPath string
|
||||
}
|
||||
|
||||
type ModuleSnapshotRequest struct {
|
||||
Config config.Config
|
||||
Resolved report.Resolved
|
||||
}
|
||||
|
||||
type ReportRequest struct {
|
||||
Config config.Config
|
||||
Resolved report.Resolved
|
||||
@@ -88,21 +94,23 @@ type BriefingResult struct {
|
||||
}
|
||||
|
||||
type ReportResult struct {
|
||||
Briefing briefing.Package
|
||||
BriefingPath string
|
||||
DataPackage promptinput.Package
|
||||
DataPackagePath string
|
||||
PreflightPath string
|
||||
ReportPath string
|
||||
OutputPath string
|
||||
NotificationPath string
|
||||
Metadata state.Metadata
|
||||
MetadataPath string
|
||||
PriorSnapshot *state.PriorSnapshot
|
||||
RecentChanges []changes.Change
|
||||
RenderResult *scriptorium.RenderResult
|
||||
RunResult *scriptorium.RunResult
|
||||
Notification *NotificationResult
|
||||
Briefing briefing.Package
|
||||
ModuleSnapshot module.Snapshot
|
||||
BriefingPath string
|
||||
ModuleSnapshotPath string
|
||||
DataPackage promptinput.Package
|
||||
DataPackagePath string
|
||||
PreflightPath string
|
||||
ReportPath string
|
||||
OutputPath string
|
||||
NotificationPath string
|
||||
Metadata state.Metadata
|
||||
MetadataPath string
|
||||
PriorSnapshot *state.PriorSnapshot
|
||||
RecentChanges []changes.Change
|
||||
RenderResult *scriptorium.RenderResult
|
||||
RunResult *scriptorium.RunResult
|
||||
Notification *NotificationResult
|
||||
}
|
||||
|
||||
type BatchResult struct {
|
||||
@@ -493,11 +501,22 @@ func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
moduleSnapshot, err := BuildModuleSnapshot(ModuleSnapshotRequest{
|
||||
Config: req.Config,
|
||||
Resolved: req.Resolved,
|
||||
}, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
briefingPath, err := store.SaveBriefing(ctx, req.Resolved, briefingPackage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
moduleSnapshotPath, err := store.SaveModuleSnapshot(ctx, req.Resolved, moduleSnapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recentChanges, err := recentChanges(ctx, store, priorSnapshot, briefingPackage, req.Config.RecentChange)
|
||||
if err != nil {
|
||||
@@ -537,6 +556,7 @@ func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, erro
|
||||
}
|
||||
metadata := state.BuildMetadata(req.Resolved, briefingPackage, state.ArtifactPaths{
|
||||
Briefing: briefingPath,
|
||||
ModuleSnapshot: moduleSnapshotPath,
|
||||
Metadata: paths.Metadata,
|
||||
DataPackage: dataPackagePath,
|
||||
Preflight: preflightPath,
|
||||
@@ -590,21 +610,23 @@ func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, erro
|
||||
}
|
||||
|
||||
return &ReportResult{
|
||||
Briefing: briefingPackage,
|
||||
BriefingPath: briefingPath,
|
||||
DataPackage: dataPackage,
|
||||
DataPackagePath: dataPackagePath,
|
||||
PreflightPath: preflightPath,
|
||||
ReportPath: reportPath,
|
||||
OutputPath: outputPath,
|
||||
NotificationPath: notificationPath,
|
||||
Metadata: metadata,
|
||||
MetadataPath: metadataPath,
|
||||
PriorSnapshot: priorSnapshot,
|
||||
RecentChanges: recentChanges,
|
||||
RenderResult: renderResult,
|
||||
RunResult: runResult,
|
||||
Notification: notification,
|
||||
Briefing: briefingPackage,
|
||||
ModuleSnapshot: moduleSnapshot,
|
||||
BriefingPath: briefingPath,
|
||||
ModuleSnapshotPath: moduleSnapshotPath,
|
||||
DataPackage: dataPackage,
|
||||
DataPackagePath: dataPackagePath,
|
||||
PreflightPath: preflightPath,
|
||||
ReportPath: reportPath,
|
||||
OutputPath: outputPath,
|
||||
NotificationPath: notificationPath,
|
||||
Metadata: metadata,
|
||||
MetadataPath: metadataPath,
|
||||
PriorSnapshot: priorSnapshot,
|
||||
RecentChanges: recentChanges,
|
||||
RenderResult: renderResult,
|
||||
RunResult: runResult,
|
||||
Notification: notification,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -807,20 +829,7 @@ func distributorUploadFiles(sourcePath string, bundlePaths []string) []distribut
|
||||
|
||||
func BuildBriefing(req BriefingRequest, bundle *weatherdata.Bundle) (briefing.Package, error) {
|
||||
collected := facts.BuildCollected(bundle)
|
||||
dayparts := make([]forecast.DaypartDefinition, 0, len(req.Config.Dayparts))
|
||||
for _, daypart := range req.Config.Dayparts {
|
||||
dayparts = append(dayparts, forecast.DaypartDefinition{
|
||||
Name: daypart.Name,
|
||||
Start: daypart.Start,
|
||||
End: daypart.End,
|
||||
})
|
||||
}
|
||||
derived, err := facts.BuildDerived(facts.BuildDerivedRequest{
|
||||
Resolved: req.Resolved,
|
||||
Timezone: req.Config.WeatherAPI.Timezone,
|
||||
Dayparts: dayparts,
|
||||
Collected: collected,
|
||||
})
|
||||
derived, err := buildDerivedFacts(req.Config, req.Resolved, collected)
|
||||
if err != nil {
|
||||
return briefing.Package{}, err
|
||||
}
|
||||
@@ -846,6 +855,62 @@ func BuildBriefing(req BriefingRequest, bundle *weatherdata.Bundle) (briefing.Pa
|
||||
}
|
||||
}
|
||||
|
||||
func BuildModuleSnapshot(req ModuleSnapshotRequest, bundle *weatherdata.Bundle) (module.Snapshot, error) {
|
||||
collected := facts.BuildCollected(bundle)
|
||||
derived, err := buildDerivedFacts(req.Config, req.Resolved, collected)
|
||||
if err != nil {
|
||||
return module.Snapshot{}, err
|
||||
}
|
||||
registry, err := briefing.DefaultModuleRegistry()
|
||||
if err != nil {
|
||||
return module.Snapshot{}, err
|
||||
}
|
||||
moduleContext := briefing.ModuleContext{
|
||||
Resolved: req.Resolved,
|
||||
Collected: collected,
|
||||
Derived: derived,
|
||||
Units: req.Config.WeatherAPI.Units,
|
||||
Timezone: req.Config.WeatherAPI.Timezone,
|
||||
Location: briefingLocation(req.Config),
|
||||
}
|
||||
var outputs []module.Output
|
||||
for _, item := range req.Resolved.Definition.Modules {
|
||||
definition, err := registry.Lookup(item.ID)
|
||||
if err != nil {
|
||||
return module.Snapshot{}, err
|
||||
}
|
||||
if definition.Builder == nil {
|
||||
continue
|
||||
}
|
||||
output, err := registry.BuildModule(moduleContext, item)
|
||||
if err != nil {
|
||||
return module.Snapshot{}, err
|
||||
}
|
||||
if output == nil {
|
||||
continue
|
||||
}
|
||||
outputs = append(outputs, *output)
|
||||
}
|
||||
return module.NewSnapshot(outputs)
|
||||
}
|
||||
|
||||
func buildDerivedFacts(cfg config.Config, resolved report.Resolved, collected facts.CollectedFacts) (facts.DerivedFacts, error) {
|
||||
dayparts := make([]forecast.DaypartDefinition, 0, len(cfg.Dayparts))
|
||||
for _, daypart := range cfg.Dayparts {
|
||||
dayparts = append(dayparts, forecast.DaypartDefinition{
|
||||
Name: daypart.Name,
|
||||
Start: daypart.Start,
|
||||
End: daypart.End,
|
||||
})
|
||||
}
|
||||
return facts.BuildDerived(facts.BuildDerivedRequest{
|
||||
Resolved: resolved,
|
||||
Timezone: cfg.WeatherAPI.Timezone,
|
||||
Dayparts: dayparts,
|
||||
Collected: collected,
|
||||
})
|
||||
}
|
||||
|
||||
func briefingLocation(cfg config.Config) *briefing.LocationContext {
|
||||
location := briefing.LocationContext{
|
||||
ID: cfg.Location.ID,
|
||||
|
||||
@@ -194,7 +194,14 @@ 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)
|
||||
}
|
||||
assertPathsExist(t, result.BriefingPath, result.DataPackagePath, result.PreflightPath, result.ReportPath, result.MetadataPath, outputPath)
|
||||
assertPathsExist(t, result.BriefingPath, result.ModuleSnapshotPath, result.DataPackagePath, result.PreflightPath, result.ReportPath, result.MetadataPath, outputPath)
|
||||
snapshotData, err := os.ReadFile(result.ModuleSnapshotPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read module snapshot: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(snapshotData), module.SnapshotSchemaVersion) || !strings.Contains(string(snapshotData), `"metadata"`) || !strings.Contains(string(snapshotData), `"derived_daily_summary"`) {
|
||||
t.Fatalf("module snapshot missing expected stanzas:\n%s", string(snapshotData))
|
||||
}
|
||||
data, err := os.ReadFile(result.DataPackagePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read data package: %v", err)
|
||||
@@ -238,7 +245,7 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
|
||||
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 {
|
||||
if result.Metadata.BriefingPath != result.BriefingPath || result.Metadata.ModuleSnapshotPath != result.ModuleSnapshotPath || result.Metadata.DataPackagePath != result.DataPackagePath {
|
||||
t.Fatalf("metadata does not link artifact paths: %#v", result.Metadata)
|
||||
}
|
||||
if result.Metadata.RenderedReportPath != result.ReportPath {
|
||||
@@ -647,6 +654,7 @@ func TestGenerateReportIncludesRecentChangesFromPriorSnapshot(t *testing.T) {
|
||||
}
|
||||
_, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{
|
||||
Briefing: priorBriefingPath,
|
||||
ModuleSnapshot: priorPaths.ModuleSnapshot,
|
||||
Metadata: priorPaths.Metadata,
|
||||
DataPackage: priorPaths.DataPackage,
|
||||
Preflight: priorPaths.Preflight,
|
||||
@@ -762,6 +770,7 @@ func TestTomorrowReportCanCompareAgainstPriorDailySnapshot(t *testing.T) {
|
||||
}
|
||||
_, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{
|
||||
Briefing: priorBriefingPath,
|
||||
ModuleSnapshot: priorPaths.ModuleSnapshot,
|
||||
Metadata: priorPaths.Metadata,
|
||||
DataPackage: priorPaths.DataPackage,
|
||||
Preflight: priorPaths.Preflight,
|
||||
@@ -829,6 +838,7 @@ func TestGenerateThreeDayReportWritesReportAndRecentChanges(t *testing.T) {
|
||||
}
|
||||
_, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{
|
||||
Briefing: priorBriefingPath,
|
||||
ModuleSnapshot: priorPaths.ModuleSnapshot,
|
||||
Metadata: priorPaths.Metadata,
|
||||
DataPackage: priorPaths.DataPackage,
|
||||
Preflight: priorPaths.Preflight,
|
||||
@@ -905,6 +915,7 @@ func TestGenerateWeekendReportWritesReportAndRecentChanges(t *testing.T) {
|
||||
}
|
||||
_, err = store.SaveMetadata(context.Background(), state.BuildMetadata(priorResolved, priorBriefing, state.ArtifactPaths{
|
||||
Briefing: priorBriefingPath,
|
||||
ModuleSnapshot: priorPaths.ModuleSnapshot,
|
||||
Metadata: priorPaths.Metadata,
|
||||
DataPackage: priorPaths.DataPackage,
|
||||
Preflight: priorPaths.Preflight,
|
||||
@@ -1045,15 +1056,15 @@ func TestInspectGeneratedReportArtifacts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("InspectMetadata() error = %v", err)
|
||||
}
|
||||
if metadata.BriefingPath != result.BriefingPath || metadata.DataPackagePath != result.DataPackagePath {
|
||||
if metadata.BriefingPath != result.BriefingPath || metadata.ModuleSnapshotPath != result.ModuleSnapshotPath || 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})
|
||||
moduleSnapshot, err := InspectModules(context.Background(), InspectRunRequest{Config: cfg, RunID: result.Metadata.RunID})
|
||||
if err != nil {
|
||||
t.Fatalf("InspectBriefing() error = %v", err)
|
||||
t.Fatalf("InspectModules() error = %v", err)
|
||||
}
|
||||
if briefingPackage.Metadata.RunID != result.Metadata.RunID {
|
||||
t.Fatalf("briefing RunID = %q, want %q", briefingPackage.Metadata.RunID, result.Metadata.RunID)
|
||||
if moduleSnapshot.SchemaVersion != module.SnapshotSchemaVersion || len(moduleSnapshot.Outputs) == 0 {
|
||||
t.Fatalf("module snapshot = %#v, want persisted outputs", moduleSnapshot)
|
||||
}
|
||||
dataPackage, err := InspectDataPackage(context.Background(), InspectRunRequest{Config: cfg, RunID: result.Metadata.RunID})
|
||||
if err != nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptinput"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/state"
|
||||
@@ -44,12 +45,12 @@ func InspectMetadata(ctx context.Context, req InspectRunRequest) (state.Metadata
|
||||
return inspection.metadata, err
|
||||
}
|
||||
|
||||
func InspectBriefing(ctx context.Context, req InspectRunRequest) (briefing.Package, error) {
|
||||
func InspectModules(ctx context.Context, req InspectRunRequest) (module.Snapshot, error) {
|
||||
inspection, err := inspectRun(ctx, req)
|
||||
if err != nil {
|
||||
return briefing.Package{}, err
|
||||
return module.Snapshot{}, err
|
||||
}
|
||||
return inspection.store.LoadBriefing(ctx, inspection.metadata.BriefingPath)
|
||||
return inspection.store.LoadModuleSnapshot(ctx, inspection.metadata.ModuleSnapshotPath)
|
||||
}
|
||||
|
||||
func InspectDataPackage(ctx context.Context, req InspectRunRequest) (promptinput.Package, error) {
|
||||
|
||||
Reference in New Issue
Block a user