- BrainTools - https://www.braintools.ru -

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

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

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

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

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

понимать:

  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
Как я готовился к Честному знаку и разработал подход к нормализации данных - 1 [1]

Что это даёт:

  • Можно обогащать данные в реальном времени

  • Можно проверять товары по внешним справочникам

  • Можно интегрироваться с чем угодно без доработки платформы

И всё это безопасно — 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"
Как я готовился к Честному знаку и разработал подход к нормализации данных - 2 [1]

После прохода через:

{
  "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"
      }
    }
  ]
}
Как я готовился к Честному знаку и разработал подход к нормализации данных - 3 [1]

И теперь, имея предметы, через матрицу соответствий автоматически отношу этот товар к категориям:

  • Фильтры (основная)

  • Масляная система (дополнительная)

Эта универсальная система категоризации теперь позволяет точно определить, какие товары подлежат маркировке по «Честному знаку». Правила маркировки привязаны к категориям товаров и их свойствам, и теперь, когда мы знаем категории всех 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%
Как я готовился к Честному знаку и разработал подход к нормализации данных - 4 [1]

Что мы видим: Корейские бренды (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%
Как я готовился к Честному знаку и разработал подход к нормализации данных - 5 [1]

Что мы видим: Фильтры (всех типов) в сумме дают около 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%
Как я готовился к Честному знаку и разработал подход к нормализации данных - 6 [1]

Что мы видим: Ожидаемо лидируют бюджетные модели (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%
Как я готовился к Честному знаку и разработал подход к нормализации данных - 7 [1]

Что мы видим: Пик приходится на 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)

Что дальше

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

Если тема нормализации данных, подготовки к Честному знаку или работа с большими объёмами неструктурированной информации вам интересна — давайте обсудим в комментариях или личных сообщениях. Мне интересно узнать, с какими похожими проблемами сталкиваетесь вы и какие подходы находите эффективными.

Особенно было бы полезно услышать от тех, кто:

  • Работает с похожими объёмами данных

  • Уже сталкивался с необходимостью маркировки товаров

  • Имеет опыт [2] внедрения ИИ-решений для обработки текстов

  • Пытался решать подобные задачи другими способами

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


P.S. Долго не решался написать статью — это моя первая попытка поделиться опытом. Писал, чтобы рассказать о решении сложной задачи нормализации данных и обсудить подходы, которые могут оказаться полезными другим разработчикам и аналитикам. Если что-то сделал не так или упустил — сори, буду рад конструктивной критике в комментариях.

Автор: IgorBatanov

Источник [3]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/26641

URLs in this post:

[1] Image: https://sourcecraft.dev/

[2] опыт: http://www.braintools.ru/article/6952

[3] Источник: https://habr.com/ru/articles/1006992/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1006992

www.BrainTools.ru

Rambler's Top100