Run report generation without workspace state

This commit is contained in:
2026-08-01 19:52:22 +00:00
parent 4bdba6f2b7
commit 7ffc3dc603
21 changed files with 343 additions and 3464 deletions

View File

@@ -21,7 +21,6 @@ import (
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptexec"
"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"
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
)
@@ -53,7 +52,6 @@ type GenerateRequest struct {
Collector Collector
Notifier Notifier
Executor promptexec.Executor
Store state.Store
}
type BatchRequest struct {
@@ -65,7 +63,6 @@ type BatchRequest struct {
LLMDebugDir string
Collector Collector
Executor promptexec.Executor
Store state.Store
Notifier Notifier
}
@@ -85,21 +82,22 @@ type ReportFacts struct {
}
type ReportResult struct {
ModuleSnapshot module.Snapshot
ModuleSnapshotPath string
DataPackage promptinput.Package
DataPackagePath string
PreparationPath string
ExecutionPath string
LLMDebugPath string
ReportPath string
OutputPath string
Metadata state.Metadata
MetadataPath string
GeneratedTextRawPath string
GeneratedTextPath string
RenderContextPath string
Notification *NotificationResult
ReportID report.ID
ReportName string
PromptID string
PromptVersion string
RunID string
GeneratedAt time.Time
Timezone string
ValidPeriod timeutil.Period
ProfileID string
BackendID string
ModelName string
SourceWarnings []weatherdata.SourceWarning
ValidationStatus promptexec.ValidationStatus
LLMDebugPath string
OutputPath string
Notification *NotificationResult
}
type BatchResult struct {
@@ -132,25 +130,26 @@ type BatchNotificationReport struct {
}
type BatchReportResult struct {
ReportID report.ID `json:"reportId"`
ReportName string `json:"reportName"`
PromptID string `json:"promptId"`
RunID string `json:"runId"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
NotificationStatus string `json:"notificationStatus,omitempty"`
NotificationRunID string `json:"notificationRunId,omitempty"`
NotificationPipelineID string `json:"notificationPipelineId,omitempty"`
NotificationError string `json:"notificationError,omitempty"`
GeneratedAt time.Time `json:"generatedAt"`
ValidPeriod timeutil.Period `json:"validPeriod"`
DataPackagePath string `json:"dataPackagePath,omitempty"`
PreparationPath string `json:"preparationPath,omitempty"`
ExecutionPath string `json:"executionPath,omitempty"`
LLMDebugPath string `json:"llmDebugPath,omitempty"`
ReportPath string `json:"reportPath,omitempty"`
OutputPath string `json:"outputPath,omitempty"`
MetadataPath string `json:"metadataPath,omitempty"`
ReportID report.ID `json:"reportId"`
ReportName string `json:"reportName"`
PromptID string `json:"promptId"`
RunID string `json:"runId"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
NotificationStatus string `json:"notificationStatus,omitempty"`
NotificationRunID string `json:"notificationRunId,omitempty"`
NotificationPipelineID string `json:"notificationPipelineId,omitempty"`
NotificationError string `json:"notificationError,omitempty"`
GeneratedAt time.Time `json:"generatedAt"`
ValidPeriod timeutil.Period `json:"validPeriod"`
Timezone string `json:"timezone"`
ProfileID string `json:"profileId,omitempty"`
BackendID string `json:"backendId,omitempty"`
ModelName string `json:"modelName,omitempty"`
SourceWarnings []weatherdata.SourceWarning `json:"sourceWarnings,omitempty"`
ValidationStatus promptexec.ValidationStatus `json:"validationStatus,omitempty"`
LLMDebugPath string `json:"llmDebugPath,omitempty"`
OutputPath string `json:"outputPath,omitempty"`
}
type BatchError struct {
@@ -260,14 +259,15 @@ func GenerateDetailed(ctx context.Context, req GenerateRequest) (*ReportResult,
if err != nil {
return nil, err
}
result := initialReportResult(req, resolved, PromptInspectionResult{})
outputPath, err := resolveReportOutputPath(req.WorkingDir, req.OutputPath, resolved)
if err != nil {
return nil, err
return result, err
}
req.OutputPath = outputPath
debugWriter, err := promptdebug.NewPromptDebugWriter(req.LLMDebugDir)
if err != nil {
return nil, promptexec.NewError(promptexec.InvalidConfiguration, "initialize prompt debug", err)
return result, promptexec.NewError(promptexec.InvalidConfiguration, "initialize prompt debug", err)
}
inspection, err := InspectPromptExecution(ctx, PromptInspectionRequest{
Resolved: resolved,
@@ -275,11 +275,12 @@ func GenerateDetailed(ctx context.Context, req GenerateRequest) (*ReportResult,
Promptkit: req.Config.Promptkit,
})
if err != nil {
return nil, err
return result, err
}
result.ProfileID, result.BackendID, result.ModelName = inspection.ProfileID, inspection.BackendID, inspection.ModelName
collection, err := collectWeather(ctx, req.Config, req.Collector)
if err != nil {
return nil, err
return result, err
}
return generatePromptReport(ctx, promptReportRequest{
GenerateRequest: req,
@@ -287,6 +288,7 @@ func GenerateDetailed(ctx context.Context, req GenerateRequest) (*ReportResult,
Collection: *collection,
Inspection: inspection,
DebugWriter: debugWriter,
Result: result,
})
}
@@ -339,14 +341,6 @@ func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, erro
return nil, err
}
if req.Batch == BatchEvening || req.Batch == BatchMorning {
store := req.Store
if store == nil {
defaultStore, err := defaultStore(req.Config)
if err != nil {
return nil, err
}
store = defaultStore
}
startedAt := now
result := &BatchResult{Batch: req.Batch, StartedAt: startedAt}
for _, planned := range plannedReports {
@@ -362,7 +356,6 @@ func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, erro
OutputPath: outputPath,
Notifier: req.Notifier,
Executor: req.Executor,
Store: store,
},
Resolved: resolved,
Collection: *collection,
@@ -398,13 +391,14 @@ func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, erro
}
func copyBatchReportPaths(item *BatchReportResult, result *ReportResult) {
item.DataPackagePath = result.DataPackagePath
item.PreparationPath = result.PreparationPath
item.ExecutionPath = result.ExecutionPath
item.LLMDebugPath = result.LLMDebugPath
item.ReportPath = result.ReportPath
item.OutputPath = result.OutputPath
item.MetadataPath = result.MetadataPath
item.ProfileID = result.ProfileID
item.BackendID = result.BackendID
item.ModelName = result.ModelName
item.Timezone = result.Timezone
item.SourceWarnings = append([]weatherdata.SourceWarning(nil), result.SourceWarnings...)
item.ValidationStatus = result.ValidationStatus
if result.Notification != nil {
item.NotificationStatus = result.Notification.Status
item.NotificationRunID = result.Notification.RunID
@@ -451,6 +445,7 @@ func batchReportResult(planned plannedBatchReport) BatchReportResult {
RunID: metadata.RunID,
GeneratedAt: metadata.GeneratedAt,
ValidPeriod: metadata.ValidPeriod,
Timezone: "",
}
}
@@ -612,78 +607,6 @@ func FetchAndSaveBundle(ctx context.Context, req FetchBundleRequest) (*weatherda
return bundle, nil
}
type finalizeRenderedReportRequest struct {
Config config.Config
Store state.Store
Resolved report.Resolved
Metadata state.Metadata
MetadataPath string
ExecutionArtifact *state.PromptExecutionArtifact
RenderedReportPath string
OutputPath string
Notifier Notifier
GenerationErr error
noNotify bool
}
type finalizeRenderedReportResult struct {
OutputPath string
Metadata state.Metadata
MetadataPath string
Notification *NotificationResult
}
func finalizeRenderedReport(ctx context.Context, req finalizeRenderedReportRequest) (finalizeRenderedReportResult, error) {
if req.Store == nil {
return finalizeRenderedReportResult{}, fmt.Errorf("state store is required")
}
if req.RenderedReportPath == "" {
return finalizeRenderedReportResult{}, fmt.Errorf("rendered report path is required for report %q", req.Resolved.Definition.ID)
}
if req.ExecutionArtifact == nil {
return finalizeRenderedReportResult{}, fmt.Errorf("prompt execution artifact is required for report %q", req.Resolved.Definition.ID)
}
result := finalizeRenderedReportResult{Metadata: req.Metadata, MetadataPath: req.MetadataPath}
if req.OutputPath != "" && req.GenerationErr == nil {
if req.OutputPath != req.RenderedReportPath {
if err := fileutil.CopyFileAtomic(req.RenderedReportPath, req.OutputPath); err != nil {
return result, err
}
result.OutputPath = req.OutputPath
if err := persistReachedPromptPath(ctx, req.Store, req.Resolved, req.ExecutionArtifact, func(paths *state.PromptExecutionPaths) {
paths.OutputPath = req.OutputPath
}); err != nil {
return result, err
}
} else {
result.OutputPath = req.OutputPath
}
}
metadata := req.Metadata
metadata.RenderedReportPath = req.RenderedReportPath
metadataPath, err := req.Store.SaveMetadata(ctx, metadata)
if err != nil {
return result, err
}
result.Metadata = metadata
result.MetadataPath = metadataPath
if req.GenerationErr != nil {
return result, req.GenerationErr
}
if req.noNotify {
return result, nil
}
notification, err := notifyReport(ctx, req.Config, req.Resolved, req.OutputPath, metadata.RunID, metadata.GeneratedAt, req.Notifier)
result.Notification = notification
if err != nil {
return result, err
}
return result, nil
}
func notifyReport(ctx context.Context, cfg config.Config, resolved report.Resolved, outputPath, runID string, generatedAt time.Time, notifier Notifier) (*NotificationResult, error) {
notifier, enabled := reportNotifier(cfg, notifier)
if !enabled {
@@ -965,7 +888,7 @@ func briefingBuildContext(cfg config.Config, resolved report.Resolved, collected
}
}
func promptMetadata(metadata state.Metadata) promptinput.Metadata {
func promptMetadata(metadata briefing.Metadata) promptinput.Metadata {
return promptinput.Metadata{
RunID: metadata.RunID,
ReportID: metadata.ReportID,
@@ -1008,10 +931,6 @@ func briefingLocation(cfg config.Config) *briefing.LocationContext {
return &location
}
func defaultStore(cfg config.Config) (*state.FilesystemStore, error) {
return state.NewFilesystemStore(cfg.Workspace)
}
func generatedReportError(resolved report.Resolved, runID string, operation string, err error) error {
if err == nil {
return nil