Class: Weather::VisualCrossingClient
- Inherits:
-
Object
- Object
- Weather::VisualCrossingClient
- Defined in:
- app/services/weather/visual_crossing_client.rb
Overview
Client for the Visual Crossing Timeline Weather API.
Supports current conditions, historical data, and forecasts for any location
(city name, postal code, address, or lat/lng) and any date range.
The source field on each day tells the LLM whether data comes from:
"obs" = historical weather station observations
"fcst" = 15-day model forecast
"histfcst" = historical forecast model
"stats" = long-term statistical forecast (beyond 15 days)
"comb" = mix of sources (e.g. today = obs + fcst)
Defined Under Namespace
Classes: Response
Constant Summary collapse
- BASE_URL =
'https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/'- UNIT_GROUPS =
%w[us metric uk base].freeze
- DAY_ELEMENTS =
Daily elements.
sourceis critical context: tells the LLM whether each day is
observed historical data, a model forecast, or a statistical projection. %w[ datetime temp tempmax tempmin feelslike feelslikemax feelslikemin humidity dew precip precipprob preciptype precipcover snow snowdepth windspeed windgust winddir conditions description source sunrise sunset uvindex cloudcover visibility pressure ].freeze
- HOUR_ELEMENTS =
Hourly elements — excludes day-only fields (tempmax/min, feelslikemax/min,
precipcover, sunrise/sunset) and adds solar data available at hourly resolution. %w[ datetime temp feelslike humidity dew precip precipprob preciptype snow snowdepth windspeed windgust winddir conditions source uvindex cloudcover visibility pressure solarradiation solarenergy ].freeze
- CURRENT_ELEMENTS =
Current-conditions fields. The API always returns these as current snapshot.
%w[ datetime temp feelslike humidity dew precip preciptype snow snowdepth windspeed windgust winddir conditions uvindex cloudcover visibility pressure ].freeze
Instance Method Summary collapse
-
#get_forecast_for(latitude, longitude, datetime) ⇒ Hash?
Fetch the forecast for a single date at a lat/lng coordinate.
-
#initialize(api_key: nil) ⇒ VisualCrossingClient
constructor
A new instance of VisualCrossingClient.
-
#lookup(location:, start_date: nil, end_date: nil, unit_group: 'us', include_hours: false) ⇒ Response
Fetch weather data for a location and optional date range.
Constructor Details
#initialize(api_key: nil) ⇒ VisualCrossingClient
Returns a new instance of VisualCrossingClient.
65 66 67 68 69 70 |
# File 'app/services/weather/visual_crossing_client.rb', line 65 def initialize(api_key: nil) @api_key = api_key || Heatwave::Configuration.fetch(:visual_crossing_weather, :api_key) raise ArgumentError, 'Visual Crossing API key not configured' if @api_key.blank? @connection = build_connection end |
Instance Method Details
#get_forecast_for(latitude, longitude, datetime) ⇒ Hash?
Fetch the forecast for a single date at a lat/lng coordinate.
Used by AverageMonthlyTemperature to build monthly averages; returns the
raw day hash (string-keyed) so callers can read fields like "temp",
"tempmax", and "tempmin" directly without unwrapping a Response object.
120 121 122 123 124 125 126 |
# File 'app/services/weather/visual_crossing_client.rb', line 120 def get_forecast_for(latitude, longitude, datetime) date = datetime.to_date.to_s result = lookup(location: "#{latitude}, #{longitude}", start_date: date, end_date: date) return nil unless result.success? result.data&.dig('days')&.first end |
#lookup(location:, start_date: nil, end_date: nil, unit_group: 'us', include_hours: false) ⇒ Response
Fetch weather data for a location and optional date range.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'app/services/weather/visual_crossing_client.rb', line 82 def lookup(location:, start_date: nil, end_date: nil, unit_group: 'us', include_hours: false) unit_group = UNIT_GROUPS.include?(unit_group) ? unit_group : 'us' path = build_path(location, start_date, end_date) sections = include_hours ? 'days,hours,current' : 'days,current' elements = include_hours ? (DAY_ELEMENTS + HOUR_ELEMENTS).uniq.join(',') : DAY_ELEMENTS.join(',') response = @connection.get(path) do |req| req.params['key'] = @api_key req.params['unitGroup'] = unit_group req.params['contentType'] = 'json' req.params['include'] = sections req.params['elements'] = elements req.params['options'] = 'nonulls' # strip nulls server-side to reduce payload end if response.success? Response.new(success?: true, data: format_response(response.body, unit_group), error: nil) else error_msg = extract_error(response) Rails.logger.warn("[VisualCrossingClient] API error #{response.status}: #{error_msg}") Response.new(success?: false, data: nil, error: error_msg) end rescue Faraday::Error => e Rails.logger.error("[VisualCrossingClient] Connection error: #{e.}") Response.new(success?: false, data: nil, error: "Connection error: #{e.}") end |