Примечание: Это первая статья из цикла, в которой я делюсь бизнес-смыслами и подходом к решению проблемы. Во второй статье планирую подробно разобрать техническую реализацию.
Вступление: Кот в мешке
Я работаю в компании, которая занимается автозапчастями. Не буду называть бренд, но представьте любой крупный интернет-магазин запчастей — у нас всё примерно так же.
Десять лет всё работало. Поставщики присылали прайсы, менеджеры загружали. В 90% случаев клиенты искали товар по артикулу — просто вбивали номер и получали результат. Оставшиеся 10% запросов — это названия вроде «хомут бмв х5». И поиск как-то справлялся.
Да, в базе была каша: один и тот же товар мог называться «Хомут винт. BMW X5/E81» и «Хомут крепления топливного шланга 12мм для BMW». Но артикулы вывозили, а на остальное закрывали глаза.
А потом мы узнали про Честный знак.
Проблема: 200 миллионов слепых котят
В 2026 году в автозапчастях вводится обязательная маркировка. Звучит скучно, пока не начинаешь разбираться.
Чтобы маркировать товар, нужно точно знать, что это за товар. Какая категория? Какие характеристики? Подпадает ли он под маркировку?
И тут мы полезли в свою базу.
200 миллионов номенклатур. Это не шутка, это реальная цифра за 10 лет работы.
И только 5% из них привязаны к категориям. Остальные 95% — это просто строки. Иногда осмысленные («Хомут винтовой 12-15 мм»), иногда — полная каша («BMW E81 12MM хомут крепл 1 шт»).
Мы не знали, что продаём. Буквально. Мы как котята, которые тыкаются в миску — вроде что-то съедобное, но что именно — непонятно.
Попытка решить вручную: 400 000 человеко-дней работы
Первая мысль — нанять студентов. Пусть сидят и вручную проставляют категории.
Посчитали: один человек обрабатывает 500 позиций в день. 200 млн / 500 = 400 000 человеко-дней.
Переведём в более понятные единицы:
-
При 250 рабочих днях в году: ~1 600 лет работы одним человеком
-
С командой из 100 человек: ~16 лет
-
С командой из 1 000 человек: ~1,6 года
Тысяча человек — это целый завод. С зарплатой 50 тыс — 50 млн в месяц только на зарплату, без налогов, без оборудования, без соцпакета.
Отбой. Ищем другой путь.
Рождение идеи: Вечера вместо сериалов
Когда увидел эти цифры, засел за эксперименты в свободное время. Не потому что на работе не давали ресурсов — просто тема оказалась настолько интересной, что вечерами и выходными я ковырялся сам. Как хобби, только полезное.
Взял выборку из 10 тысяч наших «грязных» названий. Поставил задачу: научиться из строки:
«Хомут винтовой крепления топливного шланга 12-15ММ 1 E81/82/87/88/F20, 3 E36/46/90/F30, 5 E39/60, 7 E38/65, X1 E84, X3 E83/F25, X5 E53/70, X6, Z3»
понимать:
-
что это за предмет (хомут винтовой)
-
какие у него характеристики (12-15 мм)
-
для каких машин подходит (BMW 1, 3, 5, X5, X6…)
-
и главное — к каким категориям это относится
Как дошёл до ИИ (и почему не сразу)
Этап 1. Регулярки и надежда
Первое, что приходит в голову — написать регулярные выражения. Ну правда, кажется же несложно: вырезать всё до цифр, потом взять цифры, потом найти модели машин…
Написал. Работало на 10 примерах. На 100 — начало сбоить. На 1000 — понял, что так не вывезу. Слишком много вариантов написания, слишком много исключений, слишком много ручной работы под каждого поставщика.
Этап 2. Словари и регулярки (и почему они сдохли)
Пошёл дальше. Начал писать регулярные выражения под каждый тип данных. Отдельная регулярка для размеров, отдельная — для моделей BMW, отдельная — для брендов.
Регулярки работали, но их становилось всё больше. Сначала 10, потом 50, потом 200. Каждая новая регулярка замедляла обработку. Замерил: на 10 тысячах записей скорость упала в 3 раза. На 100 тысячах — в 10 раз.
Регулярки умерли. Они просто не масштабировались.
Этап 3. Aho-Corasick (алгоритм, который спас)
Тогда вспомнил про Aho-Corasick — это алгоритм для одновременного поиска множества подстрок за один проход текста. Сделал словари: «хомут» → «хомут», «хомутик» → «хомут», «clamp» → «хомут», «хомут винтовой» → «хомут винтовой».
Скорость вернулась. Мог искать 1000 паттернов за то же время, что раньше искал 1. Но качество упёрлось в потолок. Словари не покрывали всех вариантов написания, особенно когда в строке была каша из цифр, букв и служебных символов.
Этап 4. Подключил ИИ (и здесь началось интересное)
Когда понял, что правила и словари не покрывают всех случаев, решил попробовать нейросети. И тут ждал сюрприз.
Оказалось, что:
-
Дорогие модели (GPT-4, Claude) работают отлично, но стоят денег
-
Дешёвые модели работают так себе
-
Есть локальные модели, которые можно дообучить под нашу специфику через LoRA — и они работают почти как дорогие, но за копейки
-
А для 80% простых задач ИИ вообще не нужен — хватает тех же регулярных выражений или тривиальных преобразований
Этап 5. Комбинация инструментов
Разделил задачи по сложности и под каждую подобрал свой инструмент:
-
Базовые операции (trim, lowercase, collapse_whitespace и тд.) — для приведения строк к единому виду
-
Regex — для простых замен с чёткими паттернами (например, замена 5W-20 на 5w20 или удаление лишних символов)
-
Aho-Corasick — для массовых замен по словарям (синонимы, бренды, модели)
-
Локальные модели (LoRA) — для извлечения сущностей в типовых случаях
-
Тяжёлые модели (OpenAI, YandexGPT) — только для самых сложных случаев, где нужно реальное понимание контекста
Этап 6. Всё это нужно было собрать в систему
И вот здесь родилась ключевая идея: пайплайны. Последовательность шагов, где каждый шаг делает что-то одно. Первый шаг — очистка мусора. Второй — извлечение характеристик. Третий — определение предмета. Четвёртый — маппинг на категории.
И чтобы это можно было настраивать без программистов — сделал визуальный конструктор.
Инсайты, которые родились в процессе
Инсайт 1. Не все поля нужно обрабатывать одинаково
В JSON-объекте товара есть десятки полей: артикул, бренд, название, описание, цена, остатки, характеристики. Мне нужно чистить только название и иногда описание. Остальные поля трогать нельзя — артикул должен остаться артикулом, бренд — брендом.
Пришлось придумать field selector’ы — механизм, который говорит: «примени это преобразование только к конкретному полю, а остальные поля оставь как есть». Без этого я бы поломал все данные.
Инсайт 2. Предметы и категории — это разные вещи
Сначала пытался заставить ИИ сразу определять категорию. Это плохо работало, потому что категория — вещь условная. Один и тот же предмет может лежать в разных категориях в зависимости от магазина.
Сделал иначе: ИИ вытаскивает предмет (хомут винтовой, прокладка, болт). А уже потом, через матрицу соответствий «предмет → категории», товар автоматически маппится на те категории, где этот предмет чаще всего встречается. Если предмет может относиться к нескольким категориям — берём лучшее пересечение.
Инсайт 3. ИИ должен быть сменным
Когда начинал, думал: привяжусь к OpenAI, и всё будет хорошо. А потом пришли юристы с работы и сказали: «А если данные не должны покидать РФ?»
Пришлось делать архитектуру, где можно подключить любую модель в два клика:
-
OpenAI (и всё совместимое)
-
YandexGPT (через Yandex Cloud — данные остаются в РФ)
-
Локальные модели (через Ollama или свои сервера)
-
Самописные (если вдруг)
Пользователь в интерфейсе просто выбирает, какую модель на какой шаг пайплайна поставить. Дешёвые — на простые задачи, дорогие — на сложные. Оптимизация бюджета и качества.
Инсайт 4. Скрипты должны уметь ходить наружу
Стандартных экшенов (trim, lowercase, regex, aho) хватает для 80% задач. Но всегда есть 20%, где нужно что-то своё: сходить в CRM, проверить товар по API поставщика, подтянуть цену из внешней системы.
Добавил в Rhai (безопасный скриптовый язык) возможность делать HTTP-запросы прямо из пайплайна:
let response = http_get("https://api.supplier.ru/check/" + value.sku);
if response.status == 200 {
value.verified = true;
value.supplier_price = response.body.price;
} else {
value.verified = false;
}
value
Что это даёт:
-
Можно обогащать данные в реальном времени
-
Можно проверять товары по внешним справочникам
-
Можно интегрироваться с чем угодно без доработки платформы
И всё это безопасно — Rhai работает в песочнице, никаких системных вызовов, только HTTP наружу с явного разрешения.
Инсайт 5. Масштабирование под ИИ-нагрузку
200 млн записей — это много. А ещё есть ИИ-обработка, которая может длиться секунды на одну запись. Если запустить всё последовательно, буду чистить базу до пенсии.
Особенность архитектуры — Rust-бэкенд потребляет минимум ресурсов и достаточно шустрый. Поэтому его масштабирование обходится дёшево. А провайдеры ИИ-моделей обычно не выставляют жёстких rate limit’ов — можно слать очень много запросов параллельно.
Kubernetes позволяет масштабировать обработку горизонтально: хочу обрабатывать быстрее — добавляю поды. Сейчас один под обрабатывает ~150 000 батчей в 6 часов при лимите CPU всего 300m (30% одного ядра). Технически можно увеличить лимиты и обрабатывать больше на одном поде, но мне удобнее масштабироваться через добавление подов — это даёт лучшую отказоустойчивость и упрощает управление ресурсами. Десять подов — 1.5 млн батчей в 6 часов. И это на тяжеловесных моделях.
Главное, что это линейно масштабируется. Хоть 200 млн обработаю.
Результат: Цифры, которые не стыдно показать
Через несколько месяцев вечерней работы получился работающий прототип. Прогнал через него 1 млн наших записей и получил:
|
Метрика |
Значение |
|---|---|
|
Точность обработки |
96% (проверяли ручной выборкой) |
|
Определение предмета |
>85% (с нуля) |
|
Категоризация |
78% товаров получили категорию через маппинг предметов |
|
Экономия vs ручная работа |
до 75% |
Пример того, что было и что стало:
Было (одна строка из прайс-листа поставщика):
"OC222 Фильтр масл._A-R 156 2.4TDi 97-> / Volvo 340/440/460/480 1.4/1.7/1.6D 79-95"
После прохода через:
{
"article": "W9171",
"brand": "MANN",
"raw_name": "OC222 Фильтр масл._A-R 156 2.4TDi 97-> / Volvo 340/440/460/480 1.4/1.7/1.6D 79-95",
"name": "Фильтр масляный",
"subjects": [
"масляный фильтр",
"фильтр"
],
"features": [
"a-r",
"двигатели 2.4 tdi",
"двигатели 1.4",
"двигатели 1.7",
"двигатели 1.6d"
],
"applicability": [
{
"brand": "VOLVO",
"model": "340",
"year": "1979-1995",
"brand_info": {
"name": "Volvo",
"cyrillic_name": "Вольво",
"country": "Швеция"
}
},
{
"brand": "VOLVO",
"model": "440",
"year": "1979-1995",
"brand_info": {
"name": "Volvo",
"cyrillic_name": "Вольво",
"country": "Швеция"
},
"model_info": {
"name": "440",
"cyrillic_name": "440",
"class": "C"
}
},
{
"brand": "VOLVO",
"model": "460",
"year": "1979-1995",
"brand_info": {
"name": "Volvo",
"cyrillic_name": "Вольво",
"country": "Швеция"
},
"model_info": {
"name": "460",
"cyrillic_name": "460",
"class": "C"
}
},
{
"brand": "VOLVO",
"model": "480",
"year": "1979-1995",
"brand_info": {
"name": "Volvo",
"cyrillic_name": "Вольво",
"country": "Швеция"
},
"model_info": {
"name": "480",
"cyrillic_name": "480",
"class": "C"
}
}
]
}
И теперь, имея предметы, через матрицу соответствий автоматически отношу этот товар к категориям:
-
Фильтры (основная)
-
Масляная система (дополнительная)
Эта универсальная система категоризации теперь позволяет точно определить, какие товары подлежат маркировке по «Честному знаку». Правила маркировки привязаны к категориям товаров и их свойствам, и теперь, когда мы знаем категории всех 200 миллионов позиций, можем автоматически применять эти правила. Раньше мы не могли этого сделать — не знали, к каким категориям относятся 95% товаров. Теперь система проверяет каждую позицию по матрице соответствий «категория → необходимость маркировки» и формирует чёткий список.
Что даёт чистая база кроме маркировки
Когда привёл 200 млн записей в порядок, осознал, что открыл новые возможности для монетизации, о которых раньше не подозревал.
Раньше: База была чёрным ящиком. Мы знали, что что-то продаётся, но что именно — могли понять только по артикулам и брендам, которые помнили менеджеры.
Теперь: У нас структурированные данные по каждому товару: предмет, категория, характеристики, применимость и какие то свойства товара. И мы можем отвечать на вопросы, о которых раньше даже не задумывались.
1. Какие бренды у нас вообще продаются и на какие машины?
Раньше информация о бренде бралась из поля “brand” в прайсе. Мы знали, что “ABS” — это бренд запчастей, но не знали статистику: какая категория товаров, на какие машины, какого года выпуска и т.д. Теперь мы точно знаем распределение:
Ранг | Бренд | % от всех позиций
----------------------------------------
1 | HYUNDAI | 9.0%
2 | TOYOTA | 8.2%
3 | VOLKSWAGEN | 6.6%
4 | BMW | 6.0%
5 | KIA | 5.4%
6 | AUDI | 4.9%
7 | MERCEDES-BENZ | 4.8%
8 | RENAULT | 4.5%
9 | NISSAN | 4.0%
10 | FORD | 4.0%
11 | MITSUBISHI | 3.4%
12 | CHEVROLET | 2.6%
13 | SKODA | 2.6%
14 | VOLVO | 2.6%
15 | OPEL | 2.2%
16 | LADA | 3.7%
17 | MAZDA | 1.9%
18 | CITROEN | 1.7%
19 | CHERY | 1.6%
20 | PEUGEOT | 1.6%
Что мы видим: Корейские бренды (Hyundai, Kia) в сумме дают почти 15% ассортимента — это характерная особенность нашего ассортимента. А Lada даёт 3.7% — не так много, как можно было подумать.
2. Что именно мы продаём? (ТОП предметов)
Теперь мы знаем, какие товары составляют основу нашего ассортимента:
Ранг | Предмет | % от всех позиций
----------------------------------------
1 | фильтр | 6.7%
2 | прокладка | 4.1%
3 | колодки | 3.1%
4 | тормозные колодки | 2.9%
5 | масло | 2.4%
6 | подшипник | 2.4%
7 | моторное масло | 2.3%
8 | воздушный фильтр | 2.1%
9 | датчик | 2.1%
10 | фара | 2.0%
11 | опора | 1.9%
12 | кольцо | 1.8%
13 | сальник | 1.8%
14 | масляный фильтр | 1.8%
15 | кронштейн | 1.7%
16 | накладка | 1.6%
17 | ремень | 1.5%
18 | сайлентблок | 1.5%
19 | диск | 1.5%
20 | бампер | 1.4%
Что мы видим: Фильтры (всех типов) в сумме дают около 10% ассортимента. Это товары с высокой оборачиваемостью — их нужно держать в стоке всегда.
А вот “колодки” и “тормозные колодки” — это не одно и то же. В нашей системе классификации это разные уровни детализации:
-
Предмет первого ранга (“колодки”) — общая категория товара
-
Предмет второго ранга (“тормозные колодки”) — более конкретное определение с указанием назначения
Это работает по принципу иерархической классификации: система извлекает из названия ключевые сущности (subjects) разного уровня детализации. “Колодки” — это общий тип товара, а “тормозные колодки” — уже конкретизированный вариант с указанием системы автомобиля.
Нормализация помогла нам увидеть эту структуру и правильно классифицировать товары по уровням детализации, что критически важно для точной аналитики и управления ассортиментом.
3. Какие модели автомобилей самые популярные?
Через применимость мы теперь знаем, под какие модели у нас больше всего запчастей:
Ранг | Модель | % от всех связок
----------------------------------------
1 | SOLARIS | 1.0%
2 | LOGAN | 1.0%
3 | A3 | 1.0%
4 | CAMRY | 0.9%
5 | RIO | 0.8%
6 | OCTAVIA | 0.8%
7 | GOLF | 0.8%
8 | ELANTRA | 0.7%
9 | PASSAT | 0.7%
10 | ACCENT | 0.7%
11 | POLO | 0.7%
12 | A4 | 0.7%
13 | COROLLA | 0.7%
14 | SPORTAGE | 0.7%
15 | DUSTER | 0.6%
16 | 3 | 0.6%
17 | SANTA FE | 0.6%
18 | TUCSON | 0.6%
19 | AVEO | 0.6%
20 | FOCUS | 0.6%
Что мы видим: Ожидаемо лидируют бюджетные модели (Solaris, Logan, Rio) ��� они составляют основу нашего ассортимента.
Но есть две интересные находки:
-
Audi A3 на 3-м месте — неожиданно высоко
-
Toyota Camry на 4-м месте — подтверждает репутацию надёжного седана, который долго служит и требует обслуживания
Особенно интересен случай с Audi A3. Почему так высоко в рейтинге запчастей? Несколько причин:
-
Массовость в своём сегменте: A3 — самый доступный Audi, его продавали огромными тиражами. За 10+ лет выпуска накопился большой парк.
-
Возраст парка: Многие A3 первого и второго поколений (выпуска 1996-2012 годов) уже требуют серьёзного обслуживания. Владельцы готовы вкладываться в ремонт, так как покупка нового премиум-автомобиля обходится дороже.
-
Ремонтопригодность: Немецкие автомобили хорошо поддаются ремонту, а для A3 есть огромный выбор запчастей — как оригинальных, так и аналогов.
Что касается Camry — это классический случай “вечной” машины. Её ремонтируют десятилетиями, потому что конструкция проверена временем, а стоимость обслуживания разумная.
4. Какие года выпуска самые ходовые?
Мы проанализировали все связки “товар — модель авто” и посмотрели, для каких лет выпуска чаще всего подбирают запчасти:
Формат года | % от всех связок
----------------------------------------
2006- | 5.1%
2010- | 4.1%
2007- | 4.0%
2004- | 3.9%
2005- | 3.5%
2011- | 3.4%
2008- | 3.2%
2012- | 3.2%
2003- | 2.9%
2009- | 2.7%
2002- | 2.2%
2015- | 2.0%
2013- | 1.9%
2000- | 1.8%
2014- | 1.7%
Что мы видим: Пик приходится на 2006-2010 годы — это машины, которые уже вышли из гарантии, но ещё не отправились на свалку. Для них нужны запчасти, и владельцы готовы платить. Машины младше 2015 года (2.0%) пока редко попадают в наш ассортимент — либо ещё на гарантии, либо ремонтируются у официальных дилеров. А вот машины 2000-2003 годов (ещё 2-3%) — это уже “олдтаймеры”, для которых запчасти ищут коллекционеры и энтузиасты.
5. Какие предметы в каких категориях лежат?
Мы видим, что “фильтр” может быть масляным, воздушным, топливным, салонным. “Колодки” бывают тормозными (передние/задние), а бывают — стояночного тормоза.
Это позволяет умнее строить навигацию по сайту и перекрёстные рекомендации. Например, если человек купил масляный фильтр, ему скорее всего нужно и масло (2.4% ассортимента) — и мы можем предложить это сразу.
6. Аналитика для закупок
Теперь мы понимаем, что для Hyundai Solaris (самая популярная модель) чаще всего покупают:
-
масляные фильтры (каждые 10-15 тыс км)
-
тормозные колодки (каждые 30-40 тыс км)
-
сайлентблоки (после 60-80 тыс км)
Значит, эти позиции должны быть в стоке всегда. А вот, скажем, фары для Audi A3 покупают реже, но когда покупают — готовы ждать под заказ.
7. Прогнозирование спроса
Имея чистые данные за несколько лет, можно строить модели:
-
зимой растёт спрос на свечи накала (для дизелей) и антифризы
-
перед летом — на кондиционеры и радиаторы
-
при росте курса валют — на бюджетные аналоги (корейские бренды выходят на первый план)
Цифры, которые мы получили
|
Что измерили |
До нормализации |
После |
|---|---|---|
|
Товаров с известным брендом |
~0% |
>85% |
|
Товаров с известной моделью |
~0% |
>85% |
|
Товаров с известной категорией |
5% |
78% |
|
Возможность аналитики по спросу |
почти 0 |
есть по всем разрезам |
От эксперимента к подходу
Когда я показал результат коллегам, они сказали: «Это работает. Надо внедрять». Внедрили. Система обрабатывает наши данные до сих пор.
А потом я посмотрел по сторонам и понял: такая проблема не только у нас. У любого, кто торгует сложными товарами (автозапчасти, стройматериалы, одежда, электроника) — та же беда. Поставщики присылают кто во что горазд, менеджеры правят руками, база превращается в свалку.
И Честный знак придёт ко всем.
Поэтому я решил систематизировать свой подход. Доработал интерфейс, добавил визуальный конструктор пайплайнов (чтобы любой аналитик мог настраивать без программистов), упаковал в Kubernetes.
Так сформировался подход к решению проблемы нормализации, который оказался полезным не только в моём случае.
Техническая реализация (для технарей)
Если коротко:
-
Ядро: Rust (безопасность, скорость, предсказуемость)
-
API: gRPC (строгая типизация, streaming)
-
Фронтенд: Nuxt + визуальный конструктор
-
Инфраструктура: Kubernetes (горизонтальное масштабирование, self-healing)
-
Хранилище: PostgreSQL (конфиги, история, задачи)
-
Скриптинг: Rhai (изолированная среда)
-
ИИ: Адаптеры под OpenAI, YandexGPT, локальные модели
Особенности:
-
Field selector’ы для точечной обработки полей
-
Aho-Corasick для быстрых множественных замен
-
JSON Schema для валидации
-
Регулярные выражения
-
Типовые операции смены регистров, удаления пробелов
-
Произвольный код RHAI с расширением функционала стандартной библиотеки
-
Полная поддержка работы в РФ (Yandex Cloud)
Что дальше
Этот подход начался как внутреннее решение конкретной проблемы. В процессе работы над ним родилось много инсайтов о том, как подходить к нормализации данных в современных условиях.
Если тема нормализации данных, подготовки к Честному знаку или работа с большими объёмами неструктурированной информации вам интересна — давайте обсудим в комментариях или личных сообщениях. Мне интересно узнать, с какими похожими проблемами сталкиваетесь вы и какие подходы находите эффективными.
Особенно было бы полезно услышать от тех, кто:
-
Работает с похожими объёмами данных
-
Уже сталкивался с необходимостью маркировки товаров
-
Имеет опыт внедрения ИИ-решений для обработки текстов
-
Пытался решать подобные задачи другими способами
Также планирую написать вторую, более техническую статью, где подробно разберу архитектуру решения, особенности реализации на Rust, работу с Kubernetes и нюансы интеграции разных ИИ-моделей.
P.S. Долго не решался написать статью — это моя первая попытка поделиться опытом. Писал, чтобы рассказать о решении сложной задачи нормализации данных и обсудить подходы, которые могут оказаться полезными другим разработчикам и аналитикам. Если что-то сделал не так или упустил — сори, буду рад конструктивной критике в комментариях.
Автор: IgorBatanov


