// Package promptinput builds prompt data packages from briefing packages. package promptinput import ( "encoding/json" "fmt" "os" "path/filepath" "time" "gitea.maximumdirect.net/eric/weatherreporter/internal/briefing" "gitea.maximumdirect.net/eric/weatherreporter/internal/changes" "gitea.maximumdirect.net/eric/weatherreporter/internal/forecast" "gitea.maximumdirect.net/eric/weatherreporter/internal/report" "gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil" ) const SchemaVersion = "weatherreporter.data_package.v1" type Package struct { SchemaVersion string `json:"schemaVersion"` RunID string `json:"runId"` Report Report `json:"report"` Briefing briefing.Package `json:"briefing"` RecentChanges RecentChanges `json:"recentChanges"` SourceWarnings []forecast.SourceWarning `json:"sourceWarnings,omitempty"` } type Report struct { ID report.ID `json:"id"` Variant string `json:"variant,omitempty"` PromptID string `json:"promptId"` GeneratedAt time.Time `json:"generatedAt"` Timezone string `json:"timezone"` ValidPeriod timeutil.Period `json:"validPeriod"` } type RecentChanges struct { Items []changes.Change `json:"items"` } func Build(briefingPackage briefing.Package) (Package, error) { return BuildWithRecentChanges(briefingPackage, nil) } func BuildWithRecentChanges(briefingPackage briefing.Package, recentChanges []changes.Change) (Package, error) { items := make([]changes.Change, len(recentChanges)) copy(items, recentChanges) if items == nil { items = []changes.Change{} } pkg := Package{ SchemaVersion: SchemaVersion, RunID: briefingPackage.Metadata.RunID, Report: Report{ ID: briefingPackage.Metadata.ReportID, Variant: briefingPackage.Metadata.Variant, PromptID: briefingPackage.Metadata.PromptID, GeneratedAt: briefingPackage.Metadata.GeneratedAt, Timezone: briefingPackage.Metadata.Timezone, ValidPeriod: briefingPackage.Metadata.ValidPeriod, }, Briefing: briefingPackage, RecentChanges: RecentChanges{Items: items}, SourceWarnings: briefingPackage.Metadata.SourceWarnings, } if err := Validate(pkg); err != nil { return Package{}, err } return pkg, nil } func Validate(pkg Package) error { if pkg.SchemaVersion == "" { return fmt.Errorf("schemaVersion is required") } if pkg.RunID == "" { return fmt.Errorf("runId is required") } if pkg.Report.ID == "" { return fmt.Errorf("report.id is required") } if pkg.Report.PromptID == "" { return fmt.Errorf("report.promptId is required") } if pkg.Report.GeneratedAt.IsZero() { return fmt.Errorf("report.generatedAt is required") } if pkg.Report.Timezone == "" { return fmt.Errorf("report.timezone is required") } if !pkg.Report.ValidPeriod.IsValid() { return fmt.Errorf("report.validPeriod must be valid") } if pkg.Briefing.Metadata.RunID == "" { return fmt.Errorf("briefing.metadata.runId is required") } if pkg.Briefing.Metadata.RunID != pkg.RunID { return fmt.Errorf("briefing.metadata.runId must match runId") } if pkg.Briefing.Metadata.SchemaVersion == "" { return fmt.Errorf("briefing.metadata.schemaVersion is required") } if pkg.Briefing.Metadata.PromptID != pkg.Report.PromptID { return fmt.Errorf("briefing.metadata.promptId must match report.promptId") } if pkg.Briefing.Metadata.ReportID != pkg.Report.ID { return fmt.Errorf("briefing.metadata.reportId must match report.id") } if pkg.Briefing.Daily == nil && pkg.Briefing.ThreeDay == nil && pkg.Briefing.Weekend == nil && pkg.Briefing.Storm == nil { return fmt.Errorf("briefing report content is required") } return nil } func Save(path string, pkg Package) error { if err := Validate(pkg); err != nil { return err } data, err := json.MarshalIndent(pkg, "", " ") if err != nil { return fmt.Errorf("marshal data package: %w", err) } if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return fmt.Errorf("create data package directory %q: %w", filepath.Dir(path), err) } tmp, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*.tmp") if err != nil { return fmt.Errorf("create temporary data package file: %w", err) } tmpName := tmp.Name() defer os.Remove(tmpName) if _, err := tmp.Write(data); err != nil { tmp.Close() return fmt.Errorf("write temporary data package file: %w", err) } if err := tmp.Close(); err != nil { return fmt.Errorf("close temporary data package file: %w", err) } if err := os.Rename(tmpName, path); err != nil { return fmt.Errorf("save data package %q: %w", path, err) } return nil }