mbd-studio-sdk 2.1.1 → 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.
- package/StudioConfig.js +18 -105
- package/V1/Studio.js +17 -126
- package/V1/features/Features.js +8 -123
- package/V1/ranking/Ranking.js +22 -241
- package/V1/scoring/Scoring.js +4 -92
- package/V1/search/Search.js +39 -387
- package/V1/search/filters/ConsoleAccountFilter.js +0 -10
- package/V1/search/filters/CustomFilter.js +0 -9
- package/V1/search/filters/DateFilter.js +1 -13
- package/V1/search/filters/Filter.js +1 -13
- package/V1/search/filters/GeoFilter.js +0 -9
- package/V1/search/filters/GroupBoostFilter.js +0 -14
- package/V1/search/filters/IsNullFilter.js +0 -8
- package/V1/search/filters/MatchFilter.js +0 -9
- package/V1/search/filters/NotNullFilter.js +0 -8
- package/V1/search/filters/NumericFilter.js +0 -10
- package/V1/search/filters/TermFilter.js +0 -9
- package/V1/search/filters/TermsFilter.js +0 -9
- package/V1/search/filters/TermsLookupFilter.js +0 -11
- package/V1/utils/indexUtils.js +0 -5
- package/index.js +0 -1
- package/package.json +1 -1
package/StudioConfig.js
CHANGED
|
@@ -1,127 +1,40 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {Object} ServicesUrl
|
|
3
|
-
* @property {string} searchService - URL of the search service
|
|
4
|
-
* @property {string} storiesService - URL of the stories service
|
|
5
|
-
* @property {string} featuresService - URL of the features service
|
|
6
|
-
* @property {string} scoringService - URL of the scoring service
|
|
7
|
-
* @property {string} rankingService - URL of the ranking service
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {Object} StudioConfigOptions
|
|
12
|
-
* @property {string} apiKey - API key for authentication (required)
|
|
13
|
-
* @property {string} [commonUrl] - Base URL used for all services when provided
|
|
14
|
-
* @property {ServicesUrl} [servicesUrl] - Per-service URLs when commonUrl is not provided
|
|
15
|
-
* @property {function(string): void} [log] - Optional log function, defaults to console.log
|
|
16
|
-
* @property {function(*): void} [show] - Optional show function, defaults to console.log
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Configuration for MBD Studio SDK.
|
|
21
|
-
* Use commonUrl for a single URL for all services, or servicesUrl for one URL per service.
|
|
22
|
-
*/
|
|
23
1
|
export class StudioConfig {
|
|
24
|
-
/**
|
|
25
|
-
* @param {StudioConfigOptions} options - Configuration options
|
|
26
|
-
* @throws {Error} When parameters are invalid or missing required fields
|
|
27
|
-
*/
|
|
28
2
|
constructor(options) {
|
|
29
3
|
if (!options || typeof options !== 'object') {
|
|
30
4
|
throw new Error('StudioConfig: options object is required');
|
|
31
5
|
}
|
|
32
6
|
const { apiKey, commonUrl, servicesUrl, log, show } = options;
|
|
33
|
-
|
|
34
7
|
if (typeof apiKey !== 'string' || !apiKey.trim()) {
|
|
35
8
|
throw new Error('StudioConfig: apiKey is required and must be a non-empty string');
|
|
36
9
|
}
|
|
37
|
-
|
|
38
|
-
const hasCommonUrl =
|
|
39
|
-
typeof commonUrl === 'string' && commonUrl.trim().length > 0;
|
|
40
|
-
|
|
10
|
+
const hasCommonUrl = typeof commonUrl === 'string' && commonUrl.trim().length > 0;
|
|
41
11
|
if (hasCommonUrl) {
|
|
42
12
|
const url = commonUrl.trim().replace(/\/$/, '');
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
13
|
+
this.searchService = url;
|
|
14
|
+
this.storiesService = url;
|
|
15
|
+
this.featuresService = url;
|
|
16
|
+
this.scoringService = url;
|
|
17
|
+
this.rankingService = url;
|
|
48
18
|
} else {
|
|
49
19
|
if (!servicesUrl || typeof servicesUrl !== 'object') {
|
|
50
|
-
throw new Error(
|
|
51
|
-
'StudioConfig: when commonUrl is not provided, servicesUrl must be an object with searchService, storiesService, featuresService, scoringService, rankingService'
|
|
52
|
-
);
|
|
20
|
+
throw new Error('StudioConfig: when commonUrl is not provided, servicesUrl must be an object with searchService, storiesService, featuresService, scoringService, rankingService');
|
|
53
21
|
}
|
|
54
|
-
const {
|
|
55
|
-
|
|
56
|
-
storiesService,
|
|
57
|
-
featuresService,
|
|
58
|
-
scoringService,
|
|
59
|
-
rankingService,
|
|
60
|
-
} = servicesUrl;
|
|
61
|
-
const services = {
|
|
62
|
-
searchService,
|
|
63
|
-
storiesService,
|
|
64
|
-
featuresService,
|
|
65
|
-
scoringService,
|
|
66
|
-
rankingService,
|
|
67
|
-
};
|
|
22
|
+
const { searchService, storiesService, featuresService, scoringService, rankingService } = servicesUrl;
|
|
23
|
+
const services = { searchService, storiesService, featuresService, scoringService, rankingService };
|
|
68
24
|
const missing = Object.entries(services)
|
|
69
25
|
.filter(([, v]) => typeof v !== 'string' || !v.trim())
|
|
70
26
|
.map(([k]) => k);
|
|
71
27
|
if (missing.length > 0) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`StudioConfig: when using servicesUrl, all service URLs are required. Missing or invalid: ${missing.join(', ')}`
|
|
74
|
-
);
|
|
28
|
+
throw new Error(`StudioConfig: when using servicesUrl, all service URLs are required. Missing or invalid: ${missing.join(', ')}`);
|
|
75
29
|
}
|
|
76
|
-
this.
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
30
|
+
this.searchService = searchService.trim().replace(/\/$/, '');
|
|
31
|
+
this.storiesService = storiesService.trim().replace(/\/$/, '');
|
|
32
|
+
this.featuresService = featuresService.trim().replace(/\/$/, '');
|
|
33
|
+
this.scoringService = scoringService.trim().replace(/\/$/, '');
|
|
34
|
+
this.rankingService = rankingService.trim().replace(/\/$/, '');
|
|
81
35
|
}
|
|
82
|
-
|
|
83
|
-
this.
|
|
84
|
-
this.
|
|
85
|
-
this._show = typeof show === 'function' ? show : console.log.bind(console);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** @returns {function(string): void} */
|
|
89
|
-
get log() {
|
|
90
|
-
return this._log;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** @returns {function(*): void} */
|
|
94
|
-
get show() {
|
|
95
|
-
return this._show;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** @returns {string} */
|
|
99
|
-
get searchService() {
|
|
100
|
-
return this._searchService;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** @returns {string} */
|
|
104
|
-
get storiesService() {
|
|
105
|
-
return this._storiesService;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** @returns {string} */
|
|
109
|
-
get featuresService() {
|
|
110
|
-
return this._featuresService;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** @returns {string} */
|
|
114
|
-
get scoringService() {
|
|
115
|
-
return this._scoringService;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** @returns {string} */
|
|
119
|
-
get rankingService() {
|
|
120
|
-
return this._rankingService;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** @returns {string} */
|
|
124
|
-
get apiKey() {
|
|
125
|
-
return this._apiKey;
|
|
36
|
+
this.apiKey = apiKey.trim();
|
|
37
|
+
this.log = typeof log === 'function' ? log : console.log.bind(console);
|
|
38
|
+
this.show = typeof show === 'function' ? show : console.log.bind(console);
|
|
126
39
|
}
|
|
127
40
|
}
|
package/V1/Studio.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Root entry point for the MBD Studio SDK.
|
|
3
|
-
* Holds configuration and provides factory methods for service clients.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { StudioConfig } from '../StudioConfig.js';
|
|
7
2
|
import { Search } from './search/Search.js';
|
|
8
3
|
import { Features, sortAvailableFeatures } from './features/Features.js';
|
|
@@ -10,62 +5,25 @@ import { Scoring } from './scoring/Scoring.js';
|
|
|
10
5
|
import { Ranking } from './ranking/Ranking.js';
|
|
11
6
|
import { findIndex } from './utils/indexUtils.js';
|
|
12
7
|
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {Object} StudioOptions
|
|
15
|
-
* @property {StudioConfig} [config] - Shared config instance (preferred). When provided, apiKey/commonUrl/servicesUrl are ignored.
|
|
16
|
-
* @property {string} [apiKey] - API key for authentication (required when config is not provided)
|
|
17
|
-
* @property {string} [commonUrl] - Base URL used for all services when provided
|
|
18
|
-
* @property {{ searchService: string, storiesService: string, featuresService: string, scoringService: string, rankingService: string }} [servicesUrl] - Per-service URLs when commonUrl is not provided
|
|
19
|
-
* @property {function(string): void} [log] - log function, defaults to console.log
|
|
20
|
-
* @property {function(*): void} [show] - show function, defaults to console.log
|
|
21
|
-
* @property {string} [origin='sdk'] - origin sent in backend call payloads
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Studio SDK client. Initialize with options; use search() to get a Search instance.
|
|
26
|
-
* Pass a StudioConfig instance (config) for version-agnostic initialization, or apiKey/commonUrl/servicesUrl for legacy.
|
|
27
|
-
*/
|
|
28
8
|
export class Studio {
|
|
29
|
-
/**
|
|
30
|
-
* @param {StudioOptions} options - Configuration options (config or apiKey+commonUrl/servicesUrl)
|
|
31
|
-
*/
|
|
32
9
|
constructor(options) {
|
|
33
10
|
if (!options || typeof options !== 'object') {
|
|
34
11
|
throw new Error('Studio: options object is required');
|
|
35
12
|
}
|
|
36
13
|
const { config, apiKey, commonUrl, servicesUrl, log, show, origin } = options;
|
|
37
|
-
this._config =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
: new StudioConfig({ commonUrl, servicesUrl, apiKey });
|
|
41
|
-
this._log =
|
|
42
|
-
typeof log === 'function' ? log : this._config.log;
|
|
43
|
-
this._show =
|
|
44
|
-
typeof show === 'function' ? show : this._config.show;
|
|
14
|
+
this._config = config instanceof StudioConfig ? config : new StudioConfig({ commonUrl, servicesUrl, apiKey });
|
|
15
|
+
this._log = typeof log === 'function' ? log : this._config.log;
|
|
16
|
+
this._show = typeof show === 'function' ? show : this._config.show;
|
|
45
17
|
this._origin = typeof origin === 'string' && origin.trim() ? origin.trim() : 'sdk';
|
|
46
18
|
this._forUser = null;
|
|
47
19
|
this._candidates = [];
|
|
48
20
|
}
|
|
49
|
-
|
|
50
|
-
/* Return the SDK version */
|
|
51
21
|
version() {
|
|
52
22
|
return 'V1';
|
|
53
23
|
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Set the user for context (e.g. features). Puts { index, id } in this.forUser.
|
|
57
|
-
* (Stored internally as this._forUser; read studio._forUser or call studio.forUser() to set.)
|
|
58
|
-
* @param {number} index - User index
|
|
59
|
-
* @param {string} userId - User id
|
|
60
|
-
*/
|
|
61
24
|
forUser(index, userId) {
|
|
62
25
|
this._forUser = { index, id: userId };
|
|
63
26
|
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Return a new Search instance configured with the studio's search service URL, API key, and origin.
|
|
67
|
-
* @returns {Search}
|
|
68
|
-
*/
|
|
69
27
|
search() {
|
|
70
28
|
return new Search({
|
|
71
29
|
url: this._config.searchService,
|
|
@@ -75,22 +33,11 @@ export class Studio {
|
|
|
75
33
|
show: this._show,
|
|
76
34
|
});
|
|
77
35
|
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Extend the candidates array with the given items.
|
|
81
|
-
* @param {Array} array - Items to add to candidates
|
|
82
|
-
*/
|
|
83
36
|
addCandidates(array) {
|
|
84
37
|
this._candidates.push(...array);
|
|
85
38
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
* Return a new Features instance configured with the studio's features service URL and API key.
|
|
89
|
-
* If candidates are set, they are passed to the constructor as items by default
|
|
90
|
-
* @returns {Features}
|
|
91
|
-
*/
|
|
92
|
-
features(version='v1') {
|
|
93
|
-
let items = []
|
|
39
|
+
features(version = 'v1') {
|
|
40
|
+
let items = [];
|
|
94
41
|
if (this._candidates && this._candidates.length > 0) {
|
|
95
42
|
items = this._candidates.map((hit) => ({ index: hit._index, id: hit._id }));
|
|
96
43
|
}
|
|
@@ -106,18 +53,13 @@ export class Studio {
|
|
|
106
53
|
origin: this._origin,
|
|
107
54
|
});
|
|
108
55
|
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Merge feature service response into candidates (hits). Mutates each candidate with _features and _scores.
|
|
112
|
-
* @param {Object} featureServiceResponse
|
|
113
|
-
*/
|
|
114
56
|
addFeatures(featuresResult) {
|
|
115
57
|
const hits = this._candidates || [];
|
|
116
58
|
const features = featuresResult.features;
|
|
117
59
|
const scores = featuresResult.scores;
|
|
118
60
|
if (!features && !scores) {
|
|
119
61
|
this._log('No features or scores found in featuresResult');
|
|
120
|
-
return
|
|
62
|
+
return;
|
|
121
63
|
}
|
|
122
64
|
let availableFeatures = {};
|
|
123
65
|
let availableScores = {};
|
|
@@ -132,9 +74,7 @@ export class Studio {
|
|
|
132
74
|
}
|
|
133
75
|
if (hit._features) {
|
|
134
76
|
for (const [key, value] of Object.entries(hit._features)) {
|
|
135
|
-
if (typeof value === 'number' && !Number.isNaN(value))
|
|
136
|
-
availableFeatures[key] = true;
|
|
137
|
-
}
|
|
77
|
+
if (typeof value === 'number' && !Number.isNaN(value)) availableFeatures[key] = true;
|
|
138
78
|
}
|
|
139
79
|
}
|
|
140
80
|
if (hit._scores) {
|
|
@@ -144,9 +84,7 @@ export class Studio {
|
|
|
144
84
|
}
|
|
145
85
|
if (hit._scores) {
|
|
146
86
|
for (const [key, value] of Object.entries(hit._scores)) {
|
|
147
|
-
if (typeof value === 'number' && !Number.isNaN(value))
|
|
148
|
-
availableScores[key] = true;
|
|
149
|
-
}
|
|
87
|
+
if (typeof value === 'number' && !Number.isNaN(value)) availableScores[key] = true;
|
|
150
88
|
}
|
|
151
89
|
}
|
|
152
90
|
}
|
|
@@ -155,19 +93,11 @@ export class Studio {
|
|
|
155
93
|
this._log(`Available features: ${availableFeatures}`);
|
|
156
94
|
this._log(`Available scores: ${availableScores}`);
|
|
157
95
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Return a new Scoring instance configured with the studio's scoring service URL and API key.
|
|
162
|
-
* If forUser is set, userId is passed; if candidates are set, item ids are extracted and passed.
|
|
163
|
-
* @returns {Scoring}
|
|
164
|
-
*/
|
|
165
96
|
scoring() {
|
|
166
97
|
const userId = this._forUser?.id ?? null;
|
|
167
|
-
const itemIds =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
: [];
|
|
98
|
+
const itemIds = Array.isArray(this._candidates) && this._candidates.length > 0
|
|
99
|
+
? this._candidates.map((c) => (c && (c._id != null) ? String(c._id) : null)).filter(Boolean)
|
|
100
|
+
: [];
|
|
171
101
|
return new Scoring({
|
|
172
102
|
url: this._config.scoringService,
|
|
173
103
|
apiKey: this._config.apiKey,
|
|
@@ -178,39 +108,22 @@ export class Studio {
|
|
|
178
108
|
origin: this._origin,
|
|
179
109
|
});
|
|
180
110
|
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Add scores to candidates from a scoring/rerank result (ranking order).
|
|
184
|
-
* @param {string} endpoint - Model endpoint used (key for hit._scores)
|
|
185
|
-
* @param {Array} scoringResult - Array of item ids in ranking order
|
|
186
|
-
*/
|
|
187
111
|
addScores(scoringResult, scoringKey) {
|
|
188
112
|
const rankedItemIds = scoringResult;
|
|
189
|
-
if (!this._candidates || !rankedItemIds || !Array.isArray(rankedItemIds))
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
113
|
+
if (!this._candidates || !rankedItemIds || !Array.isArray(rankedItemIds)) return;
|
|
192
114
|
const rankToScore = {};
|
|
193
115
|
rankedItemIds.forEach((itemId, index) => {
|
|
194
|
-
|
|
195
|
-
rankToScore[itemId] = score;
|
|
116
|
+
rankToScore[itemId] = 1.0 - (index / rankedItemIds.length);
|
|
196
117
|
});
|
|
197
118
|
for (const hit of this._candidates) {
|
|
198
119
|
const hitId = hit._id;
|
|
199
120
|
const hitScore = rankToScore[hitId];
|
|
200
121
|
if (hitScore) {
|
|
201
|
-
if (!hit._scores) {
|
|
202
|
-
hit._scores = {};
|
|
203
|
-
}
|
|
122
|
+
if (!hit._scores) hit._scores = {};
|
|
204
123
|
hit._scores[scoringKey] = rankToScore[hitId];
|
|
205
124
|
}
|
|
206
125
|
}
|
|
207
126
|
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Return a new Ranking instance configured with the studio's ranking service URL and API key.
|
|
211
|
-
* If candidates are set, they are passed to the constructor.
|
|
212
|
-
* @returns {Ranking}
|
|
213
|
-
*/
|
|
214
127
|
ranking() {
|
|
215
128
|
return new Ranking({
|
|
216
129
|
url: this._config.rankingService,
|
|
@@ -221,42 +134,20 @@ export class Studio {
|
|
|
221
134
|
origin: this._origin,
|
|
222
135
|
});
|
|
223
136
|
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Merge ranking result scores into candidates (hits). Mutates each candidate with _ranking_score.
|
|
227
|
-
* @param {Object} rankingResult - Result from Ranking.execute() (result.result with items array of { item_id, score })
|
|
228
|
-
*/
|
|
229
137
|
addRanking(rankingResult) {
|
|
230
138
|
const rankedItems = rankingResult?.items;
|
|
231
|
-
if (!this._candidates || !rankedItems || !Array.isArray(rankedItems))
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
139
|
+
if (!this._candidates || !rankedItems || !Array.isArray(rankedItems)) return;
|
|
234
140
|
const scoreByItemId = {};
|
|
235
|
-
rankedItems.forEach(({ item_id, score }) => {
|
|
236
|
-
scoreByItemId[item_id] = score;
|
|
237
|
-
});
|
|
141
|
+
rankedItems.forEach(({ item_id, score }) => { scoreByItemId[item_id] = score; });
|
|
238
142
|
for (const hit of this._candidates) {
|
|
239
143
|
hit._ranking_score = scoreByItemId[hit._id];
|
|
240
144
|
}
|
|
241
145
|
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Log a string. Uses custom implementation if provided to constructor, otherwise console.log.
|
|
245
|
-
* @param {string} string
|
|
246
|
-
*/
|
|
247
146
|
log(string) {
|
|
248
147
|
this._log(string);
|
|
249
148
|
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Show results. Uses custom implementation if provided to constructor, otherwise console.log.
|
|
253
|
-
* @param {*} results
|
|
254
|
-
*/
|
|
255
149
|
show(results) {
|
|
256
|
-
if (results === undefined)
|
|
257
|
-
results = this._candidates;
|
|
258
|
-
}
|
|
150
|
+
if (results === undefined) results = this._candidates;
|
|
259
151
|
this._show(results);
|
|
260
152
|
}
|
|
261
|
-
|
|
262
153
|
}
|
package/V1/features/Features.js
CHANGED
|
@@ -1,36 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Features client for MBD Studio SDK.
|
|
3
|
-
* Fluent API: methods return the instance for chaining.
|
|
4
|
-
* Enrich items with ML features (embeddings, affinity, etc.) for a given user.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {{ index: string, id: string }} UserRef
|
|
9
|
-
* @typedef {{ index: string, id: string }} ItemRef
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
export const PREFERRED_FEATURE_COLUMNS = [
|
|
13
|
-
'found',
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'sem_sim_closest',
|
|
17
|
-
'usr_primary_labels',
|
|
18
|
-
'usr_secondary_labels',
|
|
19
|
-
'usr_primary_tags',
|
|
20
|
-
'usr_secondary_tags',
|
|
21
|
-
'user_affinity_avg',
|
|
22
|
-
'user_affinity_usdc',
|
|
23
|
-
'user_affinity_count',
|
|
2
|
+
'found', 'original_rank', 'sem_sim_fuzzy', 'sem_sim_closest',
|
|
3
|
+
'usr_primary_labels', 'usr_secondary_labels', 'usr_primary_tags', 'usr_secondary_tags',
|
|
4
|
+
'user_affinity_avg', 'user_affinity_usdc', 'user_affinity_count',
|
|
24
5
|
'cluster_1', 'cluster_2', 'cluster_3', 'cluster_4', 'cluster_5',
|
|
25
6
|
'cluster_6', 'cluster_7', 'cluster_8', 'cluster_9', 'cluster_10',
|
|
26
7
|
'sem_sim_cluster1', 'sem_sim_cluster2', 'sem_sim_cluster3', 'sem_sim_cluster4', 'sem_sim_cluster5',
|
|
27
8
|
];
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Sort feature column names: preferred first, then regular, AI:, TAG:.
|
|
31
|
-
* @param {string[]} available - Array of feature column names
|
|
32
|
-
* @returns {string[]}
|
|
33
|
-
*/
|
|
34
9
|
export function sortAvailableFeatures(available) {
|
|
35
10
|
const preferred = PREFERRED_FEATURE_COLUMNS.filter((col) => available.includes(col));
|
|
36
11
|
const nonPreferred = available.filter((col) => !PREFERRED_FEATURE_COLUMNS.includes(col));
|
|
@@ -39,41 +14,12 @@ export function sortAvailableFeatures(available) {
|
|
|
39
14
|
const tagColumns = nonPreferred.filter((col) => col.startsWith('TAG:')).sort();
|
|
40
15
|
return [...preferred, ...regular, ...aiColumns, ...tagColumns];
|
|
41
16
|
}
|
|
42
|
-
|
|
43
17
|
export class Features {
|
|
44
|
-
/** @type {string} */
|
|
45
|
-
_url;
|
|
46
|
-
|
|
47
|
-
/** @type {string} */
|
|
48
|
-
_apiKey;
|
|
49
|
-
|
|
50
|
-
/** @type {string} */
|
|
51
18
|
_version = 'v1';
|
|
52
|
-
|
|
53
|
-
/** @type {UserRef | null} */
|
|
54
19
|
_user = null;
|
|
55
|
-
|
|
56
|
-
/** @type {ItemRef[]} */
|
|
57
20
|
_items = [];
|
|
58
|
-
|
|
59
|
-
/** @type {{ endpoint: string, payload: object } | null} */
|
|
60
21
|
lastCall = null;
|
|
61
|
-
|
|
62
|
-
/** @type {object | null} */
|
|
63
22
|
lastResult = null;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @param {Object} options
|
|
67
|
-
* @param {string} options.url - Features service base URL
|
|
68
|
-
* @param {string} options.apiKey - API key for authentication
|
|
69
|
-
* @param {string} [options.version='v1'] - API version
|
|
70
|
-
* @param {{ index: string, id: string }[]} [options.items] - Items to enrich
|
|
71
|
-
* @param {string} [options.userIndex] - User index for feature context
|
|
72
|
-
* @param {string} [options.userId] - User id for feature context
|
|
73
|
-
* @param {string} [options.origin='sdk'] - origin sent in backend call payloads
|
|
74
|
-
* @param {function(string): void} [options.log] - Optional override for log()
|
|
75
|
-
* @param {function(*): void} [options.show] - Optional override for show()
|
|
76
|
-
*/
|
|
77
23
|
constructor(options) {
|
|
78
24
|
if (!options || typeof options !== 'object') {
|
|
79
25
|
throw new Error('Features: options object is required');
|
|
@@ -91,69 +37,27 @@ export class Features {
|
|
|
91
37
|
this._origin = typeof origin === 'string' && origin.trim() ? origin.trim() : 'sdk';
|
|
92
38
|
this._log = typeof log === 'function' ? log : console.log.bind(console);
|
|
93
39
|
this._show = typeof show === 'function' ? show : console.log.bind(console);
|
|
94
|
-
if (Array.isArray(items) && items.length > 0)
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
if (userIndex != null && userId != null) {
|
|
98
|
-
this._user = { index: userIndex, id: userId };
|
|
99
|
-
}
|
|
40
|
+
if (Array.isArray(items) && items.length > 0) this._items = items;
|
|
41
|
+
if (userIndex != null && userId != null) this._user = { index: userIndex, id: userId };
|
|
100
42
|
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Return the endpoint path for the current version.
|
|
104
|
-
* @returns {string}
|
|
105
|
-
*/
|
|
106
43
|
getEndpoint() {
|
|
107
44
|
return `/features/${this._version}`;
|
|
108
45
|
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Build the request payload (origin + user + items).
|
|
112
|
-
* @returns {object}
|
|
113
|
-
*/
|
|
114
46
|
getPayload() {
|
|
115
|
-
return {
|
|
116
|
-
origin: this._origin,
|
|
117
|
-
user: this._user,
|
|
118
|
-
items: this._items.map((item) => ({ ...item })),
|
|
119
|
-
};
|
|
47
|
+
return { origin: this._origin, user: this._user, items: this._items.map((item) => ({ ...item })) };
|
|
120
48
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Set the API version (e.g. 'v1'). Default is 'v1'.
|
|
124
|
-
* @param {string} v - Version string
|
|
125
|
-
* @returns {this}
|
|
126
|
-
*/
|
|
127
49
|
version(v) {
|
|
128
50
|
this._version = v;
|
|
129
51
|
return this;
|
|
130
52
|
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Set the items to enrich (array of { index, id }).
|
|
134
|
-
* @param {ItemRef[]} items - Array of { index: string, id: string }
|
|
135
|
-
* @returns {this}
|
|
136
|
-
*/
|
|
137
53
|
items(items) {
|
|
138
54
|
this._items = [...items];
|
|
139
55
|
return this;
|
|
140
56
|
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Set the user for feature computation (index + userId).
|
|
144
|
-
* @param {string} index - User index/table (e.g. 'polymarket-wallets')
|
|
145
|
-
* @param {string} userId - User identifier
|
|
146
|
-
* @returns {this}
|
|
147
|
-
*/
|
|
148
57
|
user(index, userId) {
|
|
149
58
|
this._user = { index, id: userId };
|
|
150
59
|
return this;
|
|
151
60
|
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Execute the features request. Sets lastCall and lastResult on success; throws on error.
|
|
155
|
-
* @returns {Promise<object>} The result object (result.items, result.user, result.feature_names, etc.)
|
|
156
|
-
*/
|
|
157
61
|
async execute() {
|
|
158
62
|
if (!this._user || !this._user.index || !this._user.id) {
|
|
159
63
|
throw new Error('Features.execute: user must be set (call user(index, userId) first)');
|
|
@@ -168,10 +72,7 @@ export class Features {
|
|
|
168
72
|
const startTime = performance.now();
|
|
169
73
|
const response = await fetch(url, {
|
|
170
74
|
method: 'POST',
|
|
171
|
-
headers: {
|
|
172
|
-
'Content-Type': 'application/json',
|
|
173
|
-
Authorization: `Bearer ${this._apiKey}`,
|
|
174
|
-
},
|
|
75
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this._apiKey}` },
|
|
175
76
|
body: JSON.stringify(payload),
|
|
176
77
|
});
|
|
177
78
|
const frontendTime = Math.round(performance.now() - startTime);
|
|
@@ -191,13 +92,8 @@ export class Features {
|
|
|
191
92
|
result.took_frontend = frontendTime;
|
|
192
93
|
this.lastCall = { endpoint, payload };
|
|
193
94
|
this.lastResult = result;
|
|
194
|
-
|
|
195
95
|
const res = result.result;
|
|
196
|
-
if (!res)
|
|
197
|
-
throw new Error('Features.execute: result.result is undefined');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Log infos (aligned with FeaturesInfoColumn.js)
|
|
96
|
+
if (!res) throw new Error('Features.execute: result.result is undefined');
|
|
201
97
|
const infos = {
|
|
202
98
|
took_frontend_ms: result.took_frontend,
|
|
203
99
|
took_backend_ms: res.took_backend ?? 0,
|
|
@@ -227,22 +123,11 @@ export class Features {
|
|
|
227
123
|
this._log(` took_clustering_ms: ${infos.took_clustering_ms}`);
|
|
228
124
|
this._log(` hit_rate: ${infos.hit_rate_pct}%`);
|
|
229
125
|
this._log(` item_embed_rate: ${infos.item_embed_rate_pct}%`);
|
|
230
|
-
|
|
231
126
|
return res;
|
|
232
127
|
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Log a string.
|
|
236
|
-
* @param {string} string
|
|
237
|
-
*/
|
|
238
128
|
log(string) {
|
|
239
129
|
this._log(string);
|
|
240
130
|
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Show results.
|
|
244
|
-
* @param {*} results
|
|
245
|
-
*/
|
|
246
131
|
show(results) {
|
|
247
132
|
this._show(results);
|
|
248
133
|
}
|