Route report projections through prepared identity
This commit is contained in:
@@ -600,7 +600,7 @@ func testModuleContext() ModuleContext {
|
||||
hourlyHumidity := 66.0
|
||||
hourlyWindMph := 14.0
|
||||
updatedAt := mustParseModuleTime("2026-05-29T07:30:00-05:00")
|
||||
return ModuleContext{
|
||||
ctx := ModuleContext{
|
||||
Resolved: resolved,
|
||||
Collected: facts.CollectedFacts{
|
||||
Current: &weatherdata.Current{
|
||||
@@ -728,6 +728,14 @@ func testModuleContext() ModuleContext {
|
||||
Timezone: "America/Chicago",
|
||||
},
|
||||
}
|
||||
ctx.Identity = BuildPreparedIdentity(BuildContext{
|
||||
Resolved: ctx.Resolved,
|
||||
Bundle: ctx.Collected.Bundle(),
|
||||
Units: ctx.Units,
|
||||
Timezone: ctx.Timezone,
|
||||
Location: ctx.Location,
|
||||
})
|
||||
return ctx
|
||||
}
|
||||
|
||||
func moduleValue[T any](t *testing.T, output *module.Output) T {
|
||||
|
||||
@@ -31,18 +31,17 @@ type SourceWarningSummary struct {
|
||||
}
|
||||
|
||||
func buildMetadataModule(ctx ModuleContext, _ any) (*module.Output, error) {
|
||||
metadata := ctx.Resolved.Metadata()
|
||||
value := MetadataModule{
|
||||
RunID: metadata.RunID,
|
||||
ReportID: metadata.ReportID,
|
||||
Variant: variantForReport(metadata.ReportID),
|
||||
PromptID: metadata.PromptID,
|
||||
GeneratedAt: metadata.GeneratedAt,
|
||||
Units: ctx.Units,
|
||||
Timezone: ctx.Timezone,
|
||||
ValidPeriod: metadata.ValidPeriod,
|
||||
Location: copyLocation(ctx.Location),
|
||||
SourceWarnings: sourceWarningSummaries(ctx.Collected.SourceWarnings),
|
||||
RunID: ctx.Identity.RunID,
|
||||
ReportID: ctx.Identity.ReportID,
|
||||
Variant: ctx.Identity.Variant,
|
||||
PromptID: ctx.Identity.PromptID,
|
||||
GeneratedAt: ctx.Identity.GeneratedAt,
|
||||
Units: ctx.Identity.Units,
|
||||
Timezone: ctx.Identity.Timezone,
|
||||
ValidPeriod: ctx.Identity.ValidPeriod,
|
||||
Location: copyLocation(ctx.Identity.Location),
|
||||
SourceWarnings: sourceWarningSummaries(ctx.Identity.SourceWarnings),
|
||||
}
|
||||
return &module.Output{ID: module.Metadata, StanzaName: "metadata", Value: value}, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type ModuleContext struct {
|
||||
Identity PreparedIdentity
|
||||
Resolved report.Resolved
|
||||
Collected facts.CollectedFacts
|
||||
Derived facts.DerivedFacts
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
// PreparedIdentity is the single prepared authority for report identity,
|
||||
// timing, configuration context, and source warnings.
|
||||
type PreparedIdentity struct {
|
||||
RunID string `json:"runId"`
|
||||
ReportID report.ID `json:"reportId"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
@@ -21,24 +23,7 @@ type Metadata struct {
|
||||
Location *LocationContext `json:"location,omitempty"`
|
||||
SourceLocationID string `json:"sourceLocationId,omitempty"`
|
||||
SourceLocation string `json:"sourceLocation,omitempty"`
|
||||
Sources []SourceMetadata `json:"sources,omitempty"`
|
||||
SourceWarnings []weatherdata.SourceWarning `json:"sourceWarnings,omitempty"`
|
||||
Alerts *AlertStatus `json:"alerts,omitempty"`
|
||||
}
|
||||
|
||||
// PreparedIdentity is the single prepared authority for report identity,
|
||||
// timing, configuration context, and source warnings.
|
||||
type PreparedIdentity struct {
|
||||
RunID string `json:"runId"`
|
||||
ReportID report.ID `json:"reportId"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
PromptID string `json:"promptId"`
|
||||
GeneratedAt time.Time `json:"generatedAt"`
|
||||
Units string `json:"units"`
|
||||
Timezone string `json:"timezone"`
|
||||
ValidPeriod timeutil.Period `json:"validPeriod"`
|
||||
Location *LocationContext `json:"location,omitempty"`
|
||||
SourceWarnings []weatherdata.SourceWarning `json:"sourceWarnings,omitempty"`
|
||||
}
|
||||
|
||||
type LocationContext struct {
|
||||
@@ -48,24 +33,6 @@ type LocationContext struct {
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
type SourceMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
FetchedAt time.Time `json:"fetchedAt"`
|
||||
IssuedAt *time.Time `json:"issuedAt,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
DataSHA256 string `json:"dataSha256,omitempty"`
|
||||
Missing bool `json:"missing,omitempty"`
|
||||
Warnings []weatherdata.SourceWarning `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
type AlertStatus struct {
|
||||
Checked bool `json:"checked"`
|
||||
ActiveCount int `json:"activeCount"`
|
||||
RelevantCount int `json:"relevantCount"`
|
||||
Missing bool `json:"missing,omitempty"`
|
||||
}
|
||||
|
||||
type BuildContext struct {
|
||||
Resolved report.Resolved
|
||||
Bundle *weatherdata.Bundle
|
||||
@@ -76,37 +43,20 @@ type BuildContext struct {
|
||||
|
||||
func BuildPreparedIdentity(ctx BuildContext) PreparedIdentity {
|
||||
metadata := ctx.Resolved.Metadata()
|
||||
sourceLocationID, sourceLocation := sourceLocation(ctx.Bundle)
|
||||
return PreparedIdentity{
|
||||
RunID: metadata.RunID,
|
||||
ReportID: metadata.ReportID,
|
||||
Variant: variantForReport(metadata.ReportID),
|
||||
PromptID: metadata.PromptID,
|
||||
GeneratedAt: metadata.GeneratedAt,
|
||||
Units: ctx.Units,
|
||||
Timezone: ctx.Timezone,
|
||||
ValidPeriod: metadata.ValidPeriod,
|
||||
Location: copyLocation(ctx.Location),
|
||||
SourceWarnings: append([]weatherdata.SourceWarning(nil), sourceWarnings(ctx.Bundle)...),
|
||||
}
|
||||
}
|
||||
|
||||
func BuildMetadata(identity PreparedIdentity, bundle *weatherdata.Bundle) Metadata {
|
||||
sourceLocationID, sourceLocation := sourceLocation(bundle)
|
||||
return Metadata{
|
||||
RunID: identity.RunID,
|
||||
ReportID: identity.ReportID,
|
||||
Variant: identity.Variant,
|
||||
PromptID: identity.PromptID,
|
||||
GeneratedAt: identity.GeneratedAt,
|
||||
Units: identity.Units,
|
||||
Timezone: identity.Timezone,
|
||||
ValidPeriod: identity.ValidPeriod,
|
||||
Location: copyLocation(identity.Location),
|
||||
RunID: metadata.RunID,
|
||||
ReportID: metadata.ReportID,
|
||||
Variant: variantForReport(metadata.ReportID),
|
||||
PromptID: metadata.PromptID,
|
||||
GeneratedAt: metadata.GeneratedAt,
|
||||
Units: ctx.Units,
|
||||
Timezone: ctx.Timezone,
|
||||
ValidPeriod: metadata.ValidPeriod,
|
||||
Location: copyLocation(ctx.Location),
|
||||
SourceLocationID: sourceLocationID,
|
||||
SourceLocation: sourceLocation,
|
||||
Sources: sourceMetadata(bundle),
|
||||
SourceWarnings: append([]weatherdata.SourceWarning(nil), identity.SourceWarnings...),
|
||||
Alerts: alertStatus(bundle),
|
||||
SourceWarnings: append([]weatherdata.SourceWarning(nil), sourceWarnings(ctx.Bundle)...),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,26 +115,6 @@ func sourceLocation(bundle *weatherdata.Bundle) (string, string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func sourceMetadata(bundle *weatherdata.Bundle) []SourceMetadata {
|
||||
if bundle == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]SourceMetadata, 0, len(bundle.Sources))
|
||||
for _, source := range bundle.Sources {
|
||||
out = append(out, SourceMetadata{
|
||||
Name: source.Name,
|
||||
Endpoint: source.Endpoint,
|
||||
FetchedAt: source.FetchedAt,
|
||||
IssuedAt: source.IssuedAt,
|
||||
UpdatedAt: source.UpdatedAt,
|
||||
DataSHA256: source.DataSHA256,
|
||||
Missing: source.Missing,
|
||||
Warnings: source.Warnings,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sourceWarnings(bundle *weatherdata.Bundle) []weatherdata.SourceWarning {
|
||||
if bundle == nil {
|
||||
return nil
|
||||
@@ -192,27 +122,6 @@ func sourceWarnings(bundle *weatherdata.Bundle) []weatherdata.SourceWarning {
|
||||
return bundle.Warnings
|
||||
}
|
||||
|
||||
func alertStatus(bundle *weatherdata.Bundle) *AlertStatus {
|
||||
if bundle == nil {
|
||||
return nil
|
||||
}
|
||||
status := &AlertStatus{}
|
||||
if bundle.Alerts != nil {
|
||||
status.Checked = true
|
||||
status.ActiveCount = len(bundle.Alerts.Alerts)
|
||||
}
|
||||
for _, source := range bundle.Sources {
|
||||
if source.Name == "alerts" && source.Missing {
|
||||
status.Missing = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !status.Checked && !status.Missing {
|
||||
return nil
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func variantForReport(id report.ID) string {
|
||||
switch id {
|
||||
case report.Daily, report.Today:
|
||||
|
||||
@@ -3,9 +3,11 @@ package briefing
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
func TestPreparedIdentityBuildsEquivalentMetadata(t *testing.T) {
|
||||
func TestPreparedIdentityCopiesMutableFields(t *testing.T) {
|
||||
moduleContext := testModuleContext()
|
||||
context := BuildContext{
|
||||
Resolved: moduleContext.Resolved,
|
||||
@@ -15,18 +17,10 @@ func TestPreparedIdentityBuildsEquivalentMetadata(t *testing.T) {
|
||||
Location: moduleContext.Location,
|
||||
}
|
||||
identity := BuildPreparedIdentity(context)
|
||||
metadata := BuildMetadata(identity, context.Bundle)
|
||||
|
||||
if metadata.RunID != identity.RunID || metadata.ReportID != identity.ReportID || metadata.Variant != identity.Variant || metadata.PromptID != identity.PromptID || !metadata.GeneratedAt.Equal(identity.GeneratedAt) || metadata.Units != identity.Units || metadata.Timezone != identity.Timezone || metadata.ValidPeriod != identity.ValidPeriod {
|
||||
t.Fatalf("metadata identity = %#v, want %#v", metadata, identity)
|
||||
}
|
||||
if !reflect.DeepEqual(metadata.Location, identity.Location) || !reflect.DeepEqual(metadata.SourceWarnings, identity.SourceWarnings) {
|
||||
t.Fatalf("metadata location/warnings = %#v/%#v, want %#v/%#v", metadata.Location, metadata.SourceWarnings, identity.Location, identity.SourceWarnings)
|
||||
}
|
||||
|
||||
metadata.Location.Name = "consumer mutation"
|
||||
metadata.SourceWarnings = append(metadata.SourceWarnings, moduleContext.Collected.SourceWarnings[0])
|
||||
if identity.Location.Name == "consumer mutation" || len(identity.SourceWarnings) != len(moduleContext.Collected.SourceWarnings) {
|
||||
t.Fatalf("identity changed through metadata mutation: %#v", identity)
|
||||
originalWarnings := append([]weatherdata.SourceWarning(nil), moduleContext.Collected.SourceWarnings...)
|
||||
identity.Location.Name = "consumer mutation"
|
||||
identity.SourceWarnings = append(identity.SourceWarnings, moduleContext.Collected.SourceWarnings[0])
|
||||
if moduleContext.Location.Name == "consumer mutation" || !reflect.DeepEqual(moduleContext.Collected.SourceWarnings, originalWarnings) {
|
||||
t.Fatalf("prepared identity changed its source context: %#v", identity)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user