164 lines
5.2 KiB
Go
164 lines
5.2 KiB
Go
// Package module defines stable module contracts for prompt-facing stanzas.
|
|
package module
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
const SnapshotSchemaVersion = "weatherreporter.modules.v1"
|
|
|
|
type ID string
|
|
|
|
const (
|
|
Metadata ID = "metadata"
|
|
CurrentConditions ID = "current_conditions"
|
|
NarrativeForecast ID = "narrative_forecast"
|
|
HourlyForecast ID = "hourly_forecast"
|
|
DerivedDailySummary ID = "derived_daily_summary"
|
|
DerivedDaypartSummaries ID = "derived_daypart_summaries"
|
|
PrecipTiming ID = "precip_timing"
|
|
AlertDigest ID = "alert_digest"
|
|
AreaForecastDiscussion ID = "area_forecast_discussion"
|
|
WeatherStory ID = "weather_story"
|
|
SPCConvectiveOutlooks ID = "spc_convective_outlooks"
|
|
SPCConvectiveDiscussion ID = "spc_convective_discussion"
|
|
OutdoorWindows ID = "outdoor_windows"
|
|
TodayPlanning ID = "today_planning"
|
|
TomorrowPlanning ID = "tomorrow_planning"
|
|
DailyPlanning ID = "daily_planning"
|
|
)
|
|
|
|
type ConfigItem struct {
|
|
ID ID `json:"id"`
|
|
Options any `json:"options,omitempty"`
|
|
}
|
|
|
|
type Output struct {
|
|
ID ID `json:"id"`
|
|
StanzaName string `json:"stanzaName"`
|
|
Value any `json:"value"`
|
|
PromptValue any `json:"-" yaml:"-"`
|
|
}
|
|
|
|
type Snapshot struct {
|
|
SchemaVersion string `json:"schemaVersion"`
|
|
Outputs []Output `json:"outputs"`
|
|
}
|
|
|
|
func (o Output) DataPackageValue() any {
|
|
if o.PromptValue != nil {
|
|
return o.PromptValue
|
|
}
|
|
return o.Value
|
|
}
|
|
|
|
func NewSnapshot(outputs []Output) (Snapshot, error) {
|
|
snapshot := Snapshot{
|
|
SchemaVersion: SnapshotSchemaVersion,
|
|
Outputs: append([]Output(nil), outputs...),
|
|
}
|
|
if err := snapshot.Validate(); err != nil {
|
|
return Snapshot{}, err
|
|
}
|
|
return snapshot, nil
|
|
}
|
|
|
|
func (s Snapshot) Validate() error {
|
|
if s.SchemaVersion == "" {
|
|
return fmt.Errorf("schemaVersion is required")
|
|
}
|
|
seenModules := map[ID]struct{}{}
|
|
seenStanzas := map[string]struct{}{}
|
|
for i, output := range s.Outputs {
|
|
if output.ID == "" {
|
|
return fmt.Errorf("outputs[%d].id is required", i)
|
|
}
|
|
if output.StanzaName == "" {
|
|
return fmt.Errorf("outputs[%d].stanzaName is required", i)
|
|
}
|
|
if _, ok := seenModules[output.ID]; ok {
|
|
return fmt.Errorf("duplicate module output %q", output.ID)
|
|
}
|
|
seenModules[output.ID] = struct{}{}
|
|
if _, ok := seenStanzas[output.StanzaName]; ok {
|
|
return fmt.Errorf("duplicate stanza name %q", output.StanzaName)
|
|
}
|
|
seenStanzas[output.StanzaName] = struct{}{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s Snapshot) LookupStanza(name string) (Output, bool) {
|
|
for _, output := range s.Outputs {
|
|
if output.StanzaName == name {
|
|
return output, true
|
|
}
|
|
}
|
|
return Output{}, false
|
|
}
|
|
|
|
func StanzaValue[T any](s Snapshot, name string) (T, bool, error) {
|
|
var zero T
|
|
output, ok := s.LookupStanza(name)
|
|
if !ok {
|
|
return zero, false, nil
|
|
}
|
|
data, err := json.Marshal(output.Value)
|
|
if err != nil {
|
|
return zero, true, fmt.Errorf("marshal stanza %q: %w", name, err)
|
|
}
|
|
if err := json.Unmarshal(data, &zero); err != nil {
|
|
return zero, true, fmt.Errorf("decode stanza %q: %w", name, err)
|
|
}
|
|
return zero, true, nil
|
|
}
|
|
|
|
type FactRequirement string
|
|
|
|
const (
|
|
CollectedCurrentConditions FactRequirement = "collected.current_conditions"
|
|
CollectedNarrativeForecast FactRequirement = "collected.narrative_forecast"
|
|
CollectedHourlyForecast FactRequirement = "collected.hourly_forecast"
|
|
CollectedAlerts FactRequirement = "collected.alerts"
|
|
CollectedDiscussion FactRequirement = "collected.discussion"
|
|
CollectedWeatherStory FactRequirement = "collected.weather_story"
|
|
CollectedSPCConvectiveOutlooks FactRequirement = "collected.spc_convective_outlooks"
|
|
CollectedSourceMetadata FactRequirement = "collected.source_metadata"
|
|
RequiresDerivedHourlyPeriods FactRequirement = "derived.hourly_periods"
|
|
RequiresDerivedNarrativePeriods FactRequirement = "derived.narrative_periods"
|
|
RequiresDerivedAlertOverlaps FactRequirement = "derived.alert_overlaps"
|
|
RequiresDerivedDailySummaries FactRequirement = "derived.daily_summaries"
|
|
RequiresDerivedDaypartSummaries FactRequirement = "derived.daypart_summaries"
|
|
RequiresDerivedPrecipTiming FactRequirement = "derived.precip_timing"
|
|
RequiresDerivedSPCConvectiveOutlooks FactRequirement = "derived.spc_convective_outlooks"
|
|
)
|
|
|
|
type MissingDataBehavior string
|
|
|
|
const (
|
|
MissingDataOmit MissingDataBehavior = "omit"
|
|
MissingDataEmpty MissingDataBehavior = "empty"
|
|
MissingDataError MissingDataBehavior = "error"
|
|
MissingDataWarn MissingDataBehavior = "warn"
|
|
)
|
|
|
|
type MetadataOptions struct{}
|
|
type CurrentConditionsOptions struct{}
|
|
type NarrativeForecastOptions struct{}
|
|
type HourlyForecastOptions struct{}
|
|
type DerivedDailySummaryOptions struct{}
|
|
type DerivedDaypartSummariesOptions struct{}
|
|
type PrecipTimingOptions struct{}
|
|
type AlertDigestOptions struct{}
|
|
type AreaForecastDiscussionOptions struct {
|
|
Sections []string `json:"sections,omitempty" yaml:"sections,omitempty"`
|
|
}
|
|
type WeatherStoryOptions struct{}
|
|
type SPCConvectiveOutlooksOptions struct{}
|
|
type SPCConvectiveDiscussionOptions struct{}
|
|
type OutdoorWindowsOptions struct{}
|
|
type TodayPlanningOptions struct{}
|
|
type TomorrowPlanningOptions struct{}
|
|
type DailyPlanningOptions struct{}
|