Add near-term module composition
This commit is contained in:
@@ -97,6 +97,21 @@ func TestHourlyForecastModuleRejectsUnsupportedReports(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHourlyForecastModuleBuildsForNearTerm(t *testing.T) {
|
||||||
|
registry := MustDefaultModuleRegistry()
|
||||||
|
ctx := testModuleContext()
|
||||||
|
ctx.Resolved.Definition = report.DefaultRegistry().MustLookup(report.NearTerm)
|
||||||
|
|
||||||
|
output, err := registry.BuildModule(ctx, module.ConfigItem{ID: module.HourlyForecast})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildModule() error = %v", err)
|
||||||
|
}
|
||||||
|
value := moduleValue[HourlyForecastModule](t, output)
|
||||||
|
if len(value.Periods) != 1 || value.Periods[0].TextDescription != "Showers likely." {
|
||||||
|
t.Fatalf("HourlyForecast = %#v, want near-term hourly period", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNarrativeForecastModuleUsesValidPeriodNarrativePeriods(t *testing.T) {
|
func TestNarrativeForecastModuleUsesValidPeriodNarrativePeriods(t *testing.T) {
|
||||||
registry := MustDefaultModuleRegistry()
|
registry := MustDefaultModuleRegistry()
|
||||||
ctx := testModuleContext()
|
ctx := testModuleContext()
|
||||||
@@ -320,6 +335,34 @@ func TestAreaForecastDiscussionModuleCanSelectSections(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAreaForecastDiscussionModuleUsesNearTermDefaultSections(t *testing.T) {
|
||||||
|
registry := MustDefaultModuleRegistry()
|
||||||
|
ctx := testModuleContext()
|
||||||
|
ctx.Resolved.Definition = report.DefaultRegistry().MustLookup(report.NearTerm)
|
||||||
|
var item module.ConfigItem
|
||||||
|
for _, candidate := range ctx.Resolved.Definition.Modules {
|
||||||
|
if candidate.ID == module.AreaForecastDiscussion {
|
||||||
|
item = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item.ID == "" {
|
||||||
|
t.Fatal("near-term default modules missing area_forecast_discussion")
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := registry.BuildModule(ctx, item)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BuildModule() error = %v", err)
|
||||||
|
}
|
||||||
|
afd := moduleValue[AreaForecastDiscussionModule](t, output)
|
||||||
|
if len(afd.KeyMessages) != 1 || afd.ShortTerm != "Showers increase this afternoon." {
|
||||||
|
t.Fatalf("AFD = %#v, want key messages and short term", afd)
|
||||||
|
}
|
||||||
|
if afd.Product != "" || afd.LongTerm != "" {
|
||||||
|
t.Fatalf("AFD = %#v, want product and long term omitted", afd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testModuleContext() ModuleContext {
|
func testModuleContext() ModuleContext {
|
||||||
generatedAt := mustParseModuleTime("2026-05-29T08:00:00-05:00")
|
generatedAt := mustParseModuleTime("2026-05-29T08:00:00-05:00")
|
||||||
definition := report.DefaultRegistry().MustLookup(report.DailyToday)
|
definition := report.DefaultRegistry().MustLookup(report.DailyToday)
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ func (d ModuleDefinition) ValidateOptions(options any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultModuleDefinitions() []ModuleDefinition {
|
func defaultModuleDefinitions() []ModuleDefinition {
|
||||||
allReports := []report.ID{report.DailyToday, report.DailyTomorrow, report.ThreeDay, report.Weekend, report.Storm}
|
allReports := []report.ID{report.DailyToday, report.DailyTomorrow, report.NearTerm, report.ThreeDay, report.Weekend, report.Storm}
|
||||||
daypartReports := []report.ID{report.DailyToday, report.DailyTomorrow, report.ThreeDay, report.Weekend}
|
daypartReports := []report.ID{report.DailyToday, report.DailyTomorrow, report.ThreeDay, report.Weekend}
|
||||||
return []ModuleDefinition{
|
return []ModuleDefinition{
|
||||||
{
|
{
|
||||||
@@ -286,7 +286,7 @@ func defaultModuleDefinitions() []ModuleDefinition {
|
|||||||
DefaultOptions: module.HourlyForecastOptions{},
|
DefaultOptions: module.HourlyForecastOptions{},
|
||||||
RequiredCollected: []module.FactRequirement{module.CollectedHourlyForecast},
|
RequiredCollected: []module.FactRequirement{module.CollectedHourlyForecast},
|
||||||
RequiredDerived: []module.FactRequirement{module.RequiresDerivedHourlyPeriods},
|
RequiredDerived: []module.FactRequirement{module.RequiresDerivedHourlyPeriods},
|
||||||
SupportedReports: []report.ID{report.DailyToday, report.DailyTomorrow},
|
SupportedReports: []report.ID{report.DailyToday, report.DailyTomorrow, report.NearTerm},
|
||||||
MissingData: module.MissingDataOmit,
|
MissingData: module.MissingDataOmit,
|
||||||
Builder: buildHourlyForecastModule,
|
Builder: buildHourlyForecastModule,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,9 +32,6 @@ func TestDefaultReportModulesBuildSnapshots(t *testing.T) {
|
|||||||
registry := MustDefaultModuleRegistry()
|
registry := MustDefaultModuleRegistry()
|
||||||
for _, definition := range report.DefaultRegistry().All() {
|
for _, definition := range report.DefaultRegistry().All() {
|
||||||
t.Run(string(definition.ID), func(t *testing.T) {
|
t.Run(string(definition.ID), func(t *testing.T) {
|
||||||
if len(definition.Modules) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := derivedModuleContext(definition.ID)
|
ctx := derivedModuleContext(definition.ID)
|
||||||
var outputs []module.Output
|
var outputs []module.Output
|
||||||
for _, item := range definition.Modules {
|
for _, item := range definition.Modules {
|
||||||
@@ -57,6 +54,27 @@ func TestDefaultReportModulesBuildSnapshots(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNearTermDefaultModuleOptions(t *testing.T) {
|
||||||
|
definition := report.DefaultRegistry().MustLookup(report.NearTerm)
|
||||||
|
var found bool
|
||||||
|
for _, item := range definition.Modules {
|
||||||
|
if item.ID != module.AreaForecastDiscussion {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
options, ok := item.Options.(module.AreaForecastDiscussionOptions)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("AFD options type = %T, want AreaForecastDiscussionOptions", item.Options)
|
||||||
|
}
|
||||||
|
if strings.Join(options.Sections, ",") != "key_messages,short_term" {
|
||||||
|
t.Fatalf("AFD sections = %#v, want key messages and short term", options.Sections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("near-term default modules missing area_forecast_discussion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestModuleRegistryRejectsUnknownModule(t *testing.T) {
|
func TestModuleRegistryRejectsUnknownModule(t *testing.T) {
|
||||||
registry := MustDefaultModuleRegistry()
|
registry := MustDefaultModuleRegistry()
|
||||||
err := registry.ValidateComposition(report.DailyToday, []module.ConfigItem{{ID: module.ID("unknown")}})
|
err := registry.ValidateComposition(report.DailyToday, []module.ConfigItem{{ID: module.ID("unknown")}})
|
||||||
@@ -94,6 +112,24 @@ func TestModuleRegistryRejectsIncompatibleReports(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestModuleRegistryRejectsNearTermIncompatibleModules(t *testing.T) {
|
||||||
|
registry := MustDefaultModuleRegistry()
|
||||||
|
for _, id := range []module.ID{
|
||||||
|
module.NarrativeForecast,
|
||||||
|
module.DerivedDailySummary,
|
||||||
|
module.DerivedDaypartSummaries,
|
||||||
|
module.OutdoorWindows,
|
||||||
|
module.TomorrowPlanning,
|
||||||
|
} {
|
||||||
|
t.Run(string(id), func(t *testing.T) {
|
||||||
|
err := registry.ValidateComposition(report.NearTerm, []module.ConfigItem{{ID: id}})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), `not compatible with report "near_term"`) {
|
||||||
|
t.Fatalf("ValidateComposition() error = %v, want incompatible near-term module", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestModuleRegistryRejectsDefinitionsWithoutBuilders(t *testing.T) {
|
func TestModuleRegistryRejectsDefinitionsWithoutBuilders(t *testing.T) {
|
||||||
_, err := NewModuleRegistry([]ModuleDefinition{
|
_, err := NewModuleRegistry([]ModuleDefinition{
|
||||||
{ID: module.Metadata, StanzaName: "metadata", DefaultOptions: module.MetadataOptions{}},
|
{ID: module.Metadata, StanzaName: "metadata", DefaultOptions: module.MetadataOptions{}},
|
||||||
|
|||||||
@@ -25,7 +25,22 @@ func nearTermDefinition() Definition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nearTermModules() []module.ConfigItem {
|
func nearTermModules() []module.ConfigItem {
|
||||||
return nil
|
return []module.ConfigItem{
|
||||||
|
{ID: module.Metadata},
|
||||||
|
{ID: module.CurrentConditions},
|
||||||
|
{ID: module.HourlyForecast},
|
||||||
|
{ID: module.PrecipTiming},
|
||||||
|
{ID: module.AlertDigest},
|
||||||
|
{ID: module.SPCConvectiveOutlooks},
|
||||||
|
{
|
||||||
|
ID: module.AreaForecastDiscussion,
|
||||||
|
Options: module.AreaForecastDiscussionOptions{
|
||||||
|
Sections: []string{"key_messages", "short_term"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ID: module.SPCConvectiveDiscussion},
|
||||||
|
{ID: module.WeatherStory},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveNearTerm(req ResolveRequest) (timeutil.Period, error) {
|
func resolveNearTerm(req ResolveRequest) (timeutil.Period, error) {
|
||||||
|
|||||||
@@ -384,8 +384,18 @@ func TestRegistryDefinitionsDeclareDefaultModules(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: NearTerm,
|
id: NearTerm,
|
||||||
want: []module.ID{},
|
want: []module.ID{
|
||||||
|
module.Metadata,
|
||||||
|
module.CurrentConditions,
|
||||||
|
module.HourlyForecast,
|
||||||
|
module.PrecipTiming,
|
||||||
|
module.AlertDigest,
|
||||||
|
module.SPCConvectiveOutlooks,
|
||||||
|
module.AreaForecastDiscussion,
|
||||||
|
module.SPCConvectiveDiscussion,
|
||||||
|
module.WeatherStory,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ThreeDay,
|
id: ThreeDay,
|
||||||
|
|||||||
Reference in New Issue
Block a user