Пример #1
0
def test_search_with_location():
    # Make a valid search using a location string
    yelp = YelpAPI()
    result = yelp.business_search(term="sushi",
                                  location="lafayette, in",
                                  open_now=False)
    assert "businesses" in result
Пример #2
0
def get_categories(term):
    """
    Semantically parse a raw search term into Yelp categories
    :param term: search term the user entered to be semantically parsed
    :return: List of Yelp categories that the search term best matches
    """
    # Query NYC with the search term in a 20 mile radius
    yelp = YelpAPI()
    result = yelp.business_search(term=term,
                                  location="manhattan, ny",
                                  radius=20,
                                  open_now=False)

    if "businesses" not in result:
        return []

    # Otherwise, get up to 10 restaurants from the list
    restaurants = result["businesses"]
    if len(restaurants) > 10:
        restaurants = restaurants[:10]

    # Parse the unique category aliases from the restaurants
    categories = list(
        set.union(
            *[{cat_dict["alias"]
               for cat_dict in restaurant["categories"]}
              for restaurant in restaurants]))

    return categories
Пример #3
0
def test_search_with_coordinates():
    # Make a valid search using location coordinates
    yelp = YelpAPI()
    result = yelp.business_search(term="sushi",
                                  location=(40.4167, -86.8753),
                                  open_now=False)
    assert "businesses" in result
Пример #4
0
def get_categories_from_id(rest_id):
    """
    Get the categories associated with the restaurant ID
    :param rest_id: Yelp restaurant ID string
    :return: List of categories associated with the restaurant
    """
    yelp = YelpAPI()
    restaurant = yelp.business_details(rest_id)
    if "categories" in restaurant:
        return [cat_dict["alias"] for cat_dict in restaurant["categories"]]
    return []
Пример #5
0
def test_get_reviews():
    # Initialize User and test params
    user = User("Ben")
    rest_id = "5Po65ETa-YvsWwg68Ab9nA"  # Harry's
    yelp = YelpAPI()

    # Ensure the object is properly parsed from Yelp
    user.add_review(rest_id)
    assert user.get_reviews(
        yelp)[0]["restaurant"]["name"] == "Harry's Chocolate Shop"
Пример #6
0
def test_cache():
    # Initialize recommender test params
    recommender = Recommender(YelpAPI())
    user = User("Ben")
    ID = "1234"

    # Ensure the recommender can cache the entry
    assert not recommender.cache.is_cached(user.name, ID)
    recommender.cache_restaurant(user, ID)
    assert recommender.cache.is_cached(user.name, ID)
Пример #7
0
def test_get_good_restaurant():
    # Initialize recommender and test params
    recommender = Recommender(YelpAPI())
    user = User("Ben")
    params = {
        "food": "sushi",
        "price": "$$",
        "distance": "5",
        "location": (40.4167, -86.8753),
        "open_now": False
    }

    # Ensure a restaurant is returned
    result = recommender.get_restaurant(user, params)
    assert result["Name"] != "No matches found!"
Пример #8
0
def test_get_bad_restaurant():
    # Initialize recommender and test params
    recommender = Recommender(YelpAPI())
    user = User("Ben")
    params = {
        "food": "bowl of nails without any milk",
        "price": "$$",
        "distance": "1",
        "location": (90.0000, 45.0000),
        "open_now": False
    }

    # Ensure no recommendation is returned
    result = recommender.get_restaurant(user, params)
    assert result["Name"] == "No matches found!"
Пример #9
0
def test_business_reviews_bad_id():
    # Try to get business reviews using a non-existent business ID
    yelp = YelpAPI()
    result = yelp.business_reviews(business_id="bad-business-id")
    assert "error" in result
Пример #10
0
def test_business_reviews():
    # Get business reviews using a valid business ID
    yelp = YelpAPI()
    result = yelp.business_reviews(business_id="sushi-don-lafayette")
    assert "possible_languages" in result
Пример #11
0
def test_business_details():
    # Get business details using a valid business ID
    yelp = YelpAPI()
    result = yelp.business_details(business_id="sushi-don-lafayette")
    assert "id" in result
Пример #12
0
def test_bad_search():
    # Try to make a search using an invalid location value
    yelp = YelpAPI()
    result = yelp.business_search(term="sushi", location=10, open_now=False)
    assert result is None
Пример #13
0
from flask import Flask, request
from flask_cors import CORS
from backend.flaskr.authentication_utils import authenticate_user, register_user
from backend.flaskr.database_utils import DBConnection
from backend.flaskr.yelp_api_utils import YelpAPI
from backend.flaskr.recommender import Recommender
from backend.flaskr.user import UserList

# Instantiate app
app = Flask(__name__)
CORS(app)


DBConnection.setup(app)         # Configure DB connection
users = UserList()              # Create mapping from usernames to User objects
yelp = YelpAPI()                # Initialize one YelpAPI for the app
recommender = Recommender(yelp) # Instantiate the recommender to generate suggestions


def _user(name):
    """
    Helper function to get the User object for a given name or
    add it to the UserList if it is not already in memory
    """
    if name not in users:
        users.add(name)
    return users[name]


