Skip to content

Instantly share code, notes, and snippets.

@farski
Created September 23, 2014 14:44
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save farski/7d4049ac401c16c3adc6 to your computer and use it in GitHub Desktop.
Save farski/7d4049ac401c16c3adc6 to your computer and use it in GitHub Desktop.
KV_KEY = 'forecast-alert-datapoint'
module.exports = (robot) ->
postWeatherAlert = (json, callback) ->
postMessage = callback
now = new Date()
# This function posts an alert about the current forecast data always. It
# doesn't determine if the alert should be posted.
dataPoints = json['minutely']['data']
dataPointsWithPrecipitation = []
mostIntenseDataPoint = {}
mostProbableDataPoint = {}
for dataPoint in dataPoints
intensity = dataPoint['precipIntensity'] || 0
probability = dataPoint['precipProbability'] || 0
if intensity > 0
dataPointsWithPrecipitation.push(dataPoint)
hightIntensity = mostIntenseDataPoint['precipIntensity'] || 0
if intensity > hightIntensity
mostIntenseDataPoint = dataPoint
hightProbability = mostProbableDataPoint['precipProbability'] || 0
if probability > hightProbability
mostProbableDataPoint = dataPoint
_minutes = dataPointsWithPrecipitation.length
probability = mostProbableDataPoint['precipProbability']
_percent = Math.round(probability * 100)
intensity = mostIntenseDataPoint['precipIntensity']
if intensity >= 0.4
_intensity = 'heavy'
else if intensity >= 0.1
_intensity = 'moderate'
else if intensity >= 0.02
_intensity = 'light'
else
_intensity = 'very light'
timestamp = mostIntenseDataPoint['time']
date = new Date(timestamp * 1000)
delta = (date - now)
_delay = "#{Math.round(delta / 60 / 1000)}"
_now = new Date().getTime()
_link = "http://forecast.io/#/f/LAT,LNG/#{Math.round(_now / 1000)}"
msg = "WEATHER: #{_percent}% chance of inclement weather in the next hour for at least #{_minutes} minutes. It will be worst in #{_delay} minutes (#{_intensity} precipitation). #{_link}"
postMessage(msg, mostIntenseDataPoint)
handleClear = (json, callback) ->
postMessage = callback
alertDataPoint = robot.brain.get(KV_KEY) || {}
alertIntensity = alertDataPoint['precipIntensity'] || 0
if alertIntensity == 0
# This is where we end up most of the time (clear forecast currently
# following a clear forecast previously); no need to do anything
console.log '[Forecast] Continued clear weather.'
return
else
# Forecast has cleared after a period of inclement weather; post a
# notification (not checking time since last alert because this seems like
# very important information, and should be posted regardless)
console.log '[Forecast] Weather has cleared.'
dataPoints = json['minutely']['data']
dataPoint = dataPoints[0]
msg = 'WEATHER: The weather should be clear for at least an hour.'
postMessage(msg, dataPoint)
handleNewWeather = (json, callback) ->
isAnomaly = false
# This is a new inclement weather forecast. As long as it doesn't seem to be
# bogus data there should be an alert for it
dataPointsWithPrecipitation = []
mostIntenseDataPoint = {}
totalIntensity = 0
dataPoints = json['minutely']['data']
for dataPoint in dataPoints
intensity = dataPoint['precipIntensity'] || 0
totalIntensity += intensity
if intensity > 0
dataPointsWithPrecipitation.push(dataPoint)
hightIntensity = mostIntenseDataPoint['precipIntensity'] || 0
mostIntenseDataPoint = dataPoint if intensity > hightIntensity
isAnomaly = true if dataPointsWithPrecipitation.length < 5
isAnomaly = true if mostIntenseDataPoint['precipProbability'] < 0.20
isAnomaly = true if totalIntensity < (3 * mostIntenseDataPoint['precipIntensity'])
if !isAnomaly
console.log '[Forecast] Posting alert for new inclement weather'
postWeatherAlert(json, callback)
handleContinuingWeather = (json, callback) ->
now = new Date()
postMessage = callback
alertDataPoint = robot.brain.get(KV_KEY) || {}
alertIntensity = alertDataPoint['precipIntensity'] || 0
alertTime = alertDataPoint['__alertTime']
since = (now - alertTime)
if since > (3 * 60 * 60 * 1000)
# Three hours is long enough to post a new alert regardless of severity.
# Not checking for anomalies because 3+ hours of bad weather is very
# unlikely to be bad data
console.log '[Forecast] Posting reminder alert'
postWeatherAlert(json, callback)
else
# If it's been less than three hours only post an alert if the weather is
# getting significantly worse or there's enough data to predict a break
# in the weather
mostIntenseDataPoint = {}
dataPoints = json['minutely']['data']
for dataPoint in dataPoints
intensity = dataPoint['precipIntensity'] || 0
totalIntensity += intensity
if intensity > 0
dataPointsWithPrecipitation.push(dataPoint)
hightIntensity = mostIntenseDataPoint['precipIntensity'] || 0
mostIntenseDataPoint = dataPoint if intensity > hightIntensity
hightIntensity = mostIntenseDataPoint['precipIntensity']
if hightIntensity > (2 * alertIntensity) && hightIntensity > 0.072
# There's weather in the forecast that is at least twice as bad as the
# weather was at the last alert so it's worth posting another alert
console.log '[Forecast] Posting intensifying alert'
postWeatherAlert(json, callback)
return
trailingClearDataPoints = []
dataPoints.reverse()
for dataPoint in dataPoints
intensity = dataPoint['precipIntensity'] || 0
break if intensity > 0
trailingClearDataPoints.push(dataPoint)
dataPoints.reverse()
if trailingClearDataPoints.length > 30
# If at least the last 30 minutes of the current forecast is clear post
# an alert about the break in the weather. The currently cached data
# point is getting rolled over with this notification so that the cache
# still represents bad weather
console.log '[Forecast] Posting weather break alert'
msg = 'WEATHER: There should be a break in the weather for at least 30 minutes within the hour.'
postMessage(msg, alertDataPoint)
handleWeather = (json, callback) ->
alertDataPoint = robot.brain.get(KV_KEY) || {}
alertIntensity = alertDataPoint['precipIntensity'] || 0
if alertIntensity == 0
handleNewWeather(json, callback)
else
handleContinuingWeather(json, callback)
handleJSON = (json, callback) ->
if json['minutely']
dataPoints = json['minutely']['data'] || []
if dataPoints.length > 0
totalIntensity = 0
for dataPoint in dataPoints
totalIntensity += (dataPoint['precipIntensity'] || 0)
if totalIntensity == 0
handleClear(json, callback)
else
handleWeather(json, callback)
fetchForecast = (callback) ->
forecastKey = process.env.FORECAST_KEY
location = 'LAT,LNG'
exclude = 'hourly,daily,flags'
base_url = "https://api.forecast.io/forecast/#{forecastKey}/#{location}"
url = "#{base_url}?units=us&exclude=#{exclude}"
console.log "[Forecast] Requesting forecast data: #{url}"
robot.http(url).get() (err, res, body) ->
if !err
json = JSON.parse(body)
handleJSON(json, callback)
forecast = ->
now = new Date()
isWeekday = (now.getDay() < 6)
isBusinessHours = (now.getUTCHours() >= 11 && now.getUTCHours() <= 23)
if isWeekday && isBusinessHours
# Only run during business hours
room = 'some_room@conf.hipchat.com'
fetchForecast (msg, dataPoint) ->
# Cache the data point related to this alert and send the message to
# the room
dataPoint['__alertTime'] = now
robot.brain.set(KV_KEY, dataPoint)
robot.messageRoom(room, msg)
else
# Remove the alert data cache between work days
console.log '[Forecast] Sleeping'
robot.brain.remove(KV_KEY)
console.log '[Forecast] Starting weather service...'
setInterval forecast, (5 * 60 * 1000)
forecast()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment