Alexa-news-stentiment-evalu.../f-ask/samples/tidepooler/tidepooler.py

299 lines
9.6 KiB
Python
Raw Normal View History

2019-04-12 19:32:29 +00:00
import os
import logging
import datetime
import math
import re
from six.moves.urllib.request import urlopen
from six.moves.urllib.parse import urlencode
import aniso8601
from flask import Flask, json, render_template
from flask_ask import Ask, request, session, question, statement
ENDPOINT = "http://tidesandcurrents.noaa.gov/api/datagetter"
SESSION_CITY = "city"
SESSION_DATE = "date"
# NOAA station codes
STATION_CODE_SEATTLE = "9447130"
STATION_CODE_SAN_FRANCISCO = "9414290"
STATION_CODE_MONTEREY = "9413450"
STATION_CODE_LOS_ANGELES = "9410660"
STATION_CODE_SAN_DIEGO = "9410170"
STATION_CODE_BOSTON = "8443970"
STATION_CODE_NEW_YORK = "8518750"
STATION_CODE_VIRGINIA_BEACH = "8638863"
STATION_CODE_WILMINGTON = "8658163"
STATION_CODE_CHARLESTON = "8665530"
STATION_CODE_BEAUFORT = "8656483"
STATION_CODE_MYRTLE_BEACH = "8661070"
STATION_CODE_MIAMI = "8723214"
STATION_CODE_TAMPA = "8726667"
STATION_CODE_NEW_ORLEANS = "8761927"
STATION_CODE_GALVESTON = "8771341"
STATIONS = {}
STATIONS["seattle"] = STATION_CODE_SEATTLE
STATIONS["san francisco"] = STATION_CODE_SAN_FRANCISCO
STATIONS["monterey"] = STATION_CODE_MONTEREY
STATIONS["los angeles"] = STATION_CODE_LOS_ANGELES
STATIONS["san diego"] = STATION_CODE_SAN_DIEGO
STATIONS["boston"] = STATION_CODE_BOSTON
STATIONS["new york"] = STATION_CODE_NEW_YORK
STATIONS["virginia beach"] = STATION_CODE_VIRGINIA_BEACH
STATIONS["wilmington"] = STATION_CODE_WILMINGTON
STATIONS["charleston"] = STATION_CODE_CHARLESTON
STATIONS["beaufort"] = STATION_CODE_BEAUFORT
STATIONS["myrtle beach"] = STATION_CODE_MYRTLE_BEACH
STATIONS["miami"] = STATION_CODE_MIAMI
STATIONS["tampa"] = STATION_CODE_TAMPA
STATIONS["new orleans"] = STATION_CODE_NEW_ORLEANS
STATIONS["galveston"] = STATION_CODE_GALVESTON
app = Flask(__name__)
ask = Ask(app, "/")
logging.getLogger('flask_ask').setLevel(logging.DEBUG)
class TideInfo(object):
def __init__(self):
self.first_high_tide_time = None
self.first_high_tide_height = None
self.low_tide_time = None
self.low_tide_height = None
self.second_high_tide_time = None
self.second_high_tide_height = None
@ask.launch
def launch():
welcome_text = render_template('welcome')
help_text = render_template('help')
return question(welcome_text).reprompt(help_text)
@ask.intent('OneshotTideIntent',
mapping={'city': 'City', 'date': 'Date'},
convert={'date': 'date'},
default={'city': 'seattle', 'date': datetime.date.today })
def one_shot_tide(city, date):
if city.lower() not in STATIONS:
return supported_cities()
return _make_tide_request(city, date)
@ask.intent('DialogTideIntent',
mapping={'city': 'City', 'date': 'Date'},
convert={'date': 'date'})
def dialog_tide(city, date):
if city is not None:
if city.lower() not in STATIONS:
return supported_cities()
if SESSION_DATE not in session.attributes:
session.attributes[SESSION_CITY] = city
return _dialog_date(city)
date = aniso8601.parse_date(session.attributes[SESSION_DATE])
return _make_tide_request(city, date)
elif date is not None:
if SESSION_CITY not in session.attributes:
session.attributes[SESSION_DATE] = date.isoformat()
return _dialog_city(date)
city = session.attributes[SESSION_CITY]
return _make_tide_request(city, date)
else:
return _dialog_no_slot()
@ask.intent('SupportedCitiesIntent')
def supported_cities():
cities = ", ".join(sorted(STATIONS.keys()))
list_cities_text = render_template('list_cities', cities=cities)
list_cities_reprompt_text = render_template('list_cities_reprompt')
return question(list_cities_text).reprompt(list_cities_reprompt_text)
@ask.intent('AMAZON.HelpIntent')
def help():
help_text = render_template('help')
list_cities_reprompt_text = render_template('list_cities_reprompt')
return question(help_text).reprompt(list_cities_reprompt_text)
@ask.intent('AMAZON.StopIntent')
def stop():
bye_text = render_template('bye')
return statement(bye_text)
@ask.intent('AMAZON.CancelIntent')
def cancel():
bye_text = render_template('bye')
return statement(bye_text)
@ask.session_ended
def session_ended():
return "{}", 200
@app.template_filter()
def humanize_date(dt):
# http://stackoverflow.com/a/20007730/1163855
ordinal = lambda n: "%d%s" % (n,"tsnrhtdd"[(n/10%10!=1)*(n%10<4)*n%10::4])
month_and_day_of_week = dt.strftime('%A %B')
day_of_month = ordinal(dt.day)
year = dt.year if dt.year != datetime.datetime.now().year else ""
formatted_date = "{} {} {}".format(month_and_day_of_week, day_of_month, year)
formatted_date = re.sub('\s+', ' ', formatted_date)
return formatted_date
@app.template_filter()
def humanize_time(dt):
morning_threshold = 12
afternoon_threshold = 17
evening_threshold = 20
hour_24 = dt.hour
if hour_24 < morning_threshold:
period_of_day = "in the morning"
elif hour_24 < afternoon_threshold:
period_of_day = "in the afternoon"
elif hour_24 < evening_threshold:
period_of_day = "in the evening"
else:
period_of_day = " at night"
the_time = dt.strftime('%I:%M')
formatted_time = "{} {}".format(the_time, period_of_day)
return formatted_time
@app.template_filter()
def humanize_height(height):
round_down_threshold = 0.25
round_to_half_threshold = 0.75
is_negative = False
if height < 0:
height = abs(height)
is_negative = True
remainder = height % 1
if remainder < round_down_threshold:
remainder_text = ""
feet = int(math.floor(height))
elif remainder < round_to_half_threshold:
remainder_text = "and a half"
feet = int(math.floor(height))
else:
remainder_text = ""
feet = int(math.floor(height))
if is_negative:
feet *= -1
formatted_height = "{} {} feet".format(feet, remainder_text)
formatted_height = re.sub('\s+', ' ', formatted_height)
return formatted_height
def _dialog_no_slot():
if SESSION_CITY in session.attributes:
date_dialog2_text = render_template('date_dialog2')
return question(date_dialog2_text).reprompt(date_dialog2_text)
else:
return supported_cities()
def _dialog_date(city):
date_dialog_text = render_template('date_dialog', city=city)
date_dialog_reprompt_text = render_template('date_dialog_reprompt')
return question(date_dialog_text).reprompt(date_dialog_reprompt_text)
def _dialog_city(date):
session.attributes[SESSION_DATE] = date
session.attributes_encoder = _json_date_handler
city_dialog_text = render_template('city_dialog', date=date)
city_dialog_reprompt_text = render_template('city_dialog_reprompt')
return question(city_dialog_text).reprompt(city_dialog_reprompt_text)
def _json_date_handler(obj):
if isinstance(obj, datetime.date):
return obj.isoformat()
def _make_tide_request(city, date):
station = STATIONS.get(city.lower())
noaa_api_params = {
'station': station,
'product': 'predictions',
'datum': 'MLLW',
'units': 'english',
'time_zone': 'lst_ldt',
'format': 'json'
}
if date == datetime.date.today():
noaa_api_params['date'] = 'today'
else:
noaa_api_params['begin_date'] = date.strftime('%Y%m%d')
noaa_api_params['range'] = 24
url = ENDPOINT + "?" + urlencode(noaa_api_params)
resp_body = urlopen(url).read()
if len(resp_body) == 0:
statement_text = render_template('noaa_problem')
else:
noaa_response_obj = json.loads(resp_body)
predictions = noaa_response_obj['predictions']
tideinfo = _find_tide_info(predictions)
statement_text = render_template('tide_info', date=date, city=city, tideinfo=tideinfo)
return statement(statement_text).simple_card("Tide Pooler", statement_text)
def _find_tide_info(predictions):
"""
Algorithm to find the 2 high tides for the day, the first of which is smaller and occurs
mid-day, the second of which is larger and typically in the evening.
"""
last_prediction = None
first_high_tide = None
second_high_tide = None
low_tide = None
first_tide_done = False
for prediction in predictions:
if last_prediction is None:
last_prediction = prediction
continue
if last_prediction['v'] < prediction['v']:
if not first_tide_done:
first_high_tide = prediction
else:
second_high_tide = prediction
else: # we're decreasing
if not first_tide_done and first_high_tide is not None:
first_tide_done = True
elif second_high_tide is not None:
break # we're decreasing after having found the 2nd tide. We're done.
if first_tide_done:
low_tide = prediction
last_prediction = prediction
fmt = '%Y-%m-%d %H:%M'
parse = datetime.datetime.strptime
tideinfo = TideInfo()
tideinfo.first_high_tide_time = parse(first_high_tide['t'], fmt)
tideinfo.first_high_tide_height = float(first_high_tide['v'])
tideinfo.second_high_tide_time = parse(second_high_tide['t'], fmt)
tideinfo.second_high_tide_height = float(second_high_tide['v'])
tideinfo.low_tide_time = parse(low_tide['t'], fmt)
tideinfo.low_tide_height = float(low_tide['v'])
return tideinfo
if __name__ == '__main__':
if 'ASK_VERIFY_REQUESTS' in os.environ:
verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower()
if verify == 'false':
app.config['ASK_VERIFY_REQUESTS'] = False
app.run(debug=True)