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,30 @@
|
|
|
1
|
+
import { buildResolvedValidationQueryPlan } from './query-utils.js';
|
|
2
|
+
export async function getTerapeakValidationSignals(_api, request) {
|
|
3
|
+
await Promise.resolve();
|
|
4
|
+
const { queryPlan, queryResolution } = buildResolvedValidationQueryPlan(request);
|
|
5
|
+
const queryCandidates = queryPlan.map((candidate) => candidate.query);
|
|
6
|
+
const currentQuery = queryCandidates[0] ?? null;
|
|
7
|
+
const previousPobQuery = queryCandidates[1] ?? null;
|
|
8
|
+
return {
|
|
9
|
+
avgWatchersPerListing: null,
|
|
10
|
+
preOrderListingsCount: null,
|
|
11
|
+
marketPriceUsd: null,
|
|
12
|
+
avgShippingCostUsd: null,
|
|
13
|
+
competitionLevel: null,
|
|
14
|
+
previousPobAvgPriceUsd: null,
|
|
15
|
+
previousPobSellThroughPct: null,
|
|
16
|
+
currentListingsCount: null,
|
|
17
|
+
soldListingsCount: null,
|
|
18
|
+
provider: 'none',
|
|
19
|
+
confidence: 'Low',
|
|
20
|
+
queryDebug: {
|
|
21
|
+
currentQuery,
|
|
22
|
+
previousPobQuery,
|
|
23
|
+
selectedMode: 'combined',
|
|
24
|
+
currentResultCount: null,
|
|
25
|
+
previousPobResultCount: null,
|
|
26
|
+
queryResolution,
|
|
27
|
+
notes: 'Terapeak/eBay research provider contract is in place, but live authenticated research retrieval is not implemented yet.',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -13,11 +13,22 @@ export function buildValidationRecommendation(request, signals) {
|
|
|
13
13
|
: trackingCadence === 'Hourly'
|
|
14
14
|
? addHours(request.timestamp, 1)
|
|
15
15
|
: addHours(request.timestamp, 24);
|
|
16
|
-
const marketPrice = signals.
|
|
17
|
-
|
|
16
|
+
const marketPrice = signals.terapeak.marketPriceUsd ??
|
|
17
|
+
signals.sold.soldMedianPriceUsd ??
|
|
18
|
+
signals.ebay.marketPriceUsd;
|
|
19
|
+
const preorderListingsCount = signals.terapeak.preOrderListingsCount ?? signals.ebay.preOrderListingsCount;
|
|
20
|
+
const wholesale = signals.effectiveContext.hasItem ? request.item.wholesalePrice : null;
|
|
18
21
|
const marginRatio = marketPrice !== null && wholesale !== null && wholesale > 0
|
|
19
22
|
? (marketPrice - wholesale) / wholesale
|
|
20
23
|
: null;
|
|
24
|
+
const subjectLabel = signals.effectiveContext.sourceType === 'event'
|
|
25
|
+
? (signals.effectiveContext.searchEvent ??
|
|
26
|
+
signals.effectiveContext.effectiveSearchQuery ??
|
|
27
|
+
'event opportunity')
|
|
28
|
+
: (signals.effectiveContext.searchAlbum ??
|
|
29
|
+
signals.effectiveContext.searchItem ??
|
|
30
|
+
request.item.name ??
|
|
31
|
+
'release');
|
|
21
32
|
const recentSoldCount = [
|
|
22
33
|
signals.sold.soldVelocity.day1Sold,
|
|
23
34
|
signals.sold.soldVelocity.day2Sold,
|
|
@@ -25,18 +36,17 @@ export function buildValidationRecommendation(request, signals) {
|
|
|
25
36
|
].reduce((sum, value) => sum + (value ?? 0), 0);
|
|
26
37
|
let latestAiRecommendation = 'Continue watching until stronger market signal appears.';
|
|
27
38
|
let latestAiConfidence = 'Medium';
|
|
28
|
-
let monitoringNotes =
|
|
39
|
+
let monitoringNotes = `Baseline recommendation generated from current ${signals.effectiveContext.mode} validation state for ${subjectLabel}.`;
|
|
29
40
|
if (!shouldAutoTrack) {
|
|
30
41
|
latestAiRecommendation =
|
|
31
42
|
'Automatic tracking paused because the validation is no longer in a watchable state.';
|
|
32
43
|
latestAiConfidence = 'High';
|
|
33
|
-
monitoringNotes =
|
|
34
|
-
'Stop conditions were met, so automation will not schedule another validation run.';
|
|
44
|
+
monitoringNotes = `Stop conditions were met, so automation will not schedule another ${signals.effectiveContext.mode} validation run for ${subjectLabel}.`;
|
|
35
45
|
}
|
|
36
46
|
else if (marginRatio !== null &&
|
|
37
47
|
marginRatio >= 1 &&
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
preorderListingsCount !== null &&
|
|
49
|
+
preorderListingsCount >= 25) {
|
|
40
50
|
latestAiRecommendation =
|
|
41
51
|
'Demand and pricing look constructive. Continue tracking closely and be ready to upgrade from watch status if sell-through strengthens.';
|
|
42
52
|
latestAiConfidence = 'High';
|
|
@@ -63,13 +73,26 @@ export function buildValidationRecommendation(request, signals) {
|
|
|
63
73
|
monitoringNotes =
|
|
64
74
|
'Temporary sold-provider data is present, but sample depth is not yet strong enough to justify an automatic buy-decision change.';
|
|
65
75
|
}
|
|
66
|
-
else if (signals.social.
|
|
67
|
-
signals.social.
|
|
76
|
+
else if (signals.social.twitterTrending === true ||
|
|
77
|
+
(signals.social.youtubeViews24hMillions !== null &&
|
|
78
|
+
signals.social.youtubeViews24hMillions >= 0.1) ||
|
|
79
|
+
(signals.social.redditPostsCount7d !== null && signals.social.redditPostsCount7d >= 5)) {
|
|
68
80
|
latestAiRecommendation =
|
|
69
81
|
'Demand signals are mixed. Keep monitoring until eBay pricing and social momentum become more decisive.';
|
|
70
82
|
latestAiConfidence = 'Medium';
|
|
71
83
|
monitoringNotes =
|
|
72
|
-
'Cross-channel activity exists, but
|
|
84
|
+
'Cross-channel social activity exists, but it is only being used as a supporting confidence signal and is not strong enough to justify an automatic buy change.';
|
|
85
|
+
}
|
|
86
|
+
if (signals.terapeak.previousPobSellThroughPct !== null &&
|
|
87
|
+
signals.terapeak.previousPobSellThroughPct >= 50) {
|
|
88
|
+
monitoringNotes +=
|
|
89
|
+
' Previous POB sell-through research suggests the comparable release sold through efficiently.';
|
|
90
|
+
if (latestAiConfidence !== 'High') {
|
|
91
|
+
latestAiConfidence = 'Medium';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (signals.research.previousComebackFirstWeekSales !== null) {
|
|
95
|
+
monitoringNotes += ` Previous comeback first-week sales reference: ${signals.research.previousComebackFirstWeekSales}.`;
|
|
73
96
|
}
|
|
74
97
|
return {
|
|
75
98
|
buyDecision: request.validation.buyDecision,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { validationRunRequestSchema } from './schemas.js';
|
|
2
2
|
import { getEbayValidationSignals } from './providers/ebay.js';
|
|
3
3
|
import { getEbaySoldValidationSignals } from './providers/ebay-sold.js';
|
|
4
|
+
import { getTerapeakValidationSignals } from './providers/terapeak.js';
|
|
4
5
|
import { getSocialValidationSignals } from './providers/social.js';
|
|
5
6
|
import { getChartValidationSignals } from './providers/chart.js';
|
|
7
|
+
import { getPreviousComebackResearchSignals } from './providers/research.js';
|
|
8
|
+
import { buildProviderQueryResolutionDebug } from './providers/query-utils.js';
|
|
6
9
|
import { buildValidationRecommendation } from './recommendation.js';
|
|
10
|
+
import { buildValidationEffectiveContext } from './effective-context.js';
|
|
7
11
|
function addMinutes(timestamp, minutes) {
|
|
8
12
|
return new Date(new Date(timestamp).getTime() + minutes * 60 * 1000).toISOString();
|
|
9
13
|
}
|
|
@@ -23,35 +27,171 @@ function getValidationId(input) {
|
|
|
23
27
|
}
|
|
24
28
|
return '';
|
|
25
29
|
}
|
|
26
|
-
function
|
|
30
|
+
function isMeaningfulWriteValue(value) {
|
|
31
|
+
if (value === null || value === undefined) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return typeof value !== 'string' || value.length > 0;
|
|
35
|
+
}
|
|
36
|
+
function getFieldPresence(fields) {
|
|
37
|
+
const contributed = [];
|
|
38
|
+
const omitted = [];
|
|
39
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
40
|
+
if (isMeaningfulWriteValue(value)) {
|
|
41
|
+
contributed.push(field);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
omitted.push(field);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { contributed, omitted };
|
|
48
|
+
}
|
|
49
|
+
function getWriteSource(value, source) {
|
|
50
|
+
return isMeaningfulWriteValue(value) ? source : 'none';
|
|
51
|
+
}
|
|
52
|
+
function buildProviderDebug(request, ebay, sold, terapeak, social, chart, research) {
|
|
53
|
+
const requestQueryResolution = buildProviderQueryResolutionDebug(request, Boolean(ebay.queryResolution?.queryContextUsed));
|
|
54
|
+
const ebayFields = getFieldPresence({
|
|
55
|
+
avgWatchersPerListing: ebay.avgWatchersPerListing,
|
|
56
|
+
preOrderListingsCount: ebay.preOrderListingsCount,
|
|
57
|
+
marketPriceUsd: ebay.marketPriceUsd,
|
|
58
|
+
avgShippingCostUsd: ebay.avgShippingCostUsd,
|
|
59
|
+
competitionLevel: ebay.competitionLevel,
|
|
60
|
+
});
|
|
61
|
+
const soldFields = getFieldPresence({
|
|
62
|
+
soldAveragePriceUsd: sold.soldAveragePriceUsd,
|
|
63
|
+
soldMedianPriceUsd: sold.soldMedianPriceUsd,
|
|
64
|
+
soldMinPriceUsd: sold.soldMinPriceUsd,
|
|
65
|
+
soldMaxPriceUsd: sold.soldMaxPriceUsd,
|
|
66
|
+
day1Sold: sold.soldVelocity.day1Sold,
|
|
67
|
+
day2Sold: sold.soldVelocity.day2Sold,
|
|
68
|
+
day3Sold: sold.soldVelocity.day3Sold,
|
|
69
|
+
day4Sold: sold.soldVelocity.day4Sold,
|
|
70
|
+
day5Sold: sold.soldVelocity.day5Sold,
|
|
71
|
+
daysTracked: sold.soldVelocity.daysTracked,
|
|
72
|
+
});
|
|
73
|
+
const terapeakFields = getFieldPresence({
|
|
74
|
+
avgWatchersPerListing: terapeak.avgWatchersPerListing,
|
|
75
|
+
preOrderListingsCount: terapeak.preOrderListingsCount,
|
|
76
|
+
marketPriceUsd: terapeak.marketPriceUsd,
|
|
77
|
+
avgShippingCostUsd: terapeak.avgShippingCostUsd,
|
|
78
|
+
competitionLevel: terapeak.competitionLevel,
|
|
79
|
+
previousPobAvgPriceUsd: terapeak.previousPobAvgPriceUsd,
|
|
80
|
+
previousPobSellThroughPct: terapeak.previousPobSellThroughPct,
|
|
81
|
+
});
|
|
82
|
+
const socialFields = getFieldPresence({
|
|
83
|
+
twitterTrending: social.twitterTrending,
|
|
84
|
+
youtubeViews24hMillions: social.youtubeViews24hMillions,
|
|
85
|
+
redditPostsCount7d: social.redditPostsCount7d,
|
|
86
|
+
});
|
|
87
|
+
const researchFields = getFieldPresence({
|
|
88
|
+
previousAlbumTitle: research.previousAlbumTitle,
|
|
89
|
+
previousComebackFirstWeekSales: research.previousComebackFirstWeekSales,
|
|
90
|
+
});
|
|
91
|
+
const ebayStatus = (ebay.queryCandidates?.length ?? 0) === 0
|
|
92
|
+
? 'unavailable'
|
|
93
|
+
: ebay.sampleSize > 0
|
|
94
|
+
? 'ok'
|
|
95
|
+
: 'partial';
|
|
96
|
+
const socialStatus = socialFields.contributed.length > 0 ? 'ok' : social.debug ? 'partial' : 'unavailable';
|
|
97
|
+
const terapeakStatus = terapeak.provider === 'none'
|
|
98
|
+
? 'stub'
|
|
99
|
+
: terapeakFields.contributed.length > 0
|
|
100
|
+
? 'ok'
|
|
101
|
+
: 'partial';
|
|
102
|
+
const researchStatus = research.previousComebackFirstWeekSales !== null || research.previousAlbumTitle !== null
|
|
103
|
+
? 'ok'
|
|
104
|
+
: 'stub';
|
|
27
105
|
return {
|
|
28
106
|
ebay: {
|
|
29
|
-
status:
|
|
107
|
+
status: ebayStatus,
|
|
30
108
|
confidence: ebay.sampleSize >= 10 ? 'medium' : 'low',
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
109
|
+
browseSampleSize: ebay.sampleSize,
|
|
110
|
+
queryCandidates: ebay.queryCandidates ?? [],
|
|
111
|
+
selectedQuery: ebay.selectedQuery,
|
|
112
|
+
selectedQueryTier: ebay.selectedQueryTier,
|
|
113
|
+
queryDiagnostics: ebay.queryDiagnostics ?? [],
|
|
114
|
+
queryContextUsed: ebay.queryResolution?.queryContextUsed ?? requestQueryResolution.queryContextUsed,
|
|
115
|
+
querySource: ebay.queryResolution?.querySource ?? requestQueryResolution.querySource,
|
|
116
|
+
resolvedSearchQuery: ebay.queryResolution?.resolvedSearchQuery ?? requestQueryResolution.resolvedSearchQuery,
|
|
117
|
+
validationScope: ebay.queryResolution?.validationScope ?? requestQueryResolution.validationScope,
|
|
118
|
+
queryScope: ebay.queryResolution?.queryScope ?? requestQueryResolution.queryScope,
|
|
119
|
+
selectionReason: ebay.selectionReason,
|
|
120
|
+
errorMessage: ebay.errorMessage,
|
|
121
|
+
responseStatus: ebay.responseStatus,
|
|
122
|
+
responseBodyExcerpt: ebay.responseBodyExcerpt,
|
|
123
|
+
contributedFields: ebayFields.contributed,
|
|
124
|
+
omittedFields: ebayFields.omitted,
|
|
35
125
|
},
|
|
36
126
|
sold: {
|
|
37
127
|
status: sold.status,
|
|
38
128
|
provider: sold.provider,
|
|
39
129
|
confidence: sold.confidence.toLowerCase(),
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
130
|
+
soldResultsCount: sold.soldResultsCount,
|
|
131
|
+
queryCandidates: sold.queryCandidates ?? [],
|
|
132
|
+
selectedQuery: sold.selectedQuery,
|
|
133
|
+
selectedQueryTier: sold.selectedQueryTier,
|
|
134
|
+
queryContextUsed: sold.queryResolution?.queryContextUsed ?? requestQueryResolution.queryContextUsed,
|
|
135
|
+
querySource: sold.queryResolution?.querySource ?? requestQueryResolution.querySource,
|
|
136
|
+
resolvedSearchQuery: sold.queryResolution?.resolvedSearchQuery ?? requestQueryResolution.resolvedSearchQuery,
|
|
137
|
+
validationScope: sold.queryResolution?.validationScope ?? requestQueryResolution.validationScope,
|
|
138
|
+
queryScope: sold.queryResolution?.queryScope ?? requestQueryResolution.queryScope,
|
|
139
|
+
contributedFields: soldFields.contributed,
|
|
140
|
+
omittedFields: soldFields.omitted,
|
|
141
|
+
errorMessage: sold.errorMessage,
|
|
142
|
+
},
|
|
143
|
+
terapeak: {
|
|
144
|
+
status: terapeakStatus,
|
|
145
|
+
provider: terapeak.provider,
|
|
146
|
+
confidence: terapeak.confidence.toLowerCase(),
|
|
147
|
+
queryCandidates: [
|
|
148
|
+
terapeak.queryDebug.currentQuery,
|
|
149
|
+
terapeak.queryDebug.previousPobQuery,
|
|
150
|
+
].filter((value) => typeof value === 'string' && value.length > 0),
|
|
151
|
+
currentQuery: terapeak.queryDebug.currentQuery,
|
|
152
|
+
previousPobQuery: terapeak.queryDebug.previousPobQuery,
|
|
153
|
+
queryContextUsed: terapeak.queryDebug.queryResolution?.queryContextUsed ??
|
|
154
|
+
requestQueryResolution.queryContextUsed,
|
|
155
|
+
querySource: terapeak.queryDebug.queryResolution?.querySource ?? requestQueryResolution.querySource,
|
|
156
|
+
resolvedSearchQuery: terapeak.queryDebug.queryResolution?.resolvedSearchQuery ??
|
|
157
|
+
requestQueryResolution.resolvedSearchQuery,
|
|
158
|
+
validationScope: terapeak.queryDebug.queryResolution?.validationScope ??
|
|
159
|
+
requestQueryResolution.validationScope,
|
|
160
|
+
queryScope: terapeak.queryDebug.queryResolution?.queryScope ?? requestQueryResolution.queryScope,
|
|
161
|
+
selectedMode: terapeak.queryDebug.selectedMode,
|
|
162
|
+
currentResultCount: terapeak.queryDebug.currentResultCount,
|
|
163
|
+
previousPobResultCount: terapeak.queryDebug.previousPobResultCount,
|
|
164
|
+
contributedFields: terapeakFields.contributed,
|
|
165
|
+
omittedFields: terapeakFields.omitted,
|
|
166
|
+
notes: terapeak.queryDebug.notes,
|
|
43
167
|
},
|
|
44
168
|
social: {
|
|
45
|
-
status:
|
|
169
|
+
status: socialStatus,
|
|
46
170
|
confidence: 'low',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
171
|
+
queryContextUsed: requestQueryResolution.queryContextUsed,
|
|
172
|
+
querySource: requestQueryResolution.querySource,
|
|
173
|
+
resolvedSearchQuery: requestQueryResolution.resolvedSearchQuery,
|
|
174
|
+
validationScope: requestQueryResolution.validationScope,
|
|
175
|
+
queryScope: requestQueryResolution.queryScope,
|
|
176
|
+
contributedFields: socialFields.contributed,
|
|
177
|
+
omittedFields: socialFields.omitted,
|
|
178
|
+
details: social.debug,
|
|
50
179
|
},
|
|
51
180
|
chart: {
|
|
52
181
|
status: 'stub',
|
|
53
182
|
confidence: 'low',
|
|
54
|
-
|
|
183
|
+
contributedFields: [],
|
|
184
|
+
omittedFields: ['chartMomentum'],
|
|
185
|
+
},
|
|
186
|
+
research: {
|
|
187
|
+
status: researchStatus,
|
|
188
|
+
confidence: research.confidence.toLowerCase(),
|
|
189
|
+
previousAlbumTitle: research.previousAlbumTitle,
|
|
190
|
+
previousComebackFirstWeekSales: research.previousComebackFirstWeekSales,
|
|
191
|
+
contributedFields: researchFields.contributed,
|
|
192
|
+
omittedFields: researchFields.omitted,
|
|
193
|
+
notes: research.notes,
|
|
194
|
+
sources: research.sources ?? [],
|
|
55
195
|
},
|
|
56
196
|
};
|
|
57
197
|
}
|
|
@@ -71,11 +211,22 @@ export async function runValidation(api, input) {
|
|
|
71
211
|
};
|
|
72
212
|
}
|
|
73
213
|
try {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
214
|
+
const effectiveContext = buildValidationEffectiveContext(request);
|
|
215
|
+
const effectiveRequest = {
|
|
216
|
+
...request,
|
|
217
|
+
effectiveContext,
|
|
218
|
+
};
|
|
219
|
+
const ebay = await getEbayValidationSignals(api, effectiveRequest);
|
|
220
|
+
const sold = await getEbaySoldValidationSignals(effectiveRequest);
|
|
221
|
+
const terapeak = await getTerapeakValidationSignals(api, effectiveRequest);
|
|
222
|
+
const social = await getSocialValidationSignals(effectiveRequest);
|
|
223
|
+
const chart = getChartValidationSignals(effectiveRequest);
|
|
224
|
+
const research = await getPreviousComebackResearchSignals(effectiveRequest);
|
|
225
|
+
const mergedAvgWatchers = terapeak.avgWatchersPerListing ?? ebay.avgWatchersPerListing;
|
|
226
|
+
const mergedPreorderListings = terapeak.preOrderListingsCount ?? ebay.preOrderListingsCount;
|
|
227
|
+
const marketPriceUsd = terapeak.marketPriceUsd ?? sold.soldMedianPriceUsd ?? ebay.marketPriceUsd;
|
|
228
|
+
const mergedAvgShippingCostUsd = terapeak.avgShippingCostUsd ?? ebay.avgShippingCostUsd;
|
|
229
|
+
const mergedCompetitionLevel = terapeak.competitionLevel ?? ebay.competitionLevel;
|
|
79
230
|
const soldVelocity = {
|
|
80
231
|
day1Sold: sold.soldVelocity.day1Sold ?? ebay.soldVelocity.day1Sold,
|
|
81
232
|
day2Sold: sold.soldVelocity.day2Sold ?? ebay.soldVelocity.day2Sold,
|
|
@@ -84,20 +235,95 @@ export async function runValidation(api, input) {
|
|
|
84
235
|
day5Sold: sold.soldVelocity.day5Sold ?? ebay.soldVelocity.day5Sold,
|
|
85
236
|
daysTracked: sold.soldVelocity.daysTracked ?? ebay.soldVelocity.daysTracked,
|
|
86
237
|
};
|
|
87
|
-
const recommendation = buildValidationRecommendation(
|
|
88
|
-
|
|
238
|
+
const recommendation = buildValidationRecommendation(effectiveRequest, {
|
|
239
|
+
ebay,
|
|
240
|
+
sold,
|
|
241
|
+
terapeak,
|
|
242
|
+
social,
|
|
243
|
+
chart,
|
|
244
|
+
research,
|
|
245
|
+
effectiveContext,
|
|
246
|
+
});
|
|
247
|
+
const requestQueryResolution = buildProviderQueryResolutionDebug(effectiveRequest, Boolean(ebay.queryResolution?.queryContextUsed));
|
|
248
|
+
const mergedSignals = { effectiveContext, ebay, sold, terapeak, social, chart, research };
|
|
249
|
+
const socialWrites = {
|
|
250
|
+
...(social.twitterTrending !== null ? { twitterTrending: social.twitterTrending } : {}),
|
|
251
|
+
...(social.youtubeViews24hMillions !== null
|
|
252
|
+
? { youtubeViews24hMillions: social.youtubeViews24hMillions }
|
|
253
|
+
: {}),
|
|
254
|
+
...(social.redditPostsCount7d !== null
|
|
255
|
+
? { redditPostsCount7d: social.redditPostsCount7d }
|
|
256
|
+
: {}),
|
|
257
|
+
};
|
|
258
|
+
const terapeakWrites = {
|
|
259
|
+
...(terapeak.previousPobAvgPriceUsd !== null
|
|
260
|
+
? { previousPobAvgPriceUsd: terapeak.previousPobAvgPriceUsd }
|
|
261
|
+
: {}),
|
|
262
|
+
...(terapeak.previousPobSellThroughPct !== null
|
|
263
|
+
? { previousPobSellThroughPct: terapeak.previousPobSellThroughPct }
|
|
264
|
+
: {}),
|
|
265
|
+
};
|
|
266
|
+
const researchWrites = {
|
|
267
|
+
...(research.previousComebackFirstWeekSales !== null
|
|
268
|
+
? { previousComebackFirstWeekSales: research.previousComebackFirstWeekSales }
|
|
269
|
+
: {}),
|
|
270
|
+
};
|
|
271
|
+
const writeResolution = {
|
|
272
|
+
avgWatchersPerListing: terapeak.avgWatchersPerListing !== null
|
|
273
|
+
? 'terapeak'
|
|
274
|
+
: getWriteSource(ebay.avgWatchersPerListing, 'ebay'),
|
|
275
|
+
preOrderListingsCount: terapeak.preOrderListingsCount !== null
|
|
276
|
+
? 'terapeak'
|
|
277
|
+
: getWriteSource(ebay.preOrderListingsCount, 'ebay'),
|
|
278
|
+
marketPriceUsd: terapeak.marketPriceUsd !== null
|
|
279
|
+
? 'terapeak'
|
|
280
|
+
: sold.soldMedianPriceUsd !== null
|
|
281
|
+
? 'sold'
|
|
282
|
+
: getWriteSource(ebay.marketPriceUsd, 'ebay'),
|
|
283
|
+
avgShippingCostUsd: terapeak.avgShippingCostUsd !== null
|
|
284
|
+
? 'terapeak'
|
|
285
|
+
: getWriteSource(ebay.avgShippingCostUsd, 'ebay'),
|
|
286
|
+
competitionLevel: terapeak.competitionLevel !== null
|
|
287
|
+
? 'terapeak'
|
|
288
|
+
: getWriteSource(ebay.competitionLevel, 'ebay'),
|
|
289
|
+
twitterTrending: getWriteSource(social.twitterTrending, 'social'),
|
|
290
|
+
youtubeViews24hMillions: getWriteSource(social.youtubeViews24hMillions, 'social'),
|
|
291
|
+
redditPostsCount7d: getWriteSource(social.redditPostsCount7d, 'social'),
|
|
292
|
+
day1Sold: sold.soldVelocity.day1Sold !== null
|
|
293
|
+
? 'sold'
|
|
294
|
+
: getWriteSource(ebay.soldVelocity.day1Sold, 'ebay'),
|
|
295
|
+
day2Sold: sold.soldVelocity.day2Sold !== null
|
|
296
|
+
? 'sold'
|
|
297
|
+
: getWriteSource(ebay.soldVelocity.day2Sold, 'ebay'),
|
|
298
|
+
day3Sold: sold.soldVelocity.day3Sold !== null
|
|
299
|
+
? 'sold'
|
|
300
|
+
: getWriteSource(ebay.soldVelocity.day3Sold, 'ebay'),
|
|
301
|
+
day4Sold: sold.soldVelocity.day4Sold !== null
|
|
302
|
+
? 'sold'
|
|
303
|
+
: getWriteSource(ebay.soldVelocity.day4Sold, 'ebay'),
|
|
304
|
+
day5Sold: sold.soldVelocity.day5Sold !== null
|
|
305
|
+
? 'sold'
|
|
306
|
+
: getWriteSource(ebay.soldVelocity.day5Sold, 'ebay'),
|
|
307
|
+
daysTracked: sold.soldVelocity.daysTracked !== null
|
|
308
|
+
? 'sold'
|
|
309
|
+
: getWriteSource(ebay.soldVelocity.daysTracked, 'ebay'),
|
|
310
|
+
previousPobAvgPriceUsd: getWriteSource(terapeak.previousPobAvgPriceUsd, 'terapeak'),
|
|
311
|
+
previousPobSellThroughPct: getWriteSource(terapeak.previousPobSellThroughPct, 'terapeak'),
|
|
312
|
+
previousComebackFirstWeekSales: getWriteSource(research.previousComebackFirstWeekSales, 'research'),
|
|
313
|
+
};
|
|
314
|
+
const omittedOptionalWrites = Object.entries(writeResolution)
|
|
315
|
+
.filter(([, source]) => source === 'none')
|
|
316
|
+
.map(([field]) => field);
|
|
89
317
|
return {
|
|
90
318
|
status: 'ok',
|
|
91
319
|
validationId: request.validationId,
|
|
92
320
|
writes: {
|
|
93
|
-
avgWatchersPerListing:
|
|
94
|
-
preOrderListingsCount:
|
|
95
|
-
|
|
96
|
-
youtubeViews24hMillions: social.youtubeViews24hMillions,
|
|
97
|
-
redditPostsCount7d: social.redditPostsCount7d,
|
|
321
|
+
avgWatchersPerListing: mergedAvgWatchers,
|
|
322
|
+
preOrderListingsCount: mergedPreorderListings,
|
|
323
|
+
...socialWrites,
|
|
98
324
|
marketPriceUsd,
|
|
99
|
-
avgShippingCostUsd:
|
|
100
|
-
competitionLevel:
|
|
325
|
+
avgShippingCostUsd: mergedAvgShippingCostUsd,
|
|
326
|
+
competitionLevel: mergedCompetitionLevel,
|
|
101
327
|
marketPriceTrend: ebay.marketPriceTrend,
|
|
102
328
|
day1Sold: soldVelocity.day1Sold,
|
|
103
329
|
day2Sold: soldVelocity.day2Sold,
|
|
@@ -105,6 +331,8 @@ export async function runValidation(api, input) {
|
|
|
105
331
|
day4Sold: soldVelocity.day4Sold,
|
|
106
332
|
day5Sold: soldVelocity.day5Sold,
|
|
107
333
|
daysTracked: soldVelocity.daysTracked,
|
|
334
|
+
...terapeakWrites,
|
|
335
|
+
...researchWrites,
|
|
108
336
|
monitoringNotes: recommendation.monitoringNotes,
|
|
109
337
|
lastDataSnapshot: JSON.stringify(mergedSignals),
|
|
110
338
|
latestAiRecommendation: recommendation.latestAiRecommendation,
|
|
@@ -119,11 +347,31 @@ export async function runValidation(api, input) {
|
|
|
119
347
|
nextCheckAt: recommendation.nextCheckAt,
|
|
120
348
|
},
|
|
121
349
|
debug: {
|
|
350
|
+
sourceContext: effectiveRequest.sourceContext ?? null,
|
|
351
|
+
effectiveSourceType: effectiveContext.sourceType,
|
|
352
|
+
effectiveContextMode: effectiveContext.mode,
|
|
353
|
+
effectiveSearchQuery: effectiveContext.effectiveSearchQuery,
|
|
354
|
+
hasItem: effectiveContext.hasItem,
|
|
355
|
+
hasEvent: effectiveContext.hasEvent,
|
|
356
|
+
effectiveContext,
|
|
122
357
|
ebayQuery: ebay.ebayQuery,
|
|
123
358
|
soldQuery: sold.query,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
359
|
+
queryCandidates: {
|
|
360
|
+
ebay: ebay.queryCandidates ?? [],
|
|
361
|
+
sold: sold.queryCandidates ?? [],
|
|
362
|
+
terapeak: [terapeak.queryDebug.currentQuery, terapeak.queryDebug.previousPobQuery].filter((value) => typeof value === 'string' && value.length > 0),
|
|
363
|
+
},
|
|
364
|
+
browseSampleSize: ebay.sampleSize,
|
|
365
|
+
soldResultsCount: sold.soldResultsCount,
|
|
366
|
+
omittedOptionalWrites,
|
|
367
|
+
writeResolution,
|
|
368
|
+
sourceSet: ['ebay', 'sold', 'terapeak', 'social', 'chart', 'research'],
|
|
369
|
+
providers: buildProviderDebug(effectiveRequest, ebay, sold, terapeak, social, chart, research),
|
|
370
|
+
queryContextUsed: requestQueryResolution.queryContextUsed,
|
|
371
|
+
querySource: requestQueryResolution.querySource,
|
|
372
|
+
resolvedSearchQuery: requestQueryResolution.resolvedSearchQuery,
|
|
373
|
+
validationScope: requestQueryResolution.validationScope,
|
|
374
|
+
queryScope: requestQueryResolution.queryScope,
|
|
127
375
|
},
|
|
128
376
|
};
|
|
129
377
|
}
|
|
@@ -17,25 +17,46 @@ export const validationCurrentMetricsSchema = z.object({
|
|
|
17
17
|
day5Sold: z.number().nullable(),
|
|
18
18
|
daysTracked: z.number().nullable(),
|
|
19
19
|
});
|
|
20
|
-
export const
|
|
20
|
+
export const validationQueryContextSchema = z.object({
|
|
21
|
+
directQueryActive: z.boolean().nullable().optional(),
|
|
22
|
+
directSearchQuery: z.string().nullable().optional(),
|
|
23
|
+
resolvedSearchArtist: z.string().nullable().optional(),
|
|
24
|
+
resolvedSearchItem: z.string().nullable().optional(),
|
|
25
|
+
resolvedSearchEvent: z.string().nullable().optional(),
|
|
26
|
+
resolvedSearchLocation: z.string().nullable().optional(),
|
|
27
|
+
resolvedSearchQuery: z.string().nullable().optional(),
|
|
28
|
+
validationScope: z.string().nullable().optional(),
|
|
29
|
+
queryScope: z.string().nullable().optional(),
|
|
30
|
+
});
|
|
31
|
+
export const validationSourceContextSchema = z.object({
|
|
32
|
+
sourceType: z.enum(['item', 'event']).optional(),
|
|
33
|
+
hasItem: z.boolean().optional(),
|
|
34
|
+
hasEvent: z.boolean().optional(),
|
|
35
|
+
itemRecordId: z.string().nullable().optional(),
|
|
36
|
+
eventRecordId: z.string().nullable().optional(),
|
|
37
|
+
});
|
|
38
|
+
const validationItemSchema = z.object({
|
|
39
|
+
recordId: z.string().min(1).nullable(),
|
|
40
|
+
name: z.string(),
|
|
41
|
+
variation: z.array(z.string()),
|
|
42
|
+
itemType: z.array(z.string()),
|
|
43
|
+
releaseType: z.array(z.string()),
|
|
44
|
+
releaseDate: z.string().datetime({ offset: true }).nullable(),
|
|
45
|
+
releasePeriod: z.array(z.string()),
|
|
46
|
+
availability: z.array(z.string()),
|
|
47
|
+
wholesalePrice: z.number().nullable(),
|
|
48
|
+
supplierNames: z.array(z.string()),
|
|
49
|
+
canonicalArtists: z.array(z.string()),
|
|
50
|
+
relatedAlbums: z.array(z.string()),
|
|
51
|
+
});
|
|
52
|
+
export const validationRunRequestSchema = z
|
|
53
|
+
.object({
|
|
21
54
|
validationId: z.string().min(1),
|
|
22
55
|
runType: z.enum(['scheduled', 'manual']),
|
|
23
56
|
cadence: trackingCadenceSchema,
|
|
24
57
|
timestamp: z.string().datetime({ offset: true }),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
name: z.string().min(1),
|
|
28
|
-
variation: z.array(z.string()),
|
|
29
|
-
itemType: z.array(z.string()),
|
|
30
|
-
releaseType: z.array(z.string()),
|
|
31
|
-
releaseDate: z.string().datetime({ offset: true }).nullable(),
|
|
32
|
-
releasePeriod: z.array(z.string()),
|
|
33
|
-
availability: z.array(z.string()),
|
|
34
|
-
wholesalePrice: z.number().nullable(),
|
|
35
|
-
supplierNames: z.array(z.string()),
|
|
36
|
-
canonicalArtists: z.array(z.string()),
|
|
37
|
-
relatedAlbums: z.array(z.string()),
|
|
38
|
-
}),
|
|
58
|
+
sourceContext: validationSourceContextSchema.optional(),
|
|
59
|
+
item: validationItemSchema,
|
|
39
60
|
validation: z.object({
|
|
40
61
|
validationType: z.string(),
|
|
41
62
|
buyDecision: z.string(),
|
|
@@ -45,8 +66,31 @@ export const validationRunRequestSchema = z.object({
|
|
|
45
66
|
artistTier: z.string(),
|
|
46
67
|
initialBudget: z.number().nullable(),
|
|
47
68
|
reserveBudget: z.number().nullable(),
|
|
69
|
+
queryContext: validationQueryContextSchema.optional(),
|
|
48
70
|
currentMetrics: validationCurrentMetricsSchema,
|
|
49
71
|
}),
|
|
72
|
+
})
|
|
73
|
+
.superRefine((value, ctx) => {
|
|
74
|
+
const sourceType = value.sourceContext?.sourceType ?? 'item';
|
|
75
|
+
const itemRecordId = value.item.recordId?.trim() ?? '';
|
|
76
|
+
const itemName = value.item.name.trim();
|
|
77
|
+
if (sourceType === 'event') {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (itemRecordId.length === 0) {
|
|
81
|
+
ctx.addIssue({
|
|
82
|
+
code: z.ZodIssueCode.custom,
|
|
83
|
+
path: ['item', 'recordId'],
|
|
84
|
+
message: 'Item-driven validations require item.recordId.',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (itemName.length === 0) {
|
|
88
|
+
ctx.addIssue({
|
|
89
|
+
code: z.ZodIssueCode.custom,
|
|
90
|
+
path: ['item', 'name'],
|
|
91
|
+
message: 'Item-driven validations require item.name.',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
50
94
|
});
|
|
51
95
|
export const validationWritesSchema = z.object({
|
|
52
96
|
avgWatchersPerListing: z.number().nullable().optional(),
|
|
@@ -64,6 +108,9 @@ export const validationWritesSchema = z.object({
|
|
|
64
108
|
day4Sold: z.number().nullable().optional(),
|
|
65
109
|
day5Sold: z.number().nullable().optional(),
|
|
66
110
|
daysTracked: z.number().nullable().optional(),
|
|
111
|
+
previousPobAvgPriceUsd: z.number().nullable().optional(),
|
|
112
|
+
previousPobSellThroughPct: z.number().nullable().optional(),
|
|
113
|
+
previousComebackFirstWeekSales: z.number().nullable().optional(),
|
|
67
114
|
monitoringNotes: z.string().optional(),
|
|
68
115
|
lastDataSnapshot: z.string().optional(),
|
|
69
116
|
latestAiRecommendation: z.string().optional(),
|