ebay-mcp-remote-edition 3.2.0 → 3.3.1
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/README.md +261 -1
- package/build/api/analytics-and-report/analytics.js +4 -4
- package/build/api/client-trading.js +2 -2
- package/build/api/communication/feedback.js +5 -5
- package/build/api/communication/message.js +5 -5
- package/build/api/communication/negotiation.js +3 -3
- package/build/api/communication/notification.js +21 -21
- package/build/api/listing-management/inventory.js +36 -36
- package/build/api/listing-metadata/metadata.js +24 -24
- package/build/auth/oauth.js +2 -2
- package/build/auth/token-verifier.js +3 -3
- package/build/server-http.js +18 -1
- package/build/tools/index.js +5 -5
- package/build/validation/effective-context.js +77 -0
- package/build/validation/providers/ebay-sold.js +96 -51
- package/build/validation/providers/ebay.js +115 -57
- package/build/validation/providers/query-utils.js +577 -0
- package/build/validation/providers/research.js +16 -0
- package/build/validation/providers/social.js +681 -2
- package/build/validation/providers/terapeak.js +30 -0
- package/build/validation/recommendation.js +33 -10
- package/build/validation/run-validation.js +279 -31
- package/build/validation/schemas.js +62 -15
- package/package.json +22 -20
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import { getValidationEffectiveContext } from '../effective-context.js';
|
|
2
|
+
const NOISY_VERSION_PATTERNS = [
|
|
3
|
+
/\((?:weverse albums?|weverse|digipack|platform|photobook|poca|poca album|kit)\s+ver\.?\)/gi,
|
|
4
|
+
/\((?:weverse albums?|weverse|digipack|platform|photobook|poca|poca album|kit)\s+version\)/gi,
|
|
5
|
+
/\b(?:weverse albums?|weverse|digipack|platform|photobook|poca|poca album|kit)\s+ver\.?\b/gi,
|
|
6
|
+
/\b(?:weverse albums?|weverse|digipack|platform|photobook|poca|poca album|kit)\s+version\b/gi,
|
|
7
|
+
];
|
|
8
|
+
const GENERIC_QUERY_TOKENS = new Set([
|
|
9
|
+
'album',
|
|
10
|
+
'albums',
|
|
11
|
+
'ep',
|
|
12
|
+
'single',
|
|
13
|
+
'mini',
|
|
14
|
+
'the',
|
|
15
|
+
'and',
|
|
16
|
+
'ver',
|
|
17
|
+
'version',
|
|
18
|
+
'release',
|
|
19
|
+
'repackage',
|
|
20
|
+
'pob',
|
|
21
|
+
'benefit',
|
|
22
|
+
'photocard',
|
|
23
|
+
'preorder',
|
|
24
|
+
'pre',
|
|
25
|
+
'order',
|
|
26
|
+
]);
|
|
27
|
+
const POB_LIKE_PATTERN = /\bpob\b|pre\s*order|preorder|benefit|photocard/i;
|
|
28
|
+
const LISTING_NOISE_PATTERN = /\bset\b|\blot\b|\bbundle\b|fanmade|replica|unofficial/gi;
|
|
29
|
+
const GENERIC_DESCRIPTOR_PATTERN = /^(?:album|albums|standard album|standard|cd|music|release|kpop|version|ver)\b/i;
|
|
30
|
+
const BROWSE_DESCRIPTOR_HINT_PATTERN = /\blimited\b|\bdeluxe\b|\bdigipack\b|\bplatform\b|\bjewel\b|\bphotobook\b|\bkit\b|\bpoca\b|\bweverse\b|\bcompact\b|\btarget\b|\bexclusive\b|\bsigned\b|\bvinyl\b|\blp\b|\bstandard\b/i;
|
|
31
|
+
const SOCIAL_CONVERSATION_NOISE_PATTERN = /\b(?:lp|vinyl|cd|photobook|digipack|platform|jewel|compact|kit|poca|weverse|standard|limited|deluxe|edition|version|ver\.?)\b/gi;
|
|
32
|
+
const SOCIAL_LISTING_NOISE_PATTERN = /\b(?:set|lot|bundle|sealed|official merch|merch(?:andise)?|shop|store|benefit|photocard|pob|pre\s*order|preorder|fanmade|replica|unofficial|listing|sale)\b/gi;
|
|
33
|
+
export function normalizeWhitespace(value) {
|
|
34
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
35
|
+
}
|
|
36
|
+
function escapeRegExp(value) {
|
|
37
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
38
|
+
}
|
|
39
|
+
export function sanitizeQueryCandidate(query) {
|
|
40
|
+
return normalizeWhitespace(query)
|
|
41
|
+
.replace(/^[\s\-–—:;,./]+/, '')
|
|
42
|
+
.replace(/[\s\-–—:;,./]+$/, '')
|
|
43
|
+
.replace(/\s+/g, ' ')
|
|
44
|
+
.trim();
|
|
45
|
+
}
|
|
46
|
+
function tokenizeQuery(value) {
|
|
47
|
+
return sanitizeQueryCandidate(value)
|
|
48
|
+
.toLowerCase()
|
|
49
|
+
.split(/\s+/)
|
|
50
|
+
.map((token) => token.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, ''))
|
|
51
|
+
.filter((token) => token.length > 0);
|
|
52
|
+
}
|
|
53
|
+
export function extractSemanticTokens(value) {
|
|
54
|
+
return tokenizeQuery(value).filter((token) => token.length >= 3 &&
|
|
55
|
+
!GENERIC_QUERY_TOKENS.has(token) &&
|
|
56
|
+
!/^\d+$/.test(token) &&
|
|
57
|
+
!/^\d+(?:st|nd|rd|th)$/.test(token));
|
|
58
|
+
}
|
|
59
|
+
export function simplifyItemTitle(title) {
|
|
60
|
+
let simplified = title;
|
|
61
|
+
for (const pattern of NOISY_VERSION_PATTERNS) {
|
|
62
|
+
simplified = simplified.replace(pattern, ' ');
|
|
63
|
+
}
|
|
64
|
+
return normalizeWhitespace(simplified);
|
|
65
|
+
}
|
|
66
|
+
export function titleAlreadyContainsArtist(title, artist) {
|
|
67
|
+
const normalizedTitle = normalizeWhitespace(title).toLowerCase();
|
|
68
|
+
const normalizedArtist = normalizeWhitespace(artist).toLowerCase();
|
|
69
|
+
return normalizedArtist.length > 0 && normalizedTitle.includes(normalizedArtist);
|
|
70
|
+
}
|
|
71
|
+
function removeBracketedContent(value) {
|
|
72
|
+
return normalizeWhitespace(value.replace(/\([^)]*\)/g, ' '));
|
|
73
|
+
}
|
|
74
|
+
function stripBracketCharacters(value) {
|
|
75
|
+
return normalizeWhitespace(value.replace(/[\]{}()[]/g, ' '));
|
|
76
|
+
}
|
|
77
|
+
function stripArtistsFromText(value, artists) {
|
|
78
|
+
return artists.reduce((result, artist) => {
|
|
79
|
+
const sanitizedArtist = sanitizeQueryCandidate(artist);
|
|
80
|
+
if (!sanitizedArtist) {
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
return result.replace(new RegExp(escapeRegExp(sanitizedArtist), 'ig'), ' ');
|
|
84
|
+
}, value);
|
|
85
|
+
}
|
|
86
|
+
function stripPrimaryArtist(candidate, primaryArtist) {
|
|
87
|
+
const sanitizedArtist = sanitizeQueryCandidate(primaryArtist);
|
|
88
|
+
if (!sanitizedArtist) {
|
|
89
|
+
return sanitizeQueryCandidate(candidate);
|
|
90
|
+
}
|
|
91
|
+
return sanitizeQueryCandidate(candidate.replace(new RegExp(escapeRegExp(sanitizedArtist), 'ig'), ' '));
|
|
92
|
+
}
|
|
93
|
+
function dedupeQueries(candidates) {
|
|
94
|
+
const seen = new Set();
|
|
95
|
+
const result = [];
|
|
96
|
+
for (const candidate of candidates) {
|
|
97
|
+
const normalized = sanitizeQueryCandidate(candidate);
|
|
98
|
+
if (!normalized) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const key = normalized.toLowerCase();
|
|
102
|
+
if (seen.has(key)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
seen.add(key);
|
|
106
|
+
result.push(normalized);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
function getPrimaryArtist(request) {
|
|
111
|
+
return (getValidationEffectiveContext(request).searchArtist ??
|
|
112
|
+
request.item.canonicalArtists[0]?.trim() ??
|
|
113
|
+
'');
|
|
114
|
+
}
|
|
115
|
+
export function getPrimaryAlbumPhrase(request) {
|
|
116
|
+
const effectiveContext = getValidationEffectiveContext(request);
|
|
117
|
+
if (effectiveContext.sourceType === 'event') {
|
|
118
|
+
const eventPhrase = sanitizeQueryCandidate(effectiveContext.searchEvent ?? '');
|
|
119
|
+
if (eventPhrase && extractSemanticTokens(eventPhrase).length > 0) {
|
|
120
|
+
return eventPhrase;
|
|
121
|
+
}
|
|
122
|
+
const itemPhrase = sanitizeQueryCandidate(effectiveContext.searchItem ?? '');
|
|
123
|
+
if (itemPhrase && extractSemanticTokens(itemPhrase).length > 0) {
|
|
124
|
+
return itemPhrase;
|
|
125
|
+
}
|
|
126
|
+
return sanitizeQueryCandidate(effectiveContext.searchLocation ?? '');
|
|
127
|
+
}
|
|
128
|
+
const relatedAlbum = sanitizeQueryCandidate(effectiveContext.searchAlbum ?? request.item.relatedAlbums[0]?.trim() ?? '');
|
|
129
|
+
if (relatedAlbum && extractSemanticTokens(relatedAlbum).length > 0) {
|
|
130
|
+
return relatedAlbum;
|
|
131
|
+
}
|
|
132
|
+
const simplifiedTitle = simplifyItemTitle(effectiveContext.searchItem ?? request.item.name);
|
|
133
|
+
const withoutArtist = stripArtistsFromText(simplifiedTitle, request.item.canonicalArtists);
|
|
134
|
+
const cleanedWithoutArtist = sanitizeQueryCandidate(removeBracketedContent(withoutArtist));
|
|
135
|
+
if (extractSemanticTokens(cleanedWithoutArtist).length > 0) {
|
|
136
|
+
return cleanedWithoutArtist;
|
|
137
|
+
}
|
|
138
|
+
const cleanedTitle = sanitizeQueryCandidate(removeBracketedContent(simplifiedTitle));
|
|
139
|
+
return extractSemanticTokens(cleanedTitle).length > 0 ? cleanedTitle : relatedAlbum;
|
|
140
|
+
}
|
|
141
|
+
export function getPrimarySocialAlbumPhrase(request) {
|
|
142
|
+
const effectiveContext = getValidationEffectiveContext(request);
|
|
143
|
+
if (effectiveContext.sourceType === 'event') {
|
|
144
|
+
const eventPhrase = buildConversationAlbumPhrase(buildCompactPhrase(effectiveContext.searchEvent ?? '', effectiveContext.searchItem ?? '', effectiveContext.searchLocation ?? ''));
|
|
145
|
+
if (extractSemanticTokens(eventPhrase).length > 0) {
|
|
146
|
+
return eventPhrase;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const relatedAlbum of request.item.relatedAlbums) {
|
|
150
|
+
const sanitizedRelatedAlbum = sanitizeQueryCandidate(relatedAlbum?.trim() ?? '');
|
|
151
|
+
if (!sanitizedRelatedAlbum) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const conversationAlbum = buildConversationAlbumPhrase(sanitizedRelatedAlbum);
|
|
155
|
+
if (extractSemanticTokens(conversationAlbum).length > 0) {
|
|
156
|
+
return conversationAlbum;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return buildConversationAlbumPhrase(getPrimaryAlbumPhrase(request));
|
|
160
|
+
}
|
|
161
|
+
function extractMeaningfulTitleToken(value) {
|
|
162
|
+
const tokens = extractSemanticTokens(value);
|
|
163
|
+
return tokens[0] ?? '';
|
|
164
|
+
}
|
|
165
|
+
export function ensureArtistRetention(candidate, primaryArtist) {
|
|
166
|
+
if (!primaryArtist) {
|
|
167
|
+
return sanitizeQueryCandidate(candidate);
|
|
168
|
+
}
|
|
169
|
+
const sanitized = sanitizeQueryCandidate(candidate);
|
|
170
|
+
if (!sanitized) {
|
|
171
|
+
return sanitizeQueryCandidate(primaryArtist);
|
|
172
|
+
}
|
|
173
|
+
if (titleAlreadyContainsArtist(sanitized, primaryArtist)) {
|
|
174
|
+
return sanitized;
|
|
175
|
+
}
|
|
176
|
+
return sanitizeQueryCandidate(`${primaryArtist} ${sanitized}`);
|
|
177
|
+
}
|
|
178
|
+
function isValidCandidate(candidate, primaryArtist, albumPhrase) {
|
|
179
|
+
const sanitized = sanitizeQueryCandidate(candidate);
|
|
180
|
+
if (sanitized.length < 8) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (!/[\p{L}\p{N}]/u.test(sanitized)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
const semanticTokens = extractSemanticTokens(sanitized);
|
|
187
|
+
if (semanticTokens.length === 0) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const meaningfulAlbumToken = extractMeaningfulTitleToken(albumPhrase);
|
|
191
|
+
const hasArtist = primaryArtist ? titleAlreadyContainsArtist(sanitized, primaryArtist) : false;
|
|
192
|
+
const hasAlbumToken = meaningfulAlbumToken
|
|
193
|
+
? semanticTokens.includes(meaningfulAlbumToken.toLowerCase())
|
|
194
|
+
: false;
|
|
195
|
+
if (!primaryArtist) {
|
|
196
|
+
return hasAlbumToken || semanticTokens.length > 0;
|
|
197
|
+
}
|
|
198
|
+
const nonArtistSemanticTokens = extractSemanticTokens(stripPrimaryArtist(sanitized, primaryArtist));
|
|
199
|
+
return hasAlbumToken || (hasArtist && nonArtistSemanticTokens.length > 0);
|
|
200
|
+
}
|
|
201
|
+
function buildCompactPhrase(...parts) {
|
|
202
|
+
return sanitizeQueryCandidate(parts
|
|
203
|
+
.map((part) => part.trim())
|
|
204
|
+
.filter(Boolean)
|
|
205
|
+
.join(' ')
|
|
206
|
+
.replace(/[,:;/\\|]+/g, ' ')
|
|
207
|
+
.replace(/[-–—]+/g, ' ')
|
|
208
|
+
.replace(/\s+/g, ' '));
|
|
209
|
+
}
|
|
210
|
+
export function normalizeSocialSearchPhrase(value) {
|
|
211
|
+
return sanitizeQueryCandidate(stripBracketCharacters(simplifyItemTitle(value))
|
|
212
|
+
.replace(/[“”"'`]+/g, ' ')
|
|
213
|
+
.replace(/[&]+/g, ' and ')
|
|
214
|
+
.replace(/[,:;/\\|]+/g, ' ')
|
|
215
|
+
.replace(/[-–—]+/g, ' ')
|
|
216
|
+
.replace(/[.!?]+/g, ' ')
|
|
217
|
+
.replace(POB_LIKE_PATTERN, ' ')
|
|
218
|
+
.replace(LISTING_NOISE_PATTERN, ' ')
|
|
219
|
+
.replace(SOCIAL_CONVERSATION_NOISE_PATTERN, ' ')
|
|
220
|
+
.replace(SOCIAL_LISTING_NOISE_PATTERN, ' '));
|
|
221
|
+
}
|
|
222
|
+
function normalizeDescriptorPhrase(value) {
|
|
223
|
+
return sanitizeQueryCandidate(removeBracketedContent(simplifyItemTitle(value))
|
|
224
|
+
.replace(/\bpob\b|pre\s*order|preorder|benefit|photocard/gi, ' ')
|
|
225
|
+
.replace(LISTING_NOISE_PATTERN, ' ')
|
|
226
|
+
.replace(/\b(?:sealed|new|official)\b/gi, ' '));
|
|
227
|
+
}
|
|
228
|
+
function collectDescriptorPhrases(request, options) {
|
|
229
|
+
const effectiveContext = getValidationEffectiveContext(request);
|
|
230
|
+
if (effectiveContext.sourceType === 'event') {
|
|
231
|
+
const eventDescriptors = [
|
|
232
|
+
effectiveContext.searchItem,
|
|
233
|
+
effectiveContext.searchLocation,
|
|
234
|
+
options.includeValidationType ? request.validation.validationType : null,
|
|
235
|
+
]
|
|
236
|
+
.map((value) => normalizeDescriptorPhrase(value ?? ''))
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.filter((value) => !GENERIC_DESCRIPTOR_PATTERN.test(value.toLowerCase()));
|
|
239
|
+
return dedupeQueries(eventDescriptors).slice(0, 3);
|
|
240
|
+
}
|
|
241
|
+
const rawValues = [
|
|
242
|
+
...request.item.variation,
|
|
243
|
+
...request.item.itemType,
|
|
244
|
+
...request.item.releaseType,
|
|
245
|
+
...(options.includeValidationType ? [request.validation.validationType] : []),
|
|
246
|
+
];
|
|
247
|
+
const descriptors = rawValues
|
|
248
|
+
.map((value) => normalizeDescriptorPhrase(value))
|
|
249
|
+
.filter(Boolean)
|
|
250
|
+
.filter((value) => !GENERIC_DESCRIPTOR_PATTERN.test(value.toLowerCase()))
|
|
251
|
+
.filter((value) => (options.allowPobLike ? true : !POB_LIKE_PATTERN.test(value)));
|
|
252
|
+
const uniqueDescriptors = dedupeQueries(descriptors).filter((descriptor) => {
|
|
253
|
+
const semanticTokens = extractSemanticTokens(descriptor);
|
|
254
|
+
if (semanticTokens.length === 0) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (!options.browseFocused) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return (BROWSE_DESCRIPTOR_HINT_PATTERN.test(descriptor.toLowerCase()) || semanticTokens.length >= 2);
|
|
261
|
+
});
|
|
262
|
+
return uniqueDescriptors.slice(0, 3);
|
|
263
|
+
}
|
|
264
|
+
function dedupeQueryPlan(candidates) {
|
|
265
|
+
const seen = new Set();
|
|
266
|
+
const result = [];
|
|
267
|
+
for (const candidate of candidates) {
|
|
268
|
+
const normalized = sanitizeQueryCandidate(candidate.query);
|
|
269
|
+
if (!normalized) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const key = normalized.toLowerCase();
|
|
273
|
+
if (seen.has(key)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
seen.add(key);
|
|
277
|
+
result.push({
|
|
278
|
+
family: candidate.family,
|
|
279
|
+
query: normalized,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
function sanitizeQueryContextValue(value) {
|
|
285
|
+
if (typeof value !== 'string') {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
const sanitized = sanitizeQueryCandidate(value);
|
|
289
|
+
return sanitized.length > 0 ? sanitized : null;
|
|
290
|
+
}
|
|
291
|
+
function isRejectedResolvedQuery(value) {
|
|
292
|
+
return value !== null && /^error\s*:/i.test(value);
|
|
293
|
+
}
|
|
294
|
+
export function getQueryContext(request) {
|
|
295
|
+
return request.validation.queryContext;
|
|
296
|
+
}
|
|
297
|
+
export function getResolvedSearchQuery(request) {
|
|
298
|
+
return sanitizeQueryContextValue(getQueryContext(request)?.resolvedSearchQuery);
|
|
299
|
+
}
|
|
300
|
+
function getUsableResolvedSearchQuery(request) {
|
|
301
|
+
const resolvedSearchQuery = getResolvedSearchQuery(request);
|
|
302
|
+
return resolvedSearchQuery && !isRejectedResolvedQuery(resolvedSearchQuery)
|
|
303
|
+
? resolvedSearchQuery
|
|
304
|
+
: null;
|
|
305
|
+
}
|
|
306
|
+
function getNormalizedQueryScope(request) {
|
|
307
|
+
return sanitizeQueryContextValue(getQueryContext(request)?.queryScope)?.toLowerCase() ?? null;
|
|
308
|
+
}
|
|
309
|
+
function resolveDeclaredQueryScope(request) {
|
|
310
|
+
const normalizedQueryScope = getNormalizedQueryScope(request);
|
|
311
|
+
if (!normalizedQueryScope) {
|
|
312
|
+
return 'unknown';
|
|
313
|
+
}
|
|
314
|
+
if (normalizedQueryScope.includes('direct query')) {
|
|
315
|
+
return 'direct_query';
|
|
316
|
+
}
|
|
317
|
+
const hasArtist = normalizedQueryScope.includes('artist');
|
|
318
|
+
const hasItem = normalizedQueryScope.includes('item');
|
|
319
|
+
const hasAlbum = normalizedQueryScope.includes('album');
|
|
320
|
+
const hasEvent = normalizedQueryScope.includes('event');
|
|
321
|
+
const hasLocation = normalizedQueryScope.includes('city') ||
|
|
322
|
+
normalizedQueryScope.includes('country') ||
|
|
323
|
+
normalizedQueryScope.includes('state') ||
|
|
324
|
+
normalizedQueryScope.includes('province') ||
|
|
325
|
+
normalizedQueryScope.includes('location');
|
|
326
|
+
if (hasArtist && hasItem && hasLocation) {
|
|
327
|
+
return 'artist_item_location';
|
|
328
|
+
}
|
|
329
|
+
if (hasArtist && hasLocation) {
|
|
330
|
+
return 'artist_location';
|
|
331
|
+
}
|
|
332
|
+
if (hasArtist && hasEvent) {
|
|
333
|
+
return 'artist_event';
|
|
334
|
+
}
|
|
335
|
+
if (hasArtist && hasAlbum) {
|
|
336
|
+
return 'artist_album';
|
|
337
|
+
}
|
|
338
|
+
if (hasArtist && hasItem) {
|
|
339
|
+
return 'artist_item';
|
|
340
|
+
}
|
|
341
|
+
if (normalizedQueryScope === 'artist only' || normalizedQueryScope === 'artist') {
|
|
342
|
+
return 'artist_only';
|
|
343
|
+
}
|
|
344
|
+
return 'unknown';
|
|
345
|
+
}
|
|
346
|
+
function hasExclusiveDirectQueryOverride(request) {
|
|
347
|
+
const queryContext = getQueryContext(request);
|
|
348
|
+
return (queryContext?.directQueryActive === true && getNormalizedQueryScope(request) === 'direct query');
|
|
349
|
+
}
|
|
350
|
+
function finalizeLooseQueryPlan(candidates) {
|
|
351
|
+
return dedupeQueryPlan(candidates).filter((candidate) => isValidConversationQuery(candidate.query));
|
|
352
|
+
}
|
|
353
|
+
function buildArtistOnlyCommerceFallbackPlan(request) {
|
|
354
|
+
return finalizeLooseQueryPlan([
|
|
355
|
+
{
|
|
356
|
+
family: 'artist_only_fallback',
|
|
357
|
+
query: getPrimaryArtist(request),
|
|
358
|
+
},
|
|
359
|
+
]);
|
|
360
|
+
}
|
|
361
|
+
function buildArtistOnlySocialFallbackPlan(request) {
|
|
362
|
+
return finalizeLooseQueryPlan([
|
|
363
|
+
{
|
|
364
|
+
family: 'artist_only_fallback',
|
|
365
|
+
query: normalizeSocialSearchPhrase(getPrimaryArtist(request)),
|
|
366
|
+
},
|
|
367
|
+
]);
|
|
368
|
+
}
|
|
369
|
+
function constrainFallbackPlanForScope(request, fallbackPlan, scopeSpecificFallbackPlan) {
|
|
370
|
+
if (getUsableResolvedSearchQuery(request) === null) {
|
|
371
|
+
return fallbackPlan;
|
|
372
|
+
}
|
|
373
|
+
switch (resolveDeclaredQueryScope(request)) {
|
|
374
|
+
case 'artist_only':
|
|
375
|
+
return scopeSpecificFallbackPlan;
|
|
376
|
+
case 'artist_item':
|
|
377
|
+
case 'artist_album':
|
|
378
|
+
case 'unknown':
|
|
379
|
+
return fallbackPlan;
|
|
380
|
+
case 'artist_event':
|
|
381
|
+
case 'artist_location':
|
|
382
|
+
case 'artist_item_location':
|
|
383
|
+
case 'direct_query':
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
export function buildProviderQueryResolutionDebug(request, queryContextUsed) {
|
|
388
|
+
const queryContext = getQueryContext(request);
|
|
389
|
+
const resolvedSearchQuery = getResolvedSearchQuery(request);
|
|
390
|
+
return {
|
|
391
|
+
queryContextUsed,
|
|
392
|
+
querySource: queryContextUsed ? 'resolved_query_context' : 'provider_fallback',
|
|
393
|
+
resolvedSearchQuery,
|
|
394
|
+
validationScope: sanitizeQueryContextValue(queryContext?.validationScope),
|
|
395
|
+
queryScope: sanitizeQueryContextValue(queryContext?.queryScope),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
export function prependResolvedQueryCandidate(request, fallbackPlan) {
|
|
399
|
+
const usableResolvedQuery = getUsableResolvedSearchQuery(request);
|
|
400
|
+
const queryPlan = usableResolvedQuery
|
|
401
|
+
? hasExclusiveDirectQueryOverride(request)
|
|
402
|
+
? [{ family: 'resolved_query_context', query: usableResolvedQuery }]
|
|
403
|
+
: dedupeQueryPlan([
|
|
404
|
+
{ family: 'resolved_query_context', query: usableResolvedQuery },
|
|
405
|
+
...fallbackPlan,
|
|
406
|
+
])
|
|
407
|
+
: fallbackPlan;
|
|
408
|
+
return {
|
|
409
|
+
queryPlan,
|
|
410
|
+
queryResolution: buildProviderQueryResolutionDebug(request, usableResolvedQuery !== null),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function isValidConversationQuery(candidate) {
|
|
414
|
+
const sanitized = sanitizeQueryCandidate(candidate);
|
|
415
|
+
return sanitized.length >= 2 && /[\p{L}\p{N}]/u.test(sanitized);
|
|
416
|
+
}
|
|
417
|
+
function finalizeConversationQueryPlan(candidates) {
|
|
418
|
+
return dedupeQueryPlan(candidates).filter((candidate) => isValidConversationQuery(candidate.query));
|
|
419
|
+
}
|
|
420
|
+
export function buildConversationAlbumPhrase(albumPhrase) {
|
|
421
|
+
const primarySegment = normalizeSocialSearchPhrase(albumPhrase.split(',')[0] ?? albumPhrase);
|
|
422
|
+
if (extractSemanticTokens(primarySegment).length > 0) {
|
|
423
|
+
return primarySegment;
|
|
424
|
+
}
|
|
425
|
+
return normalizeSocialSearchPhrase(albumPhrase);
|
|
426
|
+
}
|
|
427
|
+
function finalizeQueryPlan(candidates, primaryArtist, albumPhrase) {
|
|
428
|
+
return dedupeQueryPlan(candidates).filter((candidate) => isValidCandidate(candidate.query, primaryArtist, albumPhrase));
|
|
429
|
+
}
|
|
430
|
+
function buildCorePhrases(request) {
|
|
431
|
+
const effectiveContext = getValidationEffectiveContext(request);
|
|
432
|
+
const primaryArtist = getPrimaryArtist(request);
|
|
433
|
+
const albumPhrase = getPrimaryAlbumPhrase(request);
|
|
434
|
+
const simplifiedTitle = effectiveContext.sourceType === 'event'
|
|
435
|
+
? sanitizeQueryCandidate(buildCompactPhrase(primaryArtist, effectiveContext.searchEvent ?? '', effectiveContext.searchItem ?? '', effectiveContext.searchLocation ?? ''))
|
|
436
|
+
: sanitizeQueryCandidate(removeBracketedContent(simplifyItemTitle(request.item.name)));
|
|
437
|
+
const artistAlbumPhrase = ensureArtistRetention(buildCompactPhrase(primaryArtist, albumPhrase), primaryArtist);
|
|
438
|
+
const titleWithArtist = ensureArtistRetention(simplifiedTitle, primaryArtist);
|
|
439
|
+
return {
|
|
440
|
+
primaryArtist,
|
|
441
|
+
albumPhrase,
|
|
442
|
+
simplifiedTitle,
|
|
443
|
+
artistAlbumPhrase,
|
|
444
|
+
titleWithArtist,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
export function buildBrowseQueryPlan(request) {
|
|
448
|
+
const { primaryArtist, albumPhrase, simplifiedTitle, artistAlbumPhrase, titleWithArtist } = buildCorePhrases(request);
|
|
449
|
+
const browseDescriptors = collectDescriptorPhrases(request, {
|
|
450
|
+
allowPobLike: false,
|
|
451
|
+
includeValidationType: false,
|
|
452
|
+
browseFocused: true,
|
|
453
|
+
});
|
|
454
|
+
return finalizeQueryPlan([
|
|
455
|
+
{ family: 'artist_album_core', query: artistAlbumPhrase },
|
|
456
|
+
...browseDescriptors.map((descriptor) => ({
|
|
457
|
+
family: 'artist_album_descriptor',
|
|
458
|
+
query: buildCompactPhrase(artistAlbumPhrase, descriptor),
|
|
459
|
+
})),
|
|
460
|
+
{ family: 'artist_title_listing', query: titleWithArtist },
|
|
461
|
+
{
|
|
462
|
+
family: 'artist_release_album',
|
|
463
|
+
query: buildCompactPhrase(primaryArtist, request.item.releaseType[0] ?? '', albumPhrase),
|
|
464
|
+
},
|
|
465
|
+
{ family: 'album_core', query: buildCompactPhrase(albumPhrase) },
|
|
466
|
+
{ family: 'simplified_title', query: simplifiedTitle },
|
|
467
|
+
], primaryArtist, albumPhrase);
|
|
468
|
+
}
|
|
469
|
+
export function buildResolvedBrowseQueryPlan(request) {
|
|
470
|
+
return prependResolvedQueryCandidate(request, constrainFallbackPlanForScope(request, buildBrowseQueryPlan(request), buildArtistOnlyCommerceFallbackPlan(request)));
|
|
471
|
+
}
|
|
472
|
+
export function buildBrowseQueryCandidates(request) {
|
|
473
|
+
return buildBrowseQueryPlan(request).map((candidate) => candidate.query);
|
|
474
|
+
}
|
|
475
|
+
export function buildSoldQueryPlan(request) {
|
|
476
|
+
const { primaryArtist, albumPhrase, titleWithArtist, artistAlbumPhrase } = buildCorePhrases(request);
|
|
477
|
+
const soldDescriptors = collectDescriptorPhrases(request, {
|
|
478
|
+
allowPobLike: true,
|
|
479
|
+
includeValidationType: true,
|
|
480
|
+
browseFocused: false,
|
|
481
|
+
});
|
|
482
|
+
const validationType = sanitizeQueryCandidate(request.validation.validationType);
|
|
483
|
+
return finalizeQueryPlan([
|
|
484
|
+
{ family: 'artist_album_core', query: artistAlbumPhrase },
|
|
485
|
+
...soldDescriptors.map((descriptor) => ({
|
|
486
|
+
family: 'artist_album_descriptor',
|
|
487
|
+
query: buildCompactPhrase(artistAlbumPhrase, descriptor),
|
|
488
|
+
})),
|
|
489
|
+
{
|
|
490
|
+
family: 'artist_album_validation_type',
|
|
491
|
+
query: buildCompactPhrase(primaryArtist, albumPhrase, validationType),
|
|
492
|
+
},
|
|
493
|
+
{ family: 'artist_title_listing', query: titleWithArtist },
|
|
494
|
+
{ family: 'album_descriptor_only', query: buildCompactPhrase(albumPhrase, validationType) },
|
|
495
|
+
], primaryArtist, albumPhrase);
|
|
496
|
+
}
|
|
497
|
+
export function buildResolvedSoldQueryPlan(request) {
|
|
498
|
+
return prependResolvedQueryCandidate(request, constrainFallbackPlanForScope(request, buildSoldQueryPlan(request), buildArtistOnlyCommerceFallbackPlan(request)));
|
|
499
|
+
}
|
|
500
|
+
export function buildSoldQueryCandidates(request) {
|
|
501
|
+
return buildSoldQueryPlan(request).map((candidate) => candidate.query);
|
|
502
|
+
}
|
|
503
|
+
export function buildTwitterQueryPlan(request) {
|
|
504
|
+
const { primaryArtist } = buildCorePhrases(request);
|
|
505
|
+
const compactArtist = normalizeSocialSearchPhrase(primaryArtist);
|
|
506
|
+
const compactAlbum = stripPrimaryArtist(getPrimarySocialAlbumPhrase(request), compactArtist || primaryArtist);
|
|
507
|
+
const artistAlbum = buildCompactPhrase(compactArtist, compactAlbum);
|
|
508
|
+
const candidates = compactAlbum
|
|
509
|
+
? [
|
|
510
|
+
{
|
|
511
|
+
family: 'artist_album_conversation',
|
|
512
|
+
query: artistAlbum,
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
family: 'quoted_artist_album',
|
|
516
|
+
query: artistAlbum ? `"${artistAlbum}"` : '',
|
|
517
|
+
},
|
|
518
|
+
]
|
|
519
|
+
: [];
|
|
520
|
+
candidates.push({ family: 'artist_only_fallback', query: compactArtist }, { family: 'album_only_fallback', query: compactAlbum });
|
|
521
|
+
return finalizeConversationQueryPlan(candidates);
|
|
522
|
+
}
|
|
523
|
+
export function buildResolvedTwitterQueryPlan(request) {
|
|
524
|
+
return prependResolvedQueryCandidate(request, constrainFallbackPlanForScope(request, buildTwitterQueryPlan(request), buildArtistOnlySocialFallbackPlan(request)));
|
|
525
|
+
}
|
|
526
|
+
export function buildTwitterQueryCandidates(request) {
|
|
527
|
+
return buildTwitterQueryPlan(request).map((candidate) => candidate.query);
|
|
528
|
+
}
|
|
529
|
+
export function buildYouTubeQueryPlan(request) {
|
|
530
|
+
const { primaryArtist } = buildCorePhrases(request);
|
|
531
|
+
const compactArtist = normalizeSocialSearchPhrase(primaryArtist);
|
|
532
|
+
const compactAlbum = stripPrimaryArtist(getPrimarySocialAlbumPhrase(request), compactArtist || primaryArtist);
|
|
533
|
+
const artistAlbum = buildCompactPhrase(compactArtist, compactAlbum);
|
|
534
|
+
return finalizeConversationQueryPlan(compactAlbum
|
|
535
|
+
? [
|
|
536
|
+
{ family: 'artist_album_media_core', query: artistAlbum },
|
|
537
|
+
{ family: 'artist_album_official', query: buildCompactPhrase(artistAlbum, 'official') },
|
|
538
|
+
{ family: 'artist_album_mv', query: buildCompactPhrase(artistAlbum, 'mv') },
|
|
539
|
+
{
|
|
540
|
+
family: 'artist_album_music_video',
|
|
541
|
+
query: buildCompactPhrase(artistAlbum, 'music video'),
|
|
542
|
+
},
|
|
543
|
+
{ family: 'artist_album_teaser', query: buildCompactPhrase(artistAlbum, 'teaser') },
|
|
544
|
+
]
|
|
545
|
+
: [{ family: 'artist_only_media_fallback', query: compactArtist }]);
|
|
546
|
+
}
|
|
547
|
+
export function buildResolvedYouTubeQueryPlan(request) {
|
|
548
|
+
return prependResolvedQueryCandidate(request, constrainFallbackPlanForScope(request, buildYouTubeQueryPlan(request), buildArtistOnlySocialFallbackPlan(request)));
|
|
549
|
+
}
|
|
550
|
+
export function buildYouTubeQueryCandidates(request) {
|
|
551
|
+
return buildYouTubeQueryPlan(request).map((candidate) => candidate.query);
|
|
552
|
+
}
|
|
553
|
+
export function buildRedditQueryPlan(request) {
|
|
554
|
+
const { primaryArtist } = buildCorePhrases(request);
|
|
555
|
+
const compactAlbum = stripPrimaryArtist(getPrimarySocialAlbumPhrase(request), normalizeSocialSearchPhrase(primaryArtist) || primaryArtist);
|
|
556
|
+
const artistAlbum = buildCompactPhrase(primaryArtist, compactAlbum);
|
|
557
|
+
return finalizeQueryPlan([
|
|
558
|
+
{ family: 'artist_album_discussion', query: artistAlbum },
|
|
559
|
+
{ family: 'album_artist_discussion', query: buildCompactPhrase(compactAlbum, primaryArtist) },
|
|
560
|
+
{
|
|
561
|
+
family: 'artist_album_comeback',
|
|
562
|
+
query: buildCompactPhrase(primaryArtist, compactAlbum, 'discussion'),
|
|
563
|
+
},
|
|
564
|
+
], primaryArtist, compactAlbum);
|
|
565
|
+
}
|
|
566
|
+
export function buildResolvedRedditQueryPlan(request) {
|
|
567
|
+
return prependResolvedQueryCandidate(request, constrainFallbackPlanForScope(request, buildRedditQueryPlan(request), buildArtistOnlySocialFallbackPlan(request)));
|
|
568
|
+
}
|
|
569
|
+
export function buildRedditQueryCandidates(request) {
|
|
570
|
+
return buildRedditQueryPlan(request).map((candidate) => candidate.query);
|
|
571
|
+
}
|
|
572
|
+
export function buildValidationQueryCandidates(request) {
|
|
573
|
+
return buildSoldQueryCandidates(request);
|
|
574
|
+
}
|
|
575
|
+
export function buildResolvedValidationQueryPlan(request) {
|
|
576
|
+
return prependResolvedQueryCandidate(request, constrainFallbackPlanForScope(request, buildSoldQueryPlan(request), buildArtistOnlyCommerceFallbackPlan(request)));
|
|
577
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getValidationEffectiveContext } from '../effective-context.js';
|
|
2
|
+
export async function getPreviousComebackResearchSignals(request) {
|
|
3
|
+
await Promise.resolve();
|
|
4
|
+
const hasPerplexityKey = (process.env.PERPLEXITY_API_KEY ?? '').trim().length > 0;
|
|
5
|
+
const effectiveContext = getValidationEffectiveContext(request);
|
|
6
|
+
const primaryAlbum = effectiveContext.searchAlbum ?? effectiveContext.searchEvent ?? effectiveContext.searchItem;
|
|
7
|
+
return {
|
|
8
|
+
previousAlbumTitle: null,
|
|
9
|
+
previousComebackFirstWeekSales: null,
|
|
10
|
+
confidence: 'Low',
|
|
11
|
+
notes: hasPerplexityKey
|
|
12
|
+
? `Research provider contract is ready, but historical comeback lookup for ${primaryAlbum ?? 'this release'} is not implemented yet.`
|
|
13
|
+
: 'Research provider contract is ready, but PERPLEXITY_API_KEY is not configured and historical comeback lookup is not implemented yet.',
|
|
14
|
+
sources: [],
|
|
15
|
+
};
|
|
16
|
+
}
|