@factorypure/client-helpers 1.1.8 → 1.1.9

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/index.d.ts CHANGED
@@ -1,4 +1,171 @@
1
1
  import { z } from 'zod';
2
+ /**
3
+ * @packageDocumentation
4
+ * @module @factorypure/client-helpers
5
+ *
6
+ * # Filter System v2
7
+ *
8
+ * This package provides an extensible, rule-based filtering system for scrape results.
9
+ *
10
+ * ## Key Concepts
11
+ *
12
+ * ### Filter Severity
13
+ * - **BLOCK**: Always hides the result, cannot be overridden
14
+ * - **WARNING**: Shows result with warning indicator, can be overridden by override rules
15
+ * - **INFO**: Metadata only, doesn't affect visibility
16
+ *
17
+ * ### Visibility States
18
+ * - **HIDDEN**: Result is filtered out
19
+ * - **VISIBLE_WITH_WARNINGS**: Result is shown but has warning indicators
20
+ * - **VISIBLE**: Result is shown without issues
21
+ *
22
+ * ### Filter Rules
23
+ * Rules implement the FilterRule interface and are evaluated against results.
24
+ * Each rule can:
25
+ * - Have a severity level (BLOCK, WARNING, INFO)
26
+ * - Have a priority (higher priority rules evaluated first)
27
+ * - Be enabled/disabled
28
+ * - Specify which override rules can override it
29
+ *
30
+ * ### Override Rules
31
+ * Special INFO-level rules that can override WARNING-level rules.
32
+ * Examples: SKU match, calculated SKU match, alternate SKU match
33
+ *
34
+ * ## Quick Start
35
+ *
36
+ * ### Using the Default Engine
37
+ * ```typescript
38
+ * import { filterScrapeResultsV2, createDefaultFilterEngine } from '@factorypure/client-helpers'
39
+ *
40
+ * const filteredResults = filterScrapeResultsV2({
41
+ * scrapeResults,
42
+ * variant,
43
+ * variantScrapeOptions,
44
+ * vendorScrapeOptions,
45
+ * globalScrapeOptions,
46
+ * })
47
+ * ```
48
+ *
49
+ * ### Custom Configuration
50
+ * ```typescript
51
+ * const filteredResults = filterScrapeResultsV2({
52
+ * scrapeResults,
53
+ * variant,
54
+ * variantScrapeOptions,
55
+ * vendorScrapeOptions,
56
+ * globalScrapeOptions,
57
+ * filterConfig: {
58
+ * rules: [
59
+ * { id: 'high_price_outlier', enabled: false },
60
+ * { id: 'competitor_exclusion', severity: FilterSeverity.BLOCK },
61
+ * { id: 'refurbished_used', priority: 100 },
62
+ * ]
63
+ * }
64
+ * })
65
+ * ```
66
+ *
67
+ * ### Adding Custom Rules
68
+ * ```typescript
69
+ * import { createCustomFilterRule, FilterSeverity } from '@factorypure/client-helpers'
70
+ *
71
+ * const myCustomRule = createCustomFilterRule({
72
+ * id: 'my_custom_rule',
73
+ * name: 'My Custom Rule',
74
+ * description: 'Filters results based on custom logic',
75
+ * severity: FilterSeverity.WARNING,
76
+ * priority: 50,
77
+ * canBeOverridden: true,
78
+ * overridableBy: ['sku_match'],
79
+ * evaluate: (context) => {
80
+ * if (context.result.title.includes('bad-keyword')) {
81
+ * return {
82
+ * ruleId: 'my_custom_rule',
83
+ * severity: FilterSeverity.WARNING,
84
+ * message: 'Contains bad keyword',
85
+ * metadata: { keyword: 'bad-keyword' },
86
+ * timestamp: new Date().toISOString(),
87
+ * }
88
+ * }
89
+ * return null
90
+ * }
91
+ * })
92
+ *
93
+ * const filteredResults = filterScrapeResultsV2({
94
+ * scrapeResults,
95
+ * variant,
96
+ * variantScrapeOptions,
97
+ * vendorScrapeOptions,
98
+ * globalScrapeOptions,
99
+ * customRules: [myCustomRule],
100
+ * })
101
+ * ```
102
+ *
103
+ * ### Using the Builder Pattern
104
+ * ```typescript
105
+ * import { FilterRuleBuilder, FilterSeverity } from '@factorypure/client-helpers'
106
+ *
107
+ * const myRule = new FilterRuleBuilder()
108
+ * .id('price_above_threshold')
109
+ * .name('Price Above Threshold')
110
+ * .description('Filters results above a price threshold')
111
+ * .severity(FilterSeverity.WARNING)
112
+ * .priority(80)
113
+ * .canBeOverridden(true)
114
+ * .overridableBy(['sku_match'])
115
+ * .evaluate((context) => {
116
+ * if (context.result.extracted_price > 10000) {
117
+ * return {
118
+ * ruleId: 'price_above_threshold',
119
+ * severity: FilterSeverity.WARNING,
120
+ * message: 'Price exceeds $10,000',
121
+ * metadata: { price: context.result.extracted_price },
122
+ * timestamp: new Date().toISOString(),
123
+ * }
124
+ * }
125
+ * return null
126
+ * })
127
+ * .build()
128
+ * ```
129
+ *
130
+ * ## Built-in Rules
131
+ *
132
+ * ### Block Rules (Cannot be overridden)
133
+ * - `high_price_outlier`: Filters results >15% more expensive than variant
134
+ * - `low_price_outlier`: Filters results significantly cheaper than variant
135
+ * - `date_outlier`: Filters results outside date window
136
+ * - `duplicate`: Filters duplicate results
137
+ * - `scam_source_exclusion`: Filters known scam sources
138
+ * - `manually_ignored`: Filters manually ignored/excluded results
139
+ * - `out_of_stock`: Filters out of stock items
140
+ *
141
+ * ### Warning Rules (Can be overridden)
142
+ * - `competitor_exclusion`: Filters excluded competitors
143
+ * - `search_exclusion`: Filters results not matching search criteria
144
+ * - `skip_sku`: Filters results with SKUs from skip list
145
+ * - `vendor_exclusion`: Filters results with excluded vendor names
146
+ * - `brand_mismatch`: Filters results with mismatched brands
147
+ * - `calculated_sku_mismatch`: Filters results with mismatched calculated SKUs
148
+ * - `critical_spec_mismatch`: Filters results with mismatched critical specs
149
+ * - `refurbished_used`: Filters refurbished/used items
150
+ *
151
+ * ### Override Rules
152
+ * - `sku_match`: SKU found in title
153
+ * - `calculated_sku_match`: Calculated SKU matches variant SKU
154
+ * - `alt_sku_match`: Alternate SKU matches
155
+ * - `product_id_match`: Product ID is linked
156
+ *
157
+ * ## Migration from Legacy System
158
+ *
159
+ * The legacy `filterScrapeResults` function is still available for backward compatibility.
160
+ * Results include both old fields (`hide_reasons`, `hide_override_reasons`) and new fields
161
+ * (`filter_results`, `visibility_state`) during the transition period.
162
+ *
163
+ * To migrate:
164
+ * 1. Replace `filterScrapeResults` with `filterScrapeResultsV2`
165
+ * 2. Update code to use `visibility_state` instead of `ignore_result`
166
+ * 3. Use `filter_results` for detailed filter information instead of string arrays
167
+ * 4. Configure rules as needed using `filterConfig` parameter
168
+ */
2
169
  export declare const regexUnitResultSchema: z.ZodObject<{
3
170
  value: z.ZodString;
4
171
  source: z.ZodString;
@@ -70,6 +237,36 @@ export type RegexUnitResultType = z.infer<typeof regexUnitResultSchema>;
70
237
  export type RegexUnitResultsType = z.infer<typeof regexUnitResultsSchema> & {
71
238
  [key: string]: RegexUnitResultType[];
72
239
  };
240
+ /**
241
+ * Severity levels for filter rules
242
+ * - BLOCK: Always hides the result, cannot be overridden
243
+ * - WARNING: Shows result with warning indicator, can be overridden
244
+ * - INFO: Metadata only, doesn't affect visibility
245
+ */
246
+ export declare enum FilterSeverity {
247
+ BLOCK = "BLOCK",
248
+ WARNING = "WARNING",
249
+ INFO = "INFO"
250
+ }
251
+ /**
252
+ * Visibility states for results after filter evaluation
253
+ */
254
+ export declare enum VisibilityState {
255
+ HIDDEN = "HIDDEN",
256
+ VISIBLE_WITH_WARNINGS = "VISIBLE_WITH_WARNINGS",
257
+ VISIBLE = "VISIBLE"
258
+ }
259
+ /**
260
+ * Structured result from a filter rule evaluation
261
+ */
262
+ export declare const filterResultSchema: z.ZodObject<{
263
+ ruleId: z.ZodString;
264
+ severity: z.ZodEnum<typeof FilterSeverity>;
265
+ message: z.ZodString;
266
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
267
+ timestamp: z.ZodString;
268
+ }, z.core.$strip>;
269
+ export type FilterResult = z.infer<typeof filterResultSchema>;
73
270
  export declare const scrapeResultsSchema: z.ZodObject<{
74
271
  id: z.ZodNumber;
75
272
  scrape_id: z.ZodNumber;
@@ -105,6 +302,14 @@ export declare const scrapeResultsSchema: z.ZodObject<{
105
302
  ignore_reasons: z.ZodArray<z.ZodString>;
106
303
  hide_reasons: z.ZodArray<z.ZodString>;
107
304
  hide_override_reasons: z.ZodArray<z.ZodString>;
305
+ filter_results: z.ZodOptional<z.ZodArray<z.ZodObject<{
306
+ ruleId: z.ZodString;
307
+ severity: z.ZodEnum<typeof FilterSeverity>;
308
+ message: z.ZodString;
309
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
310
+ timestamp: z.ZodString;
311
+ }, z.core.$strip>>>;
312
+ visibility_state: z.ZodOptional<z.ZodEnum<typeof VisibilityState>>;
108
313
  regexUnitResults: z.ZodNullable<z.ZodObject<{
109
314
  inch: z.ZodArray<z.ZodObject<{
110
315
  value: z.ZodString;
@@ -229,6 +434,14 @@ export declare const immersiveScrapeResultsSchema: z.ZodObject<{
229
434
  ignore_reasons: z.ZodArray<z.ZodString>;
230
435
  hide_reasons: z.ZodArray<z.ZodString>;
231
436
  hide_override_reasons: z.ZodArray<z.ZodString>;
437
+ filter_results: z.ZodOptional<z.ZodArray<z.ZodObject<{
438
+ ruleId: z.ZodString;
439
+ severity: z.ZodEnum<typeof FilterSeverity>;
440
+ message: z.ZodString;
441
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
442
+ timestamp: z.ZodString;
443
+ }, z.core.$strip>>>;
444
+ visibility_state: z.ZodOptional<z.ZodEnum<typeof VisibilityState>>;
232
445
  brand: z.ZodNullable<z.ZodString>;
233
446
  company_id: z.ZodNullable<z.ZodNumber>;
234
447
  regexUnitResults: z.ZodNullable<z.ZodObject<{
@@ -363,6 +576,66 @@ export declare const globalScrapeOptionsSchema: z.ZodObject<{
363
576
  scam_sources: z.ZodArray<z.ZodString>;
364
577
  }, z.core.$strip>;
365
578
  export type GlobalScrapeOptionsType = z.infer<typeof globalScrapeOptionsSchema>;
579
+ /**
580
+ * Context provided to filter rules during evaluation
581
+ */
582
+ export type FilterContext<T = ScrapeResultsType | ImmersiveScrapeResultsType> = {
583
+ result: T;
584
+ variant: {
585
+ id: number;
586
+ title: string;
587
+ sku: string;
588
+ price: number;
589
+ vendor: string;
590
+ regexUnitResults?: RegexUnitResultsType;
591
+ found_product_ids?: string[];
592
+ };
593
+ variantScrapeOptions: VariantScrapeOptionsType;
594
+ vendorScrapeOptions: VendorScrapeOptionsType;
595
+ globalScrapeOptions: GlobalScrapeOptionsType;
596
+ };
597
+ /**
598
+ * Base interface for filter rules
599
+ */
600
+ export interface FilterRule<T = ScrapeResultsType | ImmersiveScrapeResultsType> {
601
+ id: string;
602
+ name: string;
603
+ description: string;
604
+ severity: FilterSeverity;
605
+ priority: number;
606
+ enabled: boolean;
607
+ canBeOverridden: boolean;
608
+ overridableBy: string[];
609
+ /**
610
+ * Evaluate the rule against a result
611
+ * @returns FilterResult if rule triggers, null if rule doesn't apply
612
+ */
613
+ evaluate(context: FilterContext<T>): FilterResult | null;
614
+ }
615
+ /**
616
+ * Configuration for a filter rule (serializable)
617
+ */
618
+ export type FilterRuleConfig = {
619
+ id: string;
620
+ enabled?: boolean;
621
+ severity?: FilterSeverity;
622
+ priority?: number;
623
+ parameters?: Record<string, any>;
624
+ };
625
+ /**
626
+ * Overall filter configuration
627
+ */
628
+ export declare const filterConfigurationSchema: z.ZodObject<{
629
+ rules: z.ZodOptional<z.ZodArray<z.ZodObject<{
630
+ id: z.ZodString;
631
+ enabled: z.ZodOptional<z.ZodBoolean>;
632
+ severity: z.ZodOptional<z.ZodEnum<typeof FilterSeverity>>;
633
+ priority: z.ZodOptional<z.ZodNumber>;
634
+ parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
635
+ }, z.core.$strip>>>;
636
+ globalParameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
637
+ }, z.core.$strip>;
638
+ export type FilterConfiguration = z.infer<typeof filterConfigurationSchema>;
366
639
  export declare const HIDE_REASONS: {
367
640
  IGNORED: string;
368
641
  HIGH_PRICE_OUTLIER: string;
@@ -391,6 +664,114 @@ export declare const HIDE_OVERRIDE_REASONS: {
391
664
  };
392
665
  export declare const TOO_CHEAP_MULTIPLIER = 0.75;
393
666
  export declare const TOO_EXPENSIVE_MULTIPLIER = 1.15;
667
+ /**
668
+ * Registry for filter rules with configuration management
669
+ */
670
+ export declare class FilterRuleRegistry {
671
+ private rules;
672
+ private configurations;
673
+ /**
674
+ * Register a new filter rule
675
+ */
676
+ registerRule(rule: FilterRule): void;
677
+ /**
678
+ * Get a rule by ID
679
+ */
680
+ getRule(id: string): FilterRule | undefined;
681
+ /**
682
+ * Get all registered rules
683
+ */
684
+ getAllRules(): FilterRule[];
685
+ /**
686
+ * Get all enabled rules sorted by priority
687
+ */
688
+ getEnabledRules(): FilterRule[];
689
+ /**
690
+ * Apply configuration to rules
691
+ */
692
+ applyConfiguration(config: FilterConfiguration): void;
693
+ /**
694
+ * Get effective configuration for a rule
695
+ */
696
+ getRuleConfig(ruleId: string): FilterRuleConfig | undefined;
697
+ }
698
+ /**
699
+ * Engine for evaluating filter rules and determining visibility
700
+ */
701
+ export declare class FilterEngine {
702
+ private registry;
703
+ constructor(registry: FilterRuleRegistry);
704
+ /**
705
+ * Evaluate all rules for a single result
706
+ */
707
+ evaluateResult<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(context: FilterContext<T>): FilterResult[];
708
+ /**
709
+ * Evaluate rules for a batch of results
710
+ */
711
+ evaluateBatch<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(results: T[], variant: FilterContext<T>['variant'], variantScrapeOptions: VariantScrapeOptionsType, vendorScrapeOptions: VendorScrapeOptionsType, globalScrapeOptions: GlobalScrapeOptionsType): T[];
712
+ /**
713
+ * Calculate visibility state based on filter results
714
+ */
715
+ calculateVisibility(filterResults: FilterResult[]): VisibilityState;
716
+ }
717
+ /**
718
+ * Create and populate a registry with all built-in rules
719
+ */
720
+ export declare function createDefaultFilterRegistry(): FilterRuleRegistry;
721
+ /**
722
+ * Create a filter engine with default configuration
723
+ */
724
+ export declare function createDefaultFilterEngine(config?: FilterConfiguration): FilterEngine;
725
+ /**
726
+ * Builder for creating custom filter rules
727
+ */
728
+ export declare class FilterRuleBuilder {
729
+ private rule;
730
+ id(id: string): this;
731
+ name(name: string): this;
732
+ description(description: string): this;
733
+ severity(severity: FilterSeverity): this;
734
+ priority(priority: number): this;
735
+ enabled(enabled: boolean): this;
736
+ canBeOverridden(canBeOverridden: boolean): this;
737
+ overridableBy(ruleIds: string[]): this;
738
+ evaluate(evaluateFn: (context: FilterContext) => FilterResult | null): this;
739
+ build(): FilterRule;
740
+ }
741
+ /**
742
+ * Simple factory for creating custom rules without implementing the full interface
743
+ */
744
+ export declare function createCustomFilterRule(config: {
745
+ id: string;
746
+ name: string;
747
+ description: string;
748
+ severity: FilterSeverity;
749
+ priority?: number;
750
+ canBeOverridden?: boolean;
751
+ overridableBy?: string[];
752
+ evaluate: (context: FilterContext) => FilterResult | null;
753
+ }): FilterRule;
754
+ /**
755
+ * Filter scrape results using the new rule-based engine
756
+ * This provides more flexibility and better extensibility than the legacy filterScrapeResults
757
+ */
758
+ export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, }: {
759
+ scrapeResults: T[];
760
+ variant: {
761
+ id: number;
762
+ title: string;
763
+ sku: string;
764
+ price: number;
765
+ vendor: string;
766
+ regexUnitResults?: RegexUnitResultsType;
767
+ found_product_ids?: string[];
768
+ };
769
+ variantScrapeOptions: VariantScrapeOptionsType;
770
+ vendorScrapeOptions: VendorScrapeOptionsType;
771
+ globalScrapeOptions: GlobalScrapeOptionsType;
772
+ filterConfig?: FilterConfiguration;
773
+ customRules?: FilterRule[];
774
+ }) => T[];
394
775
  export declare const filterScrapeResults: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, }: {
395
776
  scrapeResults: T[];
396
777
  variant: {