gorse 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/gorse.rb +296 -16
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 842507a7200f7259e97c07f2a6e9f530013a5711a9572e426c21552786f18603
|
|
4
|
+
data.tar.gz: 2b62c853f688dd49687b8dfcd1bdc56a5a33cb29dc978c3e405b6919a3ab3e83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b6885854bd2236f9f099a9edafb2e6171c96b8766ec9228743fb697ef05436807df0fd86e3386a9bf50e356d2fb51049f4283b7b7450bbd906f0f307110512e0
|
|
7
|
+
data.tar.gz: 49f611ef4d496254942bf4f90bb01cdfdf402435e3748df08232f9b276f6faef4d342e8ff68f48e1b8d247eb4f092b684868757a9fdef49f1c097daa8085e643
|
data/lib/gorse.rb
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
require 'net/http'
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'time'
|
|
3
5
|
|
|
4
6
|
class Feedback
|
|
5
|
-
def initialize(feedback_type, user_id, item_id, timestamp)
|
|
7
|
+
def initialize(feedback_type, user_id, item_id, value, timestamp)
|
|
6
8
|
@feedback_type = feedback_type
|
|
7
9
|
@user_id = user_id
|
|
8
10
|
@item_id = item_id
|
|
11
|
+
@value = value
|
|
9
12
|
@timestamp = timestamp
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
def to_json(options = {})
|
|
13
|
-
{
|
|
16
|
+
data = {
|
|
14
17
|
'FeedbackType' => @feedback_type,
|
|
15
18
|
'UserId' => @user_id,
|
|
16
19
|
'ItemId' => @item_id,
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
}
|
|
21
|
+
data['Value'] = @value unless @value.nil?
|
|
22
|
+
data['Timestamp'] = @timestamp
|
|
23
|
+
JSON.generate(data)
|
|
19
24
|
end
|
|
20
25
|
end
|
|
21
26
|
|
|
@@ -32,6 +37,155 @@ class RowAffected
|
|
|
32
37
|
attr_reader :row_affected
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
class Score
|
|
41
|
+
def initialize(id, score)
|
|
42
|
+
@id = id
|
|
43
|
+
@score = score
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.from_json(string)
|
|
47
|
+
data = JSON.load string
|
|
48
|
+
data.map { |h| Score.new(h['Id'], h['Score']) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_reader :id, :score
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class User
|
|
55
|
+
def initialize(user_id:, labels: nil, comment: nil)
|
|
56
|
+
@user_id = user_id
|
|
57
|
+
@labels = labels
|
|
58
|
+
@comment = comment
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_h
|
|
62
|
+
{ 'UserId' => @user_id, 'Labels' => @labels, 'Comment' => @comment }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_json(*_args)
|
|
66
|
+
JSON.generate(to_h)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.from_json(string)
|
|
70
|
+
h = JSON.load string
|
|
71
|
+
User.new(user_id: h['UserId'], labels: h['Labels'], comment: h['Comment'])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
attr_reader :user_id, :labels, :comment
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class UserPatch
|
|
78
|
+
def initialize(labels: nil, comment: nil)
|
|
79
|
+
@labels = labels
|
|
80
|
+
@comment = comment
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_json(*_args)
|
|
84
|
+
data = {}
|
|
85
|
+
data['Labels'] = @labels unless @labels.nil?
|
|
86
|
+
data['Comment'] = @comment unless @comment.nil?
|
|
87
|
+
JSON.generate(data)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class UserIterator
|
|
92
|
+
def initialize(cursor, users)
|
|
93
|
+
@cursor = cursor
|
|
94
|
+
@users = users
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.from_json(string)
|
|
98
|
+
h = JSON.load string
|
|
99
|
+
users = (h['Users'] || []).map { |u| User.new(user_id: u['UserId'], labels: u['Labels'], comment: u['Comment']) }
|
|
100
|
+
UserIterator.new(h['Cursor'], users)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
attr_reader :cursor, :users
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class Item
|
|
107
|
+
def initialize(item_id:, is_hidden: nil, labels: nil, categories: nil, timestamp: nil, comment: nil)
|
|
108
|
+
@item_id = item_id
|
|
109
|
+
@is_hidden = is_hidden
|
|
110
|
+
@labels = labels
|
|
111
|
+
@categories = categories
|
|
112
|
+
@timestamp = timestamp
|
|
113
|
+
@comment = comment
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def to_h
|
|
117
|
+
data = { 'ItemId' => @item_id }
|
|
118
|
+
data['IsHidden'] = @is_hidden unless @is_hidden.nil?
|
|
119
|
+
data['Labels'] = @labels unless @labels.nil?
|
|
120
|
+
data['Categories'] = @categories unless @categories.nil?
|
|
121
|
+
data['Timestamp'] = @timestamp unless @timestamp.nil?
|
|
122
|
+
data['Comment'] = @comment unless @comment.nil?
|
|
123
|
+
data
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def to_json(*_args)
|
|
127
|
+
JSON.generate(to_h)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.from_json(string)
|
|
131
|
+
h = JSON.load string
|
|
132
|
+
Item.new(
|
|
133
|
+
item_id: h['ItemId'],
|
|
134
|
+
is_hidden: h['IsHidden'],
|
|
135
|
+
labels: h['Labels'],
|
|
136
|
+
categories: h['Categories'],
|
|
137
|
+
timestamp: h['Timestamp'],
|
|
138
|
+
comment: h['Comment']
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
attr_reader :item_id, :is_hidden, :labels, :categories, :timestamp, :comment
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class ItemPatch
|
|
146
|
+
def initialize(is_hidden: nil, categories: nil, timestamp: nil, labels: nil, comment: nil)
|
|
147
|
+
@is_hidden = is_hidden
|
|
148
|
+
@categories = categories
|
|
149
|
+
@timestamp = timestamp
|
|
150
|
+
@labels = labels
|
|
151
|
+
@comment = comment
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def to_json(*_args)
|
|
155
|
+
data = {}
|
|
156
|
+
data['IsHidden'] = @is_hidden unless @is_hidden.nil?
|
|
157
|
+
data['Categories'] = @categories unless @categories.nil?
|
|
158
|
+
data['Timestamp'] = @timestamp unless @timestamp.nil?
|
|
159
|
+
data['Labels'] = @labels unless @labels.nil?
|
|
160
|
+
data['Comment'] = @comment unless @comment.nil?
|
|
161
|
+
JSON.generate(data)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class ItemIterator
|
|
166
|
+
def initialize(cursor, items)
|
|
167
|
+
@cursor = cursor
|
|
168
|
+
@items = items
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.from_json(string)
|
|
172
|
+
h = JSON.load string
|
|
173
|
+
items = (h['Items'] || []).map do |it|
|
|
174
|
+
Item.new(
|
|
175
|
+
item_id: it['ItemId'],
|
|
176
|
+
is_hidden: it['IsHidden'],
|
|
177
|
+
labels: it['Labels'],
|
|
178
|
+
categories: it['Categories'],
|
|
179
|
+
timestamp: it['Timestamp'],
|
|
180
|
+
comment: it['Comment']
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
ItemIterator.new(h['Cursor'], items)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
attr_reader :cursor, :items
|
|
187
|
+
end
|
|
188
|
+
|
|
35
189
|
class Gorse
|
|
36
190
|
def initialize(endpoint, api_key = "")
|
|
37
191
|
@endpoint = endpoint
|
|
@@ -39,17 +193,143 @@ class Gorse
|
|
|
39
193
|
end
|
|
40
194
|
|
|
41
195
|
def insert_feedback(feedback)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
196
|
+
response = request('POST', '/api/feedback', feedback)
|
|
197
|
+
RowAffected.from_json(response)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def list_feedbacks(feedback_type, user_id)
|
|
201
|
+
JSON.parse(request('GET', "/api/user/#{escape(user_id)}/feedback/#{escape(feedback_type)}"))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def delete_feedback(feedback_type, user_id, item_id)
|
|
205
|
+
RowAffected.from_json(request('DELETE', "/api/feedback/#{escape(feedback_type)}/#{escape(user_id)}/#{escape(item_id)}"))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def delete_feedbacks(user_id, item_id)
|
|
209
|
+
RowAffected.from_json(request('DELETE', "/api/feedback/#{escape(user_id)}/#{escape(item_id)}"))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def get_recommend(user_id, category: nil, n: nil, offset: nil)
|
|
213
|
+
if category.nil? && n.nil? && offset.nil?
|
|
214
|
+
return JSON.parse(request('GET', "/api/recommend/#{escape(user_id)}"))
|
|
215
|
+
end
|
|
216
|
+
cat_seg = (category || '').to_s
|
|
217
|
+
qs = []
|
|
218
|
+
qs << ["n", n] unless n.nil?
|
|
219
|
+
qs << ["offset", offset] unless offset.nil?
|
|
220
|
+
query = qs.map { |k, v| "#{k}=#{URI.encode_www_form_component(v.to_s)}" }.join('&')
|
|
221
|
+
path = "/api/recommend/#{escape(user_id)}/#{cat_seg}"
|
|
222
|
+
path += "?#{query}" unless query.empty?
|
|
223
|
+
JSON.parse(request('GET', path))
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def get_latest_items(user_id: nil, category: nil, n:, offset: 0)
|
|
227
|
+
category_path = category && !category.empty? ? "/#{escape(category)}" : ''
|
|
228
|
+
qs = [["n", n], ["offset", offset]]
|
|
229
|
+
qs << ["user-id", user_id] unless user_id.nil? || user_id.empty?
|
|
230
|
+
query = qs.map { |k, v| "#{k}=#{URI.encode_www_form_component(v.to_s)}" }.join('&')
|
|
231
|
+
path = "/api/latest#{category_path}?#{query}"
|
|
232
|
+
JSON.parse(request('GET', path))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def session_recommend(feedbacks, n:)
|
|
236
|
+
path = "/api/session/recommend?n=#{n}"
|
|
237
|
+
JSON.parse(request('POST', path, feedbacks))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def get_neighbors(item_id, n:)
|
|
241
|
+
JSON.parse(request('GET', "/api/item/#{escape(item_id)}/neighbors?n=#{n}"))
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def get_neighbors_category(item_id, category:, n:, offset: 0)
|
|
245
|
+
path = "/api/item/#{escape(item_id)}/neighbors/#{escape(category)}?n=#{n}&offset=#{offset}"
|
|
246
|
+
JSON.parse(request('GET', path))
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def get_neighbors_users(user_id, n:, offset: 0)
|
|
250
|
+
path = "/api/user/#{escape(user_id)}/neighbors?n=#{n}&offset=#{offset}"
|
|
251
|
+
JSON.parse(request('GET', path))
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# User APIs
|
|
255
|
+
def insert_user(user)
|
|
256
|
+
RowAffected.from_json(request('POST', '/api/user', user))
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def insert_users(users)
|
|
260
|
+
RowAffected.from_json(request('POST', '/api/users', users))
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def update_user(user_id, user_patch)
|
|
264
|
+
RowAffected.from_json(request('PATCH', "/api/user/#{escape(user_id)}", user_patch))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def get_user(user_id)
|
|
268
|
+
JSON.parse(request('GET', "/api/user/#{escape(user_id)}"))
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def get_users(n:, cursor: '')
|
|
272
|
+
JSON.parse(request('GET', "/api/users?n=#{n}&cursor=#{URI.encode_www_form_component(cursor)}"))
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def delete_user(user_id)
|
|
276
|
+
RowAffected.from_json(request('DELETE', "/api/user/#{escape(user_id)}"))
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Item APIs
|
|
280
|
+
def insert_item(item)
|
|
281
|
+
RowAffected.from_json(request('POST', '/api/item', item))
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def insert_items(items)
|
|
285
|
+
RowAffected.from_json(request('POST', '/api/items', items))
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def update_item(item_id, item_patch)
|
|
289
|
+
RowAffected.from_json(request('PATCH', "/api/item/#{escape(item_id)}", item_patch))
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def get_item(item_id)
|
|
293
|
+
JSON.parse(request('GET', "/api/item/#{escape(item_id)}"))
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def get_items(n:, cursor: '')
|
|
297
|
+
JSON.parse(request('GET', "/api/items?n=#{n}&cursor=#{URI.encode_www_form_component(cursor)}"))
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def delete_item(item_id)
|
|
301
|
+
RowAffected.from_json(request('DELETE', "/api/item/#{escape(item_id)}"))
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
private
|
|
305
|
+
|
|
306
|
+
def escape(s)
|
|
307
|
+
URI.encode_www_form_component(s.to_s)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def request(method, path, body = nil)
|
|
311
|
+
base = @endpoint.end_with?('/') ? @endpoint : @endpoint + '/'
|
|
312
|
+
uri = URI.join(base, path.sub(/^\//, ''))
|
|
313
|
+
headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
|
|
314
|
+
headers['X-API-Key'] = @api_key if @api_key && !@api_key.empty?
|
|
315
|
+
|
|
316
|
+
req = case method.upcase
|
|
317
|
+
when 'GET' then Net::HTTP::Get.new(uri, headers)
|
|
318
|
+
when 'POST' then Net::HTTP::Post.new(uri, headers)
|
|
319
|
+
when 'PATCH' then Net::HTTP::Patch.new(uri, headers)
|
|
320
|
+
when 'DELETE' then Net::HTTP::Delete.new(uri, headers)
|
|
321
|
+
else raise "Unsupported method #{method}"
|
|
322
|
+
end
|
|
323
|
+
if body && %w[POST PATCH].include?(method.upcase)
|
|
324
|
+
payload = body.is_a?(String) ? body : JSON.generate(body)
|
|
325
|
+
req.body = payload
|
|
326
|
+
end
|
|
327
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |h|
|
|
328
|
+
h.request(req)
|
|
329
|
+
end
|
|
330
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
331
|
+
raise "HTTP #{response.code}: #{response.body}"
|
|
332
|
+
end
|
|
333
|
+
response.body
|
|
54
334
|
end
|
|
55
335
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gorse
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Zhenghao Zhang
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-11-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Ruby SDK for Gorse recommender system
|
|
14
14
|
email: zhangzhenghao@hotmail.com
|
|
@@ -21,7 +21,7 @@ homepage: https://gorse.io/
|
|
|
21
21
|
licenses:
|
|
22
22
|
- Apache 2
|
|
23
23
|
metadata: {}
|
|
24
|
-
post_install_message:
|
|
24
|
+
post_install_message:
|
|
25
25
|
rdoc_options: []
|
|
26
26
|
require_paths:
|
|
27
27
|
- lib
|
|
@@ -36,8 +36,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
36
36
|
- !ruby/object:Gem::Version
|
|
37
37
|
version: '0'
|
|
38
38
|
requirements: []
|
|
39
|
-
rubygems_version: 3.
|
|
40
|
-
signing_key:
|
|
39
|
+
rubygems_version: 3.3.27
|
|
40
|
+
signing_key:
|
|
41
41
|
specification_version: 4
|
|
42
42
|
summary: Ruby SDK for Gorse recommender system
|
|
43
43
|
test_files: []
|