Add collected and derived fact contracts
This commit is contained in:
139
internal/facts/facts.go
Normal file
139
internal/facts/facts.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user