Files
weatherfeeder/model/forecast.go
2026-02-08 08:56:16 -06:00

119 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// FILE: internal/model/forecast.go
package model
import "time"
// ForecastProduct distinguishes *what kind* of forecast a provider is offering.
// This is intentionally canonical and not provider-nomenclature.
type ForecastProduct string
const (
// ForecastProductHourly is a sub-daily forecast where each period is typically ~1 hour.
ForecastProductHourly ForecastProduct = "hourly"
// ForecastProductNarrative is a human-oriented sequence of periods like
// "Tonight", "Friday", "Friday Night" with variable duration.
//
// (NWS "/forecast" looks like this; it is not strictly “daily”.)
ForecastProductNarrative ForecastProduct = "narrative"
// ForecastProductDaily is a calendar-day (or day-bucketed) forecast where a period
// commonly carries min/max values (Open-Meteo daily, many others).
ForecastProductDaily ForecastProduct = "daily"
)
// WeatherForecastRun is a single issued forecast snapshot for a location and product.
//
// Design goals:
// - Immutable snapshot semantics: “provider asserted X at IssuedAt”.
// - Provider-independent schema: normalize many upstreams into one shape.
// - Retrieval-friendly: periods are inside the run, but can be stored/indexed separately.
type WeatherForecastRun struct {
// Identity / metadata (aligned with WeatherObservations StationID/StationName/Timestamp).
LocationID string `json:"locationId,omitempty"`
LocationName string `json:"locationName,omitempty"`
IssuedAt time.Time `json:"issuedAt"` // required: when this run was generated/issued
// Some providers include both a generated time and a later update time.
// Keep UpdatedAt optional; many providers wont supply it.
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
// What sort of forecast this run represents (hourly vs narrative vs daily).
Product ForecastProduct `json:"product"`
// Optional spatial context. Many providers are fundamentally lat/lon-based.
Latitude *float64 `json:"latitude,omitempty"`
Longitude *float64 `json:"longitude,omitempty"`
// Kept to align with WeatherObservation and because elevation is sometimes important
// for interpreting temps/precip, even if not always supplied by the provider.
ElevationMeters *float64 `json:"elevationMeters,omitempty"`
// The forecast periods contained in this issued run. Order should be chronological.
Periods []WeatherForecastPeriod `json:"periods"`
}
// WeatherForecastPeriod is a forecast for a specific valid interval [StartTime, EndTime).
//
// Conceptually, it mirrors WeatherObservation (condition + measurements), but:
// / - It has a time *range* (start/end) instead of a single timestamp.
// / - It adds forecast-specific fields like probability/amount of precip.
// / - It supports min/max for “daily” products (and other aggregated periods).
type WeatherForecastPeriod struct {
// Identity / validity window (required)
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
// Human-facing period label (e.g., "Tonight", "Friday"). Often empty for hourly.
Name string `json:"name,omitempty"`
// Canonical day/night hint (aligned with WeatherObservation.IsDay).
// Providers vary in whether they explicitly include this.
IsDay *bool `json:"isDay,omitempty"`
// Canonical internal representation (provider-independent).
// Like WeatherObservation, this is required; use an “unknown” WMOCode if unmappable.
ConditionCode WMOCode `json:"conditionCode"`
// Provider-independent short text describing the conditions (normalized, if possible).
ConditionText string `json:"conditionText,omitempty"`
// Provider-specific “evidence” for troubleshooting mapping and drift.
ProviderRawDescription string `json:"providerRawDescription,omitempty"`
// Human-facing narrative. Not all providers supply rich text (Open-Meteo often wont).
TextDescription string `json:"textDescription,omitempty"` // short phrase / summary
DetailedText string `json:"detailedText,omitempty"` // longer narrative, if available
// Provider-specific (legacy / transitional)
IconURL string `json:"iconUrl,omitempty"`
// Core predicted measurements (nullable; units align with WeatherObservation)
TemperatureC *float64 `json:"temperatureC,omitempty"`
// For aggregated products (notably “daily”), providers may supply min/max.
TemperatureCMin *float64 `json:"temperatureCMin,omitempty"`
TemperatureCMax *float64 `json:"temperatureCMax,omitempty"`
DewpointC *float64 `json:"dewpointC,omitempty"`
RelativeHumidityPercent *float64 `json:"relativeHumidityPercent,omitempty"`
WindDirectionDegrees *float64 `json:"windDirectionDegrees,omitempty"`
WindSpeedKmh *float64 `json:"windSpeedKmh,omitempty"`
WindGustKmh *float64 `json:"windGustKmh,omitempty"`
BarometricPressurePa *float64 `json:"barometricPressurePa,omitempty"`
VisibilityMeters *float64 `json:"visibilityMeters,omitempty"`
ApparentTemperatureC *float64 `json:"apparentTemperatureC,omitempty"`
CloudCoverPercent *float64 `json:"cloudCoverPercent,omitempty"`
// Precipitation (forecast-specific). Keep these generic and provider-independent.
ProbabilityOfPrecipitationPercent *float64 `json:"probabilityOfPrecipitationPercent,omitempty"`
// Quantitative precip is not universally available, but OpenWeather/Open-Meteo often supply it.
// Use liquid-equivalent mm for interoperability.
PrecipitationAmountMm *float64 `json:"precipitationAmountMm,omitempty"`
SnowfallDepthMM *float64 `json:"snowfallDepthMm,omitempty"`
// Optional extras that some providers supply and downstream might care about.
UVIndex *float64 `json:"uvIndex,omitempty"`
}