@app.route('/cravr/login', methods=["POST"])
def login():
Пример #14
0
class RecommendationModel:
    """
    Recommendation model class for a user to get personalized suggestions
    """

    yelp = YelpAPI()

    def __init__(self, method, data):
        """
        THIS CONSTRUCTOR IS "PRIVATE" AND SHOULD NOT BE CALLED.
        Initialization logic is handled in static factory methods.
        Description of fields:
            num_reviews = number of reviews applied to this model
            food_genres  = dict of dicts:
                count      = number of interactions the user has had this category
                propensity = floats for how much the user likes each key [-10,10]
            importances  = dict of floats for which restaurant features are most important [0,10]
        """
        if method == "state":
            self.num_reviews = data["num_reviews"]
            self.food_genres = data["food_genres"]
            self.importances = data["importances"]

        elif method == "quiz":
            self.num_reviews = 0
            self.food_genres = create_genre_dict(data.pop("favorite"),
                                                 data.pop("leastFavorite"))
            self.importances = {
                k: max(1, min(9, 2 * int(v) - 1))
                for k, v in data.items()
            }

        elif method == "blank":
            self.num_reviews = 0
            self.food_genres = {}
            self.importances = {k: 5 for k in IMPORTANCE_KEYS}

        else:
            raise ValueError("{} is not a valid method".format(method))

    @staticmethod
    def from_state(state):
        """
        Static factory method to create a RecommendationModel object from json from the database
        :param state: Model state JSON object as stored in the database
        :return: New RecommendationModel with the state taken from the JSON
        """
        return RecommendationModel("state", state)

    @staticmethod
    def from_quiz(quiz):
        """
        Static factory method to initialize the model weights according to the user's quiz answers
        :param quiz: Object containing the user's quiz answers
        :return: New RecommendationModel with initialized model weights
        """
        return RecommendationModel("quiz", quiz)

    @staticmethod
    def from_blank():
        """
        Static factory method to initialize default model weights used for testing only
        :return: New RecommendationModel with default initialized model weights
        """
        return RecommendationModel("blank", None)

    def get_bestaurant(self, restaurants):
        """
        Portmanteau for get best restaurant according to the model parameters
        :param restaurants: List of restaurant objects to be evaluated
        :return: Optimal item in restaurants
        """
        def score(restaurant):
            """Compute a numerical score for how likely the user is to like this restaurant"""
            # Start with a multiple of the yelp rating
            result = 10 * restaurant["rating"]

            # Adjust if the user likes or dislikes this genre of restaurant
            if "categories" in restaurant:
                categories = [
                    cat_dict["alias"] for cat_dict in restaurant["categories"]
                ]
                for cat in categories:
                    if cat in self.food_genres:
                        result += self.food_genres[cat]["propensity"]

            # If we have Cravr review data, scale it based on the user's importances
            review_data = read_restaurant_data(restaurant["id"])
            if review_data:
                for k in IMPORTANCE_KEYS:
                    result += self.importances[k] * (review_data[k] - 2.5)

            # Penalize long distances by subtracting the squared distance
            dist = restaurant["distance"] / 1609.34
            result -= dist**2

            return result

        return max([(restaurant, score(restaurant))
                    for restaurant in restaurants],
                   key=lambda pair: pair[1])[0]

    def get_favorite_foods(self):
        """
        Get the food categories the user is most likely to enjoy
        :return: Category keys with positive propensities in decreasing order
        """
        cats = {
            k: v["propensity"]
            for k, v in self.food_genres.items() if v["propensity"] > 0
        }
        return sorted(cats, key=cats.get, reverse=True)

    def train_review(self, rest_id, review):
        """
        Update the model weights after processing a user review.
        :param rest_id: Yelp restaurant ID string
        :param review: Review object sent from the frontend
        :return: None
        """
        self.num_reviews += 1
        is_liked = bool(review.pop("repeat"))

        # Adjust the user's importances based on which factors drove the sentiment of their review
        self.importances = {
            k: max(
                0,
                min(
                    10, v + (1 if is_liked else -1) * (2 * review[k] - v) /
                    (1 + self.num_reviews / 5)))
            for k, v in self.importances.items()
        }

        # Invert the magnitude of the review if the user would not eat here again
        if not is_liked:
            review = {k: 6 - v for k, v in review.items()}

        # Compute the review sentiment scaled based on the user's importances
        sentiment = 0
        for k in IMPORTANCE_KEYS:
            sentiment += self.importances[k] * review[k] * (1 if is_liked else
                                                            -1)
        sentiment /= len(IMPORTANCE_KEYS)  # Scale into [-10,10]

        # Adjust the food_genre weights based on this sentiment
        self.train_conversion(rest_id, sentiment)

    def train_conversion(self, rest_id, sentiment):
        """
        Update the food_genre weights for any conversion event
        :param rest_id: Yelp restaurant ID string
        :param sentiment: Positive or negative float [-10,10] for how (dis)liked the restaurant was
        """
        # Update the genre weights for the categories of the restaurant
        categories = get_categories_from_id(rest_id)
        for cat in categories:
            # Have we seen this category before?
            if cat in self.food_genres:
                # Update the count and use it as an attenuation factor
                self.food_genres[cat]["count"] += 1
                self.food_genres[cat]["propensity"] += \
                    sentiment / (1 + self.food_genres[cat]["count"] / 5)

                # Clamp the value to be within [-10,10]
                self.food_genres[cat]["propensity"] = max(
                    -10, min(10, self.food_genres[cat]["propensity"]))
            # If not, create a new entry with this sentiment
            else:
                self.food_genres[cat] = {"count": 1, "propensity": sentiment}