אתה אגרגטור עם מאה ספקים. כל אחד שולח קטלוג בפורמט משלו. אחד כותב "Smartphone Samsung S24 256Gb", אחר כותב "SAMSUNG Galaxy S24 (256 GB) Black SM-S921B". זה אותו מוצר. זו המשימה של התאמת מוצרים אוטומטית בין ספקים, או product matching. הסרת כפילויות היא רק חלק מהפתרון. ללא התאמה, אתה יוצר כפילויות, מאבד מלאי ומחירים. אנו בונים מערכת שמוצאת התאמות אוטומטית—מחוקים קשיחים ועד ML.
תוך 7–8 ימי עבודה, אתה מקבל pipeline שמכסה 80–90% מהמוצרים ללא התערבות אנושית. עם הגדרה נכונה, התאמה אוטומטית מכסה את הנפח הזה. ה-10–20% הנותרים עוברים לתור סקירה ידנית. התוצאה היא קטלוג אחיד עם עלויות מיתון מינימליות.
בפרויקט אחד עם קטלוג של 200,000 פריטים, ה-pipeline התאים אוטומטית 85% מהמוצרים בחודש הראשון. מפקח בילה 1.5 שעות ביום על 15% הנותרים. לאחר שלושה חודשים, עם אימון מחדש על החלטות המפקח, האוטומציה גדלה ל-93%.
התאמה פותרת את בעיית הכפילויות
התאמה שונה מהותית מהסרת כפילויות. הסרת כפילויות מחפשת כפילויות ברורות בקטלוג אחד. התאמה עובדת עם מערכות שמות שונות. אנו משתמשים בשרשרת שיטות—מהאמינות ביותר ועד הסתברותיות.
מזהים מדויקים
- GTIN/EAN—האמין ביותר, מכסה 40–60% ממוצרי האלקטרוניקה.
- MPN + מותג—מוסיף עוד 20–30%.
- ISBN, ASIN לקטגוריות ספציפיות.
מאפיינים מובנים
אם אין ברקוד, אנו מתאימים לפי מותג + דגם + מאפיינים (קיבולת, צבע, גודל). יעיל לקטגוריות סטנדרטיות.
טקסט מטושטש
אלגוריתמים Jaro-Winkler ו-Levenshtein על שמות מנורמלים. TF-IDF + דמיון קוסינוס על תיאורים. מכסה פריטים לא סטנדרטיים—חומרי בניין, חלקי חילוף.
התאמה וקטורית (ML)
Embeddings דרך sentence-transformers (מקומי) או OpenAI API. עובד גם ללא מילים משותפות—תופס משמעות. מוסיף עוד 5–10% דיוק על גבי הטקסט המטושטש.
השוואת שיטות התאמה
| שיטה | דיוק | כיסוי | מהירות |
|---|---|---|---|
| GTIN/EAN | 100% (אם קיים) | 40–60% | ~1 ms |
| MPN+מותג | 95%+ | 20–30% | ~2 ms |
| טקסט מטושטש | 85–90% | 10–20% | ~10 ms |
| וקטורי (ML) | 90–95% | 5–10% | ~50 ms (עם GPU) |
טקסט מטושטש זול ומהיר יותר מ-ML, אבל ML מדויק ב-15–20% יותר לתיאורים מורכבים. אנו משלבים אותם ב-pipeline.
איך עובד ה-Matching Pipeline
ה-pipeline הוא שרשרת שיטות שבה כל שיטה עוקבת מופעלת אם הקודמת לא הניבה תוצאה עם ביטחון מספק. ראשית, מופעלות שיטות מדויקות (GTIN, MPN), ולאחר מכן הסתברותיות (מטושטש, ML). זה ממזער תוצאות חיוביות שגויות וממקסם כיסוי.
יתרונות של התאמת ML
התאמת ML מוצדקת כאשר ספקים מתארים מוצרים במילים שונות. לדוגמה, אחד כותב "Smartphone Samsung Galaxy S24 256 GB", אחר כותב "Telefon Samsung S24 256 Gb". טקסט מטושטש מסורתי לא יבין שהם זהים, אבל מודל embedding תופס דמיון סמנטי. בפועל, התאמה וקטורית מוסיפה 5–10% דיוק, קריטי לקטלוגים עם 30% פריטים לא מובנים.
סכמת נתונים
-- Таблица сопоставлений + таблица эмбеддингов
CREATE TABLE product_matches (
id BIGSERIAL PRIMARY KEY,
master_id BIGINT NOT NULL REFERENCES products(id),
supplier_id INT NOT NULL REFERENCES suppliers(id),
supplier_sku VARCHAR(255) NOT NULL,
match_method VARCHAR(30) NOT NULL, -- 'gtin', 'mpn_brand', 'fuzzy', 'ml', 'manual'
confidence FLOAT, -- 0.0–1.0
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(supplier_id, supplier_sku)
);
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE product_embeddings (
product_id BIGINT PRIMARY KEY REFERENCES products(id),
embedding vector(1536),
updated_at TIMESTAMP
);
CREATE INDEX idx_matches_master ON product_matches(master_id);
CREATE INDEX idx_matches_confidence ON product_matches(confidence) WHERE status = 'pending_review';
CREATE INDEX idx_embeddings_cosine ON product_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
Matching Pipeline
class ProductMatchingPipeline {
private array $matchers = [];
public function __construct(
private GtinMatcher $gtinMatcher,
private MpnBrandMatcher $mpnBrandMatcher,
private FuzzyMatcher $fuzzyMatcher,
private VectorMatcher $vectorMatcher,
) {
$this->matchers = [
['matcher' => $gtinMatcher, 'threshold' => 1.0, 'auto_accept' => true],
['matcher' => $mpnBrandMatcher, 'threshold' => 1.0, 'auto_accept' => true],
['matcher' => $fuzzyMatcher, 'threshold' => 0.90, 'auto_accept' => true],
['matcher' => $vectorMatcher, 'threshold' => 0.85, 'auto_accept' => false],
];
}
public function match(SupplierProductDTO $dto): MatchResult {
foreach ($this->matchers as $config) {
$result = $config['matcher']->find($dto);
if (!$result) continue;
if ($result->confidence >= $config['threshold'] && $config['auto_accept']) {
return new MatchResult(
masterProductId: $result->productId,
confidence: $result->confidence,
method: $result->method,
status: 'active',
);
}
if ($result->confidence >= 0.70) {
return new MatchResult(
masterProductId: $result->productId,
confidence: $result->confidence,
method: $result->method,
status: 'pending_review',
);
}
}
return new MatchResult(masterProductId: null, confidence: 0, method: 'none', status: 'new');
}
} GTIN Matcher
class GtinMatcher {
public function find(SupplierProductDTO $dto): ?MatchCandidate {
if (!$dto->barcode) return null;
$normalized = $this->normalizeGtin($dto->barcode);
$fingerprint = ProductFingerprint::where('type', 'gtin')
->where('value', $normalized)
->first();
if (!$fingerprint) return null;
return new MatchCandidate(
productId: $fingerprint->product_id,
confidence: 1.0,
method: 'gtin',
);
}
private function normalizeGtin(string $raw): string {
$digits = preg_replace('/\D/', '', $raw);
if (strlen($digits) === 8) {
$digits = str_pad($digits, 13, '0', STR_PAD_LEFT);
}
return $digits;
}
} Vector Matcher דרך OpenAI Embeddings
class VectorMatcher {
public function find(SupplierProductDTO $dto): ?MatchCandidate {
$text = $this->buildText($dto);
$vector = $this->openai->embeddings()->create([
'model' => 'text-embedding-3-small',
'input' => $text,
])->embeddings[0]->embedding;
$result = DB::selectOne("
SELECT product_id, 1 - (embedding <=> :vec) AS similarity
FROM product_embeddings
WHERE 1 - (embedding <=> :vec) > 0.80
ORDER BY embedding <=> :vec
LIMIT 1
", ['vec' => '[' . implode(',', $vector) . ']']);
if (!$result) {
return null;
}
return new MatchCandidate(
productId: $result->product_id,
confidence: (float) $result->similarity,
method: 'vector',
);
}
private function buildText(SupplierProductDTO $dto): string {
return implode(' ', array_filter([
$dto->brand,
$dto->name,
$dto->sku,
implode(' ', array_values($dto->attributes)),
]));
}
} ממשק סקירה ידנית ולולאת משוב
מוצרים עם סטטוס -- Таблица сопоставлений + таблица эмбеддингов CREATE TABLE product_matches ( id BIGSERIAL PRIMARY KEY, master_id BIGINT NOT NULL REFERENCES products(id), supplier_id INT NOT NULL REFERENCES suppliers(id), supplier_sku VARCHAR(255) NOT NULL, match_method VARCHAR(30) NOT NULL, -- 'gtin', 'mpn_brand', 'fuzzy', 'ml', 'manual' confidence FLOAT, -- 0.0–1.0 status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMP DEFAULT NOW(), UNIQUE(supplier_id, supplier_sku) ); CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE product_embeddings ( product_id BIGINT PRIMARY KEY REFERENCES products(id), embedding vector(1536), updated_at TIMESTAMP ); CREATE INDEX idx_matches_master ON product_matches(master_id); CREATE INDEX idx_matches_confidence ON product_matches(confidence) WHERE status = 'pending_review'; CREATE INDEX idx_embeddings_cosine ON product_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); נכנסים לתור המפקח. הממשק מציג את מוצר הספק משמאל (שם, SKU, תמונה) ואת המועמד מהקטלוג מימין עם אחוז התאמה. כפתורים: אישור, דחייה, מצא אחר. מקשי קיצור (→ קבל, ← דחה). מפקח מנוסה מעבד 100–150 זוגות בשעה.
כל החלטת מפקח הופכת לדוגמת אימון ל-pipeline:
class MatchFeedbackService {
public function recordDecision(int $matchId, string $decision, int $userId): void {
$match = ProductMatch::findOrFail($matchId);
$match->update([
'status' => $decision === 'accept' ? 'active' : 'rejected',
'reviewed_by' => $userId,
]);
MatchTrainingExample::create([
'supplier_product_data' => $match->supplierProduct->toArray(),
'master_product_id' => $match->master_id,
'label' => $decision === 'accept' ? 1 : 0,
'confidence_was' => $match->confidence,
]);
if ($decision === 'reject') {
$this->createNewMaster($match->supplierProduct);
}
}
} ביצועים ואופטימיזציה
עם קטלוג של 100,000+ פריטים, לא ניתן לבצע התאמה על ידי בדיקת כל הזוגות. אנו משתמשים ב:
- Blocking—תחילה בחירת מועמדים לפי מותג/קטגוריה, ואז התאמה בתוך הבלוק.
- Batch embeddings—בקשת וקטורים בקבוצות של 100.
- אינדקס pgvector IVFFlat—חיפוש שכן קרוב משוער במילישניות.
עם קטלוג של 100,000 פריטים, ארכיטקטורה זו חוסכת עד €2,000 לחודש על מיתון. רוצה לבדוק התאמה על הנתונים שלך? צור קשר—נבחר את התצורה האופטימלית.
מה כלול בעבודה
- פיתוח pipeline התאמה לסטאק שלך (Laravel, Django, Node.js)
- הגדרת matchers מסוג GTIN, MPN, מטושטש ו-ML
- עיצוב סכמת מסד נתונים (matches, embeddings, indexes)
- ממשק סקירה ידנית עם מקשי קיצור
- אינטגרציה עם ספקים (API או ייבוא)
- תיעוד API וסכמת נתונים
- הכשרת מפקחים (שעתיים)
- חודש תמיכה לאחר ההשקה
לוח זמנים ליישום
| שלב | משך |
|---|---|
| GtinMatcher + MpnBrandMatcher + FuzzyMatcher | יומיים |
| VectorMatcher + pgvector | יומיים |
| Pipeline + תור סקירה ידנית + ממשק | 2–3 ימים |
| לולאת משוב + מדדים | יום אחד |
| סה"כ | 7–8 ימי עבודה |
למה להזמין התאמה מאיתנו?
יישמנו התאמה עבור חמישה אגרגטורים עם קטלוגים של 50,000 עד 500,000 פריטים. ה-pipeline שלנו מתאים אוטומטית 80–90% מהמוצרים. השאר הוא תור סקירה ידנית שלוקח לא יותר משעתיים ביום. אתה מקבל קטלוג אחיד ללא כפילויות או בלבול.
נעריך את המשימה שלך תוך יום אחד, נציע ארכיטקטורה ולוח זמנים מדויק. צור קשר כדי לדון בפרויקט. הזמן פיתוח התאמה לסטאק שלך.







