@ucptools/validator 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/config.d.ts +20 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +114 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +17 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +45 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +170 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/service.d.ts +80 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +298 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +375 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mock-server.d.ts +20 -0
- package/dist/cli/mock-server.d.ts.map +1 -0
- package/dist/cli/mock-server.js +261 -0
- package/dist/cli/mock-server.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 +17 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +80 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +3886 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +425 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/utils.d.ts +252 -0
- package/dist/db/utils.d.ts.map +1 -0
- package/dist/db/utils.js +295 -0
- package/dist/db/utils.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} +856 -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 +285 -0
- package/dist/feed-analyzer/types.d.ts.map +1 -0
- package/dist/feed-analyzer/types.js +175 -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/dist/lib/analytics.d.ts +337 -0
- package/dist/lib/analytics.d.ts.map +1 -0
- package/dist/lib/analytics.js +188 -0
- package/dist/lib/analytics.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 +669 -0
- package/dist/security/security-scanner.js.map +1 -0
- package/dist/security/types.d.ts +80 -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/analytics.d.ts +114 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/analytics.js +862 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/badge.d.ts +31 -0
- package/dist/services/badge.d.ts.map +1 -0
- package/dist/services/badge.js +152 -0
- package/dist/services/badge.js.map +1 -0
- package/dist/services/cron.d.ts +125 -0
- package/dist/services/cron.d.ts.map +1 -0
- package/dist/services/cron.js +613 -0
- package/dist/services/cron.js.map +1 -0
- package/dist/services/directory.d.ts +106 -0
- package/dist/services/directory.d.ts.map +1 -0
- package/dist/services/directory.js +351 -0
- package/dist/services/directory.js.map +1 -0
- package/dist/services/email.d.ts +112 -0
- package/dist/services/email.d.ts.map +1 -0
- package/dist/services/email.js +772 -0
- package/dist/services/email.js.map +1 -0
- package/dist/services/hosted-profiles.d.ts +77 -0
- package/dist/services/hosted-profiles.d.ts.map +1 -0
- package/dist/services/hosted-profiles.js +433 -0
- package/dist/services/hosted-profiles.js.map +1 -0
- package/dist/services/latency.d.ts +67 -0
- package/dist/services/latency.d.ts.map +1 -0
- package/dist/services/latency.js +274 -0
- package/dist/services/latency.js.map +1 -0
- package/dist/services/manifest-compliance.d.ts +64 -0
- package/dist/services/manifest-compliance.d.ts.map +1 -0
- package/dist/services/manifest-compliance.js +271 -0
- package/dist/services/manifest-compliance.js.map +1 -0
- package/dist/services/monitoring-diff.d.ts +31 -0
- package/dist/services/monitoring-diff.d.ts.map +1 -0
- package/dist/services/monitoring-diff.js +189 -0
- package/dist/services/monitoring-diff.js.map +1 -0
- package/dist/services/notifications.d.ts +46 -0
- package/dist/services/notifications.d.ts.map +1 -0
- package/dist/services/notifications.js +88 -0
- package/dist/services/notifications.js.map +1 -0
- package/dist/services/stripe.d.ts +93 -0
- package/dist/services/stripe.d.ts.map +1 -0
- package/dist/services/stripe.js +490 -0
- package/dist/services/stripe.js.map +1 -0
- package/dist/services/validation-history.d.ts +99 -0
- package/dist/services/validation-history.d.ts.map +1 -0
- package/dist/services/validation-history.js +344 -0
- package/dist/services/validation-history.js.map +1 -0
- package/dist/services/validation-logging.d.ts +103 -0
- package/dist/services/validation-logging.d.ts.map +1 -0
- package/dist/services/validation-logging.js +210 -0
- package/dist/services/validation-logging.js.map +1 -0
- package/dist/services/validation.d.ts +119 -0
- package/dist/services/validation.d.ts.map +1 -0
- package/dist/services/validation.js +1185 -0
- package/dist/services/validation.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts +69 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -0
- package/dist/simulator/agent-simulator.js +870 -0
- 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} +171 -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/acp-validation.d.ts +87 -0
- package/dist/types/acp-validation.d.ts.map +1 -0
- package/dist/types/acp-validation.js +40 -0
- package/dist/types/acp-validation.js.map +1 -0
- package/dist/types/analytics.d.ts +182 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +7 -0
- package/dist/types/analytics.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 +111 -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 +76 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +42 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/validator/acp/index.d.ts +31 -0
- package/dist/validator/acp/index.d.ts.map +1 -0
- package/dist/validator/acp/index.js +574 -0
- package/dist/validator/acp/index.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 +19 -0
- package/dist/validator/rules-validator.d.ts.map +1 -0
- package/dist/validator/rules-validator.js +306 -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 +549 -0
- package/dist/validator/structural-validator.js.map +1 -0
- package/dist/validator/utils.d.ts +51 -0
- package/dist/validator/utils.d.ts.map +1 -0
- package/dist/validator/utils.js +132 -0
- package/dist/validator/utils.js.map +1 -0
- package/package.json +44 -12
- 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/simulator/agent-simulator.ts +0 -941
- 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/tsconfig.json +0 -20
- package/vercel.json +0 -34
- package/vitest.config.ts +0 -22
|
@@ -1,726 +1,856 @@
|
|
|
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
|
-
const
|
|
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
|
-
|
|
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
|
-
const
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
//
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
.
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
+
// ACP-specific checks
|
|
252
|
+
if (product.name && product.name.length > 150) {
|
|
253
|
+
issues.push(createIssue('acp-title-too-long', product.name, `Title is ${product.name.length} characters (ACP limit: 150)`));
|
|
254
|
+
}
|
|
255
|
+
if (product.description && product.description.length > 5000) {
|
|
256
|
+
issues.push(createIssue('acp-description-too-long', product.name, `Description is ${product.description.length} characters (ACP limit: 5000)`));
|
|
257
|
+
}
|
|
258
|
+
// Calculate product score
|
|
259
|
+
const score = calculateProductScore(attributes);
|
|
260
|
+
return {
|
|
261
|
+
name: product.name || 'Unknown Product',
|
|
262
|
+
url: product.url,
|
|
263
|
+
sku: product.sku,
|
|
264
|
+
score,
|
|
265
|
+
issues,
|
|
266
|
+
attributes,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create a quality check issue
|
|
271
|
+
*/
|
|
272
|
+
function createIssue(checkId, productName, details) {
|
|
273
|
+
const checkDef = types_js_1.QUALITY_CHECKS[checkId];
|
|
274
|
+
return {
|
|
275
|
+
id: checkId,
|
|
276
|
+
name: checkDef.name,
|
|
277
|
+
category: checkDef.category,
|
|
278
|
+
passed: false,
|
|
279
|
+
severity: checkDef.severity,
|
|
280
|
+
message: checkDef.description,
|
|
281
|
+
details,
|
|
282
|
+
affectedProducts: productName ? [productName] : undefined,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Calculate product score based on attributes
|
|
287
|
+
*/
|
|
288
|
+
function calculateProductScore(attributes) {
|
|
289
|
+
let score = 0;
|
|
290
|
+
const weights = {
|
|
291
|
+
hasName: 15,
|
|
292
|
+
hasDescription: 10,
|
|
293
|
+
hasSku: 5,
|
|
294
|
+
hasGtin: 10,
|
|
295
|
+
hasBrand: 10,
|
|
296
|
+
hasImage: 15,
|
|
297
|
+
hasPrice: 20,
|
|
298
|
+
hasAvailability: 5,
|
|
299
|
+
hasCategory: 5,
|
|
300
|
+
descriptionQuality: 5,
|
|
301
|
+
};
|
|
302
|
+
if (attributes.hasName)
|
|
303
|
+
score += weights.hasName;
|
|
304
|
+
if (attributes.hasDescription)
|
|
305
|
+
score += weights.hasDescription;
|
|
306
|
+
if (attributes.hasSku)
|
|
307
|
+
score += weights.hasSku;
|
|
308
|
+
if (attributes.hasGtin)
|
|
309
|
+
score += weights.hasGtin;
|
|
310
|
+
if (attributes.hasBrand)
|
|
311
|
+
score += weights.hasBrand;
|
|
312
|
+
if (attributes.hasImage)
|
|
313
|
+
score += weights.hasImage;
|
|
314
|
+
if (attributes.hasPrice)
|
|
315
|
+
score += weights.hasPrice;
|
|
316
|
+
if (attributes.hasAvailability)
|
|
317
|
+
score += weights.hasAvailability;
|
|
318
|
+
if (attributes.hasCategory)
|
|
319
|
+
score += weights.hasCategory;
|
|
320
|
+
// Bonus for good description length
|
|
321
|
+
if (attributes.descriptionLength >= MIN_DESCRIPTION_LENGTH) {
|
|
322
|
+
score += weights.descriptionQuality;
|
|
323
|
+
}
|
|
324
|
+
return Math.min(100, score);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Calculate category scores from product analyses
|
|
328
|
+
*/
|
|
329
|
+
function calculateCategoryScores(products) {
|
|
330
|
+
if (products.length === 0) {
|
|
331
|
+
return {
|
|
332
|
+
completeness: 0,
|
|
333
|
+
identifiers: 0,
|
|
334
|
+
images: 0,
|
|
335
|
+
pricing: 0,
|
|
336
|
+
descriptions: 0,
|
|
337
|
+
categories: 0,
|
|
338
|
+
availability: 0,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const totals = products.reduce((acc, p) => {
|
|
342
|
+
// Completeness: name, brand
|
|
343
|
+
acc.completeness += (p.attributes.hasName ? 50 : 0) + (p.attributes.hasBrand ? 50 : 0);
|
|
344
|
+
// Identifiers: SKU, GTIN
|
|
345
|
+
acc.identifiers += (p.attributes.hasSku ? 50 : 0) + (p.attributes.hasGtin ? 50 : 0);
|
|
346
|
+
// Images
|
|
347
|
+
acc.images += p.attributes.hasImage ? (p.attributes.imageCount > 1 ? 100 : 70) : 0;
|
|
348
|
+
// Pricing
|
|
349
|
+
acc.pricing += p.attributes.hasPrice ? 100 : 0;
|
|
350
|
+
// Descriptions
|
|
351
|
+
if (p.attributes.hasDescription) {
|
|
352
|
+
acc.descriptions += p.attributes.descriptionLength >= MIN_DESCRIPTION_LENGTH ? 100 : 60;
|
|
353
|
+
}
|
|
354
|
+
// Categories
|
|
355
|
+
acc.categories += p.attributes.hasCategory ? 100 : 0;
|
|
356
|
+
// Availability
|
|
357
|
+
acc.availability += p.attributes.hasAvailability ? 100 : 0;
|
|
358
|
+
return acc;
|
|
359
|
+
}, { completeness: 0, identifiers: 0, images: 0, pricing: 0, descriptions: 0, categories: 0, availability: 0 });
|
|
360
|
+
const count = products.length;
|
|
361
|
+
return {
|
|
362
|
+
completeness: Math.round(totals.completeness / count),
|
|
363
|
+
identifiers: Math.round(totals.identifiers / count),
|
|
364
|
+
images: Math.round(totals.images / count),
|
|
365
|
+
pricing: Math.round(totals.pricing / count),
|
|
366
|
+
descriptions: Math.round(totals.descriptions / count),
|
|
367
|
+
categories: Math.round(totals.categories / count),
|
|
368
|
+
availability: Math.round(totals.availability / count),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Calculate overall score from category scores
|
|
373
|
+
*/
|
|
374
|
+
function calculateOverallScore(categoryScores) {
|
|
375
|
+
let weightedSum = 0;
|
|
376
|
+
let totalWeight = 0;
|
|
377
|
+
for (const [category, score] of Object.entries(categoryScores)) {
|
|
378
|
+
const weight = types_js_1.CATEGORY_WEIGHTS[category];
|
|
379
|
+
weightedSum += score * weight;
|
|
380
|
+
totalWeight += weight;
|
|
381
|
+
}
|
|
382
|
+
return Math.round(weightedSum / totalWeight);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Calculate agent visibility score (legacy - kept for compatibility)
|
|
386
|
+
* This is a specialized score focusing on what AI agents need most
|
|
387
|
+
*/
|
|
388
|
+
function calculateAgentVisibilityScore(summary) {
|
|
389
|
+
if (summary.totalProducts === 0)
|
|
390
|
+
return 0;
|
|
391
|
+
const total = summary.totalProducts;
|
|
392
|
+
// Critical factors for AI agents (weighted heavily)
|
|
393
|
+
const criticalScore = ((summary.withName / total) * 30) +
|
|
394
|
+
((summary.withPrice / total) * 30) +
|
|
395
|
+
((summary.withImages / total) * 20);
|
|
396
|
+
// Important factors
|
|
397
|
+
const importantScore = ((summary.withDescription / total) * 10) +
|
|
398
|
+
((summary.withGtin / total) * 5) +
|
|
399
|
+
((summary.withAvailability / total) * 5);
|
|
400
|
+
return Math.round(criticalScore + importantScore);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Calculate new additive score breakdown
|
|
404
|
+
*
|
|
405
|
+
* Scoring Model (0-100, additive):
|
|
406
|
+
* - Core Data: 40 pts max (name 15, price 15, availability 10)
|
|
407
|
+
* - Product Media: 25 pts max (has images 15, multiple images 10)
|
|
408
|
+
* - Identifiers: 20 pts max (SKU 10, GTIN 10)
|
|
409
|
+
* - Descriptions: 15 pts max (has description 10, good length 5)
|
|
410
|
+
*/
|
|
411
|
+
function calculateScoreBreakdown(summary, products) {
|
|
412
|
+
const total = summary.totalProducts;
|
|
413
|
+
// Zero state: no products = 0 score
|
|
414
|
+
if (total === 0) {
|
|
415
|
+
return {
|
|
416
|
+
coreData: {
|
|
417
|
+
score: 0,
|
|
418
|
+
maxScore: 40,
|
|
419
|
+
status: 'No products found - nothing to sell',
|
|
420
|
+
details: {
|
|
421
|
+
hasName: { score: 0, percent: 0 },
|
|
422
|
+
hasPrice: { score: 0, percent: 0 },
|
|
423
|
+
hasAvailability: { score: 0, percent: 0 },
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
productMedia: {
|
|
427
|
+
score: 0,
|
|
428
|
+
maxScore: 25,
|
|
429
|
+
status: 'No products to analyze',
|
|
430
|
+
details: {
|
|
431
|
+
hasImages: { score: 0, percent: 0 },
|
|
432
|
+
multipleImages: { score: 0, percent: 0 },
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
identifiers: {
|
|
436
|
+
score: 0,
|
|
437
|
+
maxScore: 20,
|
|
438
|
+
status: 'No products to analyze',
|
|
439
|
+
details: {
|
|
440
|
+
hasSku: { score: 0, percent: 0 },
|
|
441
|
+
hasGtin: { score: 0, percent: 0 },
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
descriptions: {
|
|
445
|
+
score: 0,
|
|
446
|
+
maxScore: 15,
|
|
447
|
+
status: 'No products to analyze',
|
|
448
|
+
details: {
|
|
449
|
+
hasDescription: { score: 0, percent: 0 },
|
|
450
|
+
goodLength: { score: 0, percent: 0 },
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
total: 0,
|
|
454
|
+
maxTotal: 100,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
// Calculate percentages
|
|
458
|
+
const namePercent = Math.round((summary.withName / total) * 100);
|
|
459
|
+
const pricePercent = Math.round((summary.withPrice / total) * 100);
|
|
460
|
+
const availabilityPercent = Math.round((summary.withAvailability / total) * 100);
|
|
461
|
+
const imagesPercent = Math.round((summary.withImages / total) * 100);
|
|
462
|
+
const skuPercent = Math.round((summary.withSku / total) * 100);
|
|
463
|
+
const gtinPercent = Math.round((summary.withGtin / total) * 100);
|
|
464
|
+
const descriptionPercent = Math.round((summary.withDescription / total) * 100);
|
|
465
|
+
// Count products with multiple images
|
|
466
|
+
const withMultipleImages = products.filter(p => p.attributes.imageCount > 1).length;
|
|
467
|
+
const multipleImagesPercent = Math.round((withMultipleImages / total) * 100);
|
|
468
|
+
// Count products with good description length
|
|
469
|
+
const withGoodDescription = products.filter(p => p.attributes.descriptionLength >= MIN_DESCRIPTION_LENGTH).length;
|
|
470
|
+
const goodDescriptionPercent = Math.round((withGoodDescription / total) * 100);
|
|
471
|
+
// Core Data scoring (40 max)
|
|
472
|
+
const nameScore = Math.round((summary.withName / total) * 15);
|
|
473
|
+
const priceScore = Math.round((summary.withPrice / total) * 15);
|
|
474
|
+
const availabilityScore = Math.round((summary.withAvailability / total) * 10);
|
|
475
|
+
const coreDataScore = nameScore + priceScore + availabilityScore;
|
|
476
|
+
// Product Media scoring (25 max)
|
|
477
|
+
const hasImagesScore = Math.round((summary.withImages / total) * 15);
|
|
478
|
+
const multipleImagesScore = Math.round((withMultipleImages / total) * 10);
|
|
479
|
+
const productMediaScore = hasImagesScore + multipleImagesScore;
|
|
480
|
+
// Identifiers scoring (20 max)
|
|
481
|
+
const skuScore = Math.round((summary.withSku / total) * 10);
|
|
482
|
+
const gtinScore = Math.round((summary.withGtin / total) * 10);
|
|
483
|
+
const identifiersScore = skuScore + gtinScore;
|
|
484
|
+
// Descriptions scoring (15 max)
|
|
485
|
+
const hasDescriptionScore = Math.round((summary.withDescription / total) * 10);
|
|
486
|
+
const goodLengthScore = Math.round((withGoodDescription / total) * 5);
|
|
487
|
+
const descriptionsScore = hasDescriptionScore + goodLengthScore;
|
|
488
|
+
// Generate status messages
|
|
489
|
+
const coreDataStatus = generateCoreDataStatus(namePercent, pricePercent, availabilityPercent);
|
|
490
|
+
const productMediaStatus = generateMediaStatus(imagesPercent, multipleImagesPercent);
|
|
491
|
+
const identifiersStatus = generateIdentifiersStatus(skuPercent, gtinPercent);
|
|
492
|
+
const descriptionsStatus = generateDescriptionsStatus(descriptionPercent, goodDescriptionPercent, summary.averageDescriptionLength);
|
|
493
|
+
const totalScore = coreDataScore + productMediaScore + identifiersScore + descriptionsScore;
|
|
494
|
+
return {
|
|
495
|
+
coreData: {
|
|
496
|
+
score: coreDataScore,
|
|
497
|
+
maxScore: 40,
|
|
498
|
+
status: coreDataStatus,
|
|
499
|
+
details: {
|
|
500
|
+
hasName: { score: nameScore, percent: namePercent },
|
|
501
|
+
hasPrice: { score: priceScore, percent: pricePercent },
|
|
502
|
+
hasAvailability: { score: availabilityScore, percent: availabilityPercent },
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
productMedia: {
|
|
506
|
+
score: productMediaScore,
|
|
507
|
+
maxScore: 25,
|
|
508
|
+
status: productMediaStatus,
|
|
509
|
+
details: {
|
|
510
|
+
hasImages: { score: hasImagesScore, percent: imagesPercent },
|
|
511
|
+
multipleImages: { score: multipleImagesScore, percent: multipleImagesPercent },
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
identifiers: {
|
|
515
|
+
score: identifiersScore,
|
|
516
|
+
maxScore: 20,
|
|
517
|
+
status: identifiersStatus,
|
|
518
|
+
details: {
|
|
519
|
+
hasSku: { score: skuScore, percent: skuPercent },
|
|
520
|
+
hasGtin: { score: gtinScore, percent: gtinPercent },
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
descriptions: {
|
|
524
|
+
score: descriptionsScore,
|
|
525
|
+
maxScore: 15,
|
|
526
|
+
status: descriptionsStatus,
|
|
527
|
+
details: {
|
|
528
|
+
hasDescription: { score: hasDescriptionScore, percent: descriptionPercent },
|
|
529
|
+
goodLength: { score: goodLengthScore, percent: goodDescriptionPercent },
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
total: totalScore,
|
|
533
|
+
maxTotal: 100,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Generate status message for core data
|
|
538
|
+
*/
|
|
539
|
+
function generateCoreDataStatus(namePercent, pricePercent, availabilityPercent) {
|
|
540
|
+
if (namePercent >= 95 && pricePercent >= 95 && availabilityPercent >= 90) {
|
|
541
|
+
return 'Excellent - All products have complete core data';
|
|
542
|
+
}
|
|
543
|
+
const issues = [];
|
|
544
|
+
if (namePercent < 95)
|
|
545
|
+
issues.push(`${100 - namePercent}% missing names`);
|
|
546
|
+
if (pricePercent < 95)
|
|
547
|
+
issues.push(`${100 - pricePercent}% missing prices`);
|
|
548
|
+
if (availabilityPercent < 90)
|
|
549
|
+
issues.push(`${100 - availabilityPercent}% missing availability`);
|
|
550
|
+
if (issues.length === 0) {
|
|
551
|
+
return `${namePercent}% with name, ${pricePercent}% with price, ${availabilityPercent}% with availability`;
|
|
552
|
+
}
|
|
553
|
+
return issues.join(', ');
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Generate status message for product media
|
|
557
|
+
*/
|
|
558
|
+
function generateMediaStatus(imagesPercent, multipleImagesPercent) {
|
|
559
|
+
if (imagesPercent >= 95 && multipleImagesPercent >= 80) {
|
|
560
|
+
return `All products have images (${multipleImagesPercent}% have multiple)`;
|
|
561
|
+
}
|
|
562
|
+
if (imagesPercent < 80) {
|
|
563
|
+
return `${100 - imagesPercent}% of products missing images`;
|
|
564
|
+
}
|
|
565
|
+
return `${imagesPercent}% have images, ${multipleImagesPercent}% have multiple images`;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Generate status message for identifiers
|
|
569
|
+
*/
|
|
570
|
+
function generateIdentifiersStatus(skuPercent, gtinPercent) {
|
|
571
|
+
if (skuPercent >= 90 && gtinPercent >= 80) {
|
|
572
|
+
return `Strong identifiers - ${skuPercent}% SKUs, ${gtinPercent}% GTINs`;
|
|
573
|
+
}
|
|
574
|
+
if (gtinPercent < 50) {
|
|
575
|
+
return `${100 - gtinPercent}% missing GTINs for cross-platform matching`;
|
|
576
|
+
}
|
|
577
|
+
return `${skuPercent}% have SKUs, ${gtinPercent}% have GTINs`;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Generate status message for descriptions
|
|
581
|
+
*/
|
|
582
|
+
function generateDescriptionsStatus(descPercent, goodLengthPercent, avgLength) {
|
|
583
|
+
if (descPercent >= 95 && goodLengthPercent >= 80) {
|
|
584
|
+
return `Strong descriptions (avg ${Math.round(avgLength)} chars)`;
|
|
585
|
+
}
|
|
586
|
+
if (descPercent < 70) {
|
|
587
|
+
return `${100 - descPercent}% of products missing descriptions`;
|
|
588
|
+
}
|
|
589
|
+
if (goodLengthPercent < 50) {
|
|
590
|
+
return `Short descriptions (avg ${Math.round(avgLength)} chars) - aim for 50+`;
|
|
591
|
+
}
|
|
592
|
+
return `${descPercent}% have descriptions (avg ${Math.round(avgLength)} chars)`;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Get grade from score
|
|
596
|
+
*/
|
|
597
|
+
function getGrade(score) {
|
|
598
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.A)
|
|
599
|
+
return 'A';
|
|
600
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.B)
|
|
601
|
+
return 'B';
|
|
602
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.C)
|
|
603
|
+
return 'C';
|
|
604
|
+
if (score >= types_js_1.GRADE_THRESHOLDS.D)
|
|
605
|
+
return 'D';
|
|
606
|
+
return 'F';
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Generate recommendations based on analysis
|
|
610
|
+
*/
|
|
611
|
+
function generateRecommendations(summary, issues) {
|
|
612
|
+
const recommendations = [];
|
|
613
|
+
const total = summary.totalProducts;
|
|
614
|
+
if (total === 0)
|
|
615
|
+
return recommendations;
|
|
616
|
+
// Missing prices - critical
|
|
617
|
+
const missingPrices = total - summary.withPrice;
|
|
618
|
+
if (missingPrices > 0) {
|
|
619
|
+
recommendations.push({
|
|
620
|
+
priority: 'high',
|
|
621
|
+
category: 'pricing',
|
|
622
|
+
title: 'Add Missing Prices',
|
|
623
|
+
description: `${missingPrices} products are missing price information. AI agents cannot complete purchases without prices.`,
|
|
624
|
+
impact: 'Critical - Products without prices cannot be purchased through AI agents',
|
|
625
|
+
affectedCount: missingPrices,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
// Missing images - critical
|
|
629
|
+
const missingImages = total - summary.withImages;
|
|
630
|
+
if (missingImages > 0) {
|
|
631
|
+
recommendations.push({
|
|
632
|
+
priority: 'high',
|
|
633
|
+
category: 'images',
|
|
634
|
+
title: 'Add Product Images',
|
|
635
|
+
description: `${missingImages} products are missing images. Visual product information is essential for AI shopping.`,
|
|
636
|
+
impact: 'High - Products without images are less likely to be recommended',
|
|
637
|
+
affectedCount: missingImages,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
// Missing names - critical
|
|
641
|
+
const missingNames = total - summary.withName;
|
|
642
|
+
if (missingNames > 0) {
|
|
643
|
+
recommendations.push({
|
|
644
|
+
priority: 'high',
|
|
645
|
+
category: 'completeness',
|
|
646
|
+
title: 'Add Product Names',
|
|
647
|
+
description: `${missingNames} products are missing names. This is required for product identification.`,
|
|
648
|
+
impact: 'Critical - Products cannot be identified without names',
|
|
649
|
+
affectedCount: missingNames,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
// Missing GTIN - medium priority
|
|
653
|
+
const missingGtin = total - summary.withGtin;
|
|
654
|
+
if (missingGtin > 0 && missingGtin / total > 0.5) {
|
|
655
|
+
recommendations.push({
|
|
656
|
+
priority: 'medium',
|
|
657
|
+
category: 'identifiers',
|
|
658
|
+
title: 'Add Global Identifiers (GTIN/UPC/EAN)',
|
|
659
|
+
description: `${missingGtin} products are missing global identifiers. These enable cross-platform product matching.`,
|
|
660
|
+
impact: 'Medium - Improves product matching across AI platforms',
|
|
661
|
+
affectedCount: missingGtin,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
// Missing descriptions - medium priority
|
|
665
|
+
const missingDescriptions = total - summary.withDescription;
|
|
666
|
+
if (missingDescriptions > 0 && missingDescriptions / total > 0.3) {
|
|
667
|
+
recommendations.push({
|
|
668
|
+
priority: 'medium',
|
|
669
|
+
category: 'descriptions',
|
|
670
|
+
title: 'Add Product Descriptions',
|
|
671
|
+
description: `${missingDescriptions} products are missing descriptions. Good descriptions help AI agents understand and recommend products.`,
|
|
672
|
+
impact: 'Medium - Better descriptions improve AI recommendations',
|
|
673
|
+
affectedCount: missingDescriptions,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
// Short descriptions
|
|
677
|
+
if (summary.averageDescriptionLength < MIN_DESCRIPTION_LENGTH && summary.withDescription > 0) {
|
|
678
|
+
recommendations.push({
|
|
679
|
+
priority: 'low',
|
|
680
|
+
category: 'descriptions',
|
|
681
|
+
title: 'Improve Description Length',
|
|
682
|
+
description: `Average description length is ${Math.round(summary.averageDescriptionLength)} characters. Aim for at least ${MIN_DESCRIPTION_LENGTH} characters.`,
|
|
683
|
+
impact: 'Low - Longer descriptions provide more context for AI agents',
|
|
684
|
+
affectedCount: summary.withDescription,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
// Missing availability
|
|
688
|
+
const missingAvailability = total - summary.withAvailability;
|
|
689
|
+
if (missingAvailability > 0 && missingAvailability / total > 0.3) {
|
|
690
|
+
recommendations.push({
|
|
691
|
+
priority: 'medium',
|
|
692
|
+
category: 'availability',
|
|
693
|
+
title: 'Add Availability Status',
|
|
694
|
+
description: `${missingAvailability} products are missing availability information.`,
|
|
695
|
+
impact: 'Medium - Availability helps AI agents make informed purchase decisions',
|
|
696
|
+
affectedCount: missingAvailability,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
// Sort by priority
|
|
700
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
701
|
+
recommendations.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
702
|
+
return recommendations;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Aggregate issues across all products
|
|
706
|
+
*/
|
|
707
|
+
function aggregateIssues(products) {
|
|
708
|
+
const issueMap = new Map();
|
|
709
|
+
for (const product of products) {
|
|
710
|
+
for (const issue of product.issues) {
|
|
711
|
+
const existing = issueMap.get(issue.id);
|
|
712
|
+
if (existing) {
|
|
713
|
+
// Aggregate affected products
|
|
714
|
+
if (issue.affectedProducts && existing.affectedProducts) {
|
|
715
|
+
existing.affectedProducts.push(...issue.affectedProducts);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
issueMap.set(issue.id, { ...issue, affectedProducts: [...(issue.affectedProducts || [])] });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return Array.from(issueMap.values());
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Calculate feed summary statistics
|
|
727
|
+
*/
|
|
728
|
+
function calculateSummary(products) {
|
|
729
|
+
const total = products.length;
|
|
730
|
+
if (total === 0) {
|
|
731
|
+
return {
|
|
732
|
+
totalProducts: 0,
|
|
733
|
+
withName: 0,
|
|
734
|
+
withDescription: 0,
|
|
735
|
+
withSku: 0,
|
|
736
|
+
withGtin: 0,
|
|
737
|
+
withBrand: 0,
|
|
738
|
+
withImages: 0,
|
|
739
|
+
withPrice: 0,
|
|
740
|
+
withAvailability: 0,
|
|
741
|
+
withCategory: 0,
|
|
742
|
+
averageDescriptionLength: 0,
|
|
743
|
+
averageImageCount: 0,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
const summary = products.reduce((acc, p) => {
|
|
747
|
+
if (p.attributes.hasName)
|
|
748
|
+
acc.withName++;
|
|
749
|
+
if (p.attributes.hasDescription)
|
|
750
|
+
acc.withDescription++;
|
|
751
|
+
if (p.attributes.hasSku)
|
|
752
|
+
acc.withSku++;
|
|
753
|
+
if (p.attributes.hasGtin)
|
|
754
|
+
acc.withGtin++;
|
|
755
|
+
if (p.attributes.hasBrand)
|
|
756
|
+
acc.withBrand++;
|
|
757
|
+
if (p.attributes.hasImage)
|
|
758
|
+
acc.withImages++;
|
|
759
|
+
if (p.attributes.hasPrice)
|
|
760
|
+
acc.withPrice++;
|
|
761
|
+
if (p.attributes.hasAvailability)
|
|
762
|
+
acc.withAvailability++;
|
|
763
|
+
if (p.attributes.hasCategory)
|
|
764
|
+
acc.withCategory++;
|
|
765
|
+
acc.totalDescriptionLength += p.attributes.descriptionLength;
|
|
766
|
+
acc.totalImageCount += p.attributes.imageCount;
|
|
767
|
+
return acc;
|
|
768
|
+
}, {
|
|
769
|
+
withName: 0,
|
|
770
|
+
withDescription: 0,
|
|
771
|
+
withSku: 0,
|
|
772
|
+
withGtin: 0,
|
|
773
|
+
withBrand: 0,
|
|
774
|
+
withImages: 0,
|
|
775
|
+
withPrice: 0,
|
|
776
|
+
withAvailability: 0,
|
|
777
|
+
withCategory: 0,
|
|
778
|
+
totalDescriptionLength: 0,
|
|
779
|
+
totalImageCount: 0,
|
|
780
|
+
});
|
|
781
|
+
return {
|
|
782
|
+
totalProducts: total,
|
|
783
|
+
withName: summary.withName,
|
|
784
|
+
withDescription: summary.withDescription,
|
|
785
|
+
withSku: summary.withSku,
|
|
786
|
+
withGtin: summary.withGtin,
|
|
787
|
+
withBrand: summary.withBrand,
|
|
788
|
+
withImages: summary.withImages,
|
|
789
|
+
withPrice: summary.withPrice,
|
|
790
|
+
withAvailability: summary.withAvailability,
|
|
791
|
+
withCategory: summary.withCategory,
|
|
792
|
+
averageDescriptionLength: summary.totalDescriptionLength / total,
|
|
793
|
+
averageImageCount: summary.totalImageCount / total,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Analyze product feed from raw product data
|
|
798
|
+
*/
|
|
799
|
+
function analyzeProductFeed(products, url, maxProducts = DEFAULT_MAX_PRODUCTS, includeProductDetails = true) {
|
|
800
|
+
const productsToAnalyze = products.slice(0, maxProducts);
|
|
801
|
+
const productAnalyses = productsToAnalyze.map(analyzeProduct);
|
|
802
|
+
const summary = calculateSummary(productAnalyses);
|
|
803
|
+
const categoryScores = calculateCategoryScores(productAnalyses);
|
|
804
|
+
const score_breakdown = calculateScoreBreakdown(summary, productAnalyses);
|
|
805
|
+
const overallScore = score_breakdown.total; // Use additive score as primary
|
|
806
|
+
const agentVisibilityScore = overallScore; // Kept for backwards compatibility
|
|
807
|
+
const issues = aggregateIssues(productAnalyses);
|
|
808
|
+
const recommendations = generateRecommendations(summary, issues);
|
|
809
|
+
// Get top issues (most impactful)
|
|
810
|
+
const topIssues = issues
|
|
811
|
+
.filter(i => i.severity === 'critical' || i.severity === 'warning')
|
|
812
|
+
.sort((a, b) => {
|
|
813
|
+
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
814
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
815
|
+
})
|
|
816
|
+
.slice(0, 5);
|
|
817
|
+
// ACP feed compliance summary
|
|
818
|
+
const acpTitleOk = productAnalyses.filter(p => !p.issues.some(i => i.id === 'acp-title-too-long')).length;
|
|
819
|
+
const acpDescOk = productAnalyses.filter(p => !p.issues.some(i => i.id === 'acp-description-too-long')).length;
|
|
820
|
+
const acpIssueCount = productAnalyses.reduce((count, p) => count + p.issues.filter(i => i.id === 'acp-title-too-long' || i.id === 'acp-description-too-long').length, 0);
|
|
821
|
+
const acp_compliance = {
|
|
822
|
+
title_length_ok: acpTitleOk,
|
|
823
|
+
description_length_ok: acpDescOk,
|
|
824
|
+
has_gtin: summary.withGtin,
|
|
825
|
+
has_currency: productAnalyses.filter(p => p.attributes.hasPrice).length, // currency required with price
|
|
826
|
+
total: productAnalyses.length,
|
|
827
|
+
acp_issues: acpIssueCount,
|
|
828
|
+
};
|
|
829
|
+
return {
|
|
830
|
+
url,
|
|
831
|
+
analyzedAt: new Date().toISOString(),
|
|
832
|
+
productsFound: products.length,
|
|
833
|
+
productsAnalyzed: productsToAnalyze.length,
|
|
834
|
+
overallScore,
|
|
835
|
+
agentVisibilityScore,
|
|
836
|
+
grade: getGrade(overallScore),
|
|
837
|
+
score_breakdown,
|
|
838
|
+
categoryScores, // Legacy - kept for compatibility
|
|
839
|
+
issues,
|
|
840
|
+
topIssues,
|
|
841
|
+
products: includeProductDetails ? productAnalyses : [],
|
|
842
|
+
recommendations,
|
|
843
|
+
summary,
|
|
844
|
+
acp_compliance,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Analyze a product feed from HTML content
|
|
849
|
+
*/
|
|
850
|
+
function analyzeProductFeedFromHtml(html, url, options = {}) {
|
|
851
|
+
const products = extractProductsFromHtml(html);
|
|
852
|
+
const maxProducts = options.maxProducts ?? DEFAULT_MAX_PRODUCTS;
|
|
853
|
+
const includeProductDetails = options.includeProductDetails ?? true;
|
|
854
|
+
return analyzeProductFeed(products, url, maxProducts, includeProductDetails);
|
|
855
|
+
}
|
|
856
|
+
//# sourceMappingURL=feed-analyzer.js.map
|