140 lines
4.6 KiB
Go
140 lines
4.6 KiB
Go
// Package facts defines collected and derived report facts.
|
|
package facts
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/forecast"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
|
)
|
|
|
|
type CollectedFacts struct {
|
|
FetchedAt time.Time
|
|
Observation *weatherdata.Observation
|
|
Current *weatherdata.Current
|
|
Hourly *weatherdata.ForecastRun
|
|
Narrative *weatherdata.ForecastRun
|
|
Alerts *weatherdata.AlertRun
|
|
Discussion *weatherdata.Discussion
|
|
Daily *weatherdata.ForecastRun
|
|
WeatherStory *weatherdata.WeatherStory
|
|
|
|
SourceProvenance []weatherdata.Source
|
|
SourceWarnings []weatherdata.SourceWarning
|
|
}
|
|
|
|
func BuildCollected(bundle *weatherdata.Bundle) CollectedFacts {
|
|
if bundle == nil {
|
|
return CollectedFacts{}
|
|
}
|
|
return CollectedFacts{
|
|
FetchedAt: bundle.FetchedAt,
|
|
Observation: bundle.Observation,
|
|
Current: bundle.Current,
|
|
Hourly: bundle.Hourly,
|
|
Narrative: bundle.Narrative,
|
|
Alerts: bundle.Alerts,
|
|
Discussion: bundle.Discussion,
|
|
Daily: bundle.Daily,
|
|
WeatherStory: bundle.WeatherStory,
|
|
SourceProvenance: append([]weatherdata.Source(nil), bundle.Sources...),
|
|
SourceWarnings: append([]weatherdata.SourceWarning(nil), bundle.Warnings...),
|
|
}
|
|
}
|
|
|
|
func (f CollectedFacts) Bundle() *weatherdata.Bundle {
|
|
return &weatherdata.Bundle{
|
|
FetchedAt: f.FetchedAt,
|
|
Observation: f.Observation,
|
|
Current: f.Current,
|
|
Hourly: f.Hourly,
|
|
Narrative: f.Narrative,
|
|
Alerts: f.Alerts,
|
|
Discussion: f.Discussion,
|
|
Daily: f.Daily,
|
|
WeatherStory: f.WeatherStory,
|
|
Sources: append([]weatherdata.Source(nil), f.SourceProvenance...),
|
|
Warnings: append([]weatherdata.SourceWarning(nil), f.SourceWarnings...),
|
|
}
|
|
}
|
|
|
|
type BuildDerivedRequest struct {
|
|
Resolved report.Resolved
|
|
Timezone string
|
|
Dayparts []forecast.DaypartDefinition
|
|
Collected CollectedFacts
|
|
}
|
|
|
|
type DerivedFacts struct {
|
|
ValidPeriodHourlyPeriods []weatherdata.ForecastPeriod
|
|
ValidPeriodNarrativePeriods []weatherdata.ForecastPeriod
|
|
ValidPeriodDailyPeriods []weatherdata.ForecastPeriod
|
|
AlertOverlaps []forecast.AlertOverlap
|
|
DailySummaries []forecast.DailySummary
|
|
DaypartSummaries []forecast.DaypartSummary
|
|
StormWindowSummary *forecast.DaypartSummary
|
|
}
|
|
|
|
func (f DerivedFacts) FirstDailySummary() *forecast.DailySummary {
|
|
if len(f.DailySummaries) == 0 {
|
|
return nil
|
|
}
|
|
return &f.DailySummaries[0]
|
|
}
|
|
|
|
func BuildDerived(req BuildDerivedRequest) (DerivedFacts, error) {
|
|
if !req.Resolved.ValidPeriod.IsValid() {
|
|
return DerivedFacts{}, fmt.Errorf("resolved valid period is required")
|
|
}
|
|
location, err := timeutil.LoadLocation(req.Timezone)
|
|
if err != nil {
|
|
return DerivedFacts{}, fmt.Errorf("load report timezone %q: %w", req.Timezone, err)
|
|
}
|
|
bundle := req.Collected.Bundle()
|
|
period := req.Resolved.ValidPeriod
|
|
derived := DerivedFacts{
|
|
ValidPeriodHourlyPeriods: forecast.SelectHourlyPeriods(req.Collected.Hourly, period),
|
|
ValidPeriodNarrativePeriods: forecast.SelectHourlyPeriods(req.Collected.Narrative, period),
|
|
ValidPeriodDailyPeriods: forecast.SelectHourlyPeriods(req.Collected.Daily, period),
|
|
AlertOverlaps: forecast.AlertOverlaps(req.Collected.Alerts, period),
|
|
}
|
|
|
|
switch req.Resolved.Definition.ID {
|
|
case report.DailyToday, report.DailyTomorrow:
|
|
summary, err := forecast.BuildDailySummary(bundle, period.Start, location, req.Dayparts)
|
|
if err != nil {
|
|
return DerivedFacts{}, err
|
|
}
|
|
derived.DailySummaries = []forecast.DailySummary{*summary}
|
|
case report.ThreeDay, report.Weekend:
|
|
summaries, err := forecast.BuildPeriodDailySummaries(bundle, period, location, req.Dayparts)
|
|
if err != nil {
|
|
return DerivedFacts{}, err
|
|
}
|
|
derived.DailySummaries = summaries
|
|
case report.Storm:
|
|
summary := forecast.SummarizeDaypart("storm window", period, derived.ValidPeriodHourlyPeriods)
|
|
summary.AlertOverlaps = derived.AlertOverlaps
|
|
derived.StormWindowSummary = &summary
|
|
default:
|
|
return DerivedFacts{}, fmt.Errorf("derived facts are not implemented for report %q", req.Resolved.Definition.ID)
|
|
}
|
|
|
|
derived.DaypartSummaries = collectDaypartSummaries(derived)
|
|
return derived, nil
|
|
}
|
|
|
|
func collectDaypartSummaries(derived DerivedFacts) []forecast.DaypartSummary {
|
|
var out []forecast.DaypartSummary
|
|
for _, summary := range derived.DailySummaries {
|
|
out = append(out, summary.Dayparts...)
|
|
}
|
|
if derived.StormWindowSummary != nil {
|
|
out = append(out, *derived.StormWindowSummary)
|
|
}
|
|
return out
|
|
}
|