Route report projections through prepared identity

This commit is contained in:
2026-08-13 02:00:18 +00:00
parent e2dd8d0e29
commit 360c665a3e
15 changed files with 163 additions and 253 deletions

View File

@@ -25,7 +25,7 @@ const (
type validator func([]byte) (any, []byte, error)
type renderContextBuilder func(report.ID, string, briefing.Metadata, module.Snapshot, facts.CollectedFacts, facts.DerivedFacts, any) (any, error)
type renderContextBuilder func(report.ID, string, briefing.PreparedIdentity, module.Snapshot, facts.CollectedFacts, facts.DerivedFacts, any) (any, error)
type catalogEntry struct {
reportID report.ID
@@ -140,11 +140,11 @@ func (h Handler) Validate(data []byte) (any, []byte, error) {
return h.validate(data)
}
func (h Handler) BuildRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
func (h Handler) BuildRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
if h.renderContextBuilder == nil {
return nil, fmt.Errorf("render-context builder is not registered for template %q on report %q", h.templateID, h.reportID)
}
return h.renderContextBuilder(h.reportID, h.templateID, metadata, snapshot, collected, derived, generated)
return h.renderContextBuilder(h.reportID, h.templateID, identity, snapshot, collected, derived, generated)
}
func (h Handler) Render(data any) ([]byte, error) {
@@ -171,34 +171,34 @@ func validateTomorrow(data []byte) (any, []byte, error) {
return ValidateTomorrow(data)
}
func buildHourlyContext(reportID report.ID, templateID string, metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
func buildHourlyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
hourly, ok := generated.(Hourly)
if !ok {
return nil, fmt.Errorf("report template %q requires hourly generated text for report %q", templateID, reportID)
}
return BuildHourlyRenderContext(metadata, snapshot, hourly, collected, derived)
return BuildHourlyRenderContext(identity, snapshot, hourly, collected, derived)
}
func buildDailyContext(reportID report.ID, templateID string, metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
func buildDailyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
daily, ok := generated.(Daily)
if !ok {
return nil, fmt.Errorf("report template %q requires daily generated text for report %q", templateID, reportID)
}
return BuildDailyRenderContext(metadata, snapshot, daily, collected, derived)
return BuildDailyRenderContext(identity, snapshot, daily, collected, derived)
}
func buildTodayContext(reportID report.ID, templateID string, metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
func buildTodayContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
today, ok := generated.(Today)
if !ok {
return nil, fmt.Errorf("report template %q requires today generated text for report %q", templateID, reportID)
}
return BuildTodayRenderContext(metadata, snapshot, today, collected, derived)
return BuildTodayRenderContext(identity, snapshot, today, collected, derived)
}
func buildTomorrowContext(reportID report.ID, templateID string, metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
func buildTomorrowContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
tomorrow, ok := generated.(Tomorrow)
if !ok {
return nil, fmt.Errorf("report template %q requires tomorrow generated text for report %q", templateID, reportID)
}
return BuildTomorrowRenderContext(metadata, snapshot, tomorrow, collected, derived)
return BuildTomorrowRenderContext(identity, snapshot, tomorrow, collected, derived)
}

View File

@@ -188,15 +188,15 @@ type dayStyleTemplateModules struct {
WeatherStory *briefing.WeatherStoryModule
}
func BuildHourlyRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, generated Hourly, collected facts.CollectedFacts, derived facts.DerivedFacts) (HourlyRenderContext, error) {
location, err := timeutil.LoadLocation(metadata.Timezone)
func BuildHourlyRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Hourly, collected facts.CollectedFacts, derived facts.DerivedFacts) (HourlyRenderContext, error) {
location, err := timeutil.LoadLocation(identity.Timezone)
if err != nil {
return HourlyRenderContext{}, fmt.Errorf("build hourly render context: %w", err)
}
if metadata.GeneratedAt.IsZero() {
if identity.GeneratedAt.IsZero() {
return HourlyRenderContext{}, fmt.Errorf("build hourly render context: generatedAt is required")
}
if !metadata.ValidPeriod.IsValid() {
if !identity.ValidPeriod.IsValid() {
return HourlyRenderContext{}, fmt.Errorf("build hourly render context: valid period is required")
}
modules, err := hourlyTemplateModules(snapshot)
@@ -206,12 +206,12 @@ func BuildHourlyRenderContext(metadata briefing.Metadata, snapshot module.Snapsh
return HourlyRenderContext{
Report: HourlyReportContext{
Title: "Hourly Report",
LocationName: locationName(metadata),
GeneratedAt: metadata.GeneratedAt,
GeneratedAtLabel: generatedAtLabel(metadata.GeneratedAt, location),
ValidPeriod: metadata.ValidPeriod,
ValidPeriodLabel: periodLabel(metadata.ValidPeriod, location),
Timezone: metadata.Timezone,
LocationName: locationName(identity),
GeneratedAt: identity.GeneratedAt,
GeneratedAtLabel: generatedAtLabel(identity.GeneratedAt, location),
ValidPeriod: identity.ValidPeriod,
ValidPeriodLabel: periodLabel(identity.ValidPeriod, location),
Timezone: identity.Timezone,
},
GeneratedText: generated,
Modules: modules,
@@ -220,8 +220,8 @@ func BuildHourlyRenderContext(metadata briefing.Metadata, snapshot module.Snapsh
}, nil
}
func BuildDailyRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, generated Daily, collected facts.CollectedFacts, derived facts.DerivedFacts) (DailyRenderContext, error) {
reportContext, err := buildDayStyleReportContext(metadata, "daily", func(dayName string) string {
func BuildDailyRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Daily, collected facts.CollectedFacts, derived facts.DerivedFacts) (DailyRenderContext, error) {
reportContext, err := buildDayStyleReportContext(identity, "daily", func(dayName string) string {
return dayName + "'s Weather"
})
if err != nil {
@@ -240,8 +240,8 @@ func BuildDailyRenderContext(metadata briefing.Metadata, snapshot module.Snapsho
}, nil
}
func BuildTodayRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, generated Today, collected facts.CollectedFacts, derived facts.DerivedFacts) (TodayRenderContext, error) {
reportContext, err := buildDayStyleReportContext(metadata, "today", func(string) string {
func BuildTodayRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Today, collected facts.CollectedFacts, derived facts.DerivedFacts) (TodayRenderContext, error) {
reportContext, err := buildDayStyleReportContext(identity, "today", func(string) string {
return "Today's Weather"
})
if err != nil {
@@ -260,8 +260,8 @@ func BuildTodayRenderContext(metadata briefing.Metadata, snapshot module.Snapsho
}, nil
}
func BuildTomorrowRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, generated Tomorrow, collected facts.CollectedFacts, derived facts.DerivedFacts) (TomorrowRenderContext, error) {
reportContext, err := buildDayStyleReportContext(metadata, "tomorrow", func(dayName string) string {
func BuildTomorrowRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, generated Tomorrow, collected facts.CollectedFacts, derived facts.DerivedFacts) (TomorrowRenderContext, error) {
reportContext, err := buildDayStyleReportContext(identity, "tomorrow", func(dayName string) string {
return dayName + "'s Weather"
})
if err != nil {
@@ -280,29 +280,29 @@ func BuildTomorrowRenderContext(metadata briefing.Metadata, snapshot module.Snap
}, nil
}
func buildDayStyleReportContext(metadata briefing.Metadata, name string, title func(string) string) (dayStyleReportContext, error) {
location, err := timeutil.LoadLocation(metadata.Timezone)
func buildDayStyleReportContext(identity briefing.PreparedIdentity, name string, title func(string) string) (dayStyleReportContext, error) {
location, err := timeutil.LoadLocation(identity.Timezone)
if err != nil {
return dayStyleReportContext{}, fmt.Errorf("build %s render context: %w", name, err)
}
if metadata.GeneratedAt.IsZero() {
if identity.GeneratedAt.IsZero() {
return dayStyleReportContext{}, fmt.Errorf("build %s render context: generatedAt is required", name)
}
if !metadata.ValidPeriod.IsValid() {
if !identity.ValidPeriod.IsValid() {
return dayStyleReportContext{}, fmt.Errorf("build %s render context: valid period is required", name)
}
forecastDate := metadata.ValidPeriod.Start.In(location)
forecastDate := identity.ValidPeriod.Start.In(location)
forecastDayName := forecastDate.Format("Monday")
return dayStyleReportContext{
Title: title(forecastDayName),
ForecastDate: forecastDate,
ForecastDateLabel: forecastDate.Format("Monday, January 2, 2006"),
ForecastDayName: forecastDayName,
GeneratedAt: metadata.GeneratedAt,
GeneratedAtLabel: generatedAtLabel(metadata.GeneratedAt, location),
ValidPeriod: metadata.ValidPeriod,
Timezone: metadata.Timezone,
GeneratedAt: identity.GeneratedAt,
GeneratedAtLabel: generatedAtLabel(identity.GeneratedAt, location),
ValidPeriod: identity.ValidPeriod,
Timezone: identity.Timezone,
}, nil
}
@@ -706,20 +706,20 @@ func daypartModuleKeyCandidates(daypart forecast.DaypartSummary) []string {
return []string{key, daypart.Period.Start.Format(timeutil.DateLayout) + "_" + key}
}
func locationName(metadata briefing.Metadata) string {
if metadata.Location != nil {
if metadata.Location.Name != "" && metadata.Location.Region != "" {
return metadata.Location.Name + ", " + metadata.Location.Region
func locationName(identity briefing.PreparedIdentity) string {
if identity.Location != nil {
if identity.Location.Name != "" && identity.Location.Region != "" {
return identity.Location.Name + ", " + identity.Location.Region
}
if metadata.Location.Name != "" {
return metadata.Location.Name
if identity.Location.Name != "" {
return identity.Location.Name
}
}
if metadata.SourceLocation != "" {
return metadata.SourceLocation
if identity.SourceLocation != "" {
return identity.SourceLocation
}
if metadata.SourceLocationID != "" {
return metadata.SourceLocationID
if identity.SourceLocationID != "" {
return identity.SourceLocationID
}
return "Unknown location"
}

View File

@@ -741,9 +741,9 @@ func TestBuildTomorrowRenderContextAllowsOmittedOptionalModules(t *testing.T) {
}
}
func testMetadata() briefing.Metadata {
func testMetadata() briefing.PreparedIdentity {
generatedAt := time.Date(2026, 5, 29, 13, 30, 0, 0, time.UTC)
return briefing.Metadata{
return briefing.PreparedIdentity{
RunID: "run-1",
ReportID: report.Hourly,
PromptID: "weather.hourly_generated_text",
@@ -761,9 +761,9 @@ func testMetadata() briefing.Metadata {
}
}
func testTomorrowMetadata() briefing.Metadata {
func testTomorrowMetadata() briefing.PreparedIdentity {
generatedAt := time.Date(2026, 6, 14, 14, 14, 0, 0, time.UTC)
return briefing.Metadata{
return briefing.PreparedIdentity{
RunID: "run-tomorrow",
ReportID: report.Tomorrow,
PromptID: "weather.tomorrow_generated_text",
@@ -781,9 +781,9 @@ func testTomorrowMetadata() briefing.Metadata {
}
}
func testDailyMetadata() briefing.Metadata {
func testDailyMetadata() briefing.PreparedIdentity {
generatedAt := time.Date(2026, 6, 13, 14, 14, 0, 0, time.UTC)
return briefing.Metadata{
return briefing.PreparedIdentity{
RunID: "run-daily",
ReportID: report.Daily,
PromptID: "weather.daily_generated_text",
@@ -801,9 +801,9 @@ func testDailyMetadata() briefing.Metadata {
}
}
func testTodayMetadata() briefing.Metadata {
func testTodayMetadata() briefing.PreparedIdentity {
generatedAt := time.Date(2026, 6, 15, 12, 14, 0, 0, time.UTC)
return briefing.Metadata{
return briefing.PreparedIdentity{
RunID: "run-today",
ReportID: report.Today,
PromptID: "weather.today_generated_text",