Zero-model, pure-algorithm SKU variant matching engine. No LLM, no ML model, no training data, no GPU — deterministic rule-based matching that runs in milliseconds.
Built for the DSers MCP Product ecosystem. When a supplier runs out of stock or a product link goes dead, this engine maps variants between the old supplier and a replacement — automatically.
Dropshipping sellers on AliExpress deal with a constant headache: suppliers disappear. Stock runs out, listings get taken down, prices spike overnight. Switching to a new supplier means remapping every SKU variant by hand — and different sellers name things differently:
| What you have | What the new supplier calls it |
|---|---|
Grey |
Gray |
Navy Blue |
Dark Blue |
红色 |
Red |
90 for 12-18M |
90 |
100*130cm |
1x1.3m |
4pcs |
4 Pieces / 4片 |
Color |
颜色 |
For a product with 30+ variants across color, size, and material — that's hours of manual work per supplier switch. This engine does it in under 150ms.
Three-layer matching pipeline:
Source variant: "Navy Blue-XL-China Mainland"
↓
1. Dimension alignment
Color ↔ 颜色 (synonym)
Size ↔ 尺码 (synonym)
Ships From → filtered out (logistics, not product attribute)
2. Value matching
"Navy Blue" → synonym table → "Dark Blue" (score: 25)
"XL" → exact match → "XL" (score: 40)
3. Score normalization
avg(25, 40) / 40 × 80 = 65
4. Auxiliary signals
+5 (price within 15%) = 70
5. Decision
70 ≥ 50 → auto match ✓
Layer 1 — Text matching: exact, normalized (case/whitespace), substring, synonym, and opaque-code detection.
Layer 2 — Unit-aware matching: parses and converts quantities (pcs/set/片), weights (g/kg/oz/lb), lengths (mm/cm/m/inch), volumes (ml/L), and dimensions (AxB / AxBxC with unit inference). Handles composite values like 4PC-32x42cm by splitting into quantity + dimensions.
Layer 3 — Image matching: dHash perceptual hashing for variant-level image comparison when text matching is ambiguous.
import { matchVariants, type VariantForMatch } from './src/provider/sku-matcher.js';
const currentSupplier: VariantForMatch[] = [
{
variant_ref: 'v1',
title: 'Red-M',
option_values: [
{ optionName: 'Color', valueName: 'Red' },
{ optionName: 'Size', valueName: 'M' },
],
supplier_price: 10.5,
stock: 0, // out of stock — need replacement
image_url: '',
},
];
const newSupplier: VariantForMatch[] = [
{
variant_ref: 'a1',
title: '红色-M',
option_values: [
{ optionName: '颜色', valueName: '红色' },
{ optionName: '尺码', valueName: 'M' },
],
supplier_price: 11.0,
stock: 100,
image_url: '',
},
];
const result = await matchVariants(currentSupplier, newSupplier);
console.log(result.summary);
// { auto_matched: 1, needs_review: 0, unmatched: 0, total: 1 }- Color synonyms — English, Chinese, French, German, Russian: Grey↔Gray, Navy↔Dark Blue, 红色↔Red, Warm White↔Warm Light
- Size synonyms — S↔Small↔小号, XS through 5XL, numeric shoe sizes
- Opaque code detection — single letters (A/B/C) and bare numbers (01/02) get lower confidence to prevent cross-product false matches
- Standard size protection — S/M/L are recognized as meaningful sizes, not opaque codes
| Unit family | Examples | Conversions |
|---|---|---|
| Count | 4pcs, 2set, 3片, 5件 | pcs/pieces/set/pack/片/件/个/双 |
| Weight | 500g, 1kg, 2oz | g↔kg↔oz↔lb |
| Length | 20mm, 100cm, 1m | mm↔cm↔m↔inch↔ft |
| Volume | 30ml, 0.5L | ml↔L↔fl.oz |
| Dimensions | 32x42cm, 100*130cm | AxB / AxBxC with mixed units |
| Composite | 4PC-32x42cm | Split into count(4) + dims(32×42cm) |
- Ships From filtering — logistics dimensions excluded automatically
- Unit-family alignment — aligns "Size" ↔ "Capacity" when values share the same unit family
- Same-product detection — image URL filename comparison catches re-listings
- Bundle pricing analysis — detects "4pcs at $6.2" vs "1pc at $2.83" (same unit price)
- Dimension parsing —
W100xH200cm,60-30cm,1x1.3m, bare numbers with unit inference
| Parameter | Default | Tune when... |
|---|---|---|
auto_confidence |
50 | Too many wrong matches → raise to 60-70 |
review_confidence |
25 | Missing matches → lower to 15-20 |
image_match_distance |
15 | Image false positives → lower; misses → raise |
min_stock |
1 | Want safety buffer → set to 5 or 10 |
unit_price_close_pct |
30 | Price sensitive → tighten to 10-15 |
// Fashion: more color synonyms, lower opaque code trust
await matchVariants(source, candidate, {
scoring: { score_synonym: 30, score_opaque: 10 },
});
// Skincare: prevent quantity collisions (1pc vs 2pc)
await matchVariants(source, candidate, {
scoring: { score_unit_count: 15 },
thresholds: { auto_confidence: 60 },
});| Parameter | Default | Description |
|---|---|---|
score_exact |
40 | Exact text match |
score_synonym |
25 | Synonym match |
score_substring |
30 | Substring containment |
score_opaque |
15 | Opaque codes (A/B/01) |
score_unit_count |
20 | Quantity match (pcs/set) |
score_unit_physical |
25 | Physical unit (g/ml/cm) |
score_unit_compound |
30 | Composite (4PC-32x42cm) |
dim_mismatch_cap |
20 | Cap when dimension mismatches |
unaligned_penalty |
15 | Penalty per missing dimension |
interface SkuMatchOutput {
variant_matches: VariantMatchResult[];
summary: {
auto_matched: number; // high confidence — ready to use
needs_review: number; // medium confidence — needs human check
unmatched: number; // no viable candidate found
total: number;
};
dimension_alignment: DimensionAlignment[];
image_compare_requests: ImageCompareRequest[];
same_product_flag: boolean;
unit_price_analysis: UnitPriceAnalysis | null;
}Each variant result includes the best match, up to 3 alternatives, and a status of auto / review / unmatched.
This engine is being integrated into DSers MCP Product to enable fully automated supplier replacement:
- Detect — monitor stock levels, link health, and price changes
- Search — find alternative suppliers via
dsers_find_product - Match — run sku-matcher to map variants between old and new supplier
- Replace — apply the new mapping with one confirmation
The goal: when a supplier goes down, your AI agent handles the replacement end-to-end.
- Node.js >= 22
sharp(image processing for dHash)
Non-Commercial License. Free for personal and non-commercial use. Attribution required. See LICENSE for details.
For commercial licensing, contact the author.