@ucptools/validator 1.0.0 → 1.0.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/.claude/settings.local.json +60 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +279 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/compliance/compliance-generator.d.ts +34 -0
- package/dist/compliance/compliance-generator.d.ts.map +1 -0
- package/dist/compliance/compliance-generator.js +320 -0
- package/dist/compliance/compliance-generator.js.map +1 -0
- package/dist/compliance/index.d.ts +8 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +17 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/compliance/templates.d.ts +34 -0
- package/dist/compliance/templates.d.ts.map +1 -0
- package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
- package/dist/compliance/templates.js.map +1 -0
- package/dist/compliance/types.d.ts +64 -0
- package/dist/compliance/types.d.ts.map +1 -0
- package/dist/compliance/types.js +64 -0
- package/dist/compliance/types.js.map +1 -0
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +63 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +444 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +65 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
- package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
- package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
- package/dist/feed-analyzer/index.d.ts +8 -0
- package/dist/feed-analyzer/index.d.ts.map +1 -0
- package/dist/feed-analyzer/index.js +19 -0
- package/dist/feed-analyzer/index.js.map +1 -0
- package/dist/feed-analyzer/types.d.ts +204 -0
- package/dist/feed-analyzer/types.d.ts.map +1 -0
- package/dist/feed-analyzer/types.js +162 -0
- package/dist/feed-analyzer/types.js.map +1 -0
- package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +13 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/key-generator.d.ts +24 -0
- package/dist/generator/key-generator.d.ts.map +1 -0
- package/dist/generator/key-generator.js +144 -0
- package/dist/generator/key-generator.js.map +1 -0
- package/dist/generator/profile-builder.d.ts +15 -0
- package/dist/generator/profile-builder.d.ts.map +1 -0
- package/dist/generator/profile-builder.js +338 -0
- package/dist/generator/profile-builder.js.map +1 -0
- package/dist/hosting/artifacts-generator.d.ts +10 -0
- package/dist/hosting/artifacts-generator.d.ts.map +1 -0
- package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
- package/dist/hosting/artifacts-generator.js.map +1 -0
- package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
- package/dist/hosting/index.d.ts.map +1 -0
- package/dist/hosting/index.js +10 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +12 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security-scanner.d.ts +10 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +541 -0
- package/dist/security/security-scanner.js.map +1 -0
- package/dist/security/types.d.ts +48 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +21 -0
- package/dist/security/types.js.map +1 -0
- package/dist/services/directory.d.ts +104 -0
- package/dist/services/directory.d.ts.map +1 -0
- package/dist/services/directory.js +333 -0
- package/dist/services/directory.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts +69 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -0
- package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
- package/dist/simulator/agent-simulator.js.map +1 -0
- package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
- package/dist/simulator/index.d.ts.map +1 -0
- package/dist/simulator/index.js +23 -0
- package/dist/simulator/index.js.map +1 -0
- package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
- package/dist/simulator/types.d.ts.map +1 -0
- package/dist/simulator/types.js +18 -0
- package/dist/simulator/types.js.map +1 -0
- package/dist/types/generator.d.ts +106 -0
- package/dist/types/generator.d.ts.map +1 -0
- package/dist/types/generator.js +6 -0
- package/dist/types/generator.js.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ucp-profile.d.ts +103 -0
- package/dist/types/ucp-profile.d.ts.map +1 -0
- package/dist/types/ucp-profile.js +45 -0
- package/dist/types/ucp-profile.js.map +1 -0
- package/dist/types/validation.d.ts +68 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +32 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/validator/index.d.ts +26 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +161 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/network-validator.d.ts +28 -0
- package/dist/validator/network-validator.d.ts.map +1 -0
- package/dist/validator/network-validator.js +319 -0
- package/dist/validator/network-validator.js.map +1 -0
- package/dist/validator/rules-validator.d.ts +11 -0
- package/dist/validator/rules-validator.d.ts.map +1 -0
- package/dist/validator/rules-validator.js +257 -0
- package/dist/validator/rules-validator.js.map +1 -0
- package/dist/validator/sdk-validator.d.ts +58 -0
- package/dist/validator/sdk-validator.d.ts.map +1 -0
- package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
- package/dist/validator/sdk-validator.js.map +1 -0
- package/dist/validator/structural-validator.d.ts +11 -0
- package/dist/validator/structural-validator.d.ts.map +1 -0
- package/dist/validator/structural-validator.js +415 -0
- package/dist/validator/structural-validator.js.map +1 -0
- package/package.json +1 -1
- package/publish-output.txt +0 -0
- package/CLAUDE.md +0 -109
- package/api/analyze-feed.js +0 -140
- package/api/badge.js +0 -185
- package/api/benchmark.js +0 -177
- package/api/directory-stats.ts +0 -29
- package/api/directory.ts +0 -73
- package/api/generate-compliance.js +0 -143
- package/api/generate-schema.js +0 -457
- package/api/generate.js +0 -132
- package/api/security-scan.js +0 -133
- package/api/simulate.js +0 -187
- package/api/tsconfig.json +0 -10
- package/api/validate.js +0 -1351
- package/apify-actor/.actor/actor.json +0 -68
- package/apify-actor/.actor/input_schema.json +0 -32
- package/apify-actor/APIFY-STORE-LISTING.md +0 -412
- package/apify-actor/Dockerfile +0 -8
- package/apify-actor/README.md +0 -166
- package/apify-actor/main.ts +0 -111
- package/apify-actor/package.json +0 -17
- package/apify-actor/src/main.js +0 -199
- package/docs/BRAND-IDENTITY.md +0 -238
- package/docs/BRAND-STYLE-GUIDE.md +0 -356
- package/drizzle/0000_black_king_cobra.sql +0 -39
- package/drizzle/meta/0000_snapshot.json +0 -309
- package/drizzle/meta/_journal.json +0 -13
- package/drizzle.config.ts +0 -10
- package/public/.well-known/ucp +0 -25
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/brand.css +0 -321
- package/public/directory.html +0 -701
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/guides/bigcommerce.html +0 -743
- package/public/guides/fastucp.html +0 -838
- package/public/guides/magento.html +0 -779
- package/public/guides/shopify.html +0 -726
- package/public/guides/squarespace.html +0 -749
- package/public/guides/wix.html +0 -747
- package/public/guides/woocommerce.html +0 -733
- package/public/index.html +0 -3835
- package/public/learn.html +0 -396
- package/public/logo.jpeg +0 -0
- package/public/og-image-icon.png +0 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +0 -6
- package/public/site.webmanifest +0 -31
- package/public/sitemap.xml +0 -69
- package/public/social/linkedin-banner-1128x191.png +0 -0
- package/public/social/temp.PNG +0 -0
- package/public/social/x-header-1500x500.png +0 -0
- package/public/verify.html +0 -410
- package/scripts/generate-favicons.js +0 -44
- package/scripts/generate-ico.js +0 -23
- package/scripts/generate-og-image.js +0 -45
- package/scripts/reset-db.ts +0 -77
- package/scripts/seed-db.ts +0 -71
- package/scripts/setup-benchmark-db.js +0 -70
- package/src/api/server.ts +0 -266
- package/src/cli/index.ts +0 -302
- package/src/compliance/compliance-generator.ts +0 -452
- package/src/compliance/index.ts +0 -28
- package/src/compliance/types.ts +0 -170
- package/src/db/index.ts +0 -28
- package/src/db/schema.ts +0 -84
- package/src/feed-analyzer/index.ts +0 -34
- package/src/feed-analyzer/types.ts +0 -354
- package/src/generator/key-generator.ts +0 -124
- package/src/generator/profile-builder.ts +0 -402
- package/src/index.ts +0 -105
- package/src/security/security-scanner.ts +0 -604
- package/src/security/types.ts +0 -55
- package/src/services/directory.ts +0 -434
- package/src/types/generator.ts +0 -140
- package/src/types/ucp-profile.ts +0 -140
- package/src/types/validation.ts +0 -89
- package/src/validator/index.ts +0 -194
- package/src/validator/network-validator.ts +0 -417
- package/src/validator/rules-validator.ts +0 -297
- package/src/validator/structural-validator.ts +0 -476
- package/tests/fixtures/non-compliant-profile.json +0 -25
- package/tests/fixtures/official-sample-profile.json +0 -75
- package/tests/integration/benchmark.test.ts +0 -207
- package/tests/integration/database.test.ts +0 -163
- package/tests/integration/directory-api.test.ts +0 -268
- package/tests/integration/simulate-api.test.ts +0 -230
- package/tests/integration/validate-api.test.ts +0 -269
- package/tests/setup.ts +0 -15
- package/tests/unit/agent-simulator.test.ts +0 -575
- package/tests/unit/compliance-generator.test.ts +0 -374
- package/tests/unit/directory-service.test.ts +0 -272
- package/tests/unit/feed-analyzer.test.ts +0 -517
- package/tests/unit/lint-suggestions.test.ts +0 -423
- package/tests/unit/official-samples.test.ts +0 -211
- package/tests/unit/pdf-report.test.ts +0 -390
- package/tests/unit/sdk-validator.test.ts +0 -531
- package/tests/unit/security-scanner.test.ts +0 -410
- package/tests/unit/validation.test.ts +0 -390
- package/vercel.json +0 -34
- package/vitest.config.ts +0 -22
|
@@ -1,726 +1,642 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!
|
|
245
|
-
issues.push(createIssue('
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (!attributes.
|
|
249
|
-
issues.push(createIssue('missing-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
withImages: 0,
|
|
644
|
-
withPrice: 0,
|
|
645
|
-
withAvailability: 0,
|
|
646
|
-
withCategory: 0,
|
|
647
|
-
totalDescriptionLength: 0,
|
|
648
|
-
totalImageCount: 0,
|
|
649
|
-
}
|
|
650
|
-
);
|
|
651
|
-
|
|
652
|
-
return {
|
|
653
|
-
totalProducts: total,
|
|
654
|
-
withName: summary.withName,
|
|
655
|
-
withDescription: summary.withDescription,
|
|
656
|
-
withSku: summary.withSku,
|
|
657
|
-
withGtin: summary.withGtin,
|
|
658
|
-
withBrand: summary.withBrand,
|
|
659
|
-
withImages: summary.withImages,
|
|
660
|
-
withPrice: summary.withPrice,
|
|
661
|
-
withAvailability: summary.withAvailability,
|
|
662
|
-
withCategory: summary.withCategory,
|
|
663
|
-
averageDescriptionLength: summary.totalDescriptionLength / total,
|
|
664
|
-
averageImageCount: summary.totalImageCount / total,
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* Analyze product feed from raw product data
|
|
670
|
-
*/
|
|
671
|
-
export function analyzeProductFeed(
|
|
672
|
-
products: ProductData[],
|
|
673
|
-
url: string,
|
|
674
|
-
maxProducts: number = DEFAULT_MAX_PRODUCTS,
|
|
675
|
-
includeProductDetails: boolean = true
|
|
676
|
-
): FeedAnalysisResult {
|
|
677
|
-
const productsToAnalyze = products.slice(0, maxProducts);
|
|
678
|
-
const productAnalyses = productsToAnalyze.map(analyzeProduct);
|
|
679
|
-
|
|
680
|
-
const summary = calculateSummary(productAnalyses);
|
|
681
|
-
const categoryScores = calculateCategoryScores(productAnalyses);
|
|
682
|
-
const overallScore = calculateOverallScore(categoryScores);
|
|
683
|
-
const agentVisibilityScore = calculateAgentVisibilityScore(summary);
|
|
684
|
-
const issues = aggregateIssues(productAnalyses);
|
|
685
|
-
const recommendations = generateRecommendations(summary, issues);
|
|
686
|
-
|
|
687
|
-
// Get top issues (most impactful)
|
|
688
|
-
const topIssues = issues
|
|
689
|
-
.filter(i => i.severity === 'critical' || i.severity === 'warning')
|
|
690
|
-
.sort((a, b) => {
|
|
691
|
-
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
692
|
-
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
693
|
-
})
|
|
694
|
-
.slice(0, 5);
|
|
695
|
-
|
|
696
|
-
return {
|
|
697
|
-
url,
|
|
698
|
-
analyzedAt: new Date().toISOString(),
|
|
699
|
-
productsFound: products.length,
|
|
700
|
-
productsAnalyzed: productsToAnalyze.length,
|
|
701
|
-
overallScore,
|
|
702
|
-
agentVisibilityScore,
|
|
703
|
-
grade: getGrade(overallScore),
|
|
704
|
-
categoryScores,
|
|
705
|
-
issues,
|
|
706
|
-
topIssues,
|
|
707
|
-
products: includeProductDetails ? productAnalyses : [],
|
|
708
|
-
recommendations,
|
|
709
|
-
summary,
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
/**
|
|
714
|
-
* Analyze a product feed from HTML content
|
|
715
|
-
*/
|
|
716
|
-
export function analyzeProductFeedFromHtml(
|
|
717
|
-
html: string,
|
|
718
|
-
url: string,
|
|
719
|
-
options: Partial<FeedAnalysisInput> = {}
|
|
720
|
-
): FeedAnalysisResult {
|
|
721
|
-
const products = extractProductsFromHtml(html);
|
|
722
|
-
const maxProducts = options.maxProducts ?? DEFAULT_MAX_PRODUCTS;
|
|
723
|
-
const includeProductDetails = options.includeProductDetails ?? true;
|
|
724
|
-
|
|
725
|
-
return analyzeProductFeed(products, url, maxProducts, includeProductDetails);
|
|
726
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Product Feed Quality Analyzer
|
|
4
|
+
* Deep analysis of product data quality for AI agent visibility
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.validateGtin = validateGtin;
|
|
8
|
+
exports.extractProductsFromHtml = extractProductsFromHtml;
|
|
9
|
+
exports.analyzeProduct = analyzeProduct;
|
|
10
|
+
exports.analyzeProductFeed = analyzeProductFeed;
|
|
11
|
+
exports.analyzeProductFeedFromHtml = analyzeProductFeedFromHtml;
|
|
12
|
+
const types_js_1 = require("./types.js");
|
|
13
|
+
/**
|
|
14
|
+
* Default maximum products to analyze
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_MAX_PRODUCTS = 50;
|
|
17
|
+
/**
|
|
18
|
+
* Minimum description length considered "good"
|
|
19
|
+
*/
|
|
20
|
+
const MIN_DESCRIPTION_LENGTH = 50;
|
|
21
|
+
/**
|
|
22
|
+
* Validate a GTIN/UPC/EAN identifier
|
|
23
|
+
*/
|
|
24
|
+
function validateGtin(gtin) {
|
|
25
|
+
if (!gtin || typeof gtin !== 'string') {
|
|
26
|
+
return { isValid: false, error: 'GTIN is empty or not a string' };
|
|
27
|
+
}
|
|
28
|
+
// Remove any spaces or dashes
|
|
29
|
+
const cleaned = gtin.replace(/[\s-]/g, '');
|
|
30
|
+
// Check if it's all digits
|
|
31
|
+
if (!/^\d+$/.test(cleaned)) {
|
|
32
|
+
return { isValid: false, error: 'GTIN must contain only digits' };
|
|
33
|
+
}
|
|
34
|
+
const length = cleaned.length;
|
|
35
|
+
// Determine type based on length
|
|
36
|
+
let type;
|
|
37
|
+
switch (length) {
|
|
38
|
+
case 8:
|
|
39
|
+
type = 'GTIN-8';
|
|
40
|
+
break;
|
|
41
|
+
case 12:
|
|
42
|
+
type = 'UPC';
|
|
43
|
+
break;
|
|
44
|
+
case 13:
|
|
45
|
+
type = 'EAN';
|
|
46
|
+
break;
|
|
47
|
+
case 14:
|
|
48
|
+
type = 'GTIN-14';
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
return { isValid: false, error: `Invalid GTIN length: ${length}. Expected 8, 12, 13, or 14 digits` };
|
|
52
|
+
}
|
|
53
|
+
// Validate check digit
|
|
54
|
+
if (!validateGtinCheckDigit(cleaned)) {
|
|
55
|
+
return { isValid: false, type, error: 'Invalid check digit' };
|
|
56
|
+
}
|
|
57
|
+
return { isValid: true, type };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validate GTIN check digit using modulo 10 algorithm
|
|
61
|
+
*/
|
|
62
|
+
function validateGtinCheckDigit(gtin) {
|
|
63
|
+
const digits = gtin.split('').map(Number);
|
|
64
|
+
const checkDigit = digits.pop();
|
|
65
|
+
let sum = 0;
|
|
66
|
+
const multipliers = gtin.length === 13 || gtin.length === 8
|
|
67
|
+
? [1, 3] // EAN-13, GTIN-8
|
|
68
|
+
: [3, 1]; // UPC-12, GTIN-14
|
|
69
|
+
for (let i = 0; i < digits.length; i++) {
|
|
70
|
+
sum += digits[i] * multipliers[i % 2];
|
|
71
|
+
}
|
|
72
|
+
const calculatedCheck = (10 - (sum % 10)) % 10;
|
|
73
|
+
return calculatedCheck === checkDigit;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Extract products from HTML content
|
|
77
|
+
*/
|
|
78
|
+
function extractProductsFromHtml(html) {
|
|
79
|
+
const products = [];
|
|
80
|
+
// Find all JSON-LD script tags
|
|
81
|
+
const jsonLdRegex = /<script[^>]*type\s*=\s*["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
|
|
82
|
+
let match;
|
|
83
|
+
while ((match = jsonLdRegex.exec(html)) !== null) {
|
|
84
|
+
try {
|
|
85
|
+
const jsonContent = match[1].trim();
|
|
86
|
+
const data = JSON.parse(jsonContent);
|
|
87
|
+
// Handle single object or array
|
|
88
|
+
const items = Array.isArray(data) ? data : [data];
|
|
89
|
+
for (const item of items) {
|
|
90
|
+
// Check if it's a Product
|
|
91
|
+
if (item['@type'] === 'Product') {
|
|
92
|
+
products.push(item);
|
|
93
|
+
}
|
|
94
|
+
// Check for @graph containing products
|
|
95
|
+
else if (item['@graph'] && Array.isArray(item['@graph'])) {
|
|
96
|
+
for (const graphItem of item['@graph']) {
|
|
97
|
+
if (graphItem['@type'] === 'Product') {
|
|
98
|
+
products.push(graphItem);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Check for ItemList containing products
|
|
103
|
+
else if (item['@type'] === 'ItemList' && item.itemListElement) {
|
|
104
|
+
const listItems = Array.isArray(item.itemListElement)
|
|
105
|
+
? item.itemListElement
|
|
106
|
+
: [item.itemListElement];
|
|
107
|
+
for (const listItem of listItems) {
|
|
108
|
+
if (listItem.item && listItem.item['@type'] === 'Product') {
|
|
109
|
+
products.push(listItem.item);
|
|
110
|
+
}
|
|
111
|
+
else if (listItem['@type'] === 'Product') {
|
|
112
|
+
products.push(listItem);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Skip invalid JSON
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return products;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get the primary offer from a product
|
|
127
|
+
*/
|
|
128
|
+
function getPrimaryOffer(product) {
|
|
129
|
+
if (!product.offers)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (Array.isArray(product.offers)) {
|
|
132
|
+
return product.offers[0];
|
|
133
|
+
}
|
|
134
|
+
return product.offers;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get image count from product data
|
|
138
|
+
*/
|
|
139
|
+
function getImageCount(product) {
|
|
140
|
+
if (!product.image)
|
|
141
|
+
return 0;
|
|
142
|
+
if (typeof product.image === 'string')
|
|
143
|
+
return 1;
|
|
144
|
+
if (Array.isArray(product.image)) {
|
|
145
|
+
return product.image.length;
|
|
146
|
+
}
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get brand name from product
|
|
151
|
+
*/
|
|
152
|
+
function getBrandName(product) {
|
|
153
|
+
if (!product.brand)
|
|
154
|
+
return undefined;
|
|
155
|
+
if (typeof product.brand === 'string')
|
|
156
|
+
return product.brand;
|
|
157
|
+
return product.brand.name;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get any GTIN value from product
|
|
161
|
+
*/
|
|
162
|
+
function getGtin(product) {
|
|
163
|
+
return product.gtin || product.gtin13 || product.gtin12 || product.gtin14 || product.gtin8;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Analyze a single product
|
|
167
|
+
*/
|
|
168
|
+
function analyzeProduct(product) {
|
|
169
|
+
const issues = [];
|
|
170
|
+
const offer = getPrimaryOffer(product);
|
|
171
|
+
const brandName = getBrandName(product);
|
|
172
|
+
const gtin = getGtin(product);
|
|
173
|
+
const imageCount = getImageCount(product);
|
|
174
|
+
const descriptionLength = product.description?.length || 0;
|
|
175
|
+
// Build attributes object
|
|
176
|
+
const attributes = {
|
|
177
|
+
hasName: Boolean(product.name && product.name.trim()),
|
|
178
|
+
hasDescription: Boolean(product.description && product.description.trim()),
|
|
179
|
+
hasSku: Boolean(product.sku),
|
|
180
|
+
hasGtin: Boolean(gtin),
|
|
181
|
+
hasBrand: Boolean(brandName),
|
|
182
|
+
hasImage: imageCount > 0,
|
|
183
|
+
hasPrice: offer?.price !== undefined && offer?.price !== null,
|
|
184
|
+
hasAvailability: Boolean(offer?.availability),
|
|
185
|
+
hasCategory: Boolean(product.category),
|
|
186
|
+
descriptionLength,
|
|
187
|
+
imageCount,
|
|
188
|
+
};
|
|
189
|
+
// Completeness checks
|
|
190
|
+
if (!attributes.hasName) {
|
|
191
|
+
issues.push(createIssue('missing-name', product.name));
|
|
192
|
+
}
|
|
193
|
+
if (!attributes.hasDescription) {
|
|
194
|
+
issues.push(createIssue('missing-description', product.name));
|
|
195
|
+
}
|
|
196
|
+
else if (descriptionLength < MIN_DESCRIPTION_LENGTH) {
|
|
197
|
+
issues.push(createIssue('short-description', product.name, `Description is only ${descriptionLength} characters`));
|
|
198
|
+
}
|
|
199
|
+
if (!attributes.hasBrand) {
|
|
200
|
+
issues.push(createIssue('missing-brand', product.name));
|
|
201
|
+
}
|
|
202
|
+
// Identifier checks
|
|
203
|
+
if (!attributes.hasSku) {
|
|
204
|
+
issues.push(createIssue('missing-sku', product.name));
|
|
205
|
+
}
|
|
206
|
+
if (!attributes.hasGtin) {
|
|
207
|
+
issues.push(createIssue('missing-gtin', product.name));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const gtinValidation = validateGtin(gtin);
|
|
211
|
+
if (!gtinValidation.isValid) {
|
|
212
|
+
issues.push(createIssue('invalid-gtin', product.name, gtinValidation.error));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Image checks
|
|
216
|
+
if (!attributes.hasImage) {
|
|
217
|
+
issues.push(createIssue('missing-image', product.name));
|
|
218
|
+
}
|
|
219
|
+
else if (imageCount === 1) {
|
|
220
|
+
issues.push(createIssue('single-image', product.name));
|
|
221
|
+
}
|
|
222
|
+
// Pricing checks
|
|
223
|
+
if (!offer) {
|
|
224
|
+
issues.push(createIssue('missing-price', product.name));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
if (!offer.price && offer.price !== 0) {
|
|
228
|
+
issues.push(createIssue('missing-price', product.name));
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const price = typeof offer.price === 'string' ? parseFloat(offer.price) : offer.price;
|
|
232
|
+
if (isNaN(price)) {
|
|
233
|
+
issues.push(createIssue('invalid-price', product.name, `Price value: "${offer.price}"`));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!offer.priceCurrency) {
|
|
237
|
+
issues.push(createIssue('missing-currency', product.name));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Availability checks
|
|
241
|
+
if (!attributes.hasAvailability) {
|
|
242
|
+
issues.push(createIssue('missing-availability', product.name));
|
|
243
|
+
}
|
|
244
|
+
else if (offer?.availability && !types_js_1.VALID_AVAILABILITY_VALUES.includes(offer.availability)) {
|
|
245
|
+
issues.push(createIssue('invalid-availability', product.name, `Value: "${offer.availability}"`));
|
|
246
|
+
}
|
|
247
|
+
// Category checks
|
|
248
|
+
if (!attributes.hasCategory) {
|
|
249
|
+
issues.push(createIssue('missing-category', product.name));
|
|
250
|
+
}
|
|
251
|
+
// Calculate product score
|
|
252
|
+
const score = calculateProductScore(attributes);
|
|
253
|
+
return {
|
|
254
|
+
name: product.name || 'Unknown Product',
|
|
255
|
+
url: product.url,
|
|
256
|
+
sku: product.sku,
|
|
257
|
+
score,
|
|
258
|
+
issues,
|
|
259
|
+
attributes,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Create a quality check issue
|
|
264
|
+
*/
|
|
265
|
+
function createIssue(checkId, productName, details) {
|
|
266
|
+
const checkDef = types_js_1.QUALITY_CHECKS[checkId];
|
|
267
|
+
return {
|
|
268
|
+
id: checkId,
|
|
269
|
+
name: checkDef.name,
|
|
270
|
+
category: checkDef.category,
|
|
271
|
+
passed: false,
|
|
272
|
+
severity: checkDef.severity,
|
|
273
|
+
message: checkDef.description,
|
|
274
|
+
details,
|
|
275
|
+
affectedProducts: productName ? [productName] : undefined,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Calculate product score based on attributes
|
|
280
|
+
*/
|
|
281
|
+
function calculateProductScore(attributes) {
|
|
282
|
+
let score = 0;
|
|
283
|
+
const weights = {
|
|
284
|
+
hasName: 15,
|
|
285
|
+
hasDescription: 10,
|
|
286
|
+
hasSku: 5,
|
|
287
|
+
hasGtin: 10,
|
|
288
|
+
hasBrand: 10,
|
|
289
|
+
hasImage: 15,
|
|
290
|
+
hasPrice: 20,
|
|
291
|
+
hasAvailability: 5,
|
|
292
|
+
hasCategory: 5,
|
|
293
|
+
descriptionQuality: 5,
|
|
294
|
+
};
|
|
295
|
+
if (attributes.hasName)
|
|
296
|
+
score += weights.hasName;
|
|
297
|
+
if (attributes.hasDescription)
|
|
298
|
+
score += weights.hasDescription;
|
|
299
|
+
if (attributes.hasSku)
|
|
300
|
+
score += weights.hasSku;
|
|
301
|
+
if (attributes.hasGtin)
|
|
302
|
+
score += weights.hasGtin;
|
|
303
|
+
if (attributes.hasBrand)
|
|
304
|
+
score += weights.hasBrand;
|
|
305
|
+
if (attributes.hasImage)
|
|
306
|
+
score += weights.hasImage;
|
|
307
|
+
if (attributes.hasPrice)
|
|
308
|
+
score += weights.hasPrice;
|
|
309
|
+
if (attributes.hasAvailability)
|
|
310
|
+
score += weights.hasAvailability;
|
|
311
|
+
if (attributes.hasCategory)
|
|
312
|
+
score += weights.hasCategory;
|
|
313
|
+
// Bonus for good description length
|
|
314
|
+
if (attributes.descriptionLength >= MIN_DESCRIPTION_LENGTH) {
|
|
315
|
+
score += weights.descriptionQuality;
|
|
316
|
+
}
|
|
317
|
+
return Math.min(100, score);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Calculate category scores from product analyses
|
|
321
|
+
*/
|
|
322
|
+
function calculateCategoryScores(products) {
|
|
323
|
+
if (products.length === 0) {
|
|
324
|
+
return {
|
|
325
|
+
completeness: 0,
|
|
326
|
+
identifiers: 0,
|
|
327
|
+
images: 0,
|
|
328
|
+
pricing: 0,
|
|
329
|
+
descriptions: 0,
|
|
330
|
+
categories: 0,
|
|
331
|
+
availability: 0,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const totals = products.reduce((acc, p) => {
|
|
335
|
+
// Completeness: name, brand
|
|
336
|
+
acc.completeness += (p.attributes.hasName ? 50 : 0) + (p.attributes.hasBrand ? 50 : 0);
|
|
337
|
+
// Identifiers: SKU, GTIN
|
|
338
|
+
acc.identifiers += (p.attributes.hasSku ? 50 : 0) + (p.attributes.hasGtin ? 50 : 0);
|
|
339
|
+
// Images
|
|
340
|
+
acc.images += p.attributes.hasImage ? (p.attributes.imageCount > 1 ? 100 : 70) : 0;
|
|
341
|
+
// Pricing
|
|
342
|
+
acc.pricing += p.attributes.hasPrice ? 100 : 0;
|
|
343
|
+
// Descriptions
|
|
344
|
+
if (p.attributes.hasDescription) {
|
|
345
|
+
acc.descriptions += p.attributes.descriptionLength >= MIN_DESCRIPTION_LENGTH ? 100 : 60;
|
|
346
|
+
}
|
|
347
|
+
// Categories
|
|
348
|
+
acc.categories += p.attributes.hasCategory ? 100 : 0;
|
|
349
|
+
// Availability
|
|
350
|
+
acc.availability += p.attributes.hasAvailability ? 100 : 0;
|
|
351
|
+
return acc;
|
|
352
|
+
}, { completeness: 0, identifiers: 0, images: 0, pricing: 0, descriptions: 0, categories: 0, availability: 0 });
|
|
353
|
+
const count = products.length;
|
|
354
|
+
return {
|
|
355
|
+
completeness: Math.round(totals.completeness / count),
|
|
356
|
+
identifiers: Math.round(totals.identifiers / count),
|
|
357
|
+
images: Math.round(totals.images / count),
|
|
358
|
+
pricing: Math.round(totals.pricing / count),
|
|
359
|
+
descriptions: Math.round(totals.descriptions / count),
|
|
360
|
+
categories: Math.round(totals.categories / count),
|
|
361
|
+
availability: Math.round(totals.availability / count),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Calculate overall score from category scores
|
|
366
|
+
*/
|
|
367
|
+
function calculateOverallScore(categoryScores) {
|
|
368
|
+
let weightedSum = 0;
|
|
369
|
+
let totalWeight = 0;
|
|
370
|
+
for (const [category, score] of Object.entries(categoryScores)) {
|
|
371
|
+
const weight = types_js_1.CATEGORY_WEIGHTS[category];
|
|
372
|
+
weightedSum += score * weight;
|
|
373
|
+
totalWeight += weight;
|
|
374
|
+
}
|
|
375
|
+
return Math.round(weightedSum / totalWeight);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Calculate agent visibility score
|
|
379
|
+
* This is a specialized score focusing on what AI agents need most
|
|
380
|
+
*/
|
|
381
|
+
function calculateAgentVisibilityScore(summary) {
|
|
382
|
+
if (summary.totalProducts === 0)
|
|
383
|
+
return 0;
|
|
384
|
+
const total = summary.totalProducts;
|
|
385
|
+
// Critical factors for AI agents (weighted heavily)
|
|
386
|
+
const criticalScore = ((summary.withName / total) * 30) +
|
|
387
|
+
((summary.withPrice / total) * 30) +
|
|
388
|
+
((summary.withImages / total) * 20);
|
|
389
|
+
// Important factors
|
|
390
|
+
const importantScore = ((summary.withDescription / total) * 10) +
|
|
391
|
+
((summary.withGtin / total) * 5) +
|
|
392
|
+
((summary.withAvailability / total) * 5);
|
|
393
|
+
return Math.round(criticalScore + importantScore);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Get grade from score
|
|
397
|
+
*/
|
|
398
|
+
function getGrade(score) {
|
|
399
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.A)
|
|
400
|
+
return 'A';
|
|
401
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.B)
|
|
402
|
+
return 'B';
|
|
403
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.C)
|
|
404
|
+
return 'C';
|
|
405
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.D)
|
|
406
|
+
return 'D';
|
|
407
|
+
return 'F';
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Generate recommendations based on analysis
|
|
411
|
+
*/
|
|
412
|
+
function generateRecommendations(summary, issues) {
|
|
413
|
+
const recommendations = [];
|
|
414
|
+
const total = summary.totalProducts;
|
|
415
|
+
if (total === 0)
|
|
416
|
+
return recommendations;
|
|
417
|
+
// Missing prices - critical
|
|
418
|
+
const missingPrices = total - summary.withPrice;
|
|
419
|
+
if (missingPrices > 0) {
|
|
420
|
+
recommendations.push({
|
|
421
|
+
priority: 'high',
|
|
422
|
+
category: 'pricing',
|
|
423
|
+
title: 'Add Missing Prices',
|
|
424
|
+
description: `${missingPrices} products are missing price information. AI agents cannot complete purchases without prices.`,
|
|
425
|
+
impact: 'Critical - Products without prices cannot be purchased through AI agents',
|
|
426
|
+
affectedCount: missingPrices,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
// Missing images - critical
|
|
430
|
+
const missingImages = total - summary.withImages;
|
|
431
|
+
if (missingImages > 0) {
|
|
432
|
+
recommendations.push({
|
|
433
|
+
priority: 'high',
|
|
434
|
+
category: 'images',
|
|
435
|
+
title: 'Add Product Images',
|
|
436
|
+
description: `${missingImages} products are missing images. Visual product information is essential for AI shopping.`,
|
|
437
|
+
impact: 'High - Products without images are less likely to be recommended',
|
|
438
|
+
affectedCount: missingImages,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
// Missing names - critical
|
|
442
|
+
const missingNames = total - summary.withName;
|
|
443
|
+
if (missingNames > 0) {
|
|
444
|
+
recommendations.push({
|
|
445
|
+
priority: 'high',
|
|
446
|
+
category: 'completeness',
|
|
447
|
+
title: 'Add Product Names',
|
|
448
|
+
description: `${missingNames} products are missing names. This is required for product identification.`,
|
|
449
|
+
impact: 'Critical - Products cannot be identified without names',
|
|
450
|
+
affectedCount: missingNames,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
// Missing GTIN - medium priority
|
|
454
|
+
const missingGtin = total - summary.withGtin;
|
|
455
|
+
if (missingGtin > 0 && missingGtin / total > 0.5) {
|
|
456
|
+
recommendations.push({
|
|
457
|
+
priority: 'medium',
|
|
458
|
+
category: 'identifiers',
|
|
459
|
+
title: 'Add Global Identifiers (GTIN/UPC/EAN)',
|
|
460
|
+
description: `${missingGtin} products are missing global identifiers. These enable cross-platform product matching.`,
|
|
461
|
+
impact: 'Medium - Improves product matching across AI platforms',
|
|
462
|
+
affectedCount: missingGtin,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
// Missing descriptions - medium priority
|
|
466
|
+
const missingDescriptions = total - summary.withDescription;
|
|
467
|
+
if (missingDescriptions > 0 && missingDescriptions / total > 0.3) {
|
|
468
|
+
recommendations.push({
|
|
469
|
+
priority: 'medium',
|
|
470
|
+
category: 'descriptions',
|
|
471
|
+
title: 'Add Product Descriptions',
|
|
472
|
+
description: `${missingDescriptions} products are missing descriptions. Good descriptions help AI agents understand and recommend products.`,
|
|
473
|
+
impact: 'Medium - Better descriptions improve AI recommendations',
|
|
474
|
+
affectedCount: missingDescriptions,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
// Short descriptions
|
|
478
|
+
if (summary.averageDescriptionLength < MIN_DESCRIPTION_LENGTH && summary.withDescription > 0) {
|
|
479
|
+
recommendations.push({
|
|
480
|
+
priority: 'low',
|
|
481
|
+
category: 'descriptions',
|
|
482
|
+
title: 'Improve Description Length',
|
|
483
|
+
description: `Average description length is ${Math.round(summary.averageDescriptionLength)} characters. Aim for at least ${MIN_DESCRIPTION_LENGTH} characters.`,
|
|
484
|
+
impact: 'Low - Longer descriptions provide more context for AI agents',
|
|
485
|
+
affectedCount: summary.withDescription,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
// Missing availability
|
|
489
|
+
const missingAvailability = total - summary.withAvailability;
|
|
490
|
+
if (missingAvailability > 0 && missingAvailability / total > 0.3) {
|
|
491
|
+
recommendations.push({
|
|
492
|
+
priority: 'medium',
|
|
493
|
+
category: 'availability',
|
|
494
|
+
title: 'Add Availability Status',
|
|
495
|
+
description: `${missingAvailability} products are missing availability information.`,
|
|
496
|
+
impact: 'Medium - Availability helps AI agents make informed purchase decisions',
|
|
497
|
+
affectedCount: missingAvailability,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
// Sort by priority
|
|
501
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
502
|
+
recommendations.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
503
|
+
return recommendations;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Aggregate issues across all products
|
|
507
|
+
*/
|
|
508
|
+
function aggregateIssues(products) {
|
|
509
|
+
const issueMap = new Map();
|
|
510
|
+
for (const product of products) {
|
|
511
|
+
for (const issue of product.issues) {
|
|
512
|
+
const existing = issueMap.get(issue.id);
|
|
513
|
+
if (existing) {
|
|
514
|
+
// Aggregate affected products
|
|
515
|
+
if (issue.affectedProducts && existing.affectedProducts) {
|
|
516
|
+
existing.affectedProducts.push(...issue.affectedProducts);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
issueMap.set(issue.id, { ...issue, affectedProducts: [...(issue.affectedProducts || [])] });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return Array.from(issueMap.values());
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Calculate feed summary statistics
|
|
528
|
+
*/
|
|
529
|
+
function calculateSummary(products) {
|
|
530
|
+
const total = products.length;
|
|
531
|
+
if (total === 0) {
|
|
532
|
+
return {
|
|
533
|
+
totalProducts: 0,
|
|
534
|
+
withName: 0,
|
|
535
|
+
withDescription: 0,
|
|
536
|
+
withSku: 0,
|
|
537
|
+
withGtin: 0,
|
|
538
|
+
withBrand: 0,
|
|
539
|
+
withImages: 0,
|
|
540
|
+
withPrice: 0,
|
|
541
|
+
withAvailability: 0,
|
|
542
|
+
withCategory: 0,
|
|
543
|
+
averageDescriptionLength: 0,
|
|
544
|
+
averageImageCount: 0,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const summary = products.reduce((acc, p) => {
|
|
548
|
+
if (p.attributes.hasName)
|
|
549
|
+
acc.withName++;
|
|
550
|
+
if (p.attributes.hasDescription)
|
|
551
|
+
acc.withDescription++;
|
|
552
|
+
if (p.attributes.hasSku)
|
|
553
|
+
acc.withSku++;
|
|
554
|
+
if (p.attributes.hasGtin)
|
|
555
|
+
acc.withGtin++;
|
|
556
|
+
if (p.attributes.hasBrand)
|
|
557
|
+
acc.withBrand++;
|
|
558
|
+
if (p.attributes.hasImage)
|
|
559
|
+
acc.withImages++;
|
|
560
|
+
if (p.attributes.hasPrice)
|
|
561
|
+
acc.withPrice++;
|
|
562
|
+
if (p.attributes.hasAvailability)
|
|
563
|
+
acc.withAvailability++;
|
|
564
|
+
if (p.attributes.hasCategory)
|
|
565
|
+
acc.withCategory++;
|
|
566
|
+
acc.totalDescriptionLength += p.attributes.descriptionLength;
|
|
567
|
+
acc.totalImageCount += p.attributes.imageCount;
|
|
568
|
+
return acc;
|
|
569
|
+
}, {
|
|
570
|
+
withName: 0,
|
|
571
|
+
withDescription: 0,
|
|
572
|
+
withSku: 0,
|
|
573
|
+
withGtin: 0,
|
|
574
|
+
withBrand: 0,
|
|
575
|
+
withImages: 0,
|
|
576
|
+
withPrice: 0,
|
|
577
|
+
withAvailability: 0,
|
|
578
|
+
withCategory: 0,
|
|
579
|
+
totalDescriptionLength: 0,
|
|
580
|
+
totalImageCount: 0,
|
|
581
|
+
});
|
|
582
|
+
return {
|
|
583
|
+
totalProducts: total,
|
|
584
|
+
withName: summary.withName,
|
|
585
|
+
withDescription: summary.withDescription,
|
|
586
|
+
withSku: summary.withSku,
|
|
587
|
+
withGtin: summary.withGtin,
|
|
588
|
+
withBrand: summary.withBrand,
|
|
589
|
+
withImages: summary.withImages,
|
|
590
|
+
withPrice: summary.withPrice,
|
|
591
|
+
withAvailability: summary.withAvailability,
|
|
592
|
+
withCategory: summary.withCategory,
|
|
593
|
+
averageDescriptionLength: summary.totalDescriptionLength / total,
|
|
594
|
+
averageImageCount: summary.totalImageCount / total,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Analyze product feed from raw product data
|
|
599
|
+
*/
|
|
600
|
+
function analyzeProductFeed(products, url, maxProducts = DEFAULT_MAX_PRODUCTS, includeProductDetails = true) {
|
|
601
|
+
const productsToAnalyze = products.slice(0, maxProducts);
|
|
602
|
+
const productAnalyses = productsToAnalyze.map(analyzeProduct);
|
|
603
|
+
const summary = calculateSummary(productAnalyses);
|
|
604
|
+
const categoryScores = calculateCategoryScores(productAnalyses);
|
|
605
|
+
const overallScore = calculateOverallScore(categoryScores);
|
|
606
|
+
const agentVisibilityScore = calculateAgentVisibilityScore(summary);
|
|
607
|
+
const issues = aggregateIssues(productAnalyses);
|
|
608
|
+
const recommendations = generateRecommendations(summary, issues);
|
|
609
|
+
// Get top issues (most impactful)
|
|
610
|
+
const topIssues = issues
|
|
611
|
+
.filter(i => i.severity === 'critical' || i.severity === 'warning')
|
|
612
|
+
.sort((a, b) => {
|
|
613
|
+
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
614
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
615
|
+
})
|
|
616
|
+
.slice(0, 5);
|
|
617
|
+
return {
|
|
618
|
+
url,
|
|
619
|
+
analyzedAt: new Date().toISOString(),
|
|
620
|
+
productsFound: products.length,
|
|
621
|
+
productsAnalyzed: productsToAnalyze.length,
|
|
622
|
+
overallScore,
|
|
623
|
+
agentVisibilityScore,
|
|
624
|
+
grade: getGrade(overallScore),
|
|
625
|
+
categoryScores,
|
|
626
|
+
issues,
|
|
627
|
+
topIssues,
|
|
628
|
+
products: includeProductDetails ? productAnalyses : [],
|
|
629
|
+
recommendations,
|
|
630
|
+
summary,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Analyze a product feed from HTML content
|
|
635
|
+
*/
|
|
636
|
+
function analyzeProductFeedFromHtml(html, url, options = {}) {
|
|
637
|
+
const products = extractProductsFromHtml(html);
|
|
638
|
+
const maxProducts = options.maxProducts ?? DEFAULT_MAX_PRODUCTS;
|
|
639
|
+
const includeProductDetails = options.includeProductDetails ?? true;
|
|
640
|
+
return analyzeProductFeed(products, url, maxProducts, includeProductDetails);
|
|
641
|
+
}
|
|
642
|
+
//# sourceMappingURL=feed-analyzer.js.map
|