5.3 KiB
Поиск товара по сообщению
1. Запуск
Нужен Python 3.12. Из корня проекта:
python -m pip install -r requirements.txt && python -m app
Сервис поднимется на http://localhost:8000, метод — POST /match.
2. Решения и компромиссы
Каталог небольшой — 466 позиций, поэтому он один раз загружается в память. Запрос нормализуется, из него извлекаются тип товара, бренд, модель и характеристики. Несовместимые позиции отсекаются точными правилами, оставшиеся ранжируются с помощью BM25, символьных триграмм и Damerau–Levenshtein.
Чистое нечёткое ранжирование я отбросил: оно помогает с опечатками, но не должно решать, совместимы ли 125 и 150, T30 и T50 или 3×2.5 и 4×2.5. Поэтому текстовая похожесть отвечает за поиск кандидатов, а числа, модели и характеристики — за жёсткую фильтрацию. Elasticsearch позволил бы собрать похожую схему, но для 466 строк добавил бы отдельный сервис и лишнюю настройку без практической пользы.
Алиасы хранятся как «канонический тип → несколько вариантов»: например, одно понятие может находиться по полному названию, сокращению или разговорному слову. Регулярные выражения используются для нормализации и разбора размеров, резьбы, мощности и других форматов; пустые сообщения отсекаются отдельно.
Трансформеры ТЗ напрямую не запрещает — запрещены внешние API, интернет и ключи. Локальную модель я осознанно не добавлял: она утяжелила бы запуск, а точные артикулы и размеры всё равно пришлось бы проверять правилами. При сомнении сервис возвращает ambiguous, а не угадывает. confidence — оценка для сортировки, не вероятность.
До продакшена я бы вынес алиасы и правила из кода, проверил качество на размеченных реальных запросах, откалибровал пороги, добавил метрики, журнал причин отказа и нагрузочные тесты. Базу, Docker, авторизацию и фронтенд не делал: для тестового задания они не нужны.
3. Масштаб
При 2 млн позиций и 50 магазинах каталог нельзя загружать и перебирать в каждом процессе. В PostgreSQL я бы хранил store_id, sku, нормализованное название, тип, бренд, модель и часто используемые характеристики в отдельных типизированных колонках; редкие атрибуты — в JSONB.
Нужны как минимум уникальный B-tree по (store_id, sku) и составные B-tree-индексы, начинающиеся со store_id, для точных фильтров — например (store_id, product_type, brand). Для размеров и других чисел индексы подбирались бы по реальным запросам. По названию B-tree не поможет: там нужны GIN-индексы для PostgreSQL FTS и/или pg_trgm.
Поиск останется двухэтапным: PostgreSQL по магазину, типу и точным характеристикам выберет небольшой набор кандидатов, приложение проверит совместимость и сформирует ответ. store_id должен приходить из контекста запроса и участвовать во всех запросах к БД; при необходимости таблицу можно секционировать по нему.
Векторный поиск через pgvector можно добавить для семантического расширения кандидатов, если замеры покажут пользу. Полностью заменять им точные фильтры нельзя: эмбеддинги ненадёжны для размеров, моделей и артикулов. Загрузку каталогов я сделал бы версионной: импорт во временную версию, проверка и атомарное переключение без простоя.