Add 3-Day Outlook generation

This commit is contained in:
2026-05-29 18:08:50 +00:00
parent 19513e42c1
commit 4c9d396f9b
24 changed files with 927 additions and 78 deletions

View File

@@ -58,13 +58,15 @@ type FetchBundleRequest struct {
OutputPath string
}
type DailyBriefingRequest struct {
type BriefingRequest struct {
Config config.Config
Resolved report.Resolved
OutputPath string
}
type DailyReportRequest struct {
type DailyBriefingRequest = BriefingRequest
type ReportRequest struct {
Config config.Config
Resolved report.Resolved
OutputPath string
@@ -72,12 +74,16 @@ type DailyReportRequest struct {
Store state.Store
}
type DailyBriefingResult struct {
type DailyReportRequest = ReportRequest
type BriefingResult struct {
Package briefing.Package
OutputPath string
}
type DailyReportResult struct {
type DailyBriefingResult = BriefingResult
type ReportResult struct {
Briefing briefing.Package
BriefingPath string
DataPackage promptinput.Package
@@ -93,6 +99,8 @@ type DailyReportResult struct {
RunResult *scriptorium.RunResult
}
type DailyReportResult = ReportResult
type Renderer interface {
Render(context.Context, scriptorium.RenderRequest) (*scriptorium.RenderResult, error)
Run(context.Context, scriptorium.RunRequest) (*scriptorium.RunResult, error)
@@ -107,8 +115,8 @@ func Generate(ctx context.Context, req GenerateRequest) error {
if err != nil {
return err
}
if isDailyReport(resolved.Definition.ID) {
_, err := GenerateDailyReport(ctx, DailyReportRequest{
if isGeneratedReport(resolved.Definition.ID) {
_, err := GenerateReport(ctx, ReportRequest{
Config: req.Config,
Resolved: resolved,
OutputPath: req.OutputPath,
@@ -127,12 +135,14 @@ func RunBatch(ctx context.Context, req BatchRequest) error {
if err != nil {
return err
}
if req.Batch == BatchEvening {
if req.Batch == BatchEvening || req.Batch == BatchMorning {
for _, resolved := range resolvedReports {
if !isDailyReport(resolved.Definition.ID) {
if !isGeneratedReport(resolved.Definition.ID) {
return fmt.Errorf("run is not implemented")
}
if _, err := GenerateDailyReport(ctx, DailyReportRequest{
}
for _, resolved := range resolvedReports {
if _, err := GenerateReport(ctx, ReportRequest{
Config: req.Config,
Resolved: resolved,
}); err != nil {
@@ -144,6 +154,10 @@ func RunBatch(ctx context.Context, req BatchRequest) error {
return fmt.Errorf("run is not implemented")
}
func isGeneratedReport(id report.ID) bool {
return isDailyReport(id) || id == report.ThreeDay
}
func isDailyReport(id report.ID) bool {
return id == report.DailyToday || id == report.DailyTomorrow
}
@@ -236,11 +250,15 @@ func FetchAndSaveBundle(ctx context.Context, req FetchBundleRequest) (*forecast.
}
func GenerateDailyBriefing(ctx context.Context, req DailyBriefingRequest) (*DailyBriefingResult, error) {
return GenerateBriefing(ctx, req)
}
func GenerateBriefing(ctx context.Context, req BriefingRequest) (*BriefingResult, error) {
bundle, err := FetchBundle(ctx, FetchBundleRequest{Config: req.Config})
if err != nil {
return nil, err
}
pkg, err := BuildDailyBriefing(req, bundle)
pkg, err := BuildBriefing(req, bundle)
if err != nil {
return nil, err
}
@@ -259,10 +277,14 @@ func GenerateDailyBriefing(ctx context.Context, req DailyBriefingRequest) (*Dail
if err := briefing.Save(outputPath, pkg); err != nil {
return nil, err
}
return &DailyBriefingResult{Package: pkg, OutputPath: outputPath}, nil
return &BriefingResult{Package: pkg, OutputPath: outputPath}, nil
}
func GenerateDailyReport(ctx context.Context, req DailyReportRequest) (*DailyReportResult, error) {
return GenerateReport(ctx, req)
}
func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, error) {
store := req.Store
if store == nil {
defaultStore, err := defaultStore(req.Config)
@@ -275,7 +297,7 @@ func GenerateDailyReport(ctx context.Context, req DailyReportRequest) (*DailyRep
if err != nil {
return nil, err
}
priorSnapshot, err := store.FindPriorDailySnapshot(ctx, req.Resolved)
priorSnapshot, err := store.FindPriorSnapshot(ctx, req.Resolved)
if err != nil {
return nil, err
}
@@ -284,7 +306,7 @@ func GenerateDailyReport(ctx context.Context, req DailyReportRequest) (*DailyRep
if err != nil {
return nil, err
}
briefingPackage, err := BuildDailyBriefing(DailyBriefingRequest{
briefingPackage, err := BuildBriefing(BriefingRequest{
Config: req.Config,
Resolved: req.Resolved,
}, bundle)
@@ -297,7 +319,7 @@ func GenerateDailyReport(ctx context.Context, req DailyReportRequest) (*DailyRep
return nil, err
}
recentChanges, err := dailyRecentChanges(ctx, store, priorSnapshot, briefingPackage, req.Config.RecentChange)
recentChanges, err := recentChanges(ctx, store, priorSnapshot, briefingPackage, req.Config.RecentChange)
if err != nil {
return nil, err
}
@@ -375,7 +397,7 @@ func GenerateDailyReport(ctx context.Context, req DailyReportRequest) (*DailyRep
return nil, runErr
}
return &DailyReportResult{
return &ReportResult{
Briefing: briefingPackage,
BriefingPath: briefingPath,
DataPackage: dataPackage,
@@ -393,6 +415,10 @@ func GenerateDailyReport(ctx context.Context, req DailyReportRequest) (*DailyRep
}
func BuildDailyBriefing(req DailyBriefingRequest, bundle *forecast.Bundle) (briefing.Package, error) {
return BuildBriefing(req, bundle)
}
func BuildBriefing(req BriefingRequest, bundle *forecast.Bundle) (briefing.Package, error) {
location, err := timeutil.LoadLocation(req.Config.WeatherAPI.Timezone)
if err != nil {
return briefing.Package{}, err
@@ -405,16 +431,32 @@ func BuildDailyBriefing(req DailyBriefingRequest, bundle *forecast.Bundle) (brie
End: daypart.End,
})
}
summary, err := forecast.BuildDailySummary(bundle, req.Resolved.ValidPeriod.Start, location, dayparts)
if err != nil {
return briefing.Package{}, err
switch req.Resolved.Definition.ID {
case report.DailyToday, report.DailyTomorrow:
summary, err := forecast.BuildDailySummary(bundle, req.Resolved.ValidPeriod.Start, location, dayparts)
if err != nil {
return briefing.Package{}, err
}
return briefing.BuildDaily(briefing.BuildContext{
Resolved: req.Resolved,
Bundle: bundle,
Units: req.Config.WeatherAPI.Units,
Timezone: req.Config.WeatherAPI.Timezone,
}, summary)
case report.ThreeDay:
summaries, err := forecast.BuildPeriodDailySummaries(bundle, req.Resolved.ValidPeriod, location, dayparts)
if err != nil {
return briefing.Package{}, err
}
return briefing.BuildThreeDay(briefing.BuildContext{
Resolved: req.Resolved,
Bundle: bundle,
Units: req.Config.WeatherAPI.Units,
Timezone: req.Config.WeatherAPI.Timezone,
}, summaries)
default:
return briefing.Package{}, fmt.Errorf("briefing is not implemented for report %q", req.Resolved.Definition.ID)
}
return briefing.BuildDaily(briefing.BuildContext{
Resolved: req.Resolved,
Bundle: bundle,
Units: req.Config.WeatherAPI.Units,
Timezone: req.Config.WeatherAPI.Timezone,
}, summary)
}
func defaultStore(cfg config.Config) (*state.FilesystemStore, error) {
@@ -422,6 +464,10 @@ func defaultStore(cfg config.Config) (*state.FilesystemStore, error) {
}
func dailyRecentChanges(ctx context.Context, store state.Store, priorSnapshot *state.PriorSnapshot, current briefing.Package, cfg config.RecentChangeConfig) ([]changes.Change, error) {
return recentChanges(ctx, store, priorSnapshot, current, cfg)
}
func recentChanges(ctx context.Context, store state.Store, priorSnapshot *state.PriorSnapshot, current briefing.Package, cfg config.RecentChangeConfig) ([]changes.Change, error) {
if priorSnapshot == nil {
return nil, nil
}
@@ -429,12 +475,20 @@ func dailyRecentChanges(ctx context.Context, store state.Store, priorSnapshot *s
if err != nil {
return nil, err
}
return changes.CompareDaily(previous, current, changes.Thresholds{
thresholds := changes.Thresholds{
TemperatureDegrees: cfg.TemperatureDegrees,
PrecipProbabilityPoints: cfg.PrecipProbabilityPoints,
WindGustMilesPerHour: cfg.WindGustMilesPerHour,
PrecipTimingShiftMinutes: cfg.PrecipTimingShiftMinutes,
})
}
switch current.Metadata.ReportID {
case report.DailyToday, report.DailyTomorrow:
return changes.CompareDaily(previous, current, thresholds)
case report.ThreeDay:
return changes.CompareThreeDay(previous, current, thresholds)
default:
return nil, nil
}
}
func copyFileAtomic(source string, target string) error {