Exemple #1
0
def place_delete(place_id, place_key_str):
    try:
        pkey = Place.make_key(place_id, place_key_str)
        cluster_ratings = ClusterRating.get_list({'place': pkey.urlsafe()})
        for cr in cluster_ratings:
            ClusterRating.delete(cr.key)
        user_ratings = Rating.get_list({'place': pkey.urlsafe()})
        for r in user_ratings:
            Rating.delete(r.key)
        res = Place.delete(pkey)
    except TypeError, e:
        return None, str(e), 400
Exemple #2
0
 def get(self):
     user_id = logic.get_current_userid(self.request.cookies.get('user'))
     if user_id is None:
         self.redirect('/')
         return
     
     rest_key = self.request.GET.get('rest_id')
     user, status, errcode = logic.user_get(user_id, None)
     if status != "OK":
         self.render("error.html", {'error_code': errcode, 'error_string': status, 'lang': LANG})
         return
     discount = Discount()
     discount.place = Place.make_key(None, rest_key)
     
     try:
         discount = Discount.to_json(discount, None, None)
         self.render('discount_edit.html', {'is_new': 'True', 'discount': discount, 'user': user, 'lang' : LANG });
     except TypeError, e:
         self.render("error.html", {'error_code': 500, 'error_string': str(e), 'lang': LANG})
         return
Exemple #3
0
def rating_count(user_id=None, user_str=None, place_id=None, place_str=None):
    """
    It counts the number of ratings for a user, a place or both.

    Parameters:
    - user_id and user_str: key (str = urlsafe) for the PFuser, only one needed
    - place_id and place_str: key (str = urlsafe) for the Place, only one needed


    It returns a tuple: 
    - the number of ratings,
    - the status (a string indicating whether an error occurred)
    - the http code indicating the type of error, if any
    """
    try:
        user_key = PFuser.make_key(user_id, user_str)
        place_key = Place.make_key(place_id, place_str)
        count = Rating.count(user_key, place_key)
    except TypeError as e:
        return None, str(e), 400

    return count, "OK", 200
Exemple #4
0
def place_update(in_place, place_id, place_key_str):
    """
    It updates the place.

    Parameters:
    - in_place: the Place containing the information to update
    - place_id: the string id of the Place
    - place_key_str: the urlsafe string representing the key.

    Only one between place_id and place_key_str should be set, since they represent the same information, 
    but encoded in two different ways. They are both accepted for generality. If both are set, the id is used.

    It returns a tuple: 
    - the place with updated information (or None in case of errors in the input),
    - the status (a string indicating whether an error occurred),
    - the http code indicating the type of error, if any
    """
    try:
        res = Place.store(
            in_place, Place.make_key(place_id, place_key_str))
    except (TypeError, ValueError, InvalidKeyException) as e:
        return None, str(e), 400

    return res, "OK", 200
Exemple #5
0
def place_get(place_id, place_key_str):
    """
    It retrieves the place.

    Parameters:
    - place_id: the string id of the Place
    - place_key_str: the urlsafe string representing the key.

    Only one between place_id and place_key_str should be set, since they represent the same information, 
    but encoded in two different ways. They are both accepted for generality. If both are set, the id is used.

    It returns a tuple: 
    - the requested place (or None in case of errors in the input),
    - the status (a string indicating whether an error occurred),
    - the http code indicating the type of error, if any
    """
    try:
        key = Place.make_key(place_id, place_key_str)
        logging.info("KEY: " + str(key))
        place = Place.get_by_key(key)
    except TypeError as e:
        logging.error("TypeError on Place.get_by_key: " + str(e))
        return None, str(e), 400
    return place, "OK", 200
