Add durable prompt execution state records
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
||||
@@ -9,12 +12,49 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
const MetadataSchemaVersion = "weatherreporter.metadata.v1"
|
||||
const (
|
||||
MetadataSchemaVersionV1 = "weatherreporter.metadata.v1"
|
||||
MetadataSchemaVersion = "weatherreporter.metadata.v2"
|
||||
)
|
||||
|
||||
// Metadata is the durable record used for report discovery and inspection.
|
||||
// V1 fields remain internal compatibility values and are emitted only for V1
|
||||
// records; V2 records have no legacy aliases in their JSON representation.
|
||||
type Metadata struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
RunID string `json:"runId"`
|
||||
MetadataPath string `json:"-"`
|
||||
ReportID report.ID `json:"reportId"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
PromptID string `json:"promptId"`
|
||||
GeneratedAt time.Time `json:"generatedAt"`
|
||||
Timezone string `json:"timezone"`
|
||||
ValidPeriod timeutil.Period `json:"validPeriod"`
|
||||
Location *briefing.LocationContext `json:"location,omitempty"`
|
||||
SourceLocationID string `json:"sourceLocationId,omitempty"`
|
||||
SourceLocation string `json:"sourceLocation,omitempty"`
|
||||
Sources []briefing.SourceMetadata `json:"sources,omitempty"`
|
||||
SourceWarnings []weatherdata.SourceWarning `json:"sourceWarnings,omitempty"`
|
||||
ModuleSnapshotPath string `json:"moduleSnapshotPath"`
|
||||
DataPackagePath string `json:"dataPackagePath"`
|
||||
PreparationPath string `json:"preparationPath,omitempty"`
|
||||
ExecutionPath string `json:"executionPath,omitempty"`
|
||||
NotificationPath string `json:"notificationPath,omitempty"`
|
||||
RenderedReportPath string `json:"renderedReportPath,omitempty"`
|
||||
GeneratedTextSchemaID string `json:"generatedTextSchemaId,omitempty"`
|
||||
GeneratedTextRawPath string `json:"generatedTextRawPath,omitempty"`
|
||||
GeneratedTextPath string `json:"generatedTextPath,omitempty"`
|
||||
RenderContextPath string `json:"renderContextPath,omitempty"`
|
||||
|
||||
// These paths are retained only to preserve records written by the temporary
|
||||
// Scriptorium flow. They are never emitted in V2 metadata.
|
||||
PreflightPath string `json:"-"`
|
||||
GeneratedTextResultPath string `json:"-"`
|
||||
}
|
||||
|
||||
type metadataJSON struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
RunID string `json:"runId"`
|
||||
MetadataPath string `json:"-"`
|
||||
ReportID report.ID `json:"reportId"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
PromptID string `json:"promptId"`
|
||||
@@ -28,7 +68,9 @@ type Metadata struct {
|
||||
SourceWarnings []weatherdata.SourceWarning `json:"sourceWarnings,omitempty"`
|
||||
ModuleSnapshotPath string `json:"moduleSnapshotPath"`
|
||||
DataPackagePath string `json:"dataPackagePath"`
|
||||
PreflightPath string `json:"preflightPath"`
|
||||
PreparationPath string `json:"preparationPath,omitempty"`
|
||||
ExecutionPath string `json:"executionPath,omitempty"`
|
||||
PreflightPath string `json:"preflightPath,omitempty"`
|
||||
NotificationPath string `json:"notificationPath,omitempty"`
|
||||
RenderedReportPath string `json:"renderedReportPath,omitempty"`
|
||||
GeneratedTextSchemaID string `json:"generatedTextSchemaId,omitempty"`
|
||||
@@ -38,10 +80,100 @@ type Metadata struct {
|
||||
RenderContextPath string `json:"renderContextPath,omitempty"`
|
||||
}
|
||||
|
||||
func (m Metadata) MarshalJSON() ([]byte, error) {
|
||||
w := metadataJSON{
|
||||
SchemaVersion: m.SchemaVersion, RunID: m.RunID, ReportID: m.ReportID,
|
||||
Variant: m.Variant, PromptID: m.PromptID, GeneratedAt: m.GeneratedAt,
|
||||
Timezone: m.Timezone, ValidPeriod: m.ValidPeriod, Location: m.Location,
|
||||
SourceLocationID: m.SourceLocationID, SourceLocation: m.SourceLocation,
|
||||
Sources: m.Sources, SourceWarnings: m.SourceWarnings,
|
||||
ModuleSnapshotPath: m.ModuleSnapshotPath, DataPackagePath: m.DataPackagePath,
|
||||
NotificationPath: m.NotificationPath, RenderedReportPath: m.RenderedReportPath,
|
||||
GeneratedTextSchemaID: m.GeneratedTextSchemaID, GeneratedTextRawPath: m.GeneratedTextRawPath,
|
||||
GeneratedTextPath: m.GeneratedTextPath, RenderContextPath: m.RenderContextPath,
|
||||
}
|
||||
switch m.SchemaVersion {
|
||||
case MetadataSchemaVersionV1:
|
||||
w.PreflightPath = m.PreflightPath
|
||||
w.GeneratedTextResultPath = m.GeneratedTextResultPath
|
||||
case MetadataSchemaVersion:
|
||||
w.PreparationPath = m.PreparationPath
|
||||
w.ExecutionPath = m.ExecutionPath
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported metadata schema version %q", m.SchemaVersion)
|
||||
}
|
||||
return json.Marshal(w)
|
||||
}
|
||||
|
||||
func (m *Metadata) UnmarshalJSON(data []byte) error {
|
||||
var header struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &header); err != nil {
|
||||
return err
|
||||
}
|
||||
if header.SchemaVersion != MetadataSchemaVersionV1 && header.SchemaVersion != MetadataSchemaVersion {
|
||||
return fmt.Errorf("unsupported metadata schema version %q", header.SchemaVersion)
|
||||
}
|
||||
var w metadataJSON
|
||||
if err := json.Unmarshal(data, &w); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = Metadata{
|
||||
SchemaVersion: w.SchemaVersion, RunID: w.RunID, ReportID: w.ReportID,
|
||||
Variant: w.Variant, PromptID: w.PromptID, GeneratedAt: w.GeneratedAt,
|
||||
Timezone: w.Timezone, ValidPeriod: w.ValidPeriod, Location: w.Location,
|
||||
SourceLocationID: w.SourceLocationID, SourceLocation: w.SourceLocation,
|
||||
Sources: w.Sources, SourceWarnings: w.SourceWarnings,
|
||||
ModuleSnapshotPath: w.ModuleSnapshotPath, DataPackagePath: w.DataPackagePath,
|
||||
NotificationPath: w.NotificationPath, RenderedReportPath: w.RenderedReportPath,
|
||||
GeneratedTextSchemaID: w.GeneratedTextSchemaID, GeneratedTextRawPath: w.GeneratedTextRawPath,
|
||||
GeneratedTextPath: w.GeneratedTextPath, RenderContextPath: w.RenderContextPath,
|
||||
}
|
||||
if w.SchemaVersion == MetadataSchemaVersionV1 {
|
||||
m.PreflightPath = w.PreflightPath
|
||||
m.GeneratedTextResultPath = w.GeneratedTextResultPath
|
||||
m.PreparationPath = w.PreflightPath
|
||||
m.ExecutionPath = w.GeneratedTextResultPath
|
||||
} else {
|
||||
m.PreparationPath = w.PreparationPath
|
||||
m.ExecutionPath = w.ExecutionPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Metadata) Validate() error {
|
||||
if strings.TrimSpace(m.RunID) == "" {
|
||||
return fmt.Errorf("metadata run id is required")
|
||||
}
|
||||
if strings.TrimSpace(m.ModuleSnapshotPath) == "" {
|
||||
return fmt.Errorf("metadata module snapshot path is required")
|
||||
}
|
||||
if strings.TrimSpace(m.DataPackagePath) == "" {
|
||||
return fmt.Errorf("metadata data package path is required")
|
||||
}
|
||||
if strings.TrimSpace(m.MetadataPath) == "" {
|
||||
return fmt.Errorf("metadata path is required")
|
||||
}
|
||||
switch m.SchemaVersion {
|
||||
case MetadataSchemaVersionV1:
|
||||
if strings.TrimSpace(m.PreflightPath) == "" {
|
||||
return fmt.Errorf("metadata preflight path is required")
|
||||
}
|
||||
case MetadataSchemaVersion:
|
||||
if strings.TrimSpace(m.PreparationPath) == "" {
|
||||
return fmt.Errorf("metadata preparation path is required")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported metadata schema version %q", m.SchemaVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildMetadataFromBriefingMetadata(resolved report.Resolved, briefingMetadata briefing.Metadata, paths ArtifactPaths) Metadata {
|
||||
metadata := resolved.Metadata()
|
||||
out := Metadata{
|
||||
SchemaVersion: MetadataSchemaVersion,
|
||||
SchemaVersion: MetadataSchemaVersionV1,
|
||||
RunID: metadata.RunID,
|
||||
MetadataPath: paths.Metadata,
|
||||
ReportID: metadata.ReportID,
|
||||
@@ -68,6 +200,19 @@ func BuildMetadataFromBriefingMetadata(resolved report.Resolved, briefingMetadat
|
||||
return out
|
||||
}
|
||||
|
||||
// BuildPromptMetadataFromBriefingMetadata creates the V2 record used by the
|
||||
// prompt execution workflow. Callers populate preparation and execution paths
|
||||
// only after their corresponding artifacts have been saved.
|
||||
func BuildPromptMetadataFromBriefingMetadata(resolved report.Resolved, briefingMetadata briefing.Metadata, paths ArtifactPaths) Metadata {
|
||||
legacy := BuildMetadataFromBriefingMetadata(resolved, briefingMetadata, paths)
|
||||
legacy.SchemaVersion = MetadataSchemaVersion
|
||||
legacy.PreparationPath = ""
|
||||
legacy.ExecutionPath = ""
|
||||
legacy.PreflightPath = ""
|
||||
legacy.GeneratedTextResultPath = ""
|
||||
return legacy
|
||||
}
|
||||
|
||||
func copyLocation(location *briefing.LocationContext) *briefing.LocationContext {
|
||||
if location == nil {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user