@factorypure/client-helpers 1.1.14 → 1.1.16

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
@@ -506,6 +506,30 @@ export declare const immersiveScrapeResultsSchema: z.ZodObject<{
506
506
  normalized: z.ZodString;
507
507
  }, z.core.$strip>>;
508
508
  }, z.core.$strip>>;
509
+ ai_extracted_sku: z.ZodOptional<z.ZodNullable<z.ZodString>>;
510
+ ai_extracted_brand: z.ZodOptional<z.ZodNullable<z.ZodString>>;
511
+ ai_best_match_sku: z.ZodOptional<z.ZodNullable<z.ZodString>>;
512
+ ai_alternate_match_skus: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
513
+ ai_extraction_confidence: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
514
+ ai_extraction_reasoning: z.ZodOptional<z.ZodNullable<z.ZodString>>;
515
+ ai_extraction_quality: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
516
+ high: "high";
517
+ medium: "medium";
518
+ low: "low";
519
+ }>>>;
520
+ ai_extraction_method: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
521
+ openai: "openai";
522
+ regex_fallback: "regex_fallback";
523
+ cached: "cached";
524
+ none: "none";
525
+ }>>>;
526
+ ai_processed_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
527
+ listing_hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
528
+ ai_unique_skus_found: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodNumber>>>;
529
+ ai_most_common_sku: z.ZodOptional<z.ZodNullable<z.ZodString>>;
530
+ ai_sku_confidence_avg: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
531
+ ai_high_quality_listing_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
532
+ ai_total_listing_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
509
533
  inline_link: z.ZodOptional<z.ZodNull>;
510
534
  delivery: z.ZodOptional<z.ZodNull>;
511
535
  }, z.core.$strip>;
@@ -576,6 +600,15 @@ export declare const globalScrapeOptionsSchema: z.ZodObject<{
576
600
  scam_sources: z.ZodArray<z.ZodString>;
577
601
  }, z.core.$strip>;
578
602
  export type GlobalScrapeOptionsType = z.infer<typeof globalScrapeOptionsSchema>;
603
+ /**
604
+ * AI Comparison result data structure
605
+ */
606
+ export type ComparisonMapType = Record<string, Record<string, {
607
+ is_match: number;
608
+ sufficient_info: number;
609
+ result_description: string;
610
+ compared_at: string;
611
+ }>>;
579
612
  /**
580
613
  * Context provided to filter rules during evaluation
581
614
  */
@@ -593,6 +626,7 @@ export type FilterContext<T = ScrapeResultsType | ImmersiveScrapeResultsType> =
593
626
  variantScrapeOptions: VariantScrapeOptionsType;
594
627
  vendorScrapeOptions: VendorScrapeOptionsType;
595
628
  globalScrapeOptions: GlobalScrapeOptionsType;
629
+ comparisonMap?: ComparisonMapType;
596
630
  };
597
631
  /**
598
632
  * Base interface for filter rules
@@ -645,6 +679,7 @@ export declare const HIDE_REASONS: {
645
679
  DUPLICATE: string;
646
680
  SEARCH_EXCLUSION: string;
647
681
  CALCULATED_SKU_MISMATCH: string;
682
+ AI_SKU_MISMATCH: string;
648
683
  CRITICAL_SPEC_MISMATCH: string;
649
684
  MANUALLY_IGNORED: string;
650
685
  MANUALLY_EXCLUDED: string;
@@ -654,6 +689,7 @@ export declare const HIDE_REASONS: {
654
689
  VENDOR_EXCLUSION: string;
655
690
  CALCULATED_BRAND_MISMATCH: string;
656
691
  SCAM_SOURCE_EXCLUSION: string;
692
+ AI_COMPARISON_MISMATCH: string;
657
693
  };
658
694
  export declare const HIDE_OVERRIDE_REASONS: {
659
695
  SKU_MATCH: string;
@@ -708,7 +744,7 @@ export declare class FilterEngine {
708
744
  /**
709
745
  * Evaluate rules for a batch of results
710
746
  */
