huginn_acumen_product_agent 1.6.1 → 2.0.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.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: huginn_acumen_product_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Spizziri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-03 00:00:00.000000000 Z
11
+ date: 2021-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -63,9 +63,16 @@ extra_rdoc_files: []
63
63
  files:
64
64
  - LICENSE.txt
65
65
  - lib/huginn_acumen_product_agent.rb
66
+ - lib/huginn_acumen_product_agent/acumen_agent_error.rb
66
67
  - lib/huginn_acumen_product_agent/acumen_client.rb
67
68
  - lib/huginn_acumen_product_agent/acumen_product_agent.rb
68
- - lib/huginn_acumen_product_agent/concerns/acumen_product_query_concern.rb
69
+ - lib/huginn_acumen_product_agent/concerns/acumen_query_concern.rb
70
+ - lib/huginn_acumen_product_agent/concerns/agent_error_concern.rb
71
+ - lib/huginn_acumen_product_agent/concerns/alternate_products_query_concern.rb
72
+ - lib/huginn_acumen_product_agent/concerns/inv_product_query_concern.rb
73
+ - lib/huginn_acumen_product_agent/concerns/prod_mkt_query_concern.rb
74
+ - lib/huginn_acumen_product_agent/concerns/product_categories_query_concern.rb
75
+ - lib/huginn_acumen_product_agent/concerns/product_contributors_query_concern.rb
69
76
  - spec/acumen_product_agent_spec.rb
70
77
  homepage: https://github.com/5-Stones/huginn_acumen_product_agent
71
78
  licenses:
