Skip to content

Commit 06e4d08

Browse files
authored
Merge pull request #3 from Recombee/user_properties
User properties
2 parents 9cc90cf + 2bb8ce6 commit 06e4d08

21 files changed

+436
-58
lines changed

README.md

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,37 @@ Or install it yourself as:
2929
require 'recombee_api_client'
3030
include RecombeeApiClient
3131

32-
# Prepare some items and users
33-
NUM = 100
34-
my_users = (1..NUM).map { |i| "user-#{i}" }
35-
my_items = (1..NUM).map { |i| "item-#{i}" }
32+
client = RecombeeClient.new('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
3633

37-
#Generate some random purchases of items by users
34+
# Generate some random purchases of items by users
35+
NUM = 100
3836
PROBABILITY_PURCHASED = 0.1
39-
my_purchases = []
40-
my_users.each do |user|
41-
p = my_items.select { |_| rand(0.0..1.0) < PROBABILITY_PURCHASED }
42-
p.each { |item| my_purchases.push('userId' => user, 'itemId' => item) }
37+
38+
users = (1..NUM).map { |i| "user-#{i}" }
39+
items = (1..NUM).map { |i| "item-#{i}" }
40+
purchases = []
41+
42+
users.each do |user_id|
43+
purchased = items.select { |_| rand(0.0..1.0) < PROBABILITY_PURCHASED }
44+
purchased.each { |item_id| purchases.push(
45+
46+
AddPurchase.new(user_id, item_id,'cascadeCreate' => true)
47+
# Use cascadeCreate to create the
48+
# yet non-existing users and items
49+
)}
50+
4351
end
4452

45-
# Use Recombee recommender
46-
client = RecombeeClient.new('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
4753
begin
48-
# Send the data to Recombee, use Batch for faster processing
49-
puts 'Send users'
50-
client.send(Batch.new(my_users.map { |userId| AddUser.new(userId) }))
51-
puts 'Send items'
52-
client.send(Batch.new(my_items.map { |itemId| AddItem.new(itemId) }))
53-
puts 'Send purchases'
54-
client.send(Batch.new(my_purchases.map { |p| AddPurchase.new(p['userId'], p['itemId']) }))
54+
# Send the data to Recombee, use Batch for faster processing of larger data
55+
client.send(Batch.new(purchases))
5556

5657
# Get recommendations for user 'user-25'
57-
puts 'Recommend for a user'
58-
recommended = client.send(UserBasedRecommendation.new('user-25', 5, 'rotationRate' => 0))
59-
puts "Recommended items: #{recommended}"
58+
recommended = client.send(UserBasedRecommendation.new('user-25', 5))
59+
puts "Recommended items for user-25: #{recommended}"
6060
rescue APIError => e
6161
puts e
62+
# Use fallback
6263
end
6364
```
6465

@@ -71,7 +72,8 @@ NUM = 100
7172
PROBABILITY_PURCHASED = 0.1
7273

7374
client = RecombeeClient.new('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
74-
client.send(ResetDatabase.new)
75+
client.send(ResetDatabase.new) # Clear everything from the database
76+
7577
# We will use computers as items in this example
7678
# Computers have three properties
7779
# - price (floating point number)
@@ -82,6 +84,7 @@ client.send(ResetDatabase.new)
8284
client.send(AddItemProperty.new('price', 'double'))
8385
client.send(AddItemProperty.new('num-cores', 'int'))
8486
client.send(AddItemProperty.new('description', 'string'))
87+
client.send(AddItemProperty.new('time', 'timestamp'))
8588

8689
# Prepare requests for setting a catalog of computers
8790
requests = (1..NUM).map do |i|
@@ -92,14 +95,18 @@ requests = (1..NUM).map do |i|
9295
'price' => rand(15000.0 .. 25000.0),
9396
'num-cores' => rand(1..8),
9497
'description' => 'Great computer',
95-
'!cascadeCreate' => true # Use !cascadeCreate for creating item
98+
'time' => DateTime.now
99+
},
100+
#optional parameters:
101+
{
102+
'cascadeCreate' => true # Use cascadeCreate for creating item
96103
# with given itemId, if it doesn't exist
97104
}
98105
)
99106
end
100107

101108
# Send catalog to the recommender system
102-
client.send(Batch.new(requests))
109+
puts client.send(Batch.new(requests))
103110

104111
# Prepare some purchases of items by users
105112
requests = []

lib/recombee_api_client.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ module RecombeeApiClient
1717
class RecombeeClient
1818
include HTTParty
1919

20+
BATCH_MAX_SIZE = 10000
21+
2022
##
2123
# - +account+ -> Name of your account at Recombee
2224
# - +token+ -> Secret token obtained from Recombee for signing requests
@@ -33,6 +35,9 @@ def initialize(account, token, protocol = 'http', options = {})
3335
##
3436
# - +request+ -> ApiRequest to be sent to Recombee recommender
3537
def send(request)
38+
39+
return send_multipart_batch(request) if request.kind_of? Batch and request.requests.size > BATCH_MAX_SIZE
40+
3641
timeout = request.timeout / 1000
3742
uri = process_request_uri(request)
3843
uri = sign_url(uri)
@@ -94,6 +99,12 @@ def check_errors(response, request)
9499
fail ResponseError.new(request, status_code, response.body)
95100
end
96101

102+
def send_multipart_batch(request)
103+
requests_parts = request.requests.each_slice(BATCH_MAX_SIZE)
104+
responses = requests_parts.map {|rqs| Batch.new(rqs)}.map{|batch| send(batch)}
105+
responses.inject([]){|result,resp| result + resp}
106+
end
107+
97108
def process_request_uri(request)
98109
uri = request.path
99110
uri.slice! ('/{databaseId}/')
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#
2+
# This file is auto-generated, do not edit
3+
#
4+
5+
module RecombeeApiClient
6+
require_relative 'request'
7+
require_relative '../errors'
8+
9+
##
10+
#Adding an user property is somehow equivalent to adding a column to the table of users. The users may be characterized by various properties of different types.
11+
#
12+
class AddUserProperty < ApiRequest
13+
attr_reader :property_name, :type
14+
attr_accessor :timeout
15+
attr_accessor :ensure_https
16+
17+
##
18+
# * *Required arguments*
19+
# - +property_name+ -> Name of the user property to be created. Currently, the following names are reserved:`id`, `userid`, case insensitively. Also, the length of the property name must not exceed 63 characters.
20+
#
21+
# - +type+ -> Value type of the user property to be created. One of: `int`, `double`, `string`, `boolean`, `timestamp`, `set`
22+
#
23+
#
24+
def initialize(property_name, type)
25+
@property_name = property_name
26+
@type = type
27+
@timeout = 1000
28+
@ensure_https = false
29+
end
30+
31+
# HTTP method
32+
def method
33+
:put
34+
end
35+
36+
# Values of body parameters as a Hash
37+
def body_parameters
38+
p = Hash.new
39+
p
40+
end
41+
42+
# Values of query parameters as a Hash.
43+
# name of parameter => value of the parameter
44+
def query_parameters
45+
params = {}
46+
params['type'] = @type
47+
params
48+
end
49+
50+
# Relative path to the endpoint
51+
def path
52+
"/{databaseId}/users/properties/#{@property_name}"
53+
end
54+
end
55+
end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#
2+
# This file is auto-generated, do not edit
3+
#
4+
5+
module RecombeeApiClient
6+
require_relative 'request'
7+
require_relative '../errors'
8+
9+
##
10+
#Deleting an user property is roughly equivalent to removing a column from the table of users.
11+
#
12+
class DeleteUserProperty < ApiRequest
13+
attr_reader :property_name
14+
attr_accessor :timeout
15+
attr_accessor :ensure_https
16+
17+
##
18+
# * *Required arguments*
19+
# - +property_name+ -> Name of the property to be deleted.
20+
#
21+
def initialize(property_name)
22+
@property_name = property_name
23+
@timeout = 1000
24+
@ensure_https = false
25+
end
26+
27+
# HTTP method
28+
def method
29+
:delete
30+
end
31+
32+
# Values of body parameters as a Hash
33+
def body_parameters
34+
p = Hash.new
35+
p
36+
end
37+
38+
# Values of query parameters as a Hash.
39+
# name of parameter => value of the parameter
40+
def query_parameters
41+
params = {}
42+
params
43+
end
44+
45+
# Relative path to the endpoint
46+
def path
47+
"/{databaseId}/users/properties/#{@property_name}"
48+
end
49+
end
50+
end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#
2+
# This file is auto-generated, do not edit
3+
#
4+
5+
module RecombeeApiClient
6+
require_relative 'request'
7+
require_relative '../errors'
8+
9+
##
10+
#Gets information about specified user property.
11+
#
12+
class GetUserPropertyInfo < ApiRequest
13+
attr_reader :property_name
14+
attr_accessor :timeout
15+
attr_accessor :ensure_https
16+
17+
##
18+
# * *Required arguments*
19+
# - +property_name+ -> Name of the property about which the information is to be retrieved.
20+
#
21+
def initialize(property_name)
22+
@property_name = property_name
23+
@timeout = 1000
24+
@ensure_https = false
25+
end
26+
27+
# HTTP method
28+
def method
29+
:get
30+
end
31+
32+
# Values of body parameters as a Hash
33+
def body_parameters
34+
p = Hash.new
35+
p
36+
end
37+
38+
# Values of query parameters as a Hash.
39+
# name of parameter => value of the parameter
40+
def query_parameters
41+
params = {}
42+
params
43+
end
44+
45+
# Relative path to the endpoint
46+
def path
47+
"/{databaseId}/users/properties/#{@property_name}"
48+
end
49+
end
50+
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#
2+
# This file is auto-generated, do not edit
3+
#
4+
5+
module RecombeeApiClient
6+
require_relative 'request'
7+
require_relative '../errors'
8+
9+
##
10+
#Get all the current property values of a given user.
11+
#
12+
class GetUserValues < ApiRequest
13+
attr_reader :user_id
14+
attr_accessor :timeout
15+
attr_accessor :ensure_https
16+
17+
##
18+
# * *Required arguments*
19+
# - +user_id+ -> ID of the user properties of which are to be obtained.
20+
#
21+
#
22+
def initialize(user_id)
23+
@user_id = user_id
24+
@timeout = 1000
25+
@ensure_https = false
26+
end
27+
28+
# HTTP method
29+
def method
30+
:get
31+
end
32+
33+
# Values of body parameters as a Hash
34+
def body_parameters
35+
p = Hash.new
36+
p
37+
end
38+
39+
# Values of query parameters as a Hash.
40+
# name of parameter => value of the parameter
41+
def query_parameters
42+
params = {}
43+
params
44+
end
45+
46+
# Relative path to the endpoint
47+
def path
48+
"/{databaseId}/users/#{@user_id}"
49+
end
50+
end
51+
end

lib/recombee_api_client/api/item_based_recommendation.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ class ItemBasedRecommendation < ApiRequest
2121
#
2222
# * *Optional arguments (given as hash optional)*
2323
# - +targetUserId+ -> ID of the user who will see the recommendations.
24+
#
25+
#Specifying the *targetUserId* is beneficial because:
26+
#
27+
#* It makes the recommendations personalized
28+
#* Allows calculations of Actions and Conversions in the graphical user interface, as Recombee can pair the user who got recommendations and who afterwards viewed/purchased an item.
29+
#
2430
# - +userImpact+ -> If *targetUserId* parameter is present, the recommendations are biased towards the user given. Using *userImpact*, you may control this bias. For an extreme case of `userImpact=0.0`, the interactions made by the user are not taken into account at all (with the exception of history-based blacklisting), for `userImpact=1.0`, you'll get user-based recommendation. The default value is `0.1`
2531
#
2632
# - +filter+ -> Boolean-returning [ReQL](https://docs.recombee.com/reql.html) expression which allows you to filter recommended items based on the values of their attributes.
@@ -70,11 +76,11 @@ class ItemBasedRecommendation < ApiRequest
7076
#
7177
# - +diversity+ -> **Expert option** Real number from [0.0, 1.0] which determines how much mutually dissimilar should the recommended items be. The default value is 0.0, i.e., no diversification. Value 1.0 means maximal diversification.
7278
#
73-
# - +minRelevance+ -> **Expert option** Specifies the threshold of how much relevant must the recommended items be to the user. Possible values one of: "low", "medium", "high". The default value is "low", meaning that the system attempts to recommend number of items equal to *count* at any cost. If there are not enough data (such as interactions or item properties), this may even lead to bestseller-based recommendations to be appended to reach the full *count*. This behavior may be suppressed by using "medium" or "high" values. In such case, the system only recommends items of at least the requested qualit, and may return less than *count* items when there is not enough data to fulfill it.
79+
# - +minRelevance+ -> **Expert option** If the *targetUserId* is provided: Specifies the threshold of how much relevant must the recommended items be to the user. Possible values one of: "low", "medium", "high". The default value is "low", meaning that the system attempts to recommend number of items equal to *count* at any cost. If there are not enough data (such as interactions or item properties), this may even lead to bestseller-based recommendations to be appended to reach the full *count*. This behavior may be suppressed by using "medium" or "high" values. In such case, the system only recommends items of at least the requested qualit, and may return less than *count* items when there is not enough data to fulfill it.
7480
#
75-
# - +rotationRate+ -> **Expert option** If your users browse the system in real-time, it may easily happen that you wish to offer them recommendations multiple times. Here comes the question: how much should the recommendations change? Should they remain the same, or should they rotate? Recombee API allows you to control this per-request in backward fashion. You may penalize an item for being recommended in the near past. For the specific user, `rotationRate=1` means maximal rotation, `rotationRate=0` means absolutely no rotation. You may also use, for example `rotationRate=0.2` for only slight rotation of recommended items.
81+
# - +rotationRate+ -> **Expert option** If the *targetUserId* is provided: If your users browse the system in real-time, it may easily happen that you wish to offer them recommendations multiple times. Here comes the question: how much should the recommendations change? Should they remain the same, or should they rotate? Recombee API allows you to control this per-request in backward fashion. You may penalize an item for being recommended in the near past. For the specific user, `rotationRate=1` means maximal rotation, `rotationRate=0` means absolutely no rotation. You may also use, for example `rotationRate=0.2` for only slight rotation of recommended items.
7682
#
77-
# - +rotationTime+ -> **Expert option** Taking *rotationRate* into account, specifies how long time it takes to an item to fully recover from the penalization. By example, `rotationTime=7200.0` means that items recommended more than 2 hours ago are definitely not penalized anymore. Currently, the penalization is linear, so for `rotationTime=7200.0`, an item is still penalized by `0.5` to the user after 1 hour.
83+
# - +rotationTime+ -> **Expert option** If the *targetUserId* is provided: Taking *rotationRate* into account, specifies how long time it takes to an item to fully recover from the penalization. For example, `rotationTime=7200.0` means that items recommended more than 2 hours ago are definitely not penalized anymore. Currently, the penalization is linear, so for `rotationTime=7200.0`, an item is still penalized by `0.5` to the user after 1 hour.
7884
#
7985
#
8086
def initialize(item_id, count, optional = {})

0 commit comments

Comments
 (0)