711
- evaluateBatch<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(results: T[], variant: FilterContext<T>['variant'], variantScrapeOptions: VariantScrapeOptionsType, vendorScrapeOptions: VendorScrapeOptionsType, globalScrapeOptions: GlobalScrapeOptionsType): T[];
747
+ evaluateBatch<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(results: T[], variant: FilterContext<T>['variant'], variantScrapeOptions: VariantScrapeOptionsType, vendorScrapeOptions: VendorScrapeOptionsType, globalScrapeOptions: GlobalScrapeOptionsType, comparisonMap?: ComparisonMapType): T[];
712
748
  /**
713
749
  * Calculate visibility state based on filter results
714
750
  */
@@ -755,7 +791,7 @@ export declare function createCustomFilterRule(config: {
755
791
  * Filter scrape results using the new rule-based engine
756
792
  * This provides more flexibility and better extensibility than the legacy filterScrapeResults
757
793
  */
758
- export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, }: {
794
+ export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, }: {
759
795
  scrapeResults: T[];
760
796
  variant: {
761
797
  id: number;
@@ -771,6 +807,7 @@ export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | Immer
771
807
  globalScrapeOptions: GlobalScrapeOptionsType;
772
808
  filterConfig?: FilterConfiguration;
773
809
  customRules?: FilterRule[];
810
+ comparisonMap?: ComparisonMapType;
774
811
  }) => T[];
775
812
  export declare const filterScrapeResults: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, }: {
776
813
  scrapeResults: T[];
package/dist/index.js CHANGED
@@ -331,6 +331,23 @@ export const immersiveScrapeResultsSchema = z.object({
331
331
  brand: z.string().nullable(),
332
332
  company_id: z.number().nullable(),
333
333
  regexUnitResults: z.nullable(regexUnitResultsSchema),
334
+ // AI extraction fields (per store listing)
335
+ ai_extracted_sku: z.string().nullable().optional(),
336
+ ai_extracted_brand: z.string().nullable().optional(),
337
+ ai_best_match_sku: z.string().nullable().optional(),
338
+ ai_alternate_match_skus: z.array(z.string()).nullable().optional(),
339
+ ai_extraction_confidence: z.number().nullable().optional(),
340
+ ai_extraction_reasoning: z.string().nullable().optional(),
341
+ ai_extraction_quality: z.enum(['high', 'medium', 'low']).nullable().optional(),
342
+ ai_extraction_method: z.enum(['openai', 'regex_fallback', 'cached', 'none']).nullable().optional(),
343
+ ai_processed_at: z.string().nullable().optional(),
344
+ listing_hash: z.string().nullable().optional(),
345
+ // AI aggregate fields (from variant_scrape_found_product_ids)
346
+ ai_unique_skus_found: z.record(z.string(), z.number()).nullable().optional(),
347
+ ai_most_common_sku: z.string().nullable().optional(),
348
+ ai_sku_confidence_avg: z.number().nullable().optional(),
349
+ ai_high_quality_listing_count: z.number().nullable().optional(),
350
+ ai_total_listing_count: z.number().nullable().optional(),
334
351
  inline_link: z.null().optional(),
335
352
  delivery: z.null().optional(),
336
353
  });
@@ -394,6 +411,7 @@ export const HIDE_REASONS = {
394
411
  DUPLICATE: 'Duplicate',
395
412
  SEARCH_EXCLUSION: 'Search Exclusion',
396
413
  CALCULATED_SKU_MISMATCH: 'Calculated SKU Mismatch',
414
+ AI_SKU_MISMATCH: 'AI SKU Mismatch',
397
415
  CRITICAL_SPEC_MISMATCH: 'Critical Spec Mismatch',
398
416
  MANUALLY_IGNORED: 'Manually ignored',
399
417
  MANUALLY_EXCLUDED: 'Manually excluded',
@@ -403,6 +421,7 @@ export const HIDE_REASONS = {
403
421
  VENDOR_EXCLUSION: 'Vendor Exclusion',
404
422
  CALCULATED_BRAND_MISMATCH: 'Calculated Brand Mismatch',
405
423
  SCAM_SOURCE_EXCLUSION: 'Scam Source Exclusion',
424
+ AI_COMPARISON_MISMATCH: 'AI Comparison Mismatch',
406
425
  };
407
426
  export const HIDE_OVERRIDE_REASONS = {
408
427
  SKU_MATCH: 'SKU Match',
@@ -419,6 +438,7 @@ const HIDE_ALWAYS_MAP = {
419
438
  [HIDE_REASONS.DUPLICATE]: true,
420
439
  [HIDE_REASONS.SEARCH_EXCLUSION]: false,
421
440
  [HIDE_REASONS.CALCULATED_SKU_MISMATCH]: false,
441
+ [HIDE_REASONS.AI_SKU_MISMATCH]: false,
422
442
  [HIDE_REASONS.CRITICAL_SPEC_MISMATCH]: false,
423
443
  [HIDE_REASONS.MANUALLY_IGNORED]: true,
424
444
  [HIDE_REASONS.MANUALLY_EXCLUDED]: true,
@@ -427,6 +447,7 @@ const HIDE_ALWAYS_MAP = {
427
447
  [HIDE_REASONS.SKIP_SKU]: false,
428
448
  [HIDE_REASONS.VENDOR_EXCLUSION]: false,
429
449
  [HIDE_REASONS.SCAM_SOURCE_EXCLUSION]: true,
450
+ [HIDE_REASONS.AI_COMPARISON_MISMATCH]: false,
430
451
  };
431
452
  export const TOO_CHEAP_MULTIPLIER = 0.75;
432
453
  export const TOO_EXPENSIVE_MULTIPLIER = 1.15;
@@ -517,7 +538,7 @@ export class FilterEngine {
517
538
  /**
518
539
  * Evaluate rules for a batch of results
519
540
  */
520
- evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions) {
541
+ evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap) {
521
542
  return results.map((result) => {
522
543
  const context = {
523
544
  result,
@@ -525,6 +546,7 @@ export class FilterEngine {
525
546
  variantScrapeOptions,
526
547
  vendorScrapeOptions,
527
548
  globalScrapeOptions,
549
+ comparisonMap,
528
550
  };
529
551
  const filterResults = this.evaluateResult(context);
530
552
  // Merge with existing filter_results from batch processing (duplicates, search exclusions, etc.)
@@ -728,6 +750,55 @@ class CompetitorExclusionRule {
728
750
  return null;
729
751
  }
730
752
  }
753
+ /**
754
+ * Rule for filtering AI comparison mismatches
755
+ */
756
+ class AIComparisonMismatchRule {
757
+ id = 'ai_comparison_mismatch';
758
+ name = 'AI Comparison Mismatch';
759
+ description = 'Filters results where AI comparison determined the listing does not match the variant';
760
+ severity = FilterSeverity.BLOCK;
761
+ priority = 85;
762
+ enabled = true;
763
+ canBeOverridden = true;
764
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
765
+ evaluate(context) {
766
+ // Only applies to results with listing_hash (primarily immersive results)
767
+ const result = context.result;
768
+ if (!result.listing_hash || !context.comparisonMap) {
769
+ return null;
770
+ }
771
+ const variantId = String(context.variant.id);
772
+ const listingHash = result.listing_hash;
773
+ // Check if we have comparison data for this variant and listing
774
+ const variantComparisons = context.comparisonMap[variantId];
775
+ if (!variantComparisons) {
776
+ return null;
777
+ }
778
+ const comparison = variantComparisons[listingHash];
779
+ if (!comparison) {
780
+ return null;
781
+ }
782
+ // Only filter confirmed mismatches (is_match = 0 AND sufficient_info = 1)
783
+ // Allow results where we don't have sufficient info to compare
784
+ if (comparison.is_match === 0 && comparison.sufficient_info === 1) {
785
+ return {
786
+ ruleId: this.id,
787
+ severity: this.severity,
788
+ message: HIDE_REASONS.AI_COMPARISON_MISMATCH,
789
+ metadata: {
790
+ listingHash,
791
+ isMatch: comparison.is_match,
792
+ sufficientInfo: comparison.sufficient_info,
793
+ resultDescription: comparison.result_description,
794
+ comparedAt: comparison.compared_at,
795
+ },
796
+ timestamp: new Date().toISOString(),
797
+ };
798
+ }
799
+ return null;
800
+ }
801
+ }
731
802
  /**
732
803
  * Rule for filtering duplicates
733
804
  */
@@ -1023,6 +1094,49 @@ class CalculatedSkuMismatchRule {
1023
1094
  };
1024
1095
  }
1025
1096
  }
1097
+ /**
1098
+ * Rule for AI SKU mismatches
1099
+ */
1100
+ class AISkuMismatchRule {
1101
+ id = 'ai_sku_mismatch';
1102
+ name = 'AI SKU Mismatch';
1103
+ description = 'Filters results with mismatched AI extracted SKUs';
1104
+ severity = FilterSeverity.WARNING;
1105
+ priority = 30;
1106
+ enabled = true;
1107
+ canBeOverridden = true;
1108
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
1109
+ evaluate(context) {
1110
+ const result = context.result;
1111
+ if (!result.ai_extracted_sku) {
1112
+ return null;
1113
+ }
1114
+ const aiSkuLower = result.ai_extracted_sku.toLowerCase();
1115
+ const variantSkuLower = context.variant.sku.toLowerCase();
1116
+ // Check if AI extracted SKU matches variant SKU
1117
+ if (aiSkuLower === variantSkuLower) {
1118
+ return null;
1119
+ }
1120
+ // Check if AI extracted SKU matches any SKU alternates
1121
+ const skuAlternates = context.variantScrapeOptions.sku_alternates || [];
1122
+ const matchesAlternate = skuAlternates.some((alt) => alt.toLowerCase() === aiSkuLower);
1123
+ if (matchesAlternate) {
1124
+ return null;
1125
+ }
1126
+ // If no match found, trigger the rule
1127
+ return {
1128
+ ruleId: this.id,
1129
+ severity: this.severity,
1130
+ message: HIDE_REASONS.AI_SKU_MISMATCH,
1131
+ metadata: {
1132
+ aiSku: result.ai_extracted_sku,
1133
+ variantSku: context.variant.sku,
1134
+ skuAlternates: skuAlternates,
1135
+ },
1136
+ timestamp: new Date().toISOString(),
1137
+ };
1138
+ }
1139
+ }
1026
1140
  /**
1027
1141
  * Rule for critical spec mismatches
1028
1142
  */
@@ -1232,6 +1346,7 @@ export function createDefaultFilterRegistry() {
1232
1346
  registry.registerRule(new LowPriceOutlierRule());
1233
1347
  registry.registerRule(new DateOutlierRule());
1234
1348
  registry.registerRule(new CompetitorExclusionRule());
1349
+ registry.registerRule(new AIComparisonMismatchRule());
1235
1350
  registry.registerRule(new DuplicateRule());
1236
1351
  registry.registerRule(new SearchExclusionRule());
1237
1352
  registry.registerRule(new SkipSkuRule());
@@ -1241,6 +1356,7 @@ export function createDefaultFilterRegistry() {
1241
1356
  registry.registerRule(new BrandMismatchRule());
1242
1357
  registry.registerRule(new OutOfStockRule());
1243
1358
  registry.registerRule(new CalculatedSkuMismatchRule());
1359
+ registry.registerRule(new AISkuMismatchRule());
1244
1360
  registry.registerRule(new CriticalSpecMismatchRule());
1245
1361
  registry.registerRule(new RefurbishedUsedRule());
1246
1362
  // Register override rules
@@ -1339,7 +1455,7 @@ export function createCustomFilterRule(config) {
1339
1455
  * Filter scrape results using the new rule-based engine
1340
1456
  * This provides more flexibility and better extensibility than the legacy filterScrapeResults
1341
1457
  */
1342
- export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, }) => {
1458
+ export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, }) => {
1343
1459
  const registry = createDefaultFilterRegistry();
1344
1460
  // Register custom rules if provided
1345
1461
  if (customRules) {
@@ -1354,7 +1470,7 @@ export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOpt
1354
1470
  let results = handleDuplicatesV2(scrapeResults);
1355
1471
  results = handleSearchExclusionsV2(results, variantScrapeOptions, variant, registry);
1356
1472
  // Evaluate all other rules
1357
- results = engine.evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions);
1473
+ results = engine.evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap);
1358
1474
  return results;
1359
1475
  };
1360
1476
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factorypure/client-helpers",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",