@factorypure/client-helpers 1.1.14 → 1.1.15

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
@@ -654,6 +688,7 @@ export declare const HIDE_REASONS: {
654
688
  VENDOR_EXCLUSION: string;
655
689
  CALCULATED_BRAND_MISMATCH: string;
656
690
  SCAM_SOURCE_EXCLUSION: string;
691
+ AI_COMPARISON_MISMATCH: string;
657
692
  };
658
693
  export declare const HIDE_OVERRIDE_REASONS: {
659
694
  SKU_MATCH: string;
@@ -708,7 +743,7 @@ export declare class FilterEngine {
708
743
  /**
709
744
  * Evaluate rules for a batch of results
710
745
  */
711
- evaluateBatch<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(results: T[], variant: FilterContext<T>['variant'], variantScrapeOptions: VariantScrapeOptionsType, vendorScrapeOptions: VendorScrapeOptionsType, globalScrapeOptions: GlobalScrapeOptionsType): T[];
746
+ evaluateBatch<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(results: T[], variant: FilterContext<T>['variant'], variantScrapeOptions: VariantScrapeOptionsType, vendorScrapeOptions: VendorScrapeOptionsType, globalScrapeOptions: GlobalScrapeOptionsType, comparisonMap?: ComparisonMapType): T[];
712
747
  /**
713
748
  * Calculate visibility state based on filter results
714
749
  */
@@ -755,7 +790,7 @@ export declare function createCustomFilterRule(config: {
755
790
  * Filter scrape results using the new rule-based engine
756
791
  * This provides more flexibility and better extensibility than the legacy filterScrapeResults
757
792
  */
758
- export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, }: {
793
+ export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, }: {
759
794
  scrapeResults: T[];
760
795
  variant: {
761
796
  id: number;
@@ -771,6 +806,7 @@ export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | Immer
771
806
  globalScrapeOptions: GlobalScrapeOptionsType;
772
807
  filterConfig?: FilterConfiguration;
773
808
  customRules?: FilterRule[];
809
+ comparisonMap?: ComparisonMapType;
774
810
  }) => T[];
775
811
  export declare const filterScrapeResults: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, }: {
776
812
  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
  });
@@ -403,6 +420,7 @@ export const HIDE_REASONS = {
403
420
  VENDOR_EXCLUSION: 'Vendor Exclusion',
404
421
  CALCULATED_BRAND_MISMATCH: 'Calculated Brand Mismatch',
405
422
  SCAM_SOURCE_EXCLUSION: 'Scam Source Exclusion',
423
+ AI_COMPARISON_MISMATCH: 'AI Comparison Mismatch',
406
424
  };
407
425
  export const HIDE_OVERRIDE_REASONS = {
408
426
  SKU_MATCH: 'SKU Match',
@@ -427,6 +445,7 @@ const HIDE_ALWAYS_MAP = {
427
445
  [HIDE_REASONS.SKIP_SKU]: false,
428
446
  [HIDE_REASONS.VENDOR_EXCLUSION]: false,
429
447
  [HIDE_REASONS.SCAM_SOURCE_EXCLUSION]: true,
448
+ [HIDE_REASONS.AI_COMPARISON_MISMATCH]: false,
430
449
  };
431
450
  export const TOO_CHEAP_MULTIPLIER = 0.75;
432
451
  export const TOO_EXPENSIVE_MULTIPLIER = 1.15;
@@ -517,7 +536,7 @@ export class FilterEngine {
517
536
  /**
518
537
  * Evaluate rules for a batch of results
519
538
  */
520
- evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions) {
539
+ evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap) {
521
540
  return results.map((result) => {
522
541
  const context = {
523
542
  result,
@@ -525,6 +544,7 @@ export class FilterEngine {
525
544
  variantScrapeOptions,
526
545
  vendorScrapeOptions,
527
546
  globalScrapeOptions,
547
+ comparisonMap,
528
548
  };
529
549
  const filterResults = this.evaluateResult(context);
530
550
  // Merge with existing filter_results from batch processing (duplicates, search exclusions, etc.)
@@ -728,6 +748,55 @@ class CompetitorExclusionRule {
728
748
  return null;
729
749
  }
730
750
  }
751
+ /**
752
+ * Rule for filtering AI comparison mismatches
753
+ */
754
+ class AIComparisonMismatchRule {
755
+ id = 'ai_comparison_mismatch';
756
+ name = 'AI Comparison Mismatch';
757
+ description = 'Filters results where AI comparison determined the listing does not match the variant';
758
+ severity = FilterSeverity.BLOCK;
759
+ priority = 85;
760
+ enabled = true;
761
+ canBeOverridden = true;
762
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
763
+ evaluate(context) {
764
+ // Only applies to results with listing_hash (primarily immersive results)
765
+ const result = context.result;
766
+ if (!result.listing_hash || !context.comparisonMap) {
767
+ return null;
768
+ }
769
+ const variantId = String(context.variant.id);
770
+ const listingHash = result.listing_hash;
771
+ // Check if we have comparison data for this variant and listing
772
+ const variantComparisons = context.comparisonMap[variantId];
773
+ if (!variantComparisons) {
774
+ return null;
775
+ }
776
+ const comparison = variantComparisons[listingHash];
777
+ if (!comparison) {
778
+ return null;
779
+ }
780
+ // Only filter confirmed mismatches (is_match = 0 AND sufficient_info = 1)
781
+ // Allow results where we don't have sufficient info to compare
782
+ if (comparison.is_match === 0 && comparison.sufficient_info === 1) {
783
+ return {
784
+ ruleId: this.id,
785
+ severity: this.severity,
786
+ message: HIDE_REASONS.AI_COMPARISON_MISMATCH,
787
+ metadata: {
788
+ listingHash,
789
+ isMatch: comparison.is_match,
790
+ sufficientInfo: comparison.sufficient_info,
791
+ resultDescription: comparison.result_description,
792
+ comparedAt: comparison.compared_at,
793
+ },
794
+ timestamp: new Date().toISOString(),
795
+ };
796
+ }
797
+ return null;
798
+ }
799
+ }
731
800
  /**
732
801
  * Rule for filtering duplicates
733
802
  */
@@ -1232,6 +1301,7 @@ export function createDefaultFilterRegistry() {
1232
1301
  registry.registerRule(new LowPriceOutlierRule());
1233
1302
  registry.registerRule(new DateOutlierRule());
1234
1303
  registry.registerRule(new CompetitorExclusionRule());
1304
+ registry.registerRule(new AIComparisonMismatchRule());
1235
1305
  registry.registerRule(new DuplicateRule());
1236
1306
  registry.registerRule(new SearchExclusionRule());
1237
1307
  registry.registerRule(new SkipSkuRule());
@@ -1339,7 +1409,7 @@ export function createCustomFilterRule(config) {
1339
1409
  * Filter scrape results using the new rule-based engine
1340
1410
  * This provides more flexibility and better extensibility than the legacy filterScrapeResults
1341
1411
  */
1342
- export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, }) => {
1412
+ export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, }) => {
1343
1413
  const registry = createDefaultFilterRegistry();
1344
1414
  // Register custom rules if provided
1345
1415
  if (customRules) {
@@ -1354,7 +1424,7 @@ export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOpt
1354
1424
  let results = handleDuplicatesV2(scrapeResults);
1355
1425
  results = handleSearchExclusionsV2(results, variantScrapeOptions, variant, registry);
1356
1426
  // Evaluate all other rules
1357
- results = engine.evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions);
1427
+ results = engine.evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap);
1358
1428
  return results;
1359
1429
  };
1360
1430
  /**
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.15",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",