737 lines
21 KiB
Go
737 lines
21 KiB
Go
// Package app owns application orchestration and top-level use cases.
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
distributoradapter "gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/distributor"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/scriptorium"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/weatherapi"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/changes"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/fileutil"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
|
"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"
|
|
)
|
|
|
|
type ReportKind string
|
|
|
|
const (
|
|
ReportDaily ReportKind = "daily"
|
|
ReportTomorrow ReportKind = "tomorrow"
|
|
ReportThreeDay ReportKind = "three-day"
|
|
ReportWeekend ReportKind = "weekend"
|
|
ReportStorm ReportKind = "storm"
|
|
)
|
|
|
|
type BatchKind string
|
|
|
|
const (
|
|
BatchMorning BatchKind = "morning"
|
|
BatchEvening BatchKind = "evening"
|
|
)
|
|
|
|
type GenerateRequest struct {
|
|
Config config.Config
|
|
Report ReportKind
|
|
OutputPath string
|
|
Now time.Time
|
|
Date time.Time
|
|
StormStart time.Time
|
|
StormEnd time.Time
|
|
Notifier Notifier
|
|
}
|
|
|
|
type BatchRequest struct {
|
|
Config config.Config
|
|
Batch BatchKind
|
|
Now time.Time
|
|
OutputDir string
|
|
Renderer Renderer
|
|
Store state.Store
|
|
Notifier Notifier
|
|
}
|
|
|
|
type FetchBundleRequest struct {
|
|
Config config.Config
|
|
OutputPath string
|
|
}
|
|
|
|
type BriefingRequest struct {
|
|
Config config.Config
|
|
Resolved report.Resolved
|
|
OutputPath string
|
|
}
|
|
|
|
type ReportRequest struct {
|
|
Config config.Config
|
|
Resolved report.Resolved
|
|
OutputPath string
|
|
Renderer Renderer
|
|
Store state.Store
|
|
Notifier Notifier
|
|
}
|
|
|
|
type BriefingResult struct {
|
|
Package briefing.Package
|
|
OutputPath string
|
|
}
|
|
|
|
type ReportResult struct {
|
|
Briefing briefing.Package
|
|
BriefingPath string
|
|
DataPackage promptinput.Package
|
|
DataPackagePath string
|
|
PreflightPath string
|
|
ReportPath string
|
|
OutputPath string
|
|
Metadata state.Metadata
|
|
MetadataPath string
|
|
PriorSnapshot *state.PriorSnapshot
|
|
RecentChanges []changes.Change
|
|
RenderResult *scriptorium.RenderResult
|
|
RunResult *scriptorium.RunResult
|
|
Notification *NotificationResult
|
|
}
|
|
|
|
type BatchResult struct {
|
|
Batch BatchKind `json:"batch"`
|
|
StartedAt time.Time `json:"startedAt"`
|
|
FinishedAt time.Time `json:"finishedAt"`
|
|
Total int `json:"total"`
|
|
Succeeded int `json:"succeeded"`
|
|
Failed int `json:"failed"`
|
|
Reports []BatchReportResult `json:"reports"`
|
|
}
|
|
|
|
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"`
|
|
GeneratedAt time.Time `json:"generatedAt"`
|
|
ValidPeriod timeutil.Period `json:"validPeriod"`
|
|
BriefingPath string `json:"briefingPath,omitempty"`
|
|
DataPackagePath string `json:"dataPackagePath,omitempty"`
|
|
PreflightPath string `json:"preflightPath,omitempty"`
|
|
ReportPath string `json:"reportPath,omitempty"`
|
|
OutputPath string `json:"outputPath,omitempty"`
|
|
MetadataPath string `json:"metadataPath,omitempty"`
|
|
}
|
|
|
|
type BatchError struct {
|
|
Result *BatchResult
|
|
}
|
|
|
|
func (e BatchError) Error() string {
|
|
if e.Result == nil {
|
|
return "batch failed"
|
|
}
|
|
return fmt.Sprintf("batch %s failed: %d of %d reports failed", e.Result.Batch, e.Result.Failed, e.Result.Total)
|
|
}
|
|
|
|
type Renderer interface {
|
|
Render(context.Context, scriptorium.RenderRequest) (*scriptorium.RenderResult, error)
|
|
Run(context.Context, scriptorium.RunRequest) (*scriptorium.RunResult, error)
|
|
}
|
|
|
|
type Notifier interface {
|
|
Notify(context.Context, NotificationRequest) (*NotificationResult, error)
|
|
}
|
|
|
|
type NotificationRequest struct {
|
|
ReportID report.ID
|
|
RunID string
|
|
BundleID string
|
|
IdempotencyKey string
|
|
ReportPath string
|
|
BundlePath string
|
|
}
|
|
|
|
type NotificationResult struct {
|
|
BundleID string
|
|
IdempotencyKey string
|
|
RunID string
|
|
Status string
|
|
}
|
|
|
|
func Generate(ctx context.Context, req GenerateRequest) error {
|
|
now := req.Now
|
|
if now.IsZero() {
|
|
now = time.Now()
|
|
}
|
|
resolved, err := ResolveGenerate(req, now)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resolved.Definition.Generated {
|
|
_, err := GenerateReport(ctx, ReportRequest{
|
|
Config: req.Config,
|
|
Resolved: resolved,
|
|
OutputPath: req.OutputPath,
|
|
Notifier: req.Notifier,
|
|
})
|
|
return err
|
|
}
|
|
return fmt.Errorf("generate is not implemented")
|
|
}
|
|
|
|
func RunBatch(ctx context.Context, req BatchRequest) error {
|
|
result, err := RunBatchDetailed(ctx, req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if result.Failed > 0 {
|
|
return BatchError{Result: result}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, error) {
|
|
now := req.Now
|
|
if now.IsZero() {
|
|
now = time.Now()
|
|
}
|
|
resolvedReports, err := ResolveBatch(req, now)
|
|
if err != nil {
|
|
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 _, resolved := range resolvedReports {
|
|
if !resolved.Definition.Generated {
|
|
return nil, fmt.Errorf("run is not implemented")
|
|
}
|
|
}
|
|
for _, resolved := range resolvedReports {
|
|
item := batchReportResult(resolved)
|
|
if paths, err := store.Paths(resolved); err == nil {
|
|
item.BriefingPath = paths.Briefing
|
|
item.DataPackagePath = paths.DataPackage
|
|
item.PreflightPath = paths.Preflight
|
|
item.ReportPath = paths.RenderedReport
|
|
item.MetadataPath = paths.Metadata
|
|
}
|
|
outputPath := batchOutputPath(req.OutputDir, resolved.Definition)
|
|
reportResult, err := GenerateReport(ctx, ReportRequest{
|
|
Config: req.Config,
|
|
Resolved: resolved,
|
|
OutputPath: outputPath,
|
|
Renderer: req.Renderer,
|
|
Store: store,
|
|
Notifier: req.Notifier,
|
|
})
|
|
if err != nil {
|
|
item.Status = "failed"
|
|
item.Error = err.Error()
|
|
result.Failed++
|
|
} else {
|
|
item.Status = "succeeded"
|
|
item.BriefingPath = reportResult.BriefingPath
|
|
item.DataPackagePath = reportResult.DataPackagePath
|
|
item.PreflightPath = reportResult.PreflightPath
|
|
item.ReportPath = reportResult.ReportPath
|
|
item.OutputPath = reportResult.OutputPath
|
|
item.MetadataPath = reportResult.MetadataPath
|
|
result.Succeeded++
|
|
}
|
|
result.Reports = append(result.Reports, item)
|
|
}
|
|
result.Total = len(result.Reports)
|
|
result.FinishedAt = time.Now()
|
|
return result, nil
|
|
}
|
|
return nil, fmt.Errorf("run is not implemented")
|
|
}
|
|
|
|
func batchReportResult(resolved report.Resolved) BatchReportResult {
|
|
metadata := resolved.Metadata()
|
|
return BatchReportResult{
|
|
ReportID: resolved.Definition.ID,
|
|
ReportName: resolved.Definition.Name,
|
|
PromptID: resolved.Definition.PromptID,
|
|
RunID: metadata.RunID,
|
|
GeneratedAt: metadata.GeneratedAt,
|
|
ValidPeriod: metadata.ValidPeriod,
|
|
}
|
|
}
|
|
|
|
func batchOutputPath(outputDir string, definition report.Definition) string {
|
|
if outputDir == "" || definition.BatchOutputName == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(outputDir, definition.BatchOutputName)
|
|
}
|
|
|
|
func ResolveGenerate(req GenerateRequest, now time.Time) (report.Resolved, error) {
|
|
location, err := timeutil.LoadLocation(req.Config.WeatherAPI.Timezone)
|
|
if err != nil {
|
|
return report.Resolved{}, err
|
|
}
|
|
id, err := reportIDForCommand(req.Report)
|
|
if err != nil {
|
|
return report.Resolved{}, err
|
|
}
|
|
return report.DefaultRegistry().Resolve(id, report.ResolveRequest{
|
|
Now: now,
|
|
Location: location,
|
|
Date: req.Date,
|
|
StormStart: req.StormStart,
|
|
StormEnd: req.StormEnd,
|
|
})
|
|
}
|
|
|
|
func ResolveBatch(req BatchRequest, now time.Time) ([]report.Resolved, error) {
|
|
location, err := timeutil.LoadLocation(req.Config.WeatherAPI.Timezone)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
batch, err := reportBatchForCommand(req.Batch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return report.DefaultRegistry().BatchReports(batch, report.ResolveRequest{
|
|
Now: now,
|
|
Location: location,
|
|
})
|
|
}
|
|
|
|
func reportIDForCommand(kind ReportKind) (report.ID, error) {
|
|
switch kind {
|
|
case ReportDaily:
|
|
return report.DailyToday, nil
|
|
case ReportTomorrow:
|
|
return report.DailyTomorrow, nil
|
|
case ReportThreeDay:
|
|
return report.ThreeDay, nil
|
|
case ReportWeekend:
|
|
return report.Weekend, nil
|
|
case ReportStorm:
|
|
return report.Storm, nil
|
|
default:
|
|
return "", fmt.Errorf("unknown report command %q", kind)
|
|
}
|
|
}
|
|
|
|
func reportBatchForCommand(kind BatchKind) (report.Batch, error) {
|
|
switch kind {
|
|
case BatchMorning:
|
|
return report.Morning, nil
|
|
case BatchEvening:
|
|
return report.Evening, nil
|
|
default:
|
|
return "", fmt.Errorf("unknown batch command %q", kind)
|
|
}
|
|
}
|
|
|
|
func FetchBundle(ctx context.Context, req FetchBundleRequest) (*forecast.Bundle, error) {
|
|
client, err := weatherapi.New(req.Config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bundle, err := client.FetchBundle(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bundle, nil
|
|
}
|
|
|
|
func FetchAndSaveBundle(ctx context.Context, req FetchBundleRequest) (*forecast.Bundle, error) {
|
|
if req.OutputPath == "" {
|
|
return nil, fmt.Errorf("output path is required")
|
|
}
|
|
bundle, err := FetchBundle(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := weatherapi.SaveBundle(req.OutputPath, bundle); err != nil {
|
|
return nil, err
|
|
}
|
|
return bundle, nil
|
|
}
|
|
|
|
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 := BuildBriefing(req, bundle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outputPath := req.OutputPath
|
|
if outputPath == "" {
|
|
store, err := defaultStore(req.Config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
paths, err := store.Paths(req.Resolved)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outputPath = paths.Briefing
|
|
}
|
|
if err := briefing.Save(outputPath, pkg); err != nil {
|
|
return nil, err
|
|
}
|
|
return &BriefingResult{Package: pkg, OutputPath: outputPath}, nil
|
|
}
|
|
|
|
func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, error) {
|
|
store := req.Store
|
|
if store == nil {
|
|
defaultStore, err := defaultStore(req.Config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
store = defaultStore
|
|
}
|
|
paths, err := store.Paths(req.Resolved)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
priorSnapshot, err := store.FindPriorSnapshot(ctx, req.Resolved)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bundle, err := FetchBundle(ctx, FetchBundleRequest{Config: req.Config})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
briefingPackage, err := BuildBriefing(BriefingRequest{
|
|
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
|
|
}
|
|
|
|
recentChanges, err := recentChanges(ctx, store, priorSnapshot, briefingPackage, req.Config.RecentChange)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPackage, err := promptinput.BuildWithRecentChanges(briefingPackage, recentChanges)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPackagePath, err := store.SaveDataPackage(ctx, req.Resolved, dataPackage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
renderer := req.Renderer
|
|
if renderer == nil {
|
|
renderer = scriptorium.Runner{
|
|
Binary: req.Config.Scriptorium.Binary,
|
|
ConfigPath: req.Config.Scriptorium.ConfigPath,
|
|
Profile: req.Config.Scriptorium.Profile,
|
|
Timeout: req.Config.Scriptorium.Timeout,
|
|
ExtraArgs: req.Config.Scriptorium.ExtraArgs,
|
|
}
|
|
}
|
|
renderResult, renderErr := renderer.Render(ctx, scriptorium.RenderRequest{
|
|
PromptID: req.Resolved.Definition.PromptID,
|
|
DataPackagePath: dataPackagePath,
|
|
})
|
|
|
|
preflightPath := paths.Preflight
|
|
if renderResult != nil {
|
|
var err error
|
|
preflightPath, err = store.SavePreflight(ctx, req.Resolved, preflightArtifact(renderResult))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
metadata := state.BuildMetadata(req.Resolved, briefingPackage, state.ArtifactPaths{
|
|
Briefing: briefingPath,
|
|
Metadata: paths.Metadata,
|
|
DataPackage: dataPackagePath,
|
|
Preflight: preflightPath,
|
|
RenderedReport: paths.RenderedReport,
|
|
})
|
|
metadataPath, metadataErr := store.SaveMetadata(ctx, metadata)
|
|
if metadataErr != nil {
|
|
return nil, metadataErr
|
|
}
|
|
if renderErr != nil {
|
|
return nil, renderErr
|
|
}
|
|
|
|
reportPath, err := store.PrepareRenderedReport(ctx, req.Resolved)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
runResult, runErr := renderer.Run(ctx, scriptorium.RunRequest{
|
|
PromptID: req.Resolved.Definition.PromptID,
|
|
DataPackagePath: dataPackagePath,
|
|
OutputPath: reportPath,
|
|
})
|
|
if runErr == nil && req.OutputPath != "" && req.OutputPath != reportPath {
|
|
if err := fileutil.CopyFileAtomic(reportPath, req.OutputPath); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
outputPath := reportPath
|
|
if req.OutputPath != "" {
|
|
outputPath = req.OutputPath
|
|
}
|
|
metadata.RenderedReportPath = reportPath
|
|
metadataPath, metadataErr = store.SaveMetadata(ctx, metadata)
|
|
if metadataErr != nil {
|
|
return nil, metadataErr
|
|
}
|
|
if runErr != nil {
|
|
return nil, runErr
|
|
}
|
|
|
|
notification, err := notifyReport(ctx, req.Config, req.Resolved, reportPath, metadata, req.Notifier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ReportResult{
|
|
Briefing: briefingPackage,
|
|
BriefingPath: briefingPath,
|
|
DataPackage: dataPackage,
|
|
DataPackagePath: dataPackagePath,
|
|
PreflightPath: preflightPath,
|
|
ReportPath: reportPath,
|
|
OutputPath: outputPath,
|
|
Metadata: metadata,
|
|
MetadataPath: metadataPath,
|
|
PriorSnapshot: priorSnapshot,
|
|
RecentChanges: recentChanges,
|
|
RenderResult: renderResult,
|
|
RunResult: runResult,
|
|
Notification: notification,
|
|
}, nil
|
|
}
|
|
|
|
func notifyReport(ctx context.Context, cfg config.Config, resolved report.Resolved, reportPath string, metadata state.Metadata, notifier Notifier) (*NotificationResult, error) {
|
|
notifier, enabled := reportNotifier(cfg, notifier)
|
|
if !enabled {
|
|
return nil, nil
|
|
}
|
|
notificationRequest, err := buildNotificationRequest(cfg, resolved, reportPath, metadata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, err := notifier.Notify(ctx, notificationRequest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("notify report %q run %q from managed report %q: %w", resolved.Definition.ID, metadata.RunID, reportPath, err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func reportNotifier(cfg config.Config, notifier Notifier) (Notifier, bool) {
|
|
if !cfg.Notify.Distributor.Enabled {
|
|
return noopNotifier{}, false
|
|
}
|
|
if notifier != nil {
|
|
return notifier, true
|
|
}
|
|
return distributorNotifier{
|
|
client: distributoradapter.New(cfg.Notify.Distributor),
|
|
}, true
|
|
}
|
|
|
|
func buildNotificationRequest(cfg config.Config, resolved report.Resolved, reportPath string, metadata state.Metadata) (NotificationRequest, error) {
|
|
values := config.DistributorTemplateValues{
|
|
LocationID: cfg.Location.ID,
|
|
ReportID: string(resolved.Definition.ID),
|
|
RunID: metadata.RunID,
|
|
ArtifactGroup: resolved.Definition.ArtifactGroup,
|
|
BatchOutputName: resolved.Definition.BatchOutputName,
|
|
}
|
|
bundleID, err := config.RenderDistributorBundleID(cfg.Notify.Distributor.BundleIDTemplate, values)
|
|
if err != nil {
|
|
return NotificationRequest{}, err
|
|
}
|
|
values.BundleID = bundleID
|
|
idempotencyKey, err := config.RenderDistributorIdempotencyKey(cfg.Notify.Distributor.IdempotencyKeyTemplate, values)
|
|
if err != nil {
|
|
return NotificationRequest{}, err
|
|
}
|
|
bundlePath, err := config.RenderDistributorReportPath(cfg.Notify.Distributor.ReportPathTemplate, values)
|
|
if err != nil {
|
|
return NotificationRequest{}, err
|
|
}
|
|
return NotificationRequest{
|
|
ReportID: resolved.Definition.ID,
|
|
RunID: metadata.RunID,
|
|
BundleID: bundleID,
|
|
IdempotencyKey: idempotencyKey,
|
|
ReportPath: reportPath,
|
|
BundlePath: bundlePath,
|
|
}, nil
|
|
}
|
|
|
|
type noopNotifier struct{}
|
|
|
|
func (noopNotifier) Notify(context.Context, NotificationRequest) (*NotificationResult, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
type distributorNotifier struct {
|
|
client *distributoradapter.Client
|
|
}
|
|
|
|
func (n distributorNotifier) Notify(ctx context.Context, req NotificationRequest) (*NotificationResult, error) {
|
|
result, err := n.client.Upload(ctx, distributoradapter.UploadRequest{
|
|
BundleID: req.BundleID,
|
|
IdempotencyKey: req.IdempotencyKey,
|
|
SourcePath: req.ReportPath,
|
|
BundlePath: req.BundlePath,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &NotificationResult{
|
|
BundleID: req.BundleID,
|
|
IdempotencyKey: req.IdempotencyKey,
|
|
RunID: result.RunID,
|
|
Status: result.Status,
|
|
}, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
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,
|
|
})
|
|
}
|
|
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,
|
|
Location: briefingLocation(req.Config),
|
|
}, summary)
|
|
case report.ThreeDay, report.Weekend:
|
|
summaries, err := forecast.BuildPeriodDailySummaries(bundle, req.Resolved.ValidPeriod, location, dayparts)
|
|
if err != nil {
|
|
return briefing.Package{}, err
|
|
}
|
|
if req.Resolved.Definition.ID == report.Weekend {
|
|
return briefing.BuildWeekend(briefing.BuildContext{
|
|
Resolved: req.Resolved,
|
|
Bundle: bundle,
|
|
Units: req.Config.WeatherAPI.Units,
|
|
Timezone: req.Config.WeatherAPI.Timezone,
|
|
Location: briefingLocation(req.Config),
|
|
}, summaries)
|
|
}
|
|
return briefing.BuildThreeDay(briefing.BuildContext{
|
|
Resolved: req.Resolved,
|
|
Bundle: bundle,
|
|
Units: req.Config.WeatherAPI.Units,
|
|
Timezone: req.Config.WeatherAPI.Timezone,
|
|
Location: briefingLocation(req.Config),
|
|
}, summaries)
|
|
case report.Storm:
|
|
return briefing.BuildStorm(briefing.BuildContext{
|
|
Resolved: req.Resolved,
|
|
Bundle: bundle,
|
|
Units: req.Config.WeatherAPI.Units,
|
|
Timezone: req.Config.WeatherAPI.Timezone,
|
|
Location: briefingLocation(req.Config),
|
|
})
|
|
default:
|
|
return briefing.Package{}, fmt.Errorf("briefing is not implemented for report %q", req.Resolved.Definition.ID)
|
|
}
|
|
}
|
|
|
|
func briefingLocation(cfg config.Config) *briefing.LocationContext {
|
|
location := briefing.LocationContext{
|
|
ID: cfg.Location.ID,
|
|
Name: cfg.Location.Name,
|
|
Region: cfg.Location.Region,
|
|
Timezone: cfg.WeatherAPI.Timezone,
|
|
}
|
|
if location.ID == "" && location.Name == "" && location.Region == "" && location.Timezone == "" {
|
|
return nil
|
|
}
|
|
return &location
|
|
}
|
|
|
|
func defaultStore(cfg config.Config) (*state.FilesystemStore, error) {
|
|
return state.NewFilesystemStore(cfg.Workspace)
|
|
}
|
|
|
|
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
|
|
}
|
|
previous, err := store.LoadBriefing(ctx, priorSnapshot.BriefingPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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)
|
|
case report.Weekend:
|
|
return changes.CompareWeekend(previous, current, thresholds)
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func preflightArtifact(result *scriptorium.RenderResult) state.PreflightArtifact {
|
|
if result == nil {
|
|
return state.PreflightArtifact{}
|
|
}
|
|
return state.PreflightArtifact{
|
|
Command: append([]string(nil), result.Command...),
|
|
Stdout: result.Stdout,
|
|
Stderr: result.Stderr,
|
|
StdoutTruncated: result.StdoutTruncated,
|
|
StderrTruncated: result.StderrTruncated,
|
|
ExitCode: result.ExitCode,
|
|
}
|
|
}
|