Automatic Supplier Product Matching

You are an aggregator with a hundred suppliers. Each sends a catalog in its own format. One has "Smartphone Samsung S24 256Gb", another has "SAMSUNG Galaxy S24 (256 GB) Black SM-S921B". It's the same product. This is the task of automatic supplier product matching, or product matching. Product dedup

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1422
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    984
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    986
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    1000

You are an aggregator with a hundred suppliers. Each sends a catalog in its own format. One has "Smartphone Samsung S24 256Gb", another has "SAMSUNG Galaxy S24 (256 GB) Black SM-S921B". It's the same product. This is the task of automatic supplier product matching, or product matching. Product deduplication is only part of the solution. Without matching, you create duplicates, lose stock and prices. We build a system that automatically finds matches—from hard rules to ML.

In 7–8 working days, you get a pipeline covering 80–90% of products without human intervention. With proper configuration, automatic matching covers that volume. The remaining 10–20% goes to a manual review queue. The result is a unified catalog with minimal moderation costs.

In one project with a catalog of 200,000 items, the pipeline automatically matched 85% of products in the first month. A moderator spent 1.5 hours per day on the remaining 15%. After three months, with re-training on the moderator's decisions, automation grew to 93%.

Matching Solves the Duplication Problem

Matching is fundamentally different from deduplication. Deduplication looks for obvious duplicates in one catalog. Matching works with different naming systems. We use a chain of methods—from the most reliable to probabilistic.

Exact Identifiers

  • GTIN/EAN—most reliable, covers 40–60% of electronics products.
  • MPN + brand—adds another 20–30%.
  • ISBN, ASIN for specific categories.

Structured Attributes

If no barcode, we match by brand + model + characteristics (capacity, color, size). Effective for standardized categories.

Fuzzy Text

Algorithms Jaro-Winkler and Levenshtein on normalized names. TF-IDF + cosine similarity on descriptions. Covers non-standardized items—building materials, spare parts.

Vector Matching (ML)

Embeddings via sentence-transformers (local) or OpenAI API. Works even with no common words—captures meaning. Adds another 5–10% accuracy on top of fuzzy.

Comparison of Matching Methods

Method Precision Coverage Speed
GTIN/EAN 100% (if present) 40–60% ~1 ms
MPN+brand 95%+ 20–30% ~2 ms
Fuzzy text 85–90% 10–20% ~10 ms
Vector (ML) 90–95% 5–10% ~50 ms (with GPU)

Fuzzy is cheaper and faster than ML, but ML is 15–20% more accurate for complex descriptions. We combine them in a pipeline.

How the Matching Pipeline Works

The pipeline is a chain of methods where each subsequent method is activated if the previous one didn't produce a result with sufficient confidence. First, exact methods (GTIN, MPN) are applied, then probabilistic ones (fuzzy, ML). This minimizes false positives and maximizes coverage.

Advantages of ML Matching

ML matching is justified when suppliers describe products with different words. For example, one writes "Smartphone Samsung Galaxy S24 256 GB", another "Telefon Samsung S24 256 Gb". Traditional fuzzy won't understand they are the same, but an embedding model captures semantic similarity. In practice, vector matching adds 5–10% accuracy, critical for catalogs with 30% unstructured items.

Data Schema

-- Matching table + embeddings table 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 via 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)), ])); } } 

Manual Review Interface and Feedback Loop

Products with status pending_review enter the moderator queue. The interface shows the supplier product on the left (name, SKU, photo) and the catalog candidate on the right with a match percentage. Buttons: Confirm, Reject, Find Another. Hotkeys (→ accept, ← reject). An experienced moderator processes 100–150 pairs per hour.

Each moderator decision becomes a training example for the 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); } } } 

Performance and Optimization

With a catalog of 100,000+ items, matching cannot be done by brute-forcing all pairs. We use:

  • Blocking—first select candidates by brand/category, then match within the block.
  • Batch embeddings—request vectors in batches of 100.
  • pgvector IVFFlat index—approximate nearest neighbor in milliseconds.

With a catalog of 100,000 items, this architecture saves up to €2,000 per month on moderation. Want to test matching on your data? Contact us—we'll select the optimal configuration.

What's Included in the Work

  • Development of a matching pipeline for your stack (Laravel, Django, Node.js)
  • Configuration of GTIN, MPN, fuzzy, and ML matchers
  • Database schema design (matches, embeddings, indexes)
  • Manual review interface with hotkeys
  • Integration with suppliers (API or import)
  • API documentation and data schema
  • Moderator training (2 hours)
  • 1 month support after launch

Implementation Timeline

Stage Duration
GtinMatcher + MpnBrandMatcher + FuzzyMatcher 2 days
VectorMatcher + pgvector 2 days
Pipeline + manual review queue + interface 2–3 days
Feedback loop + metrics 1 day
Total 7–8 working days

Why Order Matching from Us?

We have implemented matching for five aggregators with catalogs from 50,000 to 500,000 items. Our pipeline automatically matches 80–90% of products. The remainder is a manual review queue that takes no more than 2 hours per day. You get a unified catalog without duplicates or confusion.

We'll evaluate your task in 1 day, propose an architecture and accurate timeline. Contact us to discuss the project. Order matching development for your stack.