// Package briefing builds prompt-facing module values. package briefing import ( "time" "gitea.maximumdirect.net/eric/weatherreporter/internal/report" "gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil" "gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata" ) type Metadata 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"` 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"` } 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 { 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 Units string Timezone string Location *LocationContext } func BuildMetadata(ctx BuildContext) Metadata { metadata := ctx.Resolved.Metadata() sourceLocationID, sourceLocation := sourceLocation(ctx.Bundle) return Metadata{ 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(ctx.Bundle), SourceWarnings: sourceWarnings(ctx.Bundle), Alerts: alertStatus(ctx.Bundle), } } func copyLocation(location *LocationContext) *LocationContext { if location == nil { return nil } copied := *location return &copied } func copyBool(value *bool) *bool { if value == nil { return nil } copied := *value return &copied } func copyInt(value *int) *int { if value == nil { return nil } copied := *value return &copied } func copyFloat(value *float64) *float64 { if value == nil { return nil } copied := *value return &copied } func copyTime(value *time.Time) *time.Time { if value == nil { return nil } copied := *value return &copied } func sourceLocation(bundle *weatherdata.Bundle) (string, string) { if bundle == nil { return "", "" } for _, run := range []*weatherdata.ForecastRun{bundle.Hourly, bundle.Narrative, bundle.Daily} { if run == nil { continue } if run.LocationID != "" || run.LocationName != "" { return run.LocationID, run.LocationName } } 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 } 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.DailyToday: return "today" case report.Tomorrow: return "tomorrow" default: return "" } }