@factorypure/client-helpers 1.0.0 → 1.0.2

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.
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ export declare const scrapeResultsSchema: any;
3
+ export type ScrapeResultsType = z.infer<typeof scrapeResultsSchema>;
4
+ export type ScrapeFiltersType = {
5
+ found_id_exclusions: number[];
6
+ showHighPriceOutliers: boolean;
7
+ showLowPriceOutliers: boolean;
8
+ dayWindow: number | null;
9
+ competitor_exclusions: string[];
10
+ match_values: string[];
11
+ skip_skus: string[];
12
+ vendor_search_exclusions: string[];
13
+ search_exclusions: string[];
14
+ };
15
+ export type FilterOptionsType = {
16
+ exclude_linked_variants_enabled?: boolean;
17
+ found_id_exclusions_enabled?: boolean;
18
+ price_filter_enabled?: boolean;
19
+ date_filter_enabled?: boolean;
20
+ competitor_filter_enabled?: boolean;
21
+ duplicates_filter_enabled?: boolean;
22
+ search_index_enabled?: boolean;
23
+ skip_skus_enabled?: boolean;
24
+ wattage_exclusions_enabled?: boolean;
25
+ search_exclusions_enabled?: boolean;
26
+ };
27
+ export declare const filterScrapeResults: ({ scrapeResults, variant, filters, filterOptions, }: {
28
+ scrapeResults: ScrapeResultsType[];
29
+ variant: {
30
+ id: number;
31
+ title: string;
32
+ sku: string;
33
+ price: number;
34
+ };
35
+ filters: ScrapeFiltersType;
36
+ filterOptions?: FilterOptionsType;
37
+ }) => z.infer<any>[];
package/dist/index.js ADDED
@@ -0,0 +1,256 @@
1
+ import { subDays } from "date-fns";
2
+ // @ts-ignore
3
+ import { Index } from "flexsearch";
4
+ // @ts-ignore
5
+ import EnglishPreset from "flexsearch/lang/en";
6
+ // @ts-ignore
7
+ import { z } from "zod";
8
+ export const scrapeResultsSchema = z.object({
9
+ id: z.number(),
10
+ scrape_id: z.number(),
11
+ variant_id: z.number(),
12
+ type: z.string(),
13
+ format: z.nullable(z.any()),
14
+ position: z.number(),
15
+ title: z.string(),
16
+ found_product_id: z.number(),
17
+ product_link: z.string(),
18
+ inline_link: z.nullable(z.any()),
19
+ immersive_product_page_token: z.string(),
20
+ serpapi_immersive_product_api: z.string(),
21
+ source: z.string(),
22
+ source_icon: z.string(),
23
+ multiple_sources: z.number(),
24
+ price: z.string(),
25
+ extracted_price: z.number(),
26
+ old_price: z.nullable(z.any()),
27
+ extracted_old_price: z.nullable(z.any()),
28
+ rating: z.nullable(z.any()),
29
+ reviews: z.nullable(z.any()),
30
+ snippet: z.nullable(z.any()),
31
+ thumbnail: z.string(),
32
+ serpapi_thumbnail: z.string(),
33
+ tag: z.nullable(z.any()),
34
+ extensions: z.nullable(z.any()),
35
+ delivery: z.nullable(z.any()),
36
+ created_at: z.string(),
37
+ store_id: z.number(),
38
+ match_score: z.number().nullable(),
39
+ });
40
+ const TOO_CHEAP_MULTIPLIER = 0.75;
41
+ const TOO_EXPENSIVE_MULTIPLIER = 1.25;
42
+ const wattages = Array.from({ length: 41 }, (_, i) => (5000 + i * 500).toString());
43
+ export const filterScrapeResults = ({ scrapeResults, variant, filters, filterOptions = {
44
+ exclude_linked_variants_enabled: true,
45
+ found_id_exclusions_enabled: true,
46
+ price_filter_enabled: true,
47
+ date_filter_enabled: true,
48
+ competitor_filter_enabled: true,
49
+ duplicates_filter_enabled: true,
50
+ search_index_enabled: true,
51
+ skip_skus_enabled: true,
52
+ wattage_exclusions_enabled: true,
53
+ search_exclusions_enabled: true,
54
+ }, }) => {
55
+ let filteredResults = scrapeResults;
56
+ if (filterOptions.exclude_linked_variants_enabled) {
57
+ filteredResults = filterLinkedElsewhereResults(filteredResults, variant.id);
58
+ }
59
+ if (filterOptions.found_id_exclusions_enabled) {
60
+ filteredResults = filterFoundProductIdExclusions(filteredResults, filters.found_id_exclusions);
61
+ }
62
+ if (filterOptions.price_filter_enabled) {
63
+ filteredResults = filterPriceOutliers(filteredResults, variant.price, filters.showHighPriceOutliers, filters.showLowPriceOutliers);
64
+ }
65
+ if (filterOptions.date_filter_enabled) {
66
+ filteredResults = filterDateWindow(filteredResults, filters.dayWindow);
67
+ }
68
+ if (filterOptions.competitor_filter_enabled) {
69
+ filteredResults = filterCompetitors(filteredResults, filters.competitor_exclusions);
70
+ }
71
+ if (filterOptions.duplicates_filter_enabled) {
72
+ filteredResults = filterDuplicateResults(filteredResults);
73
+ }
74
+ if (filterOptions.search_index_enabled) {
75
+ filteredResults = handleIndexSearch(filteredResults, filters, variant, filterOptions);
76
+ }
77
+ return filteredResults;
78
+ };
79
+ const handleIndexSearch = (dataToSearch, filters, variant, filterOptions) => {
80
+ const { title, sku } = variant;
81
+ function customEncoder(content) {
82
+ const tokens = [];
83
+ const str = content.toLowerCase();
84
+ // Remove symbols from the string (keep only letters, numbers, commas, and spaces)
85
+ const cleanedStr = str.replace(/[\/-]/g, " ");
86
+ const cleanedStr2 = cleanedStr.replace(/[^a-z0-9,\/\s]/gi, "");
87
+ const words = cleanedStr2.split(/\s+/);
88
+ for (let word of words) {
89
+ tokens.push(word);
90
+ }
91
+ return tokens;
92
+ }
93
+ const index = new Index({
94
+ charset: EnglishPreset,
95
+ // encoder: encoder,
96
+ encode: customEncoder,
97
+ tokenize: "strict",
98
+ });
99
+ dataToSearch.forEach((item, id) => {
100
+ index.add(id, item.title);
101
+ });
102
+ const searchTerms = filters.match_values;
103
+ const nots = [];
104
+ if (filters.skip_skus.length && filterOptions.skip_skus_enabled) {
105
+ const formatted = filters.skip_skus
106
+ .filter((sku) => sku.toLowerCase() !== sku.toLowerCase())
107
+ .map((sku) => ` ${sku} `);
108
+ nots.push(...formatted);
109
+ }
110
+ const lowerCaseTitle = title.toLowerCase();
111
+ const titleWithoutSku = lowerCaseTitle.replace(sku.toLowerCase(), "");
112
+ const wattageTerms = Array.from(titleWithoutSku.matchAll(/\b(\d{4,5})w\b/gi)).map((match) => match[1]);
113
+ const wattagesToExclude = wattages.filter((wattage) => !wattageTerms.includes(wattage));
114
+ if (filterOptions.wattage_exclusions_enabled) {
115
+ wattagesToExclude.forEach((wattage) => {
116
+ nots.push(wattage);
117
+ nots.push(` ${wattage}w `);
118
+ // Push wattage with comma formatting for numbers over 999
119
+ if (Number(wattage) > 999) {
120
+ nots.push(Number(wattage).toLocaleString());
121
+ }
122
+ });
123
+ }
124
+ if (filters.search_exclusions.length &&
125
+ filterOptions.search_exclusions_enabled) {
126
+ nots.push(...filters.search_exclusions);
127
+ }
128
+ if (filters.vendor_search_exclusions.length &&
129
+ filterOptions.skip_skus_enabled) {
130
+ nots.push(...filters.vendor_search_exclusions.filter((sku) => sku.toLowerCase() !== sku.toLowerCase()));
131
+ }
132
+ if (title.toLowerCase().includes("dual fuel")) {
133
+ // nots.push('tri fuel', 'trifuel', 'tri-fuel')
134
+ }
135
+ let final = null;
136
+ let urlFinal = null;
137
+ searchTerms.forEach((term) => {
138
+ if (final === null && urlFinal === null) {
139
+ final = index.search(term, {
140
+ resolve: false,
141
+ suggest: true,
142
+ });
143
+ }
144
+ else {
145
+ final = final.or({
146
+ index: index,
147
+ query: term,
148
+ resolve: false,
149
+ suggest: true,
150
+ });
151
+ }
152
+ });
153
+ // final = final.and({
154
+ // index: index,
155
+ // query: 'Chipper',
156
+ // resolve: false,
157
+ // // suggest: true,
158
+ // })
159
+ // MUST INCLUDE SKU???
160
+ final = final.and({
161
+ index: index,
162
+ query: sku,
163
+ resolve: false,
164
+ suggest: true,
165
+ });
166
+ nots.forEach((term) => {
167
+ final = final.not({
168
+ index: index,
169
+ query: term,
170
+ resolve: false,
171
+ });
172
+ });
173
+ const result = final.resolve({ limit: 1000 });
174
+ const resultsArray = [];
175
+ result.forEach((i) => {
176
+ resultsArray.push(dataToSearch[i]);
177
+ });
178
+ return resultsArray;
179
+ };
180
+ const filterLinkedElsewhereResults = (results, variantId) => {
181
+ const filtered = results.filter((result) => {
182
+ const productLinkedElsewhere = result.linked_variant_ids?.length > 0 &&
183
+ !result.linked_variant_ids.includes(variantId);
184
+ if (productLinkedElsewhere) {
185
+ return false;
186
+ }
187
+ return true;
188
+ });
189
+ return filtered;
190
+ };
191
+ const filterFoundProductIdExclusions = (results, found_id_exclusions) => {
192
+ const filtered = results.filter((item) => {
193
+ if (found_id_exclusions.length) {
194
+ if (found_id_exclusions.includes(item.found_product_id)) {
195
+ return false;
196
+ }
197
+ }
198
+ return true;
199
+ });
200
+ return filtered;
201
+ };
202
+ const filterPriceOutliers = (results, variantPrice, showHighPriceOutliers, showLowPriceOutliers) => {
203
+ const filtered = results.filter((item) => {
204
+ const showMoreExpensive = showHighPriceOutliers === true;
205
+ const isMoreExpensive = item.extracted_price > variantPrice * TOO_EXPENSIVE_MULTIPLIER;
206
+ const showTooCheap = showLowPriceOutliers === true;
207
+ const isTooCheap = item.extracted_price < variantPrice * TOO_CHEAP_MULTIPLIER;
208
+ if (isMoreExpensive && !showMoreExpensive) {
209
+ return false;
210
+ }
211
+ if (isTooCheap && !showTooCheap) {
212
+ return false;
213
+ }
214
+ return true;
215
+ });
216
+ return filtered;
217
+ };
218
+ const filterDateWindow = (results, dayWindow) => {
219
+ const filtered = results.filter((item) => {
220
+ if (!dayWindow)
221
+ return true;
222
+ const itemDate = new Date(item.created_at);
223
+ const variantDate = subDays(new Date(), Number(dayWindow) || 7);
224
+ return itemDate >= variantDate;
225
+ });
226
+ return filtered;
227
+ };
228
+ const filterCompetitors = (results, competitor_exclusions) => {
229
+ const filtered = results.filter((item) => {
230
+ const lowerSource = item.source.toLowerCase();
231
+ for (const exclusion of competitor_exclusions) {
232
+ if (exclusion && lowerSource === exclusion.toLowerCase()) {
233
+ return false;
234
+ }
235
+ }
236
+ return true;
237
+ });
238
+ return filtered;
239
+ };
240
+ const filterDuplicateResults = (results) => {
241
+ const filteredUniqueResultsMap = {};
242
+ results.forEach((item) => {
243
+ const key = `${item.source}-${item.title}-${item.extracted_price}`;
244
+ if (!filteredUniqueResultsMap[key]) {
245
+ filteredUniqueResultsMap[key] = item;
246
+ }
247
+ else {
248
+ const existingItem = filteredUniqueResultsMap[key];
249
+ if (new Date(item.created_at) > new Date(existingItem.created_at)) {
250
+ filteredUniqueResultsMap[key] = item;
251
+ }
252
+ }
253
+ });
254
+ const filtered = Object.values(filteredUniqueResultsMap);
255
+ return filtered;
256
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factorypure/client-helpers",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,6 +21,7 @@
21
21
  "dependencies": {
22
22
  "@types/date-fns": "^2.5.3",
23
23
  "date-fns": "^4.1.0",
24
- "flexsearch": "^0.8.212"
24
+ "flexsearch": "^0.8.212",
25
+ "zod": "^4.3.5"
25
26
  }
26
27
  }