mbd-studio-sdk 2.1.2 → 2.2.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.
@@ -1,55 +1,14 @@
1
- /**
2
- * Ranking client for MBD Studio SDK.
3
- * Fluent API: methods return the instance for chaining.
4
- * Rank items (candidates) with sort, diversity, and limits-by-field.
5
- */
6
-
7
1
  export class Ranking {
8
- /** @type {string} */
9
- _url;
10
-
11
- /** @type {string} */
12
- _apiKey;
13
-
14
- /** @type {Array} */
15
2
  _candidates = [];
16
-
17
- /** @type {string} */
18
3
  _sortMethod = 'sort';
19
-
20
- /** @type {{ fields?: string[], direction?: string[] } | Array<{ field: string, weight: number }> | Array<{ field: string, direction: string, percentage: number }> | null} */
21
4
  _sortParams = null;
22
-
23
- /** @type {string | null} */
24
5
  _diversityMethod = null;
25
-
26
- /** @type {{ fields?: string[] } | { lambda?: number, horizon?: number } | null} */
27
6
  _diversityParams = null;
28
-
29
- /** @type {boolean} */
30
7
  _limitsByFieldEnabled = false;
31
-
32
- /** @type {number} */
33
8
  _everyN = 10;
34
-
35
- /** @type {Array<{ field: string, limit: number }>} */
36
9
  _limitRules = [];
37
-
38
- /** @type {{ endpoint: string, payload: object } | null} */
39
10
  lastCall = null;
40
-
41
- /** @type {object | null} */
42
11
  lastResult = null;
43
-
44
- /**
45
- * @param {Object} options
46
- * @param {string} options.url - Ranking service base URL
47
- * @param {string} options.apiKey - API key for authentication
48
- * @param {Array} [options.candidates] - Items to rank (hits with _id, _features, _scores, _source)
49
- * @param {string} [options.origin='sdk'] - origin sent in backend call payloads
50
- * @param {function(string): void} [options.log] - Optional override for log()
51
- * @param {function(*): void} [options.show] - Optional override for show()
52
- */
53
12
  constructor(options) {
54
13
  if (!options || typeof options !== 'object') {
55
14
  throw new Error('Ranking: options object is required');
@@ -68,23 +27,12 @@ export class Ranking {
68
27
  this._show = typeof show === 'function' ? show : console.log.bind(console);
69
28
  this._candidates = candidates;
70
29
  }
71
-
72
- /**
73
- * Return the endpoint path for the ranking feed.
74
- * @returns {string}
75
- */
76
30
  getEndpoint() {
77
31
  return '/ranking/feed';
78
32
  }
79
-
80
- /**
81
- * Collect useful field names from sort, diversity, and limits config.
82
- * @private
83
- */
84
33
  _getUsefulFieldsAndNeedEmbedding() {
85
34
  const useful = new Set();
86
35
  let needEmbedding = false;
87
-
88
36
  if (this._sortParams) {
89
37
  if (this._sortMethod === 'sort' && Array.isArray(this._sortParams.fields)) {
90
38
  this._sortParams.fields.forEach((f) => useful.add(f));
@@ -99,24 +47,15 @@ export class Ranking {
99
47
  if (this._diversityMethod === 'fields' && this._diversityParams?.fields) {
100
48
  this._diversityParams.fields.forEach((f) => useful.add(f));
101
49
  }
102
- if (this._diversityMethod === 'semantic') {
103
- needEmbedding = true;
104
- }
50
+ if (this._diversityMethod === 'semantic') needEmbedding = true;
105
51
  if (this._limitsByFieldEnabled && this._limitRules.length > 0) {
106
52
  this._limitRules.forEach((r) => r.field && useful.add(r.field));
107
53
  }
108
-
109
54
  return { usefulFields: useful, needEmbedding };
110
55
  }
111
-
112
- /**
113
- * Build the request payload: items from candidates plus sort, diversity, limits_by_field.
114
- * @returns {object}
115
- */
116
56
  getPayload() {
117
57
  const { usefulFields, needEmbedding } = this._getUsefulFieldsAndNeedEmbedding();
118
58
  const hits = this._candidates || [];
119
-
120
59
  const items = hits.map((hit) => {
121
60
  const item = { item_id: hit._id };
122
61
  for (const key of usefulFields) {
@@ -125,31 +64,20 @@ export class Ranking {
125
64
  }
126
65
  if (needEmbedding) {
127
66
  let embed = hit._source?.item_sem_embed2;
128
- if (!embed || !Array.isArray(embed)) {
129
- embed = hit._source?.text_vector;
130
- }
67
+ if (!embed || !Array.isArray(embed)) embed = hit._source?.text_vector;
131
68
  if (embed) item.embed = embed;
132
69
  }
133
70
  return item;
134
71
  });
135
-
136
72
  const payload = { origin: this._origin, items };
137
-
138
73
  const sortConfig = this._buildSortConfig();
139
74
  if (sortConfig) payload.sort = sortConfig;
140
-
141
75
  const diversityConfig = this._buildDiversityConfig();
142
76
  if (diversityConfig) payload.diversity = diversityConfig;
143
-
144
77
  const limitsByFieldConfig = this._buildLimitsByFieldConfig();
145
78
  if (limitsByFieldConfig) payload.limits_by_field = limitsByFieldConfig;
146
-
147
79
  return payload;
148
80
  }
149
-
150
- /**
151
- * @private
152
- */
153
81
  _buildSortConfig() {
154
82
  if (!this._sortParams) return undefined;
155
83
  if (this._sortMethod === 'sort' && this._sortParams.fields?.length > 0) {
@@ -159,17 +87,10 @@ export class Ranking {
159
87
  return { method: 'linear', params: this._sortParams.map((p) => ({ field: p.field, weight: p.weight })) };
160
88
  }
161
89
  if (this._sortMethod === 'mix' && Array.isArray(this._sortParams) && this._sortParams.length > 0) {
162
- return {
163
- method: 'mix',
164
- params: this._sortParams.map((p) => ({ field: p.field, direction: p.direction, percentage: p.percentage })),
165
- };
90
+ return { method: 'mix', params: this._sortParams.map((p) => ({ field: p.field, direction: p.direction, percentage: p.percentage })) };
166
91
  }
167
92
  return undefined;
168
93
  }
169
-
170
- /**
171
- * @private
172
- */
173
94
  _buildDiversityConfig() {
174
95
  if (!this._diversityMethod) return undefined;
175
96
  if (this._diversityMethod === 'fields' && this._diversityParams?.fields?.length > 0) {
@@ -182,25 +103,12 @@ export class Ranking {
182
103
  }
183
104
  return undefined;
184
105
  }
185
-
186
- /**
187
- * @private
188
- */
189
106
  _buildLimitsByFieldConfig() {
190
107
  if (!this._limitsByFieldEnabled || !this._limitRules.length) return undefined;
191
108
  const everyN = Number(this._everyN);
192
109
  if (!Number.isInteger(everyN) || everyN < 2) return undefined;
193
- return {
194
- every_n: everyN,
195
- rules: this._limitRules.map((r) => ({ field: r.field, limit: Number(r.limit) || 0 })),
196
- };
110
+ return { every_n: everyN, rules: this._limitRules.map((r) => ({ field: r.field, limit: Number(r.limit) || 0 })) };
197
111
  }
198
-
199
- /**
200
- * Set the sorting method. Applies to subsequent sortBy / weight / mix calls.
201
- * @param {'sort' | 'linear' | 'mix'} x - Sorting method
202
- * @returns {this}
203
- */
204
112
  sortingMethod(x) {
205
113
  if (x !== 'sort' && x !== 'linear' && x !== 'mix') {
206
114
  throw new Error('Ranking.sortingMethod: must be "sort", "linear", or "mix"');
@@ -211,31 +119,15 @@ export class Ranking {
211
119
  if (x === 'mix') this._sortParams = [];
212
120
  return this;
213
121
  }
214
-
215
- /**
216
- * Set sort by field(s) and direction(s). Applies when sortingMethod is 'sort'.
217
- * If sorting method was not set yet, it is set to 'sort' for convenience.
218
- * @param {string} field - Primary sort field
219
- * @param {'asc' | 'desc'} [direction='desc'] - Primary direction
220
- * @param {string} [field2] - Optional second sort field
221
- * @param {'asc' | 'desc'} [direction2='desc'] - Optional second direction
222
- * @returns {this}
223
- */
224
122
  sortBy(field, direction = 'desc', field2, direction2 = 'desc') {
225
123
  if (this._sortMethod === 'linear' || this._sortMethod === 'mix') {
226
124
  throw new Error('Ranking.sortBy: only applies when sortingMethod is "sort" (already set to something else)');
227
125
  }
228
126
  this._sortMethod = 'sort';
229
- if (!this._sortParams || !this._sortParams.fields) {
230
- this._sortParams = { fields: [], direction: [] };
231
- }
127
+ if (!this._sortParams || !this._sortParams.fields) this._sortParams = { fields: [], direction: [] };
232
128
  const f = typeof field === 'string' && field.trim() ? field.trim() : null;
233
- if (!f) {
234
- throw new Error('Ranking.sortBy: field must be a non-empty string');
235
- }
236
- if (direction !== 'asc' && direction !== 'desc') {
237
- throw new Error('Ranking.sortBy: direction must be "asc" or "desc"');
238
- }
129
+ if (!f) throw new Error('Ranking.sortBy: field must be a non-empty string');
130
+ if (direction !== 'asc' && direction !== 'desc') throw new Error('Ranking.sortBy: direction must be "asc" or "desc"');
239
131
  this._sortParams = { fields: [f], direction: [direction] };
240
132
  if (typeof field2 === 'string' && field2.trim()) {
241
133
  this._sortParams.fields.push(field2.trim());
@@ -243,168 +135,74 @@ export class Ranking {
243
135
  }
244
136
  return this;
245
137
  }
246
-
247
- /**
248
- * Add a weighted field for linear combination. Applies when sortingMethod is 'linear'.
249
- * @param {string} field - Field name
250
- * @param {number} w - Weight
251
- * @returns {this}
252
- */
253
138
  weight(field, w) {
254
- if (this._sortMethod !== 'linear') {
255
- throw new Error('Ranking.weight: only applies when sortingMethod is "linear"');
256
- }
139
+ if (this._sortMethod !== 'linear') throw new Error('Ranking.weight: only applies when sortingMethod is "linear"');
257
140
  const f = typeof field === 'string' && field.trim() ? field.trim() : null;
258
- if (!f) {
259
- throw new Error('Ranking.weight: field must be a non-empty string');
260
- }
141
+ if (!f) throw new Error('Ranking.weight: field must be a non-empty string');
261
142
  if (!Array.isArray(this._sortParams)) this._sortParams = [];
262
143
  this._sortParams.push({ field: f, weight: Number(w) });
263
144
  return this;
264
145
  }
265
-
266
- /**
267
- * Add a mix row (field, direction, percentage). Applies when sortingMethod is 'mix'.
268
- * If sorting method was not set yet, it is set to 'mix' for convenience.
269
- * @param {string} field - Field name
270
- * @param {'asc' | 'desc'} direction - Sort direction
271
- * @param {number} percentage - Percentage weight (0–100)
272
- * @returns {this}
273
- */
274
146
  mix(field, direction, percentage) {
275
- if (this._sortMethod === 'linear') {
276
- throw new Error('Ranking.mix: only applies when sortingMethod is "mix" (already set to "linear")');
277
- }
147
+ if (this._sortMethod === 'linear') throw new Error('Ranking.mix: only applies when sortingMethod is "mix" (already set to "linear")');
278
148
  this._sortMethod = 'mix';
279
149
  if (!Array.isArray(this._sortParams)) this._sortParams = [];
280
150
  const f = typeof field === 'string' && field.trim() ? field.trim() : null;
281
- if (!f) {
282
- throw new Error('Ranking.mix: field must be a non-empty string');
283
- }
284
- if (direction !== 'asc' && direction !== 'desc') {
285
- throw new Error('Ranking.mix: direction must be "asc" or "desc"');
286
- }
151
+ if (!f) throw new Error('Ranking.mix: field must be a non-empty string');
152
+ if (direction !== 'asc' && direction !== 'desc') throw new Error('Ranking.mix: direction must be "asc" or "desc"');
287
153
  this._sortParams.push({ field: f, direction, percentage: Number(percentage) || 0 });
288
154
  return this;
289
155
  }
290
-
291
- /**
292
- * Set the diversity method.
293
- * @param {'fields' | 'semantic'} method - Diversity method
294
- * @returns {this}
295
- */
296
156
  diversity(method) {
297
- if (method !== 'fields' && method !== 'semantic') {
298
- throw new Error('Ranking.diversity: method must be "fields" or "semantic"');
299
- }
157
+ if (method !== 'fields' && method !== 'semantic') throw new Error('Ranking.diversity: method must be "fields" or "semantic"');
300
158
  this._diversityMethod = method;
301
159
  if (method === 'fields') this._diversityParams = { fields: [] };
302
160
  if (method === 'semantic') this._diversityParams = { lambda: 0.5, horizon: 20 };
303
161
  return this;
304
162
  }
305
-
306
- /**
307
- * Add field(s) for diversity when diversityMethod is 'fields'. Pass array or single field name.
308
- * @param {string[] | string} arrayOrItem - Field name(s)
309
- * @returns {this}
310
- */
311
163
  fields(arrayOrItem) {
312
- if (this._diversityMethod !== 'fields') {
313
- throw new Error('Ranking.fields: only applies when diversity(method) is "fields"');
314
- }
315
- if (!this._diversityParams || !Array.isArray(this._diversityParams.fields)) {
316
- this._diversityParams = { fields: [] };
317
- }
164
+ if (this._diversityMethod !== 'fields') throw new Error('Ranking.fields: only applies when diversity(method) is "fields"');
165
+ if (!this._diversityParams || !Array.isArray(this._diversityParams.fields)) this._diversityParams = { fields: [] };
318
166
  const add = (name) => {
319
167
  const s = typeof name === 'string' && name.trim() ? name.trim() : null;
320
168
  if (s && !this._diversityParams.fields.includes(s)) this._diversityParams.fields.push(s);
321
169
  };
322
- if (Array.isArray(arrayOrItem)) {
323
- arrayOrItem.forEach(add);
324
- } else {
325
- add(arrayOrItem);
326
- }
170
+ if (Array.isArray(arrayOrItem)) arrayOrItem.forEach(add);
171
+ else add(arrayOrItem);
327
172
  return this;
328
173
  }
329
-
330
- /**
331
- * Set horizon for diversity method 'semantic'. Default 20.
332
- * @param {number} n - Horizon value
333
- * @returns {this}
334
- */
335
174
  horizon(n) {
336
- if (this._diversityMethod !== 'semantic') {
337
- throw new Error('Ranking.horizon: only applies when diversity(method) is "semantic"');
338
- }
175
+ if (this._diversityMethod !== 'semantic') throw new Error('Ranking.horizon: only applies when diversity(method) is "semantic"');
339
176
  if (!this._diversityParams) this._diversityParams = { lambda: 0.5, horizon: 20 };
340
177
  this._diversityParams.horizon = Number(n);
341
178
  return this;
342
179
  }
343
-
344
- /**
345
- * Set lambda for diversity method 'semantic'. Default 0.5.
346
- * @param {number} value - Lambda value (0–1)
347
- * @returns {this}
348
- */
349
180
  lambda(value) {
350
- if (this._diversityMethod !== 'semantic') {
351
- throw new Error('Ranking.lambda: only applies when diversity(method) is "semantic"');
352
- }
181
+ if (this._diversityMethod !== 'semantic') throw new Error('Ranking.lambda: only applies when diversity(method) is "semantic"');
353
182
  if (!this._diversityParams) this._diversityParams = { lambda: 0.5, horizon: 20 };
354
183
  this._diversityParams.lambda = Number(value);
355
184
  return this;
356
185
  }
357
-
358
- /**
359
- * Enable limits-by-field. Use every(n) and limit(field, max) to configure.
360
- * @returns {this}
361
- */
362
186
  limitByField() {
363
187
  this._limitsByFieldEnabled = true;
364
188
  return this;
365
189
  }
366
-
367
- /**
368
- * Set window size (every_n) for limits by field. Default 10.
369
- * @param {number} n - Window size (e.g. every 10 items)
370
- * @returns {this}
371
- */
372
190
  every(n) {
373
191
  this._everyN = Number(n);
374
192
  return this;
375
193
  }
376
-
377
- /**
378
- * Add a limit rule: max occurrences of field value per window.
379
- * @param {string} field - Field name
380
- * @param {number} max - Maximum count per window
381
- * @returns {this}
382
- */
383
194
  limit(field, max) {
384
195
  const f = typeof field === 'string' && field.trim() ? field.trim() : null;
385
- if (!f) {
386
- throw new Error('Ranking.limit: field must be a non-empty string');
387
- }
196
+ if (!f) throw new Error('Ranking.limit: field must be a non-empty string');
388
197
  const existing = this._limitRules.find((r) => r.field === f);
389
198
  if (existing) existing.limit = Number(max) || 0;
390
199
  else this._limitRules.push({ field: f, limit: Number(max) || 0 });
391
200
  return this;
392
201
  }
393
-
394
- /**
395
- * Set the candidates (hits) to rank. Each should have _id, and optionally _features, _scores, _source.
396
- * @param {Array} candidates - Array of hit objects
397
- * @returns {this}
398
- */
399
202
  candidates(candidates) {
400
203
  this._candidates = candidates;
401
204
  return this;
402
205
  }
403
-
404
- /**
405
- * Execute the ranking request. Sets lastCall and lastResult on success; throws on error.
406
- * @returns {Promise<object>} The result object (result.result.items with item_id and score)
407
- */
408
206
  async execute() {
409
207
  if (!Array.isArray(this._candidates) || this._candidates.length === 0) {
410
208
  throw new Error('Ranking.execute: candidates must be set and non-empty (pass to constructor or call candidates([...]))');
@@ -423,10 +221,7 @@ export class Ranking {
423
221
  const startTime = performance.now();
424
222
  const response = await fetch(url, {
425
223
  method: 'POST',
426
- headers: {
427
- 'Content-Type': 'application/json',
428
- Authorization: `Bearer ${this._apiKey}`,
429
- },
224
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this._apiKey}` },
430
225
  body: JSON.stringify(payload),
431
226
  });
432
227
  const frontendTime = Math.round(performance.now() - startTime);
@@ -446,31 +241,17 @@ export class Ranking {
446
241
  result.took_frontend = frontendTime;
447
242
  this.lastCall = { endpoint, payload };
448
243
  this.lastResult = result;
449
-
450
244
  const res = result.result;
451
- if (!res) {
452
- throw new Error('Ranking.execute: response result is undefined');
453
- }
454
-
245
+ if (!res) throw new Error('Ranking.execute: response result is undefined');
455
246
  const resultItems = res.items || [];
456
247
  this._log('Ranking result:');
457
248
  this._log(` took_frontend_ms: ${result.took_frontend}`);
458
249
  this._log(` items: ${resultItems.length}`);
459
250
  return res;
460
251
  }
461
-
462
- /**
463
- * Log a string.
464
- * @param {string} string
465
- */
466
252
  log(string) {
467
253
  this._log(string);
468
254
  }
469
-
470
- /**
471
- * Show results.
472
- * @param {*} results
473
- */
474
255
  show(results) {
475
256
  this._show(results);
476
257
  }
@@ -1,41 +1,9 @@
1
- /**
2
- * Scoring client for MBD Studio SDK.
3
- * Fluent API: methods return the instance for chaining.
4
- * Score or rerank items for a given user using a selected model.
5
- */
6
-
7
1
  export class Scoring {
8
- /** @type {string} */
9
- _url;
10
-
11
- /** @type {string} */
12
- _apiKey;
13
-
14
- /** @type {string | null} */
15
2
  _userId = null;
16
-
17
- /** @type {string[]} */
18
3
  _itemIds = [];
19
-
20
- /** @type {string | null} */
21
4
  _modelEndpoint = null;
22
-
23
- /** @type {{ endpoint: string, payload: object } | null} */
24
5
  lastCall = null;
25
-
26
- /** @type {object | null} */
27
6
  lastResult = null;
28
-
29
- /**
30
- * @param {Object} options
31
- * @param {string} options.url - Scoring service base URL
32
- * @param {string} options.apiKey - API key for authentication
33
- * @param {string} [options.userId] - User id for scoring context
34
- * @param {string[]} [options.itemIds] - Item ids to score/rerank
35
- * @param {string} [options.origin='sdk'] - origin sent in backend call payloads
36
- * @param {function(string): void} [options.log] - Optional override for log()
37
- * @param {function(*): void} [options.show] - Optional override for show()
38
- */
39
7
  constructor(options) {
40
8
  if (!options || typeof options !== 'object') {
41
9
  throw new Error('Scoring: options object is required');
@@ -52,71 +20,32 @@ export class Scoring {
52
20
  this._origin = typeof origin === 'string' && origin.trim() ? origin.trim() : 'sdk';
53
21
  this._log = typeof log === 'function' ? log : console.log.bind(console);
54
22
  this._show = typeof show === 'function' ? show : console.log.bind(console);
55
- if (userId != null && typeof userId === 'string' && userId.trim()) {
56
- this._userId = userId.trim();
57
- }
23
+ if (userId != null && typeof userId === 'string' && userId.trim()) this._userId = userId.trim();
58
24
  if (Array.isArray(itemIds) && itemIds.length > 0) {
59
25
  this._itemIds = itemIds.map((id) => (typeof id === 'string' ? id : String(id)));
60
26
  }
61
27
  }
62
-
63
- /**
64
- * Return the endpoint path for the current model (must be set via model(endpoint)).
65
- * @returns {string}
66
- */
67
28
  getEndpoint() {
68
29
  if (!this._modelEndpoint || !this._modelEndpoint.trim()) {
69
30
  throw new Error('Scoring.getEndpoint: model endpoint must be set (call model(endpoint) first)');
70
31
  }
71
32
  return this._modelEndpoint.startsWith('/') ? this._modelEndpoint : `/${this._modelEndpoint}`;
72
33
  }
73
-
74
- /**
75
- * Build the request payload (origin + user_id + item_ids).
76
- * @returns {object}
77
- */
78
34
  getPayload() {
79
- return {
80
- origin: this._origin,
81
- user_id: this._userId,
82
- item_ids: [...this._itemIds],
83
- };
35
+ return { origin: this._origin, user_id: this._userId, item_ids: [...this._itemIds] };
84
36
  }
85
-
86
- /**
87
- * Set the model endpoint for scoring/reranking (required).
88
- * @param {string} endpoint - Endpoint path (e.g. '/scoring/ranking_model/polymarket-rerank-v1')
89
- * @returns {this}
90
- */
91
37
  model(endpoint) {
92
38
  this._modelEndpoint = endpoint;
93
39
  return this;
94
40
  }
95
-
96
- /**
97
- * Set the user id for scoring context.
98
- * @param {string} userId - User identifier
99
- * @returns {this}
100
- */
101
41
  userId(userId) {
102
42
  this._userId = userId;
103
43
  return this;
104
44
  }
105
-
106
- /**
107
- * Set the item ids to score/rerank.
108
- * @param {string[]} itemIds - Array of item id strings
109
- * @returns {this}
110
- */
111
45
  itemIds(itemIds) {
112
46
  this._itemIds = itemIds;
113
47
  return this;
114
48
  }
115
-
116
- /**
117
- * Execute the scoring request. Sets lastCall and lastResult on success; throws on error.
118
- * @returns {Promise<object>} The result object (e.g. result.result with personalizedRanking, etc.)
119
- */
120
49
  async execute() {
121
50
  if (!this._modelEndpoint || !this._modelEndpoint.trim()) {
122
51
  throw new Error('Scoring.execute: model endpoint must be set (call model(endpoint) first)');
@@ -134,10 +63,7 @@ export class Scoring {
134
63
  const startTime = performance.now();
135
64
  const response = await fetch(url, {
136
65
  method: 'POST',
137
- headers: {
138
- 'Content-Type': 'application/json',
139
- Authorization: `Bearer ${this._apiKey}`,
140
- },
66
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this._apiKey}` },
141
67
  body: JSON.stringify(payload),
142
68
  });
143
69
  const frontendTime = Math.round(performance.now() - startTime);
@@ -157,30 +83,16 @@ export class Scoring {
157
83
  result.took_frontend = frontendTime;
158
84
  this.lastCall = { endpoint, payload };
159
85
  this.lastResult = result;
160
-
161
86
  const res = result.result;
162
- if (res === undefined) {
163
- throw new Error('Scoring.execute: result.result is undefined');
164
- }
165
-
87
+ if (res === undefined) throw new Error('Scoring.execute: result.result is undefined');
166
88
  this._log('Scoring result:');
167
89
  this._log(` took_frontend_ms: ${result.took_frontend}`);
168
90
  this._log(` Array length: ${res.length}`);
169
91
  return res;
170
92
  }
171
-
172
- /**
173
- * Log a string.
174
- * @param {string} string
175
- */
176
93
  log(string) {
177
94
  this._log(string);
178
95
  }
179
-
180
- /**
181
- * Show results.
182
- * @param {*} results
183
- */
184
96
  show(results) {
185
97
  this._show(results);
186
98
  }