Files

109 lines
5.0 KiB
Go
Raw Permalink Normal View History

// FILE: 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 WeatherObservation’s 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
2026-06-10 20:28:41 +00:00
// Some providers include both a generated time and a subsequent update time.
// Keep UpdatedAt optional; many providers won’t 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), when applicable.
// Some products (notably narrative) may not provide or imply a canonical WMO code.
ConditionCode *WMOCode `json:"conditionCode,omitempty"`
2026-03-26 21:35:08 -05:00
// Human-facing narrative summary for this period.
TextDescription string `json:"textDescription,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"`
}