Система автоматической категоризации транзакций использует продвинутый NLP-алгоритм, который комбинирует несколько техник для максимальной точности определения категории расходов.
Цель: Обработать сложные назначения платежей, содержащие несколько смысловых единиц.
Пример:
Входной текст: "Оплата за цветы. Доставка завтра утром"
Результат: ["Оплата за цветы", "Доставка завтра утром"]
Реализация:
- Разбивка по знакам препинания (
.,!,?,;,,) - Фильтрация пустых и слишком коротких фрагментов (< 3 символов)
- Каждое предложение обрабатывается независимо
Цель: Привести слова к начальной форме для лучшего сопоставления.
Пример:
"цветочки" → "цветок"
"купили" → "купить"
"красивые" → "красивый"
Реализация:
- Используется библиотека
pymorphy2для морфологического анализа - Кэширование результатов (LRU cache на 1000 записей)
- Извлечение только существительных и глаголов как ключевых слов
Цель: Расширить поиск через семантически близкие слова.
Пример:
"цветок" → {"тюльпан", "роза", "букет", "растение", ...}
"оплата" → {"платёж", "расчёт", "выплата", ...}
Реализация:
- Используется
RuWordNet- русская версия WordNet - Берутся первые 3 синсета, до 10 синонимов на слово
- Кэширование результатов (LRU cache на 500 записей)
Цель: Найти семантически близкие категории через векторное представление.
Пример:
calculate_similarity("оплата за доставку цветов", "логистика") → 0.72
calculate_similarity("оплата за доставку цветов", "реклама") → 0.15
Реализация:
- Используется OpenAI Embeddings API (
text-embedding-3-small) - Вычисление косинусного расстояния между векторами
- Кэширование эмбеддингов для часто встречающихся текстов
Цель: Увеличить вес совпадений на уровне лемм.
Бонус: +0.15 к базовому скору
Пример:
Транзакция: "Купили красивые цветочки"
Ключевое слово: "цветок"
Леммы транзакции: {"купить", "красивый", "цветок"}
Лемма ключевого слова: "цветок"
Совпадение! → Бонус +0.15
Цель: Максимально увеличить вес при точном совпадении слова.
Бонус: +0.25 к базовому скору
Пример:
Транзакция: "Оплата за цветы и доставку"
Ключевое слово: "цветы"
"цветы" in "оплата за цветы и доставку" → True
Совпадение! → Бонус +0.25
Цель: Учесть семантическую близость через синонимы.
Бонус: +0.10 к базовому скору
Пример:
Транзакция: "Покупка букета роз"
Ключевое слово: "цветок"
Синонимы "цветок": {"роза", "тюльпан", "букет", ...}
Леммы транзакции: {"покупка", "букет", "роза"}
Пересечение: {"букет", "роза"} → Бонус +0.10
final_score = min(
base_embedding_similarity
+ (0.25 if exact_match else 0)
+ (0.15 if lemma_match else 0)
+ (0.10 if synonym_match else 0),
1.0 # Максимум
)Назначение платежа:
"Оплата за доставку цветов клиенту. Срочная курьерская служба"
Категории и ключевые слова:
{
"Логистика": ["доставка", "курьер", "транспорт"],
"Маркетинг": ["реклама", "продвижение"],
"Офисные расходы": ["канцелярия", "офис"]
}Предложения:
1. "Оплата за доставку цветов клиенту"
2. "Срочная курьерская служба"
Ключевые слова: ["оплата", "доставка", "цветок", "клиент", "срочный", "курьерский", "служба"]
Vector Search:
similarity("Оплата за доставку цветов клиенту", "доставка") = 0.68
Exact Match:
"доставка" in "оплата за доставку цветов клиенту" → True
Бонус: +0.25
Lemma Match:
"доставка" in {"оплата", "доставка", "цветок", ...} → True
Бонус: +0.15
Synonym Match:
Синонимы "доставка": {"курьер", "транспортировка", ...}
"курьерский" → "курьер" (лемма)
Пересечение найдено!
Бонус: +0.10
Итоговый скор:
0.68 + 0.25 + 0.15 + 0.10 = 1.18 → 1.0 (ограничено максимумом)
✅ Категория: "Логистика"
✅ Скор: 1.000
✅ Примененные бусты: exact, lemma, synonym
Система логирует детальную информацию о процессе категоризации:
[CATEGORY-NLP][batch-uuid] Категория определена: Логистика (score=1.000) [boosts: exact, lemma, synonym]
Минимальный скор для принятия категории (по умолчанию: 0.7)
# В .env
EMBEDDING_SIMILARITY_THRESHOLD=0.7Если NLP не дал результата (скор < threshold):
- Проверка маппинга по ИНН - если есть ИНН контрагента, проверяется
ContractorExpenseMapping - Оставить пустым - если маппинга нет, категория остается
None
-
LRU кэширование:
- Лемматизация: 1000 записей
- Синонимы: 500 записей
- Эмбеддинги: неограниченно (в
embedding_cache.py)
-
Lazy loading:
pymorphy2иRuWordNetзагружаются только при первом использовании- Singleton паттерн для переиспользования экземпляров
-
Graceful degradation:
- Если
pymorphy2не установлен → простая нормализация.lower() - Если
RuWordNetне установлен → работа без синонимов
- Если
- Одна транзакция: 50-150ms (с холодным кэшем)
- Одна транзакция: 5-15ms (с прогретым кэшем)
- Batch из 100 транзакций: ~2-5 секунд
pip install pymorphy2 pymorphy2-dicts-ru ruwordnetИли через requirements.txt:
pip install -r requirements.txtfrom coin_desk.utils.nlp_processor import get_category_matcher
matcher = get_category_matcher()
category, score, boosts = matcher.find_best_category(
transaction_text="Оплата за доставку цветов",
category_keywords={
"Логистика": ["доставка", "курьер"],
"Маркетинг": ["реклама", "продвижение"]
},
threshold=0.7
)
print(f"Категория: {category}, Скор: {score:.3f}")
# Вывод: Категория: Логистика, Скор: 1.000from coin_desk.utils.nlp_processor import get_nlp_processor
nlp = get_nlp_processor()
# Лемматизация
lemma = nlp.lemmatize("цветочки") # → "цветок"
# Синонимы
synonyms = nlp.get_synonyms("цветок") # → {"роза", "тюльпан", ...}
# Ключевые слова
keywords = nlp.extract_keywords("Купили красивые цветы") # → ["купить", "цветок"]
# Расширение запроса
expanded = nlp.expand_query_with_synonyms("доставка цветов")
# → {"доставка", "цветок", "курьер", "роза", "тюльпан", ...}Логи сохраняются в debug.log с префиксом [CATEGORY-NLP]:
tail -f debug.log | grep "CATEGORY-NLP"from coin_desk.models import Transaction
# Транзакции, категоризованные через NLP
nlp_transactions = Transaction.objects.filter(category_source='nlp')
print(f"Всего NLP-категоризаций: {nlp_transactions.count()}")-
Machine Learning:
- Обучение классификатора на исторических данных
- Fine-tuning эмбеддинг-модели на доменных данных
-
Контекстный анализ:
- Учет истории транзакций контрагента
- Анализ сезонности и трендов
-
Feedback loop:
- Обучение на ручных корректировках пользователя
- A/B тестирование разных порогов и весов
-
Многоязычность:
- Поддержка английского языка
- Автоопределение языка транзакции
Разработано для проекта CoinCounter.
MIT License