Поставщик пишет, что продаёт, как умеет: с опечатками и без половины свойств. Система находит позицию КТРУ, сужает группу до одного кода и заполняет характеристики значениями из справочника.






Код КТРУ выдаётся, только когда по правилам справочника осталась ровно одна позиция. Модели находят кандидатов и проверяют формулировки, но код выбирают не они: статус «позиция определена» вычисляется из оставшихся позиций, а тест
test_indistinct_positions_are_ambiguous_in_catalogпадает, если неразличимые позиции получат код.
Чтобы завести карточку на Портале поставщиков, нужно найти позицию КТРУ среди 1 967 позиций трёх категорий и для каждой характеристики выбрать допустимое значение и единицу. В выгрузках СТЕ 299 926 строк, и один товар назван десятками способов: по названию из случайной строки выгрузки поиск ставит правильную группу первой лишь в 23 случаях из 42 (доказательства).
Поставщик пишет «Шкаф для одежды металлический, глубина 0,55 м». Система находит группу «Шкаф для
одежды металлический» из двух позиций, исключает одну с причиной «вид товара: секция шкафа;
указано шкаф», раскладывает глубину по характеристике с единицей и задаёт один вопрос про тип
шкафа. После ответа карточка готова, с кодом 31.01.11.121-00000003.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#ffffff","primaryTextColor":"#1a1a1a","primaryBorderColor":"#c92723","secondaryColor":"#e7eef7","tertiaryColor":"#f1f5fb","lineColor":"#7f8792","textColor":"#1a1a1a","noteBkgColor":"#e7eef7","noteTextColor":"#5f6672","actorBkg":"#ffffff","actorBorder":"#c92723","fontFamily":"Open Sans"}}}%%
flowchart LR
A["Описание товара"] --> B["Поиск групп<br/>TF-IDF, векторы, синонимы"]
B --> C["Проверка назначения<br/>OpenJev"]
C --> D{"Одна группа<br/>подтверждена?"}
D -- "нет" --> Q["Вопрос о назначении"]
D -- "да" --> E["Факты из текста<br/>и их проверка"]
E --> F["Правила КТРУ<br/>сужают позиции"]
F --> G{"Позиций осталось"}
G -- "одна" --> R["Код и карточка"]
G -- "несколько" --> Q2["Вопрос по различающей<br/>характеристике"]
G -- "ноль" --> K["Противоречие<br/>с причинами"]
|
1. Три метода поиска сливаются в один список Символьные n-граммы ловят опечатки, векторы Qwen3-Embedding — смысл, словарь синонимов — бытовые названия вроде «болгарки». Ранги сливаются RRF, на проверку уходят десять групп. R@5 = 42 из 46, поиск групп |
2. Модель проверяет назначение, но не выбирает наугад OpenJev оценивает каждую группу: соответствует, противоречит или нет сведений. Группа выбирается сама, только если подтверждена одна, с оценками не ниже 0,90; иначе поставщику задаётся вопрос. 0 ошибочных автовыборов из 10, доказательства |
3. Правила справочника сужают позиции и объясняют каждое исключение Факт исключает позицию, если противоречит её значению или диапазону с учётом единиц. Неизвестное не исключает. У каждой исключённой позиции — причина словами справочника.
|
После запуска, адрес http://localhost:5173/sell.
| Что сделать | Что увидите | |
|---|---|---|
| 1 | Ввести «Шкаф для одежды металлический, глубина 0,55 м» и нажать «Подобрать позицию» | Путь «Мебель › Шкаф для одежды металлический», «Подходит позиций: 1 из 2», вторая позиция исключена с причиной |
| 2 | «Продолжить», в строке «Тип шкафа» выбрать «Секционный» | Таблица характеристик, «Заполнено обязательных: 2 из 2» |
| 3 | «Новая карточка», ввести «Перчатки» | Вопрос «Для какого назначения нужен товар?» вместо угаданной группы |
| Модель | Accuracy@1 | Hit@5 | Hit@20 | MRR@20 |
|---|---|---|---|---|
| Qwen3-Embedding-0.6B | 62 % | 79 % | 90 % | 0,70 |
| Qwen3-Embedding-4B | 77 % | 91 % | 97 % | 0,83 |
| Qwen3-Embedding-8B | 79 % | 93 % | 98 % | 0,85 |
Accuracy@1 — доля запросов с правильным результатом на первом месте; Hit@5 и Hit@20 — доля запросов с правильным результатом в первых 5 и 20 кандидатах. MRR@20 — средний обратный ранг первого правильного результата в топ-20; если его нет, вклад запроса равен нулю.
Через HTTP API продукта на временной базе, с настоящими моделями на A100 40 ГБ; наборы зафиксированы по хешу, пороги и их источники — в плане валидации.
| Метрика | Решение | База | Порог |
|---|---|---|---|
Правильная группа в топ-5, smoke_v0 |
42 из 46 | 42 из 46, TF-IDF и bge-m3 | ≥ 42 из 46 |
| Правильная группа в топ-5, реальные СТЕ | 27 из 42 | 26 из 42 без словаря синонимов | не задан |
Верный выданный код, dev_v1 |
21 из 24 | — | ≥ 95 % |
| Верные автоматически принятые значения | 20 из 20 | — | ≥ 99 % |
| Ошибочные автовыборы группы | 0 из 10 | — | не задан |
| Видеопамять всех моделей | 37,1 ГБ | — | ≤ 40 ГБ |
Где проигрываем. На реальных названиях из выгрузок правильная группа в топ-5 только у 27 из 42: бренды и артикулы не похожи на наименования групп. Выданный код верен в 88 % случаев при цели 95 %; прогон сделан до подключения проверки назначения.
Пересчитать все числа раздела
backend/.venv/bin/python evals/smoke/bench_search.py --embeddings-url http://127.0.0.1:8081/v1 \
--database artifacts/ktru-search.sqlite3 --reuse-database
cd backend && uv run python ../evals/e2e/run.py --embeddings-url http://127.0.0.1:8081/v1 && cd ..
backend/.venv/bin/python evals/enum/run.py --url http://127.0.0.1:18080 --threshold 0.90 \
--holdout evals/datasets/enum_holdout_v2.json --out var/enum-mapping.json
backend/.venv/bin/python evals/groups/run.py --output var/group-validation.jsonНужны сервисы моделей на GPU из запуска. Отчёты пишутся в
evals/reports/ и var/; сохранённые отчёты — в evals/reports/.
mkdir -p var && cp artifacts/ktru-search.sqlite3 var/tender.db
cd backend && uv sync && uv run alembic upgrade head && uv run uvicorn --factory main:create_appcd frontend && npm ci --silent && npm run mock-api &
cd frontend && STOREFRONT_URL=http://localhost:8001 API_URL=http://localhost:8000 npm run devИнтерфейс — http://localhost:5173, Swagger — http://localhost:8000/api/docs. Справочник,
витрина и проверки работают без GPU и сети. Анализ описаний требует трёх локальных моделей:
EMBEDDINGS_URL, DECISION_URL и EXTRACTOR_URL; без них сессия получает статус технического
сбоя. Платные API не нужны.
Модели, проверки, переменные
OPENJEV_PORT=18080 docker compose --profile gpu up --build -d openjev
docker compose -f compose.yaml -f compose.qwen.yaml --profile gpu up -dПроверки: бэкенд — cd backend && uv run ruff check . && uv run mypy . && uv run pytest,
фронтенд — cd frontend && npm run lint && npm run typecheck && npm test && npm run e2e.
Переменные окружения, профили GPU и тесты — в запуске и проверках.
Интерфейс на React, API на FastAPI с SQLite и три модели в отдельных GPU-сервисах. Ключевое решение — выдать код или задать вопрос — принимают правила справочника в сценарии анализа, а не модели.
| Документ | Что внутри |
|---|---|
| Архитектура | Части стенда, путь одного сообщения, слои, что заменяется без правки кода |
| API | Методы, ответ анализа, формат ошибок; OpenAPI — /api/docs |
| Зависимости | Модели, библиотеки и сервисы с версиями и лицензиями |
| Глоссарий | Термины КТРУ, работы поставщика и статусы анализа |
| Оглавление | Все страницы документации |
- На реальных названиях поиск слабее. Правильная группа в топ-5 у 27 из 42 строк выгрузки СТЕ; вместо угадывания система спрашивает о назначении.
- Анализ требует GPU. Три модели занимают 37,1 ГБ на A100 40 ГБ; без них работают справочник, витрина и все тесты, а анализ описаний — нет.
- Витрина работает на моке. Позиции и характеристики настоящие, цены и поставщики выдуманы.
Все ограничения, со статусом и тем, что закрыло бы каждое, — в ограничениях.

Команда GoatWhistle, Tender Hack, Казань, 2026. Лицензия MIT.














