Примечание: Это первая статья из цикла, в которой я делюсь бизнес-смыслами и подходом к решению проблемы. Во второй статье планирую подробно разобрать техническПримечание: Это первая статья из цикла, в которой я делюсь бизнес-смыслами и подходом к решению проблемы. Во второй статье планирую подробно разобрать техническ

Как я готовился к Честному знаку и разработал подход к нормализации данных

2026/03/05 18:16
16м. чтение
Для обратной связи или замечаний по поводу данного контента, свяжитесь с нами по адресу [email protected]

Примечание: Это первая статья из цикла, в которой я делюсь бизнес-смыслами и подходом к решению проблемы. Во второй статье планирую подробно разобрать техническую реализацию.

Вступление: Кот в мешке

Я работаю в компании, которая занимается автозапчастями. Не буду называть бренд, но представьте любой крупный интернет-магазин запчастей — у нас всё примерно так же.

Десять лет всё работало. Поставщики присылали прайсы, менеджеры загружали. В 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 тысяч наших «грязных» названий. Поставил задачу: научиться из строки:

понимать:

  1. что это за предмет (хомут винтовой)

  2. какие у него характеристики (12-15 мм)

  3. для каких машин подходит (BMW 1, 3, 5, X5, X6...)

  4. и главное — к каким категориям это относится

Как дошёл до ИИ (и почему не сразу)

Этап 1. Регулярки и надежда

Первое, что приходит в голову — написать регулярные выражения. Ну правда, кажется же несложно: вырезать всё до цифр, потом взять цифры, потом найти модели машин...

Написал. Работало на 10 примерах. На 100 — начало сбоить. На 1000 — понял, что так не вывезу. Слишком много вариантов написания, слишком много исключений, слишком много ручной работы под каждого поставщика.

Этап 2. Словари и регулярки (и почему они сдохли)

Пошёл дальше. Начал писать регулярные выражения под каждый тип данных. Отдельная регулярка для размеров, отдельная — для моделей BMW, отдельная — для брендов.

Регулярки работали, но их становилось всё больше. Сначала 10, потом 50, потом 200. Каждая новая регулярка замедляла обработку. Замерил: на 10 тысячах записей скорость упала в 3 раза. На 100 тысячах — в 10 раз.

Регулярки умерли. Они просто не масштабировались.

Этап 3. Aho-Corasick (алгоритм, который спас)

Тогда вспомнил про Aho-Corasick — это алгоритм для одновременного поиска множества подстрок за один проход текста. Сделал словари: «хомут» → «хомут», «хомутик» → «хомут», «clamp» → «хомут», «хомут винтовой» → «хомут винтовой».

Скорость вернулась. Мог искать 1000 паттернов за то же время, что раньше искал 1. Но качество упёрлось в потолок. Словари не покрывали всех вариантов написания, особенно когда в строке была каша из цифр, букв и служебных символов.

Этап 4. Подключил ИИ (и здесь началось интересное)

Когда понял, что правила и словари не покрывают всех случаев, решил попробовать нейросети. И тут ждал сюрприз.

Оказалось, что:

  1. Дорогие модели (GPT-4, Claude) работают отлично, но стоят денег

  2. Дешёвые модели работают так себе

  3. Есть локальные модели, которые можно дообучить под нашу специфику через LoRA — и они работают почти как дорогие, но за копейки

  4. А для 80% простых задач ИИ вообще не нужен — хватает тех же регулярных выражений или тривиальных преобразований

Этап 5. Комбинация инструментов

Разделил задачи по сложности и под каждую подобрал свой инструмент:

  1. Базовые операции (trim, lowercase, collapse_whitespace и тд.) — для приведения строк к единому виду

  2. Regex — для простых замен с чёткими паттернами (например, замена 5W-20 на 5w20 или удаление лишних символов)

  3. Aho-Corasick — для массовых замен по словарям (синонимы, бренды, модели)

  4. Локальные модели (LoRA) — для извлечения сущностей в типовых случаях

  5. Тяжёлые модели (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. Почему так высоко в рейтинге запчастей? Несколько причин:

  1. Массовость в своём сегменте: A3 — самый доступный Audi, его продавали огромными тиражами. За 10+ лет выпуска накопился большой парк.

  2. Возраст парка: Многие A3 первого и второго поколений (выпуска 1996-2012 годов) уже требуют серьёзного обслуживания. Владельцы готовы вкладываться в ремонт, так как покупка нового премиум-автомобиля обходится дороже.

  3. Ремонтопригодность: Немецкие автомобили хорошо поддаются ремонту, а для 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. Долго не решался написать статью — это моя первая попытка поделиться опытом. Писал, чтобы рассказать о решении сложной задачи нормализации данных и обсудить подходы, которые могут оказаться полезными другим разработчикам и аналитикам. Если что-то сделал не так или упустил — сори, буду рад конструктивной критике в комментариях.

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.

Вам также может быть интересно