Exemple #6
0
def recommend(user_id, filters, purpose='dinner with tourists', n=5):
    """
    It computes the recommendations for the user, according to specified filters and parameters.
    When possible, the recommendation list is personalized, using the cluster-based algorithm.
    If the personalized algorithm fails to find the required number of recommended place, an average-based
    non-personalized recommendation algorithm is used.
    If still other places are needed, the recommendation list is filled with places ordered by distance from user. 

    Input:
    - user_id: is of the requester
    - filters: filters for places of interest for the user
    - purpose: the purpose the user is interested in
    - n: number of recommended places requested by the user

    Available filters:
    //- 'city': 'city!province!state!country'
        The 'city' filter contains the full description of the city, with values separated with a '!'. 
        This string is split and used to retrieve only the places that are in the specified city. 
        'null' is used if part of the full city description is not available [example: 'Trento!TN!null!Italy'
        or if a bigger reagion is considered [example: 'null!TN!null!Italy' retrieves all places in the province of Trento]
    - 'lat', 'lon' and 'max_dist': lat and lon indicates the user position, while max_dist is a measure expressed in meters 
        and represnt the radius of the circular region the user is interested in. 

    Returns a list of n places in json format
    """
    logging.info("recommender.recommend START - user_id=" + str(user_id) +
                 ', filters=' + str(filters) + ', purpose=' + str(purpose) + ', n=' + str(n))
    
    # places is already a json list
    start = datetime.now()
    user_max_dist = None
    if filters is not None and 'max_dist' in filters and filters['max_dist'] is not None and filters['max_dist'] > 0:
        user_max_dist = filters['max_dist']
        #get places for a larger area
        filters['max_dist'] = 2 * user_max_dist
    places, status, errcode = logic.place_list_get(filters, user_id)
    logging.info("RECOMMEND places loaded ")

    if status != "OK" or places is None or len(places) < 1:
        # the system do not know any place within these filters
        logging.info("recommender.recommend END - no places")
        logging.error(str(errcode) + ": " + status)
        return None
    logging.warning("Loaded places for double distance: " + str(datetime.now() - start))
    start = datetime.now()
    closest = []
    out_distance = []
    for p in places:
        if 'lat' in filters and 'lon' in filters and filters['lat'] is not None and filters['lon'] is not None:
            # add distance to user for each place
            p['distance'] = distance(
                    p['address']['lat'], p['address']['lon'], filters['lat'], filters['lon'])
        if p['distance'] is not None and user_max_dist is not None and p['distance'] <= user_max_dist:
            closest.append(p)
        else:
            out_distance.append(p)
    if len(closest) >= n:
        places = closest
    elif len(closest) == 0:
        places = out_distance
    else:
        #TODO: fill missing spaces with outliers?
        places = closest
    logging.warning("removing places that are too far: " + str(datetime.now() - start))
    place_ids = []
    if places is not None:
        place_ids = [Place.make_key(None, place['key']).id() for place in places]
    scores = None
    purpose_list = ["dinner with tourists", "romantic dinner", "dinner with friends", "best price/quality ratio"]
    start = datetime.now()
