mbd-studio-sdk 2.0.3
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/V1/Studio.js +262 -0
- package/V1/features/Features.js +249 -0
- package/V1/index.js +1 -0
- package/V1/ranking/Ranking.js +477 -0
- package/V1/scoring/Scoring.js +187 -0
- package/V1/search/Search.js +673 -0
- package/V1/search/filters/ConsoleAccountFilter.js +18 -0
- package/V1/search/filters/CustomFilter.js +16 -0
- package/V1/search/filters/DateFilter.js +23 -0
- package/V1/search/filters/Filter.js +20 -0
- package/V1/search/filters/GeoFilter.js +16 -0
- package/V1/search/filters/GroupBoostFilter.js +26 -0
- package/V1/search/filters/IsNullFilter.js +14 -0
- package/V1/search/filters/MatchFilter.js +16 -0
- package/V1/search/filters/NotNullFilter.js +14 -0
- package/V1/search/filters/NumericFilter.js +18 -0
- package/V1/search/filters/TermFilter.js +16 -0
- package/V1/search/filters/TermsFilter.js +16 -0
- package/V1/search/filters/TermsLookupFilter.js +20 -0
- package/V1/search/filters/index.js +13 -0
- package/V1/utils/indexUtils.js +12 -0
- package/index.js +4 -0
- package/package.json +14 -0
package/V1/Studio.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root entry point for the MBD Studio SDK.
|
|
3
|
+
* Holds configuration and provides factory methods for service clients.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { StudioConfig } from '../StudioConfig.js';
|
|
7
|
+
import { Search } from './search/Search.js';
|
|
8
|
+
import { Features, sortAvailableFeatures } from './features/Features.js';
|
|
9
|
+
import { Scoring } from './scoring/Scoring.js';
|
|
10
|
+
import { Ranking } from './ranking/Ranking.js';
|
|
11
|
+
import { findIndex } from './utils/indexUtils.js';
|
|
12
|
+
|
|
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
|
+
export class Studio {
|
|
29
|
+
/**
|
|
30
|
+
* @param {StudioOptions} options - Configuration options (config or apiKey+commonUrl/servicesUrl)
|
|
31
|
+
*/
|
|
32
|
+
constructor(options) {
|
|
33
|
+
if (!options || typeof options !== 'object') {
|
|
34
|
+
throw new Error('Studio: options object is required');
|
|
35
|
+
}
|
|
36
|
+
const { config, apiKey, commonUrl, servicesUrl, log, show, origin } = options;
|
|
37
|
+
this._config =
|
|
38
|
+
config instanceof StudioConfig
|
|
39
|
+
? config
|
|
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;
|
|
45
|
+
this._origin = typeof origin === 'string' && origin.trim() ? origin.trim() : 'sdk';
|
|
46
|
+
this._forUser = null;
|
|
47
|
+
this._candidates = [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Return the SDK version */
|
|
51
|
+
version() {
|
|
52
|
+
return 'V1';
|
|
53
|
+
}
|
|
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
|
+
forUser(index, userId) {
|
|
62
|
+
this._forUser = { index, id: userId };
|
|
63
|
+
}
|
|
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
|
+
search() {
|
|
70
|
+
return new Search({
|
|
71
|
+
url: this._config.searchService,
|
|
72
|
+
apiKey: this._config.apiKey,
|
|
73
|
+
origin: this._origin,
|
|
74
|
+
log: this._log,
|
|
75
|
+
show: this._show,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extend the candidates array with the given items.
|
|
81
|
+
* @param {Array} array - Items to add to candidates
|
|
82
|
+
*/
|
|
83
|
+
addCandidates(array) {
|
|
84
|
+
this._candidates.push(...array);
|
|
85
|
+
}
|
|
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 = []
|
|
94
|
+
if (this._candidates && this._candidates.length > 0) {
|
|
95
|
+
items = this._candidates.map((hit) => ({ index: hit._index, id: hit._id }));
|
|
96
|
+
}
|
|
97
|
+
return new Features({
|
|
98
|
+
url: this._config.featuresService,
|
|
99
|
+
apiKey: this._config.apiKey,
|
|
100
|
+
log: this._log,
|
|
101
|
+
show: this._show,
|
|
102
|
+
version,
|
|
103
|
+
items,
|
|
104
|
+
userIndex: this._forUser?.index,
|
|
105
|
+
userId: this._forUser?.id,
|
|
106
|
+
origin: this._origin,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Merge feature service response into candidates (hits). Mutates each candidate with _features and _scores.
|
|
112
|
+
* @param {Object} featureServiceResponse
|
|
113
|
+
*/
|
|
114
|
+
addFeatures(featuresResult) {
|
|
115
|
+
const hits = this._candidates || [];
|
|
116
|
+
const features = featuresResult.features;
|
|
117
|
+
const scores = featuresResult.scores;
|
|
118
|
+
if (!features && !scores) {
|
|
119
|
+
this._log('No features or scores found in featuresResult');
|
|
120
|
+
return ;
|
|
121
|
+
}
|
|
122
|
+
let availableFeatures = {};
|
|
123
|
+
let availableScores = {};
|
|
124
|
+
for (const hit of hits) {
|
|
125
|
+
const hitIndex = findIndex(hit._index);
|
|
126
|
+
const hitFeatures = features?.[hitIndex]?.[hit._id];
|
|
127
|
+
const hitScores = scores?.[hitIndex]?.[hit._id];
|
|
128
|
+
if (hit._features) {
|
|
129
|
+
hit._features = { ...hit._features, ...hitFeatures };
|
|
130
|
+
} else {
|
|
131
|
+
hit._features = hitFeatures;
|
|
132
|
+
}
|
|
133
|
+
if (hit._features) {
|
|
134
|
+
for (const [key, value] of Object.entries(hit._features)) {
|
|
135
|
+
if (typeof value === 'number' && !Number.isNaN(value)) {
|
|
136
|
+
availableFeatures[key] = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (hit._scores) {
|
|
141
|
+
hit._scores = { ...hit._scores, ...hitScores };
|
|
142
|
+
} else {
|
|
143
|
+
hit._scores = hitScores;
|
|
144
|
+
}
|
|
145
|
+
if (hit._scores) {
|
|
146
|
+
for (const [key, value] of Object.entries(hit._scores)) {
|
|
147
|
+
if (typeof value === 'number' && !Number.isNaN(value)) {
|
|
148
|
+
availableScores[key] = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
availableFeatures = sortAvailableFeatures(Object.keys(availableFeatures));
|
|
154
|
+
availableScores = sortAvailableFeatures(Object.keys(availableScores));
|
|
155
|
+
this._log(`Available features: ${availableFeatures}`);
|
|
156
|
+
this._log(`Available scores: ${availableScores}`);
|
|
157
|
+
}
|
|
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
|
+
scoring() {
|
|
166
|
+
const userId = this._forUser?.id ?? null;
|
|
167
|
+
const itemIds =
|
|
168
|
+
Array.isArray(this._candidates) && this._candidates.length > 0
|
|
169
|
+
? this._candidates.map((c) => (c && (c._id != null) ? String(c._id) : null)).filter(Boolean)
|
|
170
|
+
: [];
|
|
171
|
+
return new Scoring({
|
|
172
|
+
url: this._config.scoringService,
|
|
173
|
+
apiKey: this._config.apiKey,
|
|
174
|
+
log: this._log,
|
|
175
|
+
show: this._show,
|
|
176
|
+
userId,
|
|
177
|
+
itemIds,
|
|
178
|
+
origin: this._origin,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
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
|
+
addScores(scoringResult, scoringKey) {
|
|
188
|
+
const rankedItemIds = scoringResult;
|
|
189
|
+
if (!this._candidates || !rankedItemIds || !Array.isArray(rankedItemIds)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const rankToScore = {};
|
|
193
|
+
rankedItemIds.forEach((itemId, index) => {
|
|
194
|
+
const score = 1.0 - (index / rankedItemIds.length);
|
|
195
|
+
rankToScore[itemId] = score;
|
|
196
|
+
});
|
|
197
|
+
for (const hit of this._candidates) {
|
|
198
|
+
const hitId = hit._id;
|
|
199
|
+
const hitScore = rankToScore[hitId];
|
|
200
|
+
if (hitScore) {
|
|
201
|
+
if (!hit._scores) {
|
|
202
|
+
hit._scores = {};
|
|
203
|
+
}
|
|
204
|
+
hit._scores[scoringKey] = rankToScore[hitId];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
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
|
+
ranking() {
|
|
215
|
+
return new Ranking({
|
|
216
|
+
url: this._config.rankingService,
|
|
217
|
+
apiKey: this._config.apiKey,
|
|
218
|
+
log: this._log,
|
|
219
|
+
show: this._show,
|
|
220
|
+
candidates: this._candidates,
|
|
221
|
+
origin: this._origin,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
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
|
+
addRanking(rankingResult) {
|
|
230
|
+
const rankedItems = rankingResult?.items;
|
|
231
|
+
if (!this._candidates || !rankedItems || !Array.isArray(rankedItems)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const scoreByItemId = {};
|
|
235
|
+
rankedItems.forEach(({ item_id, score }) => {
|
|
236
|
+
scoreByItemId[item_id] = score;
|
|
237
|
+
});
|
|
238
|
+
for (const hit of this._candidates) {
|
|
239
|
+
hit._ranking_score = scoreByItemId[hit._id];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Log a string. Uses custom implementation if provided to constructor, otherwise console.log.
|
|
245
|
+
* @param {string} string
|
|
246
|
+
*/
|
|
247
|
+
log(string) {
|
|
248
|
+
this._log(string);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Show results. Uses custom implementation if provided to constructor, otherwise console.log.
|
|
253
|
+
* @param {*} results
|
|
254
|
+
*/
|
|
255
|
+
show(results) {
|
|
256
|
+
if (results === undefined) {
|
|
257
|
+
results = this._candidates;
|
|
258
|
+
}
|
|
259
|
+
this._show(results);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
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
|
+
export const PREFERRED_FEATURE_COLUMNS = [
|
|
13
|
+
'found',
|
|
14
|
+
'original_rank',
|
|
15
|
+
'sem_sim_fuzzy',
|
|
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',
|
|
24
|
+
'cluster_1', 'cluster_2', 'cluster_3', 'cluster_4', 'cluster_5',
|
|
25
|
+
'cluster_6', 'cluster_7', 'cluster_8', 'cluster_9', 'cluster_10',
|
|
26
|
+
'sem_sim_cluster1', 'sem_sim_cluster2', 'sem_sim_cluster3', 'sem_sim_cluster4', 'sem_sim_cluster5',
|
|
27
|
+
];
|
|
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
|
+
export function sortAvailableFeatures(available) {
|
|
35
|
+
const preferred = PREFERRED_FEATURE_COLUMNS.filter((col) => available.includes(col));
|
|
36
|
+
const nonPreferred = available.filter((col) => !PREFERRED_FEATURE_COLUMNS.includes(col));
|
|
37
|
+
const regular = nonPreferred.filter((col) => !col.startsWith('AI:') && !col.startsWith('TAG:')).sort();
|
|
38
|
+
const aiColumns = nonPreferred.filter((col) => col.startsWith('AI:')).sort();
|
|
39
|
+
const tagColumns = nonPreferred.filter((col) => col.startsWith('TAG:')).sort();
|
|
40
|
+
return [...preferred, ...regular, ...aiColumns, ...tagColumns];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class Features {
|
|
44
|
+
/** @type {string} */
|
|
45
|
+
_url;
|
|
46
|
+
|
|
47
|
+
/** @type {string} */
|
|
48
|
+
_apiKey;
|
|
49
|
+
|
|
50
|
+
/** @type {string} */
|
|
51
|
+
_version = 'v1';
|
|
52
|
+
|
|
53
|
+
/** @type {UserRef | null} */
|
|
54
|
+
_user = null;
|
|
55
|
+
|
|
56
|
+
/** @type {ItemRef[]} */
|
|
57
|
+
_items = [];
|
|
58
|
+
|
|
59
|
+
/** @type {{ endpoint: string, payload: object } | null} */
|
|
60
|
+
lastCall = null;
|
|
61
|
+
|
|
62
|
+
/** @type {object | null} */
|
|
63
|
+
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
|
+
constructor(options) {
|
|
78
|
+
if (!options || typeof options !== 'object') {
|
|
79
|
+
throw new Error('Features: options object is required');
|
|
80
|
+
}
|
|
81
|
+
const { url, apiKey, version = 'v1', items = [], userIndex, userId, origin = 'sdk', log, show } = options;
|
|
82
|
+
if (typeof url !== 'string' || !url.trim()) {
|
|
83
|
+
throw new Error('Features: options.url is required and must be a non-empty string');
|
|
84
|
+
}
|
|
85
|
+
if (typeof apiKey !== 'string' || !apiKey.trim()) {
|
|
86
|
+
throw new Error('Features: options.apiKey is required and must be a non-empty string');
|
|
87
|
+
}
|
|
88
|
+
this._url = url.trim().replace(/\/$/, '');
|
|
89
|
+
this._apiKey = apiKey.trim();
|
|
90
|
+
this._version = version;
|
|
91
|
+
this._origin = typeof origin === 'string' && origin.trim() ? origin.trim() : 'sdk';
|
|
92
|
+
this._log = typeof log === 'function' ? log : console.log.bind(console);
|
|
93
|
+
this._show = typeof show === 'function' ? show : console.log.bind(console);
|
|
94
|
+
if (Array.isArray(items) && items.length > 0) {
|
|
95
|
+
this._items = items;
|
|
96
|
+
}
|
|
97
|
+
if (userIndex != null && userId != null) {
|
|
98
|
+
this._user = { index: userIndex, id: userId };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Return the endpoint path for the current version.
|
|
104
|
+
* @returns {string}
|
|
105
|
+
*/
|
|
106
|
+
getEndpoint() {
|
|
107
|
+
return `/features/${this._version}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Build the request payload (origin + user + items).
|
|
112
|
+
* @returns {object}
|
|
113
|
+
*/
|
|
114
|
+
getPayload() {
|
|
115
|
+
return {
|
|
116
|
+
origin: this._origin,
|
|
117
|
+
user: this._user,
|
|
118
|
+
items: this._items.map((item) => ({ ...item })),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set the API version (e.g. 'v1'). Default is 'v1'.
|
|
124
|
+
* @param {string} v - Version string
|
|
125
|
+
* @returns {this}
|
|
126
|
+
*/
|
|
127
|
+
version(v) {
|
|
128
|
+
this._version = v;
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
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
|
+
items(items) {
|
|
138
|
+
this._items = [...items];
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
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
|
+
user(index, userId) {
|
|
149
|
+
this._user = { index, id: userId };
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
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
|
+
async execute() {
|
|
158
|
+
if (!this._user || !this._user.index || !this._user.id) {
|
|
159
|
+
throw new Error('Features.execute: user must be set (call user(index, userId) first)');
|
|
160
|
+
}
|
|
161
|
+
if (!Array.isArray(this._items) || this._items.length === 0) {
|
|
162
|
+
throw new Error('Features.execute: items must be set and non-empty (call items([...]) first)');
|
|
163
|
+
}
|
|
164
|
+
const endpoint = this.getEndpoint();
|
|
165
|
+
const payload = this.getPayload();
|
|
166
|
+
const url = `${this._url}${endpoint}`;
|
|
167
|
+
this.log(`Sending request to ${url}`);
|
|
168
|
+
const startTime = performance.now();
|
|
169
|
+
const response = await fetch(url, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: {
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
174
|
+
},
|
|
175
|
+
body: JSON.stringify(payload),
|
|
176
|
+
});
|
|
177
|
+
const frontendTime = Math.round(performance.now() - startTime);
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
const text = await response.text();
|
|
180
|
+
let message = `Features API error: ${response.status} ${response.statusText}`;
|
|
181
|
+
if (text) message += ` — ${text}`;
|
|
182
|
+
this.log(message);
|
|
183
|
+
throw new Error(message);
|
|
184
|
+
}
|
|
185
|
+
const result = await response.json();
|
|
186
|
+
if (result && typeof result.error !== 'undefined' && result.error !== null) {
|
|
187
|
+
const msg = typeof result.error === 'string' ? result.error : String(result.error);
|
|
188
|
+
this.log(msg);
|
|
189
|
+
throw new Error(msg);
|
|
190
|
+
}
|
|
191
|
+
result.took_frontend = frontendTime;
|
|
192
|
+
this.lastCall = { endpoint, payload };
|
|
193
|
+
this.lastResult = result;
|
|
194
|
+
|
|
195
|
+
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)
|
|
201
|
+
const infos = {
|
|
202
|
+
took_frontend_ms: result.took_frontend,
|
|
203
|
+
took_backend_ms: res.took_backend ?? 0,
|
|
204
|
+
took_dynamo_user_ms: res.took_dynamo_user ?? 0,
|
|
205
|
+
took_dynamo_items_ms: res.took_dynamo_items ?? 0,
|
|
206
|
+
took_dynamo_interactions_ms: res.took_dynamo_interactions ?? 0,
|
|
207
|
+
took_features_ms: res.took_features ?? 0,
|
|
208
|
+
took_semantic_ms: res.took_semantic ?? 0,
|
|
209
|
+
took_topics_ms: res.took_topics ?? 0,
|
|
210
|
+
took_topic_similarity_ms: res.took_topic_similarity ?? 0,
|
|
211
|
+
took_user_affinity_ms: res.took_user_affinity ?? 0,
|
|
212
|
+
took_clustering_ms: res.took_clustering ?? 0,
|
|
213
|
+
hit_rate_pct: ((res.hit_rate ?? 0) * 100).toFixed(1),
|
|
214
|
+
item_embed_rate_pct: ((res.item_embed_rate ?? 0) * 100).toFixed(1),
|
|
215
|
+
};
|
|
216
|
+
this._log('Features result:');
|
|
217
|
+
this._log(` took_frontend_ms: ${infos.took_frontend_ms}`);
|
|
218
|
+
this._log(` took_backend_ms: ${infos.took_backend_ms}`);
|
|
219
|
+
this._log(` took_dynamo_user_ms: ${infos.took_dynamo_user_ms}`);
|
|
220
|
+
this._log(` took_dynamo_items_ms: ${infos.took_dynamo_items_ms}`);
|
|
221
|
+
this._log(` took_dynamo_interactions_ms: ${infos.took_dynamo_interactions_ms}`);
|
|
222
|
+
this._log(` took_features_ms: ${infos.took_features_ms}`);
|
|
223
|
+
this._log(` took_semantic_ms: ${infos.took_semantic_ms}`);
|
|
224
|
+
this._log(` took_topics_ms: ${infos.took_topics_ms}`);
|
|
225
|
+
this._log(` took_topic_similarity_ms: ${infos.took_topic_similarity_ms}`);
|
|
226
|
+
this._log(` took_user_affinity_ms: ${infos.took_user_affinity_ms}`);
|
|
227
|
+
this._log(` took_clustering_ms: ${infos.took_clustering_ms}`);
|
|
228
|
+
this._log(` hit_rate: ${infos.hit_rate_pct}%`);
|
|
229
|
+
this._log(` item_embed_rate: ${infos.item_embed_rate_pct}%`);
|
|
230
|
+
|
|
231
|
+
return res;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Log a string.
|
|
236
|
+
* @param {string} string
|
|
237
|
+
*/
|
|
238
|
+
log(string) {
|
|
239
|
+
this._log(string);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Show results.
|
|
244
|
+
* @param {*} results
|
|
245
|
+
*/
|
|
246
|
+
show(results) {
|
|
247
|
+
this._show(results);
|
|
248
|
+
}
|
|
249
|
+
}
|
package/V1/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Studio } from './Studio.js';
|