class WeatherRetriever class << self def get(date, postal_code) position = Position.find_or_create_by(postal_code: postal_code) weather = Weather.find_by(date: date, position_id: position&.id) return weather if weather.present? if position&.latitude.blank? || position&.longitude.blank? Rails.logger.warn "No coordinates found for postal_code #{postal_code}: #{position.inspect}" return end forecast = get_forecast(date, position) if forecast.status != 200 Rails.logger.warn "No forecast found for position #{position.inspect}: response code was #{forecast.status}, headers were #{forecast.headers}, body contained #{forecast.body}" return end create_weather(forecast, position.id) end private def get_forecast(date, position) Tomorrowiorb.forecast( "#{position.latitude},#{position.longitude}", ["1d"], "imperial" ) end def create_weather(forecast, position_id) today = JSON.parse(forecast.body, symbolize_names: true).dig(:timelines, :daily, 0) the_time = today.dig(:time) today = today.dig(:values) rain_intensity = today.dig(:rainIntensityAvg) sleet_intensity = today.dig(:sleetIntensityAvg) snow_intensity = today.dig(:snowIntensityAvg) icon = get_icon_legacy(today) summary = "General conditions are #{icon}, with an average temperature of #{today[:temperatureAvg]}." Weather.create( date: Date.strptime(the_time, "%Y-%m-%d"), humidity: today.dig(:humidityAvg).round, icon: icon, position_id: position_id, precip_intensity: rain_intensity + sleet_intensity + snow_intensity, pressure: today.dig(:pressureSurfaceLevelAvg), summary: summary, temperature_max: today.dig(:temperatureMax), temperature_min: today.dig(:temperatureMin) ) end def get_icon_legacy(today) # Our icons do not coverage their full range of weather codes. We could pull in their icons (linked below) on the frontend to expand options # This method adapts their weather codes to our existing icons as best as possible # Icons and codes found here: https://docs.tomorrow.io/reference/data-layers-weather-codes # Icon files here: https://github.com/Tomorrow-IO-API/tomorrow-weather-codes # Daily forecast is always daytime weather codes / icons regardless of actual time code = if today["weatherCodeMin"] today["weatherCodeMin"] elsif today["weatherCodeFullDay"] today["weatherCodeFullDay"] else today["weatherCode"] end case code when 1000, 1100 "clear-day" when 1101 "partly-cloudy-day" when 1102, 1001, 8000 "cloudy" when 2000, 2100 "fog" when 4000, 4001, 4200, 4201 "rain" when 5000, 5001, 5100, 5101 "snow" when 6000, 6001, 6200, 6201, 7000, 7101, 7102 "sleet" else "default" end end end end