#     logging.warning("RECOMMEND START get cluster-based predictions for all purposes: " + str(start))
    for p in purpose_list:
        if p == purpose:
            start2 = datetime.now()
            scores = cluster_based(user_id, place_ids, p, n, loc_filters=filters)
            logging.warning("RECOMMEND END get cluster-based predictions: " + str(datetime.now()-start2))
        else:
            q = taskqueue.Queue('recommendations')
             
            task = taskqueue.Task(params={'user_id': user_id, 'place_ids': place_ids, 'purpose': p, 'n': n, 'loc_filters': str(filters)},
                url='/recommender/compute_cluster_based', method='POST', countdown=10)
            q.add(task)

    logging.warning("Getting recommendations from cluster and starting computation for other purposes: " + str(datetime.now() - start))
    log_text = "RECOMMEND scores from cluster-based : "
    if scores is None:
        log_text += "None"
    else:
        log_text += str(len(scores))
    logging.info(log_text)
    

    start = datetime.now()
    if scores is None or (len(scores) < n and len(scores) < len(places)):
        # cluster-based recommendation failed
        # non-personalized recommendation
        rating_filters = {}
        if places is not None:
            rating_filters['places'] = place_ids
        rating_filters['purpose'] = purpose
        ratings = load_data(rating_filters)
        if ratings is None:
            logging.info("ratings for places: None")
        else:
            logging.info("ratings for places: " + str(len(ratings)))
        items = {}
        if ratings is not None:
            for other in ratings:
                if other != user_id:
                    for item in ratings[other]:
                        if purpose in ratings[other][item]:
                            if item not in items.keys():
                                items[item] = []
                            items[item].append(ratings[other][item][purpose])
 
        avg_scores = [(sum(items[item]) / len(items[item]), item)
                      for item in items]
        logging.info("avg_scores: " + str(len(avg_scores)))
        filters = {'purpose': purpose, 'user': user_id}
        if places is not None:
            filters['places'] = place_ids
        
        user_ratings = Rating.get_list(filters)
        logging.info("Loaded user ratings: " + str(len(user_ratings)))
        if scores is None:
            scores = []
        for value, key in avg_scores:
            toadd = True
            for ur in user_ratings:
                if value < 3.0:
                    #skip this place, too low rating
                    toadd = False
                    continue
                if key == ur.place.urlsafe() and ur.value < 3.0:
                    #skip this place, user doesn't like it
                    toadd = False
                    continue
                
                for svalue, skey in scores:
                    if key == skey:
                        #already in list because of cluster
                        toadd = False
                        break
                    
            if toadd:
                scores.append((value, key))
                logging.info("Appending place with value " + str(value))
            if len(scores) >= n:
                # we have enough recommended places
                break
                
                
        scores = sorted(scores, key=lambda x: x[0], reverse = True)
        if len(scores) > n:
            scores = scores[0:n]
#         if debug:
#             log_text = "RECOMMEND scores from average-based : "
#             if scores is None:
#                 log_text += "None"
#             else:
#                 log_text += str(len(scores))
#             logging.info(log_text)
# 
#     if scores is None or (len(scores) < n and len(scores) < len(places)):
#         # cluster-based and average recommendations both failed to fill the recommendation list
#         # just add some other places
#         for p in places:
#             in_list = False
#             for score, key in scores:
#                 if key == p['key']:
#                     in_list = True
#                     break
#             if not in_list:
#                 scores.append((0, p['key']))
#             if len(scores) >= n:
#                 # we have enough recommended places
#                 break
#             
#     if debug:
#         log_text = "RECOMMEND final scores : "
#         if scores is None:
#             log_text += "None"
#         else:
#             log_text += str(len(scores))
#         logging.info(log_text)

    logging.warning("Filling empty space with full average predictions: " + str(datetime.now() - start))

    start = datetime.now()
    places_scores = []
    for p in places:
#         found = False
        for (score, item) in scores:
            if item == p['key']:
                places_scores.append((score, p))
#                 found = True
#         if not found:
#             places_scores.append((0, p))
    logging.info('places_scores: ' + str(len(places_scores)))
    places_scores = sorted(places_scores, key=lambda x: x[0], reverse = True)
    logging.warning("Moving mapping from place ids to full place data: " + str(datetime.now() - start))
    if len(places_scores) > n:
        places_scores = places_scores[0:n]
#     logging.info('recommender.recommend - places_scores: ' + str(places_scores))
    items = []
    start = datetime.now()
    for (score, place) in places_scores:
        
        #TODO: make discount loading asynchronous in javascript page, after visualization of places!!!
        
        disc_filters = {'place': place['key'], 'published': 'True', 'passed': 'False'}
        discounts, status, errcode = logic.discount_list_get(disc_filters, user_id)
        logging.info("discounts loaded: " + str(errcode) + " - " + status)
        if discounts is not None and status == "OK":
            try:
                json_discounts = [Discount.to_json(d, None, None) for d in discounts]
                place['discounts'] = json_discounts
            except (TypeError, ValueError) as e:
                #do nothing
                logging.error('Discounts not loaded: ' + str(e))
                pass
        place['predicted'] = score
        items.append(place)
    logging.warning("Time for loading discounts: " + str(datetime.now() - start))
#     logging.info("Recommended items: " + str(items))
    logging.info("recommender.recommend END ")#- items: " + str(items))
    return items