@@ -1,456 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AcumenProductQueryConcern
4
- extend ActiveSupport::Concern
5
-
6
- UNIT_MAP = {
7
- 'oz.' => 'OZ',
8
- 'Inches (US)' => 'INH',
9
- }
10
-
11
- def get_products_by_ids(acumen_client, ids)
12
- response = acumen_client.get_products(ids)
13
- products = []
14
-
15
- # Filter out Not_On_Website === '1'
16
- unless response[0]['Inv_Product.Not_On_Website']['__content__'] === '1'
17
- products = parse_product_request(response)
18
- end
19
-
20
- response = acumen_client.get_products_marketing(ids)
21
- marketing = parse_product_marketing_request(response)
22
-
23
- merge_products_and_marketing(products, marketing)
24
- end
25
-
26
- def get_variants_for_ids(acumen_client, ids)
27
- result = get_linked_products_by_ids(acumen_client, ids)
28
-
29
- # Filtering out duplicate links getting sent from acumen
30
- filter = []
31
- result.each do |link|
32
- if (link['alt_format'].to_s != 0.to_s && !link.in?(filter))
33
- filter.push(link)
34
- end
35
- end
36
-
37
- return filter
38
- end
39
-
40
- def get_linked_products_by_ids(acumen_client, ids)
41
- response = acumen_client.get_linked_products(ids)
42
- process_linked_product_query(response)
43
- end
44
-
45
- def get_product_contributors(acumen_client, products)
46
- ids = products.map {|product| product['acumenAttributes']['product_marketing_id']}
47
- response = acumen_client.get_product_contributors(ids)
48
- product_contributors = process_product_contributor_query(response)
49
-
50
- products.each do |product|
51
- id = product['acumenAttributes']['product_marketing_id']
52
- product_contributor = product_contributors[id]
53
-
54
-
55
- if product_contributor
56
- contributor_ids = product_contributor.map do |pc|
57
- pc['contributor_id']
58
- end
59
-
60
- type_response = acumen_client.get_contributor_types(contributor_ids)
61
- contributor_types = process_contributor_types_query(type_response)
62
-
63
- product['contributors'] = product_contributor.map do |pc|
64
- {
65
- '@type' => 'Person',
66
- 'identifier' => pc['contributor_id'],
67
- 'acumenAttributes' => {
68
- 'contrib_type' => contributor_types[pc['contributor_id']]
69
- }
70
- }
71
- end
72
- end
73
- end
74
- products
75
- end
76
-
77
- def get_product_variants(acumen_client, products, physical_formats, digital_formats)
78
- ids = products.map { |product| product['identifier'] }
79
- # fetch product/variant relationships
80
- variant_links = get_variants_for_ids(acumen_client, ids)
81
- variant_ids = variant_links.map { |link| link['to_id'] }
82
-
83
- variant_ids = variant_links.map { |link| link['to_id'] }
84
-
85
- # fetch product variants
86
- variants = get_products_by_ids(acumen_client, variant_ids)
87
-
88
- # merge variants and products together
89
- process_products_and_variants(products, variants, variant_links, physical_formats, digital_formats)
90
- end
91
-
92
- def get_product_categories(acumen_client, products)
93
- # fetch categories
94
-
95
- skus = products.map { |product| product['model'].map { |m| m['sku'] } }[0]
96
- response = acumen_client.get_product_categories(skus)
97
- categories = process_product_categories_query(response)
98
-
99
- # map categories to products
100
- products.each do |product|
101
- product['model'].each do |variant|
102
- variant['categories'] = []
103
- sku = variant['sku']
104
- if categories[sku]
105
- active = categories[sku].select { |c| c['inactive'] == '0' }
106
- active.map do |category|
107
- variant['categories'].push({
108
- '@type' => 'Thing',
109
- 'identifier' => category['category_id']
110
- })
111
- end
112
- end
113
- end
114
- end
115
-
116
- products
117
- end
118
-
119
- def parse_product_request(products)
120
- products.map do |p|
121
- variant = response_mapper(p, {
122
- 'Inv_Product.ID' => 'identifier',
123
- 'Inv_Product.ProdCode' => 'sku',
124
- 'Inv_Product.SubTitle' => 'disambiguatingDescription',
125
- 'Inv_Product.ISBN_UPC' => 'isbn',
126
- 'Inv_Product.Pub_Date' => 'datePublished',
127
- 'Inv_Product.Next_Release' => 'releaseDate',
128
- })
129
- variant['@type'] = 'ProductModel'
130
- variant['isDefault'] = false
131
- variant['isTaxable'] = field_value(p, 'Inv_Product.Taxable') == '1'
132
- variant['acumenAttributes'] = {
133
- 'is_master' => field_value(p, 'Inv_Product.OnWeb_LinkOnly') == '0'
134
- }
135
-
136
- variant['offers'] = [{
137
- '@type' => 'Offer',
138
- 'price' => field_value(p, 'Inv_Product.Price_1'),
139
- 'availability' => field_value(p, 'Inv_Product.BO_Reason')
140
- }]
141
- if field_value(p, 'Inv_Product.Price_2')
142
- variant['offers'].push({
143
- '@type' => 'Offer',
144
- 'price' => field_value(p, 'Inv_Product.Price_2'),
145
- 'availability' => field_value(p, 'Inv_Product.BO_Reason')
146
- })
147
- end
148
-
149
- weight = field_value(p, 'Inv_Product.Weight')
150
- variant['weight'] = quantitative_value(weight, 'oz.')
151
-
152
- product = {
153
- '@type' => 'Product',
154
- 'identifier' => variant['identifier'],
155
- 'sku' => variant['sku'],
156
- 'name' => field_value(p, 'Inv_Product.Full_Title'),
157
- 'disambiguatingDescription' => field_value(p, 'Inv_Product.SubTitle'),
158
- 'model' => [
159
- variant
160
- ],
161
- 'additionalProperty' => [],
162
- 'acumenAttributes' => {
163
- 'info_alpha_1' => field_value(p, 'Inv_Product.Info_Alpha_1'),
164
- 'info_boolean_1' => field_value(p, 'Inv_Product.Info_Boolean_1'),
165
- },
166
- }
167
-
168
- category = field_value(p, 'Inv_Product.Category')
169
- if category
170
-
171
- if variant['acumenAttributes']
172
- variant['acumenAttributes']['category'] = category
173
- else
174
- variant['acumenAttributes'] = { 'category' => category }
175
- end
176
-
177
- if category == 'Paperback'
178
- product['additionalType'] = variant['additionalType'] = 'Book'
179
- variant['bookFormat'] = "http://schema.org/Paperback"
180
- variant['accessMode'] = "textual"
181
- variant['isDigital'] = false
182
- elsif category == 'Hardcover'
183
- product['additionalType'] = variant['additionalType'] = 'Book'
184
- variant['bookFormat'] = "http://schema.org/Hardcover"
185
- variant['accessMode'] = "textual"
186
- variant['isDigital'] = false
187
- elsif category == 'eBook'
188
- product['additionalType'] = variant['additionalType'] = 'Book'
189
- variant['bookFormat'] = "http://schema.org/EBook"
190
- variant['accessMode'] = "textual"
191
- variant['isDigital'] = true
192
- elsif category == 'CD'
193
- product['additionalType'] = variant['additionalType'] = 'CreativeWork'
194
- variant['accessMode'] = "auditory"
195
- variant['isDigital'] = false
196
- else
197
- variant['isDigital'] = false
198
- end
199
- end
200
-
201
- product
202
- end
203
- end
204
-
205
- def process_linked_product_query(links)
206
- links.map do |link|
207
- response_mapper(link, {
208
- 'Product_Link.Link_From_ID' => 'from_id',
209
- 'Product_Link.Link_To_ID' => 'to_id',
210
- 'Product_Link.Alt_Format' => 'alt_format',
211
- })
212
- end
213
- end
214
-
215
- def parse_product_marketing_request(products)
216
- results = {}
217
- products.each do |product|
218
- mapped = response_mapper(product, {
219
- 'ProdMkt.Product_ID' => 'product_id',
220
- 'ProdMkt.Product_Code' => 'sku',
221
- 'ProdMkt.ID' => 'id',
222
- 'ProdMkt.Pages' => 'pages',
223
- 'ProdMkt.Publisher' => 'publisher',
224
- 'ProdMkt.Description_Short' => 'description_short',
225
- 'ProdMkt.Description_Long' => 'description_long',
226
- 'ProdMkt.Height' => 'height',
227
- 'ProdMkt.Width' => 'width',
228
- 'ProdMkt.Thickness' => 'depth',
229
- 'ProdMkt.Meta_Keywords' => 'meta_keywords',
230
- 'ProdMkt.Meta_Description' => 'meta_description',
231
- 'ProdMkt.Extent_Unit' => 'extent_unit',
232
- 'ProdMkt.Extent_Value' => 'extent_value',
233
- 'ProdMkt.Age_Highest' => 'age_highest',
234
- 'ProdMkt.Age_Lowest' => 'age_lowest',
235
- 'ProdMkt.Awards' => 'awards',
236
- 'ProdMkt.Dimensions_Unit_Measure' => 'dimensions_unit_measure',
237
- 'ProdMkt.Excerpt' => 'excerpt',
238
- 'ProdMkt.Grade_Highest' => 'grade_highest',
239
- 'ProdMkt.Grade_Lowest' => 'grade_lowest',
240
- 'ProdMkt.Status' => 'status',
241
- 'ProdMkt.UPC' => 'upc',
242
- 'ProdMkt.Weight_Unit_Measure' => 'weight_unit_measure',
243
- 'ProdMkt.Weight' => 'weight',
244
- 'ProdMkt.Info_Text_01' => 'info_text_01',
245
- 'ProdMkt.Info_Text_02' => 'info_text_02',
246
- 'ProdMkt.Religious_Text_Identifier' => 'religious_text_identifier',
247
- 'ProdMkt.Info_Alpha_07' => 'info_alpha_07',
248
- })
249
-
250
- results[mapped['product_id']] = mapped
251
- end
252
-
253
- results
254
- end
255
-
256
- def merge_products_and_marketing(products, product_marketing)
257
- products.each do |product|
258
- marketing = product_marketing[product['identifier']]
259
- if marketing
260
- product['acumenAttributes']['product_marketing_id'] = marketing['id']
261
-
262
- product['publisher'] = {
263
- '@type': 'Organization',
264
- 'name' => marketing['publisher']
265
- };
266
- product['description'] = marketing['description_long']
267
- product['abstract'] = marketing['description_short']
268
- product['keywords'] = marketing['meta_keywords']
269
- product['text'] = marketing['excerpt']
270
-
271
- if marketing['age_lowest'] || marketing['age_highest']
272
- product['typicalAgeRange'] = "#{marketing['age_lowest']}-#{marketing['age_highest']}"
273
- end
274
-
275
- # properties for product pages
276
- if marketing['grade_lowest'] || marketing['grade_highest']
277
- # educationalUse? educationalAlignment?
278
- product['additionalProperty'].push({
279
- '@type' => 'PropertyValue',
280
- 'name' => 'Grade',
281
- 'propertyID' => 'grade_range',
282
- 'minValue' => marketing['grade_lowest'],
283
- 'maxValue' => marketing['grade_highest'],
284
- 'value' => "#{marketing['grade_lowest']}-#{marketing['grade_highest']}",
285
- })
286
- end
287
- if marketing['awards']
288
- product['additionalProperty'].push({
289
- '@type' => 'PropertyValue',
290
- 'propertyID' => 'awards',
291
- 'name' => 'Awards',
292
- 'value' => marketing['awards'],
293
- })
294
- end
295
-
296
- # acumen specific properties
297
- product['acumenAttributes']['extent_unit'] = marketing['extent_unit']
298
- product['acumenAttributes']['extent_value'] = marketing['extent_value']
299
- product['acumenAttributes']['info_text_01'] = marketing['info_text_01']
300
- product['acumenAttributes']['info_text_02'] = marketing['info_text_02']
301
- product['acumenAttributes']['info_alpha_07'] = marketing['info_alpha_07']
302
- product['acumenAttributes']['meta_description'] = marketing['meta_description']
303
- product['acumenAttributes']['religious_text_identifier'] = marketing['religious_text_identifier']
304
- product['acumenAttributes']['status'] = marketing['status']
305
-
306
- variant = product['model'][0]
307
- variant['gtin12'] = marketing['upc']
308
- variant['numberOfPages'] = marketing['pages']
309
-
310
- variant['height'] = quantitative_value(
311
- marketing['height'], marketing['dimensions_unit_measure']
312
- )
313
- variant['width'] = quantitative_value(
314
- marketing['width'], marketing['dimensions_unit_measure']
315
- )
316
- variant['depth'] = quantitative_value(
317
- marketing['thickness'], marketing['dimensions_unit_measure']
318
- )
319
- if variant['weight']['value'] == '0'
320
- variant['weight'] = quantitative_value(
321
- marketing['weight'], marketing['weight_unit_measure']
322
- )
323
- end
324
- end
325
- end
326
-
327
- products
328
- end
329
-
330
- def process_product_categories_query(categories)
331
- results = {}
332
- categories.each do |category|
333
- mapped = response_mapper(category, {
334
- 'ProdMkt_WPC.ProdCode' => 'sku',
335
- 'ProdMkt_WPC.WPC_ID' => 'category_id',
336
- 'ProdMkt_WPC.Inactive' => 'inactive',
337
- })
338
-
339
- if results[mapped['sku']]
340
- results[mapped['sku']].push(mapped)
341
- else
342
- results[mapped['sku']] = [mapped]
343
- end
344
- end
345
-
346
- results
347
- end
348
-
349
- def process_product_contributor_query(contributors)
350
- results = {}
351
- contributors.each do |contributor|
352
- mapped = response_mapper(contributor, {
353
- 'ProdMkt_Contrib_Link.ProdMkt_Contrib_ID' => 'contributor_id',
354
- 'ProdMkt_Contrib_Link.ProdMkt_ID' => 'product_marketing_id',
355
- 'ProdMkt_Contrib_Link.Inactive' => 'inactive',
356
- })
357
-
358
- if mapped['inactive'] == '0'
359
-
360
- if results[mapped['product_marketing_id']]
361
- results[mapped['product_marketing_id']].push(mapped)
362
- else
363
- results[mapped['product_marketing_id']] = [mapped]
364
- end
365
- end
366
- end
367
- results
368
- end
369
-
370
- def process_contributor_types_query(types)
371
- results = {}
372
- types.each do |type|
373
- mapped = response_mapper(type, {
374
- 'ProdMkt_Contributor.ID' => 'contributor_id',
375
- 'ProdMkt_Contributor.Contrib_Type' => 'type',
376
- })
377
-
378
- if !results[mapped['contributor_id']]
379
- results[mapped['contributor_id']] = mapped['type']
380
- end
381
- end
382
-
383
- results
384
- end
385
-
386
- def process_products_and_variants(products, variants, links, physical_formats, digital_formats)
387
- products_map = {}
388
- products.each { |product| products_map[product['identifier']] = product }
389
-
390
- variants_map = {}
391
- variants.each { |variant| variants_map[variant['identifier']] = variant }
392
-
393
- links.each do |link|
394
- from_id = link['from_id']
395
- to_id = link['to_id']
396
- if variants_map.key?(to_id)
397
- variant = variants_map[to_id]
398
- variant['isDefault'] = false
399
- products_map[from_id]['model'].push(*variant['model'])
400
- end
401
- end
402
-
403
- result = []
404
- products_map.each_value { |p| result.push(p) }
405
-
406
- result.each do |product|
407
- if product['model'].length == 1
408
- product['model'][0]['isDefault'] = true
409
- next
410
- else
411
- physical_formats.each do |val|
412
- match = product['model'].select { |v| v['acumenAttributes']['category'] == val }
413
-
414
- if match && match.length > 0
415
- match[0]['isDefault'] = true
416
- break
417
- end
418
- end
419
-
420
- digital_formats.each do |val|
421
- match = product['model'].select { |v| v['acumenAttributes']['category'] == val }
422
-
423
- if match && match.length > 0
424
- match[0]['isDefault'] = true
425
- break
426
- end
427
- end
428
- end
429
- end
430
- result
431
- end
432
-
433
- private
434
-
435
- def response_mapper(data, map)
436
- result = {}
437
- map.each do |key,val|
438
- result[val] = field_value(data, key)
439
- end
440
-
441
- result
442
- end
443
-
444
- def field_value(field, key)
445
- field[key]['__content__'] if field[key]
446
- end
447
-
448
- def quantitative_value(value, unit)
449
- {
450
- '@type' => 'QuantitativeValue',
451
- 'value' => value,
452
- 'unitText' => unit,
453
- 'unitCode' => (UNIT_MAP[unit] if unit),
454
- } if value
455
- end
456
- end