@factorypure/client-helpers 1.1.23 → 1.1.25

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
@@ -520,6 +520,11 @@ export declare const immersiveScrapeResultsSchema: z.ZodObject<{
520
520
  delivery: z.ZodOptional<z.ZodNull>;
521
521
  candidate_result: z.ZodOptional<z.ZodAny>;
522
522
  confidence_override_active: z.ZodOptional<z.ZodBoolean>;
523
+ matchConfirmations: z.ZodOptional<z.ZodArray<z.ZodObject<{
524
+ confirmation_source: z.ZodString;
525
+ confirmed_by: z.ZodNumber;
526
+ confirmed_at: z.ZodString;
527
+ }, z.core.$strip>>>;
523
528
  }, z.core.$strip>;
524
529
  export type ScrapeResultsType = z.infer<typeof scrapeResultsSchema>;
525
530
  export type ImmersiveScrapeResultsType = z.infer<typeof immersiveScrapeResultsSchema>;
@@ -571,6 +576,12 @@ export declare const variantScrapeOptionsSchema: z.ZodObject<{
571
576
  day_window: z.ZodNullable<z.ZodString>;
572
577
  }, z.core.$strip>;
573
578
  export type VariantScrapeOptionsType = z.infer<typeof variantScrapeOptionsSchema>;
579
+ declare const matchConfirmationSchema: z.ZodObject<{
580
+ confirmation_source: z.ZodString;
581
+ confirmed_by: z.ZodNumber;
582
+ confirmed_at: z.ZodString;
583
+ }, z.core.$strip>;
584
+ export type MatchConfirmationType = z.infer<typeof matchConfirmationSchema>;
574
585
  export declare const vendorScrapeOptionsSchema: z.ZodObject<{
575
586
  vendor_skip_skus: z.ZodArray<z.ZodString>;
576
587
  default_skip_skus: z.ZodArray<z.ZodString>;
@@ -597,6 +608,14 @@ export type ComparisonMapType = Record<string, Record<string, {
597
608
  result_description: string;
598
609
  compared_at: string;
599
610
  }>>;
611
+ /**
612
+ * Manual match confirmation map, keyed by variant_id → listing_hash
613
+ */
614
+ export type MatchConfirmationMapType = Record<string, Record<string, {
615
+ confirmation_source: string;
616
+ confirmed_by: number;
617
+ confirmed_at: string;
618
+ }>>;
600
619
  /**
601
620
  * Context provided to filter rules during evaluation
602
621
  */
@@ -615,6 +634,7 @@ export type FilterContext<T = ScrapeResultsType | ImmersiveScrapeResultsType> =
615
634
  vendorScrapeOptions: VendorScrapeOptionsType;
616
635
  globalScrapeOptions: GlobalScrapeOptionsType;
617
636
  comparisonMap?: ComparisonMapType;
637
+ matchConfirmationMap?: MatchConfirmationMapType;
618
638
  };
619
639
  /**
620
640
  * Base interface for filter rules
@@ -686,6 +706,7 @@ export declare const HIDE_OVERRIDE_REASONS: {
686
706
  CALCULATED_SKU_PARTIAL_MATCH: string;
687
707
  CALCULATED_ALT_SKU_MATCH: string;
688
708
  AI_COMPARISON_MATCH: string;
709
+ MATCH_CONFIRMATION: string;
689
710
  };
690
711
  export declare const TOO_CHEAP_MULTIPLIER = 0.75;
691
712
  export declare const TOO_EXPENSIVE_MULTIPLIER = 1.15;
@@ -757,7 +778,7 @@ export declare class FilterEngine {
757
778
  /**
758
779
  * Evaluate rules for a batch of results
759
780
  */
760
- evaluateBatch<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(results: T[], variant: FilterContext<T>['variant'], variantScrapeOptions: VariantScrapeOptionsType, vendorScrapeOptions: VendorScrapeOptionsType, globalScrapeOptions: GlobalScrapeOptionsType, comparisonMap?: ComparisonMapType): T[];
781
+ evaluateBatch<T extends ScrapeResultsType | ImmersiveScrapeResultsType>(results: T[], variant: FilterContext<T>['variant'], variantScrapeOptions: VariantScrapeOptionsType, vendorScrapeOptions: VendorScrapeOptionsType, globalScrapeOptions: GlobalScrapeOptionsType, comparisonMap?: ComparisonMapType, matchConfirmationMap?: MatchConfirmationMapType): T[];
761
782
  /**
762
783
  * Calculate visibility state based on filter results
763
784
  */
@@ -804,7 +825,7 @@ export declare function createCustomFilterRule(config: {
804
825
  * Filter scrape results using the new rule-based engine
805
826
  * This provides more flexibility and better extensibility than the legacy filterScrapeResults
806
827
  */
807
- export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, }: {
828
+ export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, matchConfirmationMap, }: {
808
829
  scrapeResults: T[];
809
830
  variant: {
810
831
  id: number;
@@ -821,6 +842,7 @@ export declare const filterScrapeResultsV2: <T extends ScrapeResultsType | Immer
821
842
  filterConfig?: FilterConfiguration;
822
843
  customRules?: FilterRule[];
823
844
  comparisonMap?: ComparisonMapType;
845
+ matchConfirmationMap?: MatchConfirmationMapType;
824
846
  }) => T[];
825
847
  export declare const filterScrapeResults: <T extends ScrapeResultsType | ImmersiveScrapeResultsType>({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, }: {
826
848
  scrapeResults: T[];
@@ -856,3 +878,4 @@ export declare const calculateHideOverrideReasons: (result: ScrapeResultsType |
856
878
  found_product_ids?: string[];
857
879
  }, sku_alternates: string[]) => string[];
858
880
  export declare function findUnitVariations(data: any): any;
881
+ export {};
package/dist/index.js CHANGED
@@ -3,6 +3,10 @@ import { startOfDay, subDays } from 'date-fns';
3
3
  import { Index } from 'flexsearch';
4
4
  import EnglishPreset from 'flexsearch/lang/en';
5
5
  import { z } from 'zod';
6
+ // GOTCHA must restart client
7
+ // npm install /Users/kokkoalex/Documents/GitHub/npm-packages/client-helpers
8
+ // npm uninstall @factorypure/client-helpers
9
+ // npm install @factorypure/client-helpers --latest
6
10
  /**
7
11
  * @packageDocumentation
8
12
  * @module @factorypure/client-helpers
@@ -348,6 +352,13 @@ export const immersiveScrapeResultsSchema = z.object({
348
352
  // Populated by matching engine helpers
349
353
  candidate_result: z.any().optional(),
350
354
  confidence_override_active: z.boolean().optional(),
355
+ matchConfirmations: z
356
+ .array(z.object({
357
+ confirmation_source: z.string(),
358
+ confirmed_by: z.number(),
359
+ confirmed_at: z.string(),
360
+ }))
361
+ .optional(),
351
362
  });
352
363
  export const ResultIgnoreSchema = z.object({
353
364
  id: z.number(),
@@ -370,6 +381,11 @@ export const variantScrapeOptionsSchema = z.object({
370
381
  result_ignore_keys: z.array(ResultIgnoreSchema).nullable(),
371
382
  day_window: z.string().nullable(),
372
383
  });
384
+ const matchConfirmationSchema = z.object({
385
+ confirmation_source: z.string(),
386
+ confirmed_by: z.number(),
387
+ confirmed_at: z.string(),
388
+ });
373
389
  export const vendorScrapeOptionsSchema = z.object({
374
390
  vendor_skip_skus: z.array(z.string()),
375
391
  default_skip_skus: z.array(z.string()),
@@ -428,6 +444,7 @@ export const HIDE_OVERRIDE_REASONS = {
428
444
  CALCULATED_SKU_PARTIAL_MATCH: 'Calculated SKU Partial Match',
429
445
  CALCULATED_ALT_SKU_MATCH: 'Calc. Alt SKU Match',
430
446
  AI_COMPARISON_MATCH: 'AI Comparison Match',
447
+ MATCH_CONFIRMATION: 'Match Confirmation',
431
448
  };
432
449
  const HIDE_ALWAYS_MAP = {
433
450
  [HIDE_REASONS.HIGH_PRICE_OUTLIER]: true,
@@ -591,7 +608,7 @@ export class FilterEngine {
591
608
  /**
592
609
  * Evaluate rules for a batch of results
593
610
  */
594
- evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap) {
611
+ evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap, matchConfirmationMap) {
595
612
  return results.map((result) => {
596
613
  const context = {
597
614
  result,
@@ -600,6 +617,7 @@ export class FilterEngine {
600
617
  vendorScrapeOptions,
601
618
  globalScrapeOptions,
602
619
  comparisonMap,
620
+ matchConfirmationMap,
603
621
  };
604
622
  const filterResults = this.evaluateResult(context);
605
623
  // Merge with existing filter_results from batch processing (duplicates, search exclusions, etc.)
@@ -787,7 +805,7 @@ class CompetitorExclusionRule {
787
805
  priority = 80;
788
806
  enabled = true;
789
807
  canBeOverridden = true;
790
- overridableBy = [];
808
+ overridableBy = ['match_confirmation_override'];
791
809
  evaluate(context) {
792
810
  const lowerSource = context.result.source.toLowerCase();
793
811
  const isExcluded = context.variantScrapeOptions.competitor_exclusions.some((exclusion) => exclusion && lowerSource === exclusion.toLowerCase());
@@ -814,7 +832,7 @@ class AIComparisonMismatchRule {
814
832
  priority = 85;
815
833
  enabled = true;
816
834
  canBeOverridden = true;
817
- overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
835
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match', 'match_confirmation_override'];
818
836
  evaluate(context) {
819
837
  // Only applies to results with listing_hash (primarily immersive results)
820
838
  const result = context.result;
@@ -881,7 +899,7 @@ class SearchExclusionRule {
881
899
  priority = 60;
882
900
  enabled = true;
883
901
  canBeOverridden = true;
884
- overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
902
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match', 'match_confirmation_override'];
885
903
  evaluate(context) {
886
904
  // This is handled in batch processing due to FlexSearch requirement
887
905
  return null;
@@ -945,7 +963,7 @@ class VendorExclusionRule {
945
963
  priority = 50;
946
964
  enabled = true;
947
965
  canBeOverridden = true;
948
- overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
966
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match', 'match_confirmation_override'];
949
967
  evaluate(context) {
950
968
  const nots = [];
951
969
  const formatted = context.vendorScrapeOptions.default_skip_vendors
@@ -1052,7 +1070,7 @@ class BrandMismatchRule {
1052
1070
  priority = 40;
1053
1071
  enabled = true;
1054
1072
  canBeOverridden = true;
1055
- overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
1073
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match', 'match_confirmation_override'];
1056
1074
  evaluate(context) {
1057
1075
  if (!context.result.brand) {
1058
1076
  return null;
@@ -1115,7 +1133,7 @@ class CalculatedSkuMismatchRule {
1115
1133
  priority = 30;
1116
1134
  enabled = true;
1117
1135
  canBeOverridden = true;
1118
- overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
1136
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match', 'match_confirmation_override'];
1119
1137
  evaluate(context) {
1120
1138
  const result = context.result;
1121
1139
  if (!result.calculated_sku) {
@@ -1158,7 +1176,7 @@ class AISkuMismatchRule {
1158
1176
  priority = 30;
1159
1177
  enabled = true;
1160
1178
  canBeOverridden = true;
1161
- overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
1179
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match', 'match_confirmation_override'];
1162
1180
  evaluate(context) {
1163
1181
  const result = context.result;
1164
1182
  if (!result.ai_extracted_sku) {
@@ -1201,7 +1219,7 @@ class CriticalSpecMismatchRule {
1201
1219
  priority = 35;
1202
1220
  enabled = true;
1203
1221
  canBeOverridden = true;
1204
- overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match'];
1222
+ overridableBy = ['sku_match', 'calculated_sku_match', 'alt_sku_match', 'match_confirmation_override'];
1205
1223
  evaluate(context) {
1206
1224
  if (!context.result.regexUnitResults || !context.variant.regexUnitResults) {
1207
1225
  return null;
@@ -1230,7 +1248,7 @@ class RefurbishedUsedRule {
1230
1248
  priority = 25;
1231
1249
  enabled = true;
1232
1250
  canBeOverridden = true;
1233
- overridableBy = [];
1251
+ overridableBy = ['match_confirmation_override'];
1234
1252
  evaluate(context) {
1235
1253
  const refurbishedKeywords = getRefurbishedKeywords(context.variant);
1236
1254
  const normalizedTitle = context.result.title?.toLowerCase().replace(/[^a-z0-9-]/g, ' ') ?? '';
@@ -1438,6 +1456,49 @@ class ProductIdMatchOverrideRule {
1438
1456
  // ===============================
1439
1457
  // Registry Factory
1440
1458
  // ===============================
1459
+ /**
1460
+ * Override rule for manually confirmed matches
1461
+ */
1462
+ class MatchConfirmationOverrideRule {
1463
+ id = 'match_confirmation_override';
1464
+ name = 'Match Confirmation Override';
1465
+ description = 'Overrides hide reasons when a match has been manually confirmed via matchConfirmationMap';
1466
+ severity = FilterSeverity.INFO;
1467
+ priority = 300;
1468
+ enabled = true;
1469
+ canBeOverridden = false;
1470
+ overridableBy = [];
1471
+ evaluate(context) {
1472
+ if (!context.matchConfirmationMap) {
1473
+ return null;
1474
+ }
1475
+ const result = context.result;
1476
+ if (!result.listing_hash) {
1477
+ return null;
1478
+ }
1479
+ const variantId = String(context.variant.id);
1480
+ const confirmation = context.matchConfirmationMap[variantId]?.[result.listing_hash];
1481
+ if (!confirmation) {
1482
+ return null;
1483
+ }
1484
+ return {
1485
+ ruleId: this.id,
1486
+ severity: this.severity,
1487
+ message: HIDE_OVERRIDE_REASONS.MATCH_CONFIRMATION,
1488
+ metadata: {
1489
+ isOverride: true,
1490
+ listingHash: result.listing_hash,
1491
+ confirmationSource: confirmation.confirmation_source,
1492
+ confirmedBy: confirmation.confirmed_by,
1493
+ confirmedAt: confirmation.confirmed_at,
1494
+ },
1495
+ timestamp: new Date().toISOString(),
1496
+ };
1497
+ }
1498
+ }
1499
+ // ===============================
1500
+ // Registry Factory
1501
+ // ===============================
1441
1502
  /**
1442
1503
  * Create and populate a registry with all built-in rules
1443
1504
  */
@@ -1467,6 +1528,7 @@ export function createDefaultFilterRegistry() {
1467
1528
  registry.registerRule(new CalculatedSkuMatchOverrideRule());
1468
1529
  registry.registerRule(new AltSkuMatchOverrideRule());
1469
1530
  registry.registerRule(new ProductIdMatchOverrideRule());
1531
+ registry.registerRule(new MatchConfirmationOverrideRule());
1470
1532
  return registry;
1471
1533
  }
1472
1534
  /**
@@ -1558,7 +1620,7 @@ export function createCustomFilterRule(config) {
1558
1620
  * Filter scrape results using the new rule-based engine
1559
1621
  * This provides more flexibility and better extensibility than the legacy filterScrapeResults
1560
1622
  */
1561
- export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, }) => {
1623
+ export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, filterConfig, customRules, comparisonMap, matchConfirmationMap, }) => {
1562
1624
  const registry = createDefaultFilterRegistry();
1563
1625
  // Register custom rules if provided
1564
1626
  if (customRules) {
@@ -1573,7 +1635,7 @@ export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOpt
1573
1635
  let results = handleDuplicatesV2(scrapeResults);
1574
1636
  results = handleSearchExclusionsV2(results, variantScrapeOptions, variant, registry);
1575
1637
  // Evaluate all other rules
1576
- results = engine.evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap);
1638
+ results = engine.evaluateBatch(results, variant, variantScrapeOptions, vendorScrapeOptions, globalScrapeOptions, comparisonMap, matchConfirmationMap);
1577
1639
  return results;
1578
1640
  };
1579
1641
  /**
@@ -1582,8 +1644,8 @@ export const filterScrapeResultsV2 = ({ scrapeResults, variant, variantScrapeOpt
1582
1644
  function handleDuplicatesV2(results) {
1583
1645
  const filteredUniqueResultsMap = {};
1584
1646
  results.forEach((item) => {
1585
- // TODO ALEX this should probably compare on listing_hash
1586
- const key = `${item.source}-${item.title}-${item.extracted_total ?? item.extracted_price}`;
1647
+ // @ts-ignore
1648
+ const key = item.listing_hash;
1587
1649
  if (!filteredUniqueResultsMap[key]) {
1588
1650
  filteredUniqueResultsMap[key] = item;
1589
1651
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factorypure/client-helpers",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",