Розробка AI-системи дедуплікації контактів і компаній у CRM
У CRM з 100 000 контактів кожен п'ятий — дубль. Один і той самий клієнт заведений тричі: як «Іванов Іван», «Іван Іванов» та «[email protected]». Результат: аналітика бреше, листи потрапляють у спам, менеджери витрачають години на «нові» ліди, які вже є клієнтами. Ми вирішуємо цю проблему комбінацією трьох методів: rule-based, ML та embedding. Після впровадження база стає чистою, відсоток дублів падає з 20% до 2-5%, а точність email-маркетингу зростає (unsubscribe rate знижується на 30%). За 5 років ми провели дедуплікацію на 50+ проектах із загальним обсягом даних понад 50 мільйонів записів.
Entity Resolution — класична задача, але в CRM є своя специфіка: поля заповнені нерегулярно, транслітерація, помилки. Прості точні збіги покривають лише 40% дублів. Тому ми будуємо багатошарову систему.
Які проблеми вирішуємо?
- Роздута база: дублі займають місце та спотворюють аналітику. Типова CRM з 100 000 контактів містить 10-25% дублів. Скорочення бази на 8-15% дає економію на зберіганні та розсилках — до $2.7k–3.9kів на рік для середнього бізнесу.
- Провали в комунікації: клієнт отримує три однакові листи — потрапляє в спам, відписується. Після дедуплікації unsubscribe rate падає на 30%, що зберігає до 15% маркетингового бюджету.
- Помилки в продажах: менеджер витрачає час на «новий» лід, який вже є існуючим клієнтом. Втрати часу — до 20 годин на місяць на відділ з 10 осіб.
Як AI знаходить дублікати контактів?
Використовуємо три шари детекції:
- Rule-based (швидкий відсів): точний збіг email або телефону — впевнений дубль. Точність 99%, але низький recall (близько 40%).
- ML-модель (entity resolution): бібліотека
dedupe— навчається на розмічених парах. Враховує помилки, транслітерацію, пропущені поля. Точність 92-95%, повнота 80-85%. - Embedding-based (масштабування): перетворюємо кожен контакт у вектор (all-MiniLM-L6-v2, 384-вимірні ембеддінги) та шукаємо найближчих сусідів через faiss. Обробляє мільйони записів за секунди, точність 88%, повнота 85%.
Порівняння методів:
| Метод | Точність | Повнота | Швидкість | Коли застосовувати |
|---|---|---|---|---|
| Rule-based | 99% | 40% | миттєво | email/телефон точні поля |
| ML (dedupe) | 92% | 80% | хвилини | база 10k-500k записів |
| Embedding | 88% | 85% | секунди | база >1M записів, нечіткі імена |
ML-модель у 1.5 рази точніша за rule-based підхід і в 2 рази швидша за embedding-only підхід при розмірі бази до 500k записів.
ML-модель дедуплікації (код)
import pandas as pd import dedupe from dedupe import Dedupe class ContactDeduplicator: def __init__(self): self.deduper = None def setup_fields(self): """Опис полів для dedupe""" fields = [ dedupe.variables.String('first_name'), dedupe.variables.String('last_name'), dedupe.variables.String('email', has_missing=True), dedupe.variables.String('phone', has_missing=True), dedupe.variables.String('company'), dedupe.variables.String('job_title', has_missing=True), ] return dedupe.Dedupe(fields) def train(self, records: dict, training_file: str = None): """Навчання на позначених парах (match/not-match)""" self.deduper = self.setup_fields() if training_file and os.path.exists(training_file): with open(training_file) as f: self.deduper.prepare_training(records, f) else: self.deduper.prepare_training(records) # Активне навчання: розмітка зразкових пар dedupe.console_label(self.deduper) with open(training_file, 'w') as f: self.deduper.write_training(f) self.deduper.train() def find_duplicates(self, records: dict, threshold: float = 0.5) -> list[tuple]: """Пошук дублів з ймовірностями""" clustered_dupes = self.deduper.partition(records, threshold) duplicate_groups = [] for (cluster_id, record_ids, scores) in clustered_dupes: if len(record_ids) > 1: duplicate_groups.append({ 'records': list(record_ids), 'scores': list(scores), 'max_score': max(scores) }) return sorted(duplicate_groups, key=lambda x: x['max_score'], reverse=True) Нечітке порівняння рядків
from rapidfuzz import fuzz, process def compute_similarity(record1: dict, record2: dict) -> float: scores = [] # Email: точний або domain збіг if record1.get('email') and record2.get('email'): if record1['email'].lower() == record2['email'].lower(): return 1.0 # Точний збіг email — точно дубль email1_domain = record1['email'].split('@')[1] email2_domain = record2['email'].split('@')[1] if email1_domain == email2_domain: scores.append(0.5) # Один домен — схожі # Ім'я: нечіткий збіг name1 = f"{record1.get('first_name', '')} {record1.get('last_name', '')}" name2 = f"{record2.get('first_name', '')} {record2.get('last_name', '')}" name_score = fuzz.token_sort_ratio(name1, name2) / 100 scores.append(name_score * 0.4) # Телефон: нормалізація та порівняння phone1 = re.sub(r'\D', '', record1.get('phone', '')) phone2 = re.sub(r'\D', '', record2.get('phone', '')) if phone1 and phone2: if phone1[-10:] == phone2[-10:]: # Останні 10 цифр scores.append(0.9) # Компанія if record1.get('company') and record2.get('company'): company_score = fuzz.token_set_ratio( record1['company'], record2['company'] ) / 100 scores.append(company_score * 0.2) return sum(scores) / len(scores) if scores else 0.0 Стратегія злиття записів
def merge_duplicates(records: list[dict]) -> dict: """Злиття групи дублів в один запис""" merged = {} field_priority = ['email', 'phone', 'first_name', 'last_name', 'company'] for field in field_priority: values = [r.get(field) for r in records if r.get(field)] if not values: continue # Беремо найчастіше значення merged[field] = max(set(values), key=values.count) # Для created_at беремо найранішу дату dates = [r.get('created_at') for r in records if r.get('created_at')] if dates: merged['created_at'] = min(dates) # Об'єднуємо теги та мітки all_tags = [] for r in records: all_tags.extend(r.get('tags', [])) merged['tags'] = list(set(all_tags)) merged['merged_from'] = [r['id'] for r in records] return merged Чому варто впроваджувати дедуплікацію на базі ML?
Rule-based пропускає помилки та транслітерацію. Embedding-based без донавчання дає хибні спрацьовування. ML-модель на dedupe — золота середина: навчається на ваших даних за пару годин активного розмічання, точність 92-95%, повнота 80-85%. Гарантуємо зниження відсотка дублів у базі мінімум на 10% — досвід впровадження на 50+ проектах. Вартість проекту розраховується індивідуально залежно від обсягу даних та складності інтеграції.
Процес роботи
- Аудит бази — вивантажуємо контакти, оцінюємо поточний відсоток дублів.
- Вибір стратегії — rule-based + ML або embedding для великих обсягів.
- Розмітка та навчання — готуємо тренувальний набір, навчаємо модель.
- Інтеграція — API або прямий доступ до CRM (Bitrix24, AmoCRM, Salesforce).
- Тестування — A/B порівняння: автоматичне злиття vs ручний аудит.
- Деплой та моніторинг — пайплайн періодичної дедуплікації, алерти по аномаліях.
Порівняння часу для різних обсягів даних:
| Обсяг бази | Тривалість проекту |
|---|---|
| до 100 000 записів | 7-14 днів |
| 100k-1M | 14-30 днів |
| >1M записів | індивідуально |
Що входить в роботу
- Документація: опис моделі, налаштування порогів, інструкція з донавчання.
- Доступи: до вихідного коду (GitLab), до навченої моделі (MLflow), до дашборду метрик.
- Навчання: сесія для аналітиків (як розмічати нові дані).
- Підтримка: 1 місяць — виправлення помилок, доналаштування порогів.
Замовте безкоштовний аудит вашої CRM — ми оцінимо відсоток дублів та економічний ефект. Зв'яжіться з нами через форму на сайті або по телефону.







