Added a location block to the data package
This commit is contained in:
@@ -51,6 +51,19 @@ config test suite.
|
|||||||
Timezone values may be IANA names, configured aliases such as `Chicago` and
|
Timezone values may be IANA names, configured aliases such as `Chicago` and
|
||||||
`Stl`, US timezone abbreviations, or UTC offsets such as `-5` and `+09:30`.
|
`Stl`, US timezone abbreviations, or UTC offsets such as `-5` and `+09:30`.
|
||||||
|
|
||||||
|
### `location`
|
||||||
|
|
||||||
|
`location` is descriptive prompt context included in briefing metadata and
|
||||||
|
Scriptorium data packages. It does not select a Weather API endpoint or enable
|
||||||
|
multiple configured forecast locations.
|
||||||
|
|
||||||
|
- `id`: short local identifier. Default: `home`.
|
||||||
|
- `name`: human-readable location name. Default: `Brentwood`.
|
||||||
|
- `region`: broader forecast area context. Default: `St. Louis Metro`.
|
||||||
|
|
||||||
|
The prompt-facing location object also includes `timezone`, derived from the
|
||||||
|
effective `weather_api.timezone` after CLI overrides such as `--tz`.
|
||||||
|
|
||||||
### `missing_source`
|
### `missing_source`
|
||||||
|
|
||||||
- `default`: missing-source behavior for optional sources. One of `error`, `warn`, or `none`. Default: `warn`.
|
- `default`: missing-source behavior for optional sources. One of `error`, `warn`, or `none`. Default: `warn`.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Inputs:
|
|||||||
- resolved report definition, generation time, timezone, and valid period
|
- resolved report definition, generation time, timezone, and valid period
|
||||||
- forecast bundle with source provenance and warnings
|
- forecast bundle with source provenance and warnings
|
||||||
- derived daily or period summaries where required
|
- derived daily or period summaries where required
|
||||||
- configured units and timezone
|
- configured units, timezone, and descriptive location context
|
||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
|
|
||||||
@@ -34,6 +34,8 @@ Outputs:
|
|||||||
|
|
||||||
The package receives configured units and timezone from the app layer. Daypart
|
The package receives configured units and timezone from the app layer. Daypart
|
||||||
configuration is consumed by `internal/forecast` before briefing builders run.
|
configuration is consumed by `internal/forecast` before briefing builders run.
|
||||||
|
Configured `location` values are prompt context only; Weather API
|
||||||
|
`sourceLocationId` and `sourceLocation` remain source provenance.
|
||||||
|
|
||||||
## External Adapters Used
|
## External Adapters Used
|
||||||
|
|
||||||
@@ -74,6 +76,6 @@ Inspect:
|
|||||||
|
|
||||||
- Briefings contain structured weather facts and source context.
|
- Briefings contain structured weather facts and source context.
|
||||||
- Common metadata includes RunID, report ID, prompt ID, valid period, source
|
- Common metadata includes RunID, report ID, prompt ID, valid period, source
|
||||||
provenance, source hashes, and source warnings.
|
provenance, source hashes, source warnings, and configured prompt location.
|
||||||
- LLM prompt input packaging and Scriptorium execution remain outside this
|
- LLM prompt input packaging and Scriptorium execution remain outside this
|
||||||
boundary.
|
boundary.
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ Outputs:
|
|||||||
|
|
||||||
- `promptinput.Package` containing schema version, RunID, report metadata,
|
- `promptinput.Package` containing schema version, RunID, report metadata,
|
||||||
briefing content, Recent Changes, and source warnings. Briefing content
|
briefing content, Recent Changes, and source warnings. Briefing content
|
||||||
includes discussion key messages and short/long-term AFD narratives when the
|
includes configured location context, discussion key messages, and
|
||||||
Weather API provides them.
|
short/long-term AFD narratives when the Weather API provides them.
|
||||||
- optional JSON file written by `promptinput.Save`
|
- optional JSON file written by `promptinput.Save`
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
@@ -30,8 +30,8 @@ Outputs:
|
|||||||
|
|
||||||
## Config Fields Used
|
## Config Fields Used
|
||||||
|
|
||||||
None directly. Config-derived values are already present in briefing metadata
|
None directly. Config-derived values, including prompt location context, are
|
||||||
before this package runs.
|
already present in briefing metadata before this package runs.
|
||||||
|
|
||||||
## External Adapters Used
|
## External Adapters Used
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ valid-period start date for JSON artifacts, and the RunID.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Metadata is stored beside briefing snapshots and links the briefing, data
|
Metadata is stored beside briefing snapshots and links the briefing, data
|
||||||
package, preflight, and report paths. Report listing walks metadata files under
|
package, preflight, report paths, and configured prompt location. Report
|
||||||
the snapshots directory.
|
listing walks metadata files under the snapshots directory.
|
||||||
|
|
||||||
## Prior Lookup
|
## Prior Lookup
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ weather_api:
|
|||||||
timezone: "America/Chicago"
|
timezone: "America/Chicago"
|
||||||
format: json
|
format: json
|
||||||
|
|
||||||
|
location:
|
||||||
|
id: home
|
||||||
|
name: Brentwood
|
||||||
|
region: St. Louis Metro
|
||||||
|
|
||||||
missing_source:
|
missing_source:
|
||||||
default: warn
|
default: warn
|
||||||
sources:
|
sources:
|
||||||
|
|||||||
@@ -522,6 +522,7 @@ func BuildBriefing(req BriefingRequest, bundle *forecast.Bundle) (briefing.Packa
|
|||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
Units: req.Config.WeatherAPI.Units,
|
Units: req.Config.WeatherAPI.Units,
|
||||||
Timezone: req.Config.WeatherAPI.Timezone,
|
Timezone: req.Config.WeatherAPI.Timezone,
|
||||||
|
Location: briefingLocation(req.Config),
|
||||||
}, summary)
|
}, summary)
|
||||||
case report.ThreeDay, report.Weekend:
|
case report.ThreeDay, report.Weekend:
|
||||||
summaries, err := forecast.BuildPeriodDailySummaries(bundle, req.Resolved.ValidPeriod, location, dayparts)
|
summaries, err := forecast.BuildPeriodDailySummaries(bundle, req.Resolved.ValidPeriod, location, dayparts)
|
||||||
@@ -534,6 +535,7 @@ func BuildBriefing(req BriefingRequest, bundle *forecast.Bundle) (briefing.Packa
|
|||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
Units: req.Config.WeatherAPI.Units,
|
Units: req.Config.WeatherAPI.Units,
|
||||||
Timezone: req.Config.WeatherAPI.Timezone,
|
Timezone: req.Config.WeatherAPI.Timezone,
|
||||||
|
Location: briefingLocation(req.Config),
|
||||||
}, summaries)
|
}, summaries)
|
||||||
}
|
}
|
||||||
return briefing.BuildThreeDay(briefing.BuildContext{
|
return briefing.BuildThreeDay(briefing.BuildContext{
|
||||||
@@ -541,6 +543,7 @@ func BuildBriefing(req BriefingRequest, bundle *forecast.Bundle) (briefing.Packa
|
|||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
Units: req.Config.WeatherAPI.Units,
|
Units: req.Config.WeatherAPI.Units,
|
||||||
Timezone: req.Config.WeatherAPI.Timezone,
|
Timezone: req.Config.WeatherAPI.Timezone,
|
||||||
|
Location: briefingLocation(req.Config),
|
||||||
}, summaries)
|
}, summaries)
|
||||||
case report.Storm:
|
case report.Storm:
|
||||||
return briefing.BuildStorm(briefing.BuildContext{
|
return briefing.BuildStorm(briefing.BuildContext{
|
||||||
@@ -548,12 +551,26 @@ func BuildBriefing(req BriefingRequest, bundle *forecast.Bundle) (briefing.Packa
|
|||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
Units: req.Config.WeatherAPI.Units,
|
Units: req.Config.WeatherAPI.Units,
|
||||||
Timezone: req.Config.WeatherAPI.Timezone,
|
Timezone: req.Config.WeatherAPI.Timezone,
|
||||||
|
Location: briefingLocation(req.Config),
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
return briefing.Package{}, fmt.Errorf("briefing is not implemented for report %q", req.Resolved.Definition.ID)
|
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) {
|
func defaultStore(cfg config.Config) (*state.FilesystemStore, error) {
|
||||||
return state.NewFilesystemStore(cfg.Workspace)
|
return state.NewFilesystemStore(cfg.Workspace)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -195,6 +196,16 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
|
|||||||
if !strings.Contains(string(data), `"recentChanges"`) || !strings.Contains(string(data), `data_package.v1`) {
|
if !strings.Contains(string(data), `"recentChanges"`) || !strings.Contains(string(data), `data_package.v1`) {
|
||||||
t.Fatalf("data package missing expected content:\n%s", string(data))
|
t.Fatalf("data package missing expected content:\n%s", string(data))
|
||||||
}
|
}
|
||||||
|
var savedDataPackage struct {
|
||||||
|
Briefing briefing.Package `json:"briefing"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &savedDataPackage); err != nil {
|
||||||
|
t.Fatalf("decode data package: %v", err)
|
||||||
|
}
|
||||||
|
location := savedDataPackage.Briefing.Metadata.Location
|
||||||
|
if location == nil || location.ID != "home" || location.Name != "Brentwood" || location.Region != "St. Louis Metro" || location.Timezone != "America/Chicago" {
|
||||||
|
t.Fatalf("data package location = %#v, want configured prompt location", location)
|
||||||
|
}
|
||||||
if !strings.Contains(string(data), "Short-term AFD narrative for generated report.") || !strings.Contains(string(data), "Long-term AFD narrative for generated report.") {
|
if !strings.Contains(string(data), "Short-term AFD narrative for generated report.") || !strings.Contains(string(data), "Long-term AFD narrative for generated report.") {
|
||||||
t.Fatalf("data package missing AFD short/long-term discussion:\n%s", string(data))
|
t.Fatalf("data package missing AFD short/long-term discussion:\n%s", string(data))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ func TestDailyBriefingFromRepresentativeFixture(t *testing.T) {
|
|||||||
Bundle: bundle,
|
Bundle: bundle,
|
||||||
Units: "us",
|
Units: "us",
|
||||||
Timezone: "America/Chicago",
|
Timezone: "America/Chicago",
|
||||||
|
Location: &LocationContext{
|
||||||
|
ID: "home",
|
||||||
|
Name: "Brentwood",
|
||||||
|
Region: "St. Louis Metro",
|
||||||
|
Timezone: "America/Chicago",
|
||||||
|
},
|
||||||
}, summary)
|
}, summary)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("BuildDaily() error = %v", err)
|
t.Fatalf("BuildDaily() error = %v", err)
|
||||||
@@ -46,6 +52,9 @@ func TestDailyBriefingFromRepresentativeFixture(t *testing.T) {
|
|||||||
if pkg.Metadata.Units != "us" || pkg.Metadata.Timezone != "America/Chicago" {
|
if pkg.Metadata.Units != "us" || pkg.Metadata.Timezone != "America/Chicago" {
|
||||||
t.Fatalf("metadata units/timezone = %q/%q", pkg.Metadata.Units, pkg.Metadata.Timezone)
|
t.Fatalf("metadata units/timezone = %q/%q", pkg.Metadata.Units, pkg.Metadata.Timezone)
|
||||||
}
|
}
|
||||||
|
if pkg.Metadata.Location == nil || pkg.Metadata.Location.ID != "home" || pkg.Metadata.Location.Name != "Brentwood" || pkg.Metadata.Location.Region != "St. Louis Metro" || pkg.Metadata.Location.Timezone != "America/Chicago" {
|
||||||
|
t.Fatalf("metadata location = %#v, want configured prompt location", pkg.Metadata.Location)
|
||||||
|
}
|
||||||
if len(pkg.Metadata.Sources) != 1 || pkg.Metadata.Sources[0].DataSHA256 != "abc123" {
|
if len(pkg.Metadata.Sources) != 1 || pkg.Metadata.Sources[0].DataSHA256 != "abc123" {
|
||||||
t.Fatalf("Sources = %#v, want source hash", pkg.Metadata.Sources)
|
t.Fatalf("Sources = %#v, want source hash", pkg.Metadata.Sources)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type Metadata struct {
|
|||||||
Units string `json:"units"`
|
Units string `json:"units"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone"`
|
||||||
ValidPeriod timeutil.Period `json:"validPeriod"`
|
ValidPeriod timeutil.Period `json:"validPeriod"`
|
||||||
|
Location *LocationContext `json:"location,omitempty"`
|
||||||
SourceLocationID string `json:"sourceLocationId,omitempty"`
|
SourceLocationID string `json:"sourceLocationId,omitempty"`
|
||||||
SourceLocation string `json:"sourceLocation,omitempty"`
|
SourceLocation string `json:"sourceLocation,omitempty"`
|
||||||
Sources []SourceMetadata `json:"sources,omitempty"`
|
Sources []SourceMetadata `json:"sources,omitempty"`
|
||||||
@@ -38,6 +39,13 @@ type Metadata struct {
|
|||||||
Alerts *AlertStatus `json:"alerts,omitempty"`
|
Alerts *AlertStatus `json:"alerts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LocationContext struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Region string `json:"region,omitempty"`
|
||||||
|
Timezone string `json:"timezone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type SourceMetadata struct {
|
type SourceMetadata struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Endpoint string `json:"endpoint,omitempty"`
|
Endpoint string `json:"endpoint,omitempty"`
|
||||||
@@ -61,6 +69,7 @@ type BuildContext struct {
|
|||||||
Bundle *forecast.Bundle
|
Bundle *forecast.Bundle
|
||||||
Units string
|
Units string
|
||||||
Timezone string
|
Timezone string
|
||||||
|
Location *LocationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildMetadata(ctx BuildContext) Metadata {
|
func BuildMetadata(ctx BuildContext) Metadata {
|
||||||
@@ -76,6 +85,7 @@ func BuildMetadata(ctx BuildContext) Metadata {
|
|||||||
Units: ctx.Units,
|
Units: ctx.Units,
|
||||||
Timezone: ctx.Timezone,
|
Timezone: ctx.Timezone,
|
||||||
ValidPeriod: metadata.ValidPeriod,
|
ValidPeriod: metadata.ValidPeriod,
|
||||||
|
Location: copyLocation(ctx.Location),
|
||||||
SourceLocationID: sourceLocationID,
|
SourceLocationID: sourceLocationID,
|
||||||
SourceLocation: sourceLocation,
|
SourceLocation: sourceLocation,
|
||||||
Sources: sourceMetadata(ctx.Bundle),
|
Sources: sourceMetadata(ctx.Bundle),
|
||||||
@@ -84,6 +94,14 @@ func BuildMetadata(ctx BuildContext) Metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyLocation(location *LocationContext) *LocationContext {
|
||||||
|
if location == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied := *location
|
||||||
|
return &copied
|
||||||
|
}
|
||||||
|
|
||||||
func Save(path string, pkg Package) error {
|
func Save(path string, pkg Package) error {
|
||||||
if err := fileutil.WriteJSONAtomic(path, pkg); err != nil {
|
if err := fileutil.WriteJSONAtomic(path, pkg); err != nil {
|
||||||
return fmt.Errorf("save briefing package: %w", err)
|
return fmt.Errorf("save briefing package: %w", err)
|
||||||
|
|||||||
@@ -437,6 +437,7 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
|
|||||||
"generate", "daily",
|
"generate", "daily",
|
||||||
"--config", configPath,
|
"--config", configPath,
|
||||||
"--date", "2026-05-29",
|
"--date", "2026-05-29",
|
||||||
|
"--tz", "UTC",
|
||||||
"--out", outPath,
|
"--out", outPath,
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -463,6 +464,24 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
|
|||||||
if !strings.Contains(string(data), `data_package.v1`) || !strings.Contains(string(data), `"daily_today"`) {
|
if !strings.Contains(string(data), `data_package.v1`) || !strings.Contains(string(data), `"daily_today"`) {
|
||||||
t.Fatalf("data package output missing expected content:\n%s", string(data))
|
t.Fatalf("data package output missing expected content:\n%s", string(data))
|
||||||
}
|
}
|
||||||
|
var decoded struct {
|
||||||
|
Briefing struct {
|
||||||
|
Metadata struct {
|
||||||
|
Location struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
} `json:"location"`
|
||||||
|
} `json:"metadata"`
|
||||||
|
} `json:"briefing"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||||
|
t.Fatalf("decode data package: %v", err)
|
||||||
|
}
|
||||||
|
if decoded.Briefing.Metadata.Location.ID != "home" || decoded.Briefing.Metadata.Location.Name != "Brentwood" || decoded.Briefing.Metadata.Location.Region != "St. Louis Metro" || decoded.Briefing.Metadata.Location.Timezone != "UTC" {
|
||||||
|
t.Fatalf("location = %#v, want configured location with overridden timezone", decoded.Briefing.Metadata.Location)
|
||||||
|
}
|
||||||
preflightMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "preflight", "daily", "2026-05-29", "*.render.json"))
|
preflightMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "preflight", "daily", "2026-05-29", "*.render.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("glob preflight: %v", err)
|
t.Fatalf("glob preflight: %v", err)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
WeatherAPI WeatherAPIConfig `yaml:"weather_api"`
|
WeatherAPI WeatherAPIConfig `yaml:"weather_api"`
|
||||||
|
Location LocationConfig `yaml:"location"`
|
||||||
MissingSource MissingSourceConfig `yaml:"missing_source"`
|
MissingSource MissingSourceConfig `yaml:"missing_source"`
|
||||||
Scriptorium ScriptoriumConfig `yaml:"scriptorium"`
|
Scriptorium ScriptoriumConfig `yaml:"scriptorium"`
|
||||||
Workspace WorkspaceConfig `yaml:"workspace"`
|
Workspace WorkspaceConfig `yaml:"workspace"`
|
||||||
@@ -30,6 +31,12 @@ type WeatherAPIConfig struct {
|
|||||||
Format string `yaml:"format"`
|
Format string `yaml:"format"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LocationConfig struct {
|
||||||
|
ID string `yaml:"id"`
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Region string `yaml:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
type MissingSourceConfig struct {
|
type MissingSourceConfig struct {
|
||||||
Default MissingSourcePolicy `yaml:"default"`
|
Default MissingSourcePolicy `yaml:"default"`
|
||||||
Sources map[string]MissingSourcePolicy `yaml:"sources"`
|
Sources map[string]MissingSourcePolicy `yaml:"sources"`
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ func TestDefaults(t *testing.T) {
|
|||||||
if cfg.WeatherAPI.Format != "json" {
|
if cfg.WeatherAPI.Format != "json" {
|
||||||
t.Fatalf("Format = %q, want json", cfg.WeatherAPI.Format)
|
t.Fatalf("Format = %q, want json", cfg.WeatherAPI.Format)
|
||||||
}
|
}
|
||||||
|
if cfg.Location.ID != "home" || cfg.Location.Name != "Brentwood" || cfg.Location.Region != "St. Louis Metro" {
|
||||||
|
t.Fatalf("Location = %#v, want home/Brentwood/St. Louis Metro", cfg.Location)
|
||||||
|
}
|
||||||
if cfg.MissingSource.Default != MissingSourceWarn {
|
if cfg.MissingSource.Default != MissingSourceWarn {
|
||||||
t.Fatalf("MissingSource.Default = %q, want warn", cfg.MissingSource.Default)
|
t.Fatalf("MissingSource.Default = %q, want warn", cfg.MissingSource.Default)
|
||||||
}
|
}
|
||||||
@@ -43,6 +46,9 @@ func TestLoadExampleConfig(t *testing.T) {
|
|||||||
if cfg.MissingSource.Sources["alerts"] != MissingSourceNone {
|
if cfg.MissingSource.Sources["alerts"] != MissingSourceNone {
|
||||||
t.Fatalf("alerts policy = %q, want none", cfg.MissingSource.Sources["alerts"])
|
t.Fatalf("alerts policy = %q, want none", cfg.MissingSource.Sources["alerts"])
|
||||||
}
|
}
|
||||||
|
if cfg.Location.ID != "home" || cfg.Location.Name != "Brentwood" || cfg.Location.Region != "St. Louis Metro" {
|
||||||
|
t.Fatalf("Location = %#v, want example location", cfg.Location)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadMinimalExampleConfig(t *testing.T) {
|
func TestLoadMinimalExampleConfig(t *testing.T) {
|
||||||
@@ -63,6 +69,9 @@ func TestLoadMinimalExampleConfig(t *testing.T) {
|
|||||||
if cfg.Workspace.Root != "workspace" {
|
if cfg.Workspace.Root != "workspace" {
|
||||||
t.Fatalf("Workspace.Root = %q, want default workspace", cfg.Workspace.Root)
|
t.Fatalf("Workspace.Root = %q, want default workspace", cfg.Workspace.Root)
|
||||||
}
|
}
|
||||||
|
if cfg.Location.Name != "Brentwood" {
|
||||||
|
t.Fatalf("Location.Name = %q, want default Brentwood", cfg.Location.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExplicitMissingConfigReturnsError(t *testing.T) {
|
func TestExplicitMissingConfigReturnsError(t *testing.T) {
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ func Defaults() Config {
|
|||||||
Timezone: "America/Chicago",
|
Timezone: "America/Chicago",
|
||||||
Format: "json",
|
Format: "json",
|
||||||
},
|
},
|
||||||
|
Location: LocationConfig{
|
||||||
|
ID: "home",
|
||||||
|
Name: "Brentwood",
|
||||||
|
Region: "St. Louis Metro",
|
||||||
|
},
|
||||||
MissingSource: MissingSourceConfig{
|
MissingSource: MissingSourceConfig{
|
||||||
Default: MissingSourceWarn,
|
Default: MissingSourceWarn,
|
||||||
Sources: map[string]MissingSourcePolicy{},
|
Sources: map[string]MissingSourcePolicy{},
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ func TestBuildDailyDataPackage(t *testing.T) {
|
|||||||
if pkg.Briefing.Daily == nil {
|
if pkg.Briefing.Daily == nil {
|
||||||
t.Fatal("Briefing.Daily = nil")
|
t.Fatal("Briefing.Daily = nil")
|
||||||
}
|
}
|
||||||
|
if pkg.Briefing.Metadata.Location == nil || pkg.Briefing.Metadata.Location.Name != "Brentwood" {
|
||||||
|
t.Fatalf("Briefing.Metadata.Location = %#v, want configured location", pkg.Briefing.Metadata.Location)
|
||||||
|
}
|
||||||
if pkg.RecentChanges.Items == nil || len(pkg.RecentChanges.Items) != 0 {
|
if pkg.RecentChanges.Items == nil || len(pkg.RecentChanges.Items) != 0 {
|
||||||
t.Fatalf("RecentChanges.Items = %#v, want empty slice", pkg.RecentChanges.Items)
|
t.Fatalf("RecentChanges.Items = %#v, want empty slice", pkg.RecentChanges.Items)
|
||||||
}
|
}
|
||||||
@@ -155,6 +158,12 @@ func validBriefingPackage() briefing.Package {
|
|||||||
GeneratedAt: generatedAt,
|
GeneratedAt: generatedAt,
|
||||||
Units: "us",
|
Units: "us",
|
||||||
Timezone: "America/Chicago",
|
Timezone: "America/Chicago",
|
||||||
|
Location: &briefing.LocationContext{
|
||||||
|
ID: "home",
|
||||||
|
Name: "Brentwood",
|
||||||
|
Region: "St. Louis Metro",
|
||||||
|
Timezone: "America/Chicago",
|
||||||
|
},
|
||||||
ValidPeriod: timeutil.Period{
|
ValidPeriod: timeutil.Period{
|
||||||
Start: time.Date(2026, 5, 29, 5, 0, 0, 0, time.UTC),
|
Start: time.Date(2026, 5, 29, 5, 0, 0, 0, time.UTC),
|
||||||
End: time.Date(2026, 5, 30, 5, 0, 0, 0, time.UTC),
|
End: time.Date(2026, 5, 30, 5, 0, 0, 0, time.UTC),
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ func TestSaveArtifactsAndMetadataRoundTrip(t *testing.T) {
|
|||||||
if decoded.RenderedReportPath != renderedReportPath {
|
if decoded.RenderedReportPath != renderedReportPath {
|
||||||
t.Fatalf("RenderedReportPath = %q, want %q", decoded.RenderedReportPath, renderedReportPath)
|
t.Fatalf("RenderedReportPath = %q, want %q", decoded.RenderedReportPath, renderedReportPath)
|
||||||
}
|
}
|
||||||
|
if decoded.Location == nil || decoded.Location.Name != "Brentwood" || decoded.Location.Timezone != "America/Chicago" {
|
||||||
|
t.Fatalf("metadata location = %#v, want briefing location", decoded.Location)
|
||||||
|
}
|
||||||
if strings.Contains(string(data), "MetadataPath") || strings.Contains(string(data), "metadataPath") {
|
if strings.Contains(string(data), "MetadataPath") || strings.Contains(string(data), "metadataPath") {
|
||||||
t.Fatalf("metadata JSON includes runtime-only MetadataPath:\n%s", string(data))
|
t.Fatalf("metadata JSON includes runtime-only MetadataPath:\n%s", string(data))
|
||||||
}
|
}
|
||||||
@@ -437,7 +440,13 @@ func stateBriefingPackage(resolved report.Resolved) briefing.Package {
|
|||||||
GeneratedAt: resolved.GeneratedAt,
|
GeneratedAt: resolved.GeneratedAt,
|
||||||
Units: "us",
|
Units: "us",
|
||||||
Timezone: resolved.Timezone,
|
Timezone: resolved.Timezone,
|
||||||
ValidPeriod: resolved.ValidPeriod,
|
Location: &briefing.LocationContext{
|
||||||
|
ID: "home",
|
||||||
|
Name: "Brentwood",
|
||||||
|
Region: "St. Louis Metro",
|
||||||
|
Timezone: resolved.Timezone,
|
||||||
|
},
|
||||||
|
ValidPeriod: resolved.ValidPeriod,
|
||||||
},
|
},
|
||||||
Daily: &briefing.Daily{ForecastSummaryDate: "2026-05-29"},
|
Daily: &briefing.Daily{ForecastSummaryDate: "2026-05-29"},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type Metadata struct {
|
|||||||
GeneratedAt time.Time `json:"generatedAt"`
|
GeneratedAt time.Time `json:"generatedAt"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone"`
|
||||||
ValidPeriod timeutil.Period `json:"validPeriod"`
|
ValidPeriod timeutil.Period `json:"validPeriod"`
|
||||||
|
Location *briefing.LocationContext `json:"location,omitempty"`
|
||||||
SourceLocationID string `json:"sourceLocationId,omitempty"`
|
SourceLocationID string `json:"sourceLocationId,omitempty"`
|
||||||
SourceLocation string `json:"sourceLocation,omitempty"`
|
SourceLocation string `json:"sourceLocation,omitempty"`
|
||||||
Sources []briefing.SourceMetadata `json:"sources,omitempty"`
|
Sources []briefing.SourceMetadata `json:"sources,omitempty"`
|
||||||
@@ -43,6 +44,7 @@ func BuildMetadata(resolved report.Resolved, briefingPackage briefing.Package, p
|
|||||||
GeneratedAt: metadata.GeneratedAt,
|
GeneratedAt: metadata.GeneratedAt,
|
||||||
Timezone: metadata.Timezone,
|
Timezone: metadata.Timezone,
|
||||||
ValidPeriod: metadata.ValidPeriod,
|
ValidPeriod: metadata.ValidPeriod,
|
||||||
|
Location: copyLocation(briefingPackage.Metadata.Location),
|
||||||
SourceLocationID: briefingPackage.Metadata.SourceLocationID,
|
SourceLocationID: briefingPackage.Metadata.SourceLocationID,
|
||||||
SourceLocation: briefingPackage.Metadata.SourceLocation,
|
SourceLocation: briefingPackage.Metadata.SourceLocation,
|
||||||
Sources: briefingPackage.Metadata.Sources,
|
Sources: briefingPackage.Metadata.Sources,
|
||||||
@@ -53,3 +55,11 @@ func BuildMetadata(resolved report.Resolved, briefingPackage briefing.Package, p
|
|||||||
RenderedReportPath: paths.RenderedReport,
|
RenderedReportPath: paths.RenderedReport,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyLocation(location *briefing.LocationContext) *briefing.LocationContext {
|
||||||
|
if location == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied := *location
|
||||||
|
return &copied
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user