AAYMA
RU
Войти

Ассистент EKT.KZ

Веб-ассистент поиска по каталогу ekt.kz: импорт из EKT API в PostgreSQL, поиск по названию и артикулу, карточка с остатками и расхождениями…

Прототип
Команда «Powerpuff» 6💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: Powerpuff
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Прототип

Задача кейса: AI-консультант по каталогу ekt.kz: товары, аналоги, цены, остатки, корзина

Суть

Веб-ассистент поиска по каталогу ekt.kz: импорт из EKT API в PostgreSQL, поиск по названию и артикулу, карточка с остатками и расхождениями характеристик, кандидаты-аналоги, корзина через цепочку «предложение → подтверждение». LLM-чат есть в бэкенде, но UI его не вызывает.

Как устроено

Spring Boot синхронизирует каталог EKT (профиль catalog-sync) в PostgreSQL с миграциями Flyway. Frontend на React ищет по локальной БД и запрашивает детали карточки у EKT. Бэкенд нормализует единицы и находит расхождения номинального тока. Корзина работает по схеме «предложение → явное подтверждение → повторная проверка цены и остатка». Отдельно в ml/ на Python сделан поиск по эмбеддингам: гибридный, двухэтапный, с парсером запросов. Он не подключён к основному сценарию.

Ключевые решения

  • гибридный поиск (эмбеддинги + лексический)
  • двухэтапный retrieval
  • query parser
  • детекция расхождений характеристик
  • подтверждение корзины с перепроверкой

Особенности

README честно говорит, что интерфейс работает без LLM: это поиск и сценарии по данным API. Отмечена странность EKT API, где после последней страницы повторяется первая. Но в дереве основной ветки видны только Item-CRUD и ML-заглушка, а README сам предупреждает, что код ассистента может лежать в другой ветке.

Стек

Java 21 · Spring Boot · PostgreSQL · Flyway · React 19 · TypeScript · Vite · Docker Compose · Python · FastAPI · scikit-learn · JUnit · Testcontainers

Модели

OpenAI Responses API (LLM_MODEL из env)


Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-beb0181c-powerpuff.

README

README.md

Ассистент EKT.KZ — Powerpuff

Проект команды Powerpuff для кейса ТОО «Электрокомплект» (ekt.kz): веб-интерфейс поиска электротехнических товаров с характеристиками, складскими остатками и демонстрационной корзиной.

Покупателю или закупщику нужно найти нужный артикул, проверить наличие и понять, подходит ли товар. Информация может быть неполной или противоречивой: например, в названии указано 160 А, а в свойствах — 250 А. Ассистент собирает эти сведения в одном интерфейсе, показывает расхождения и требует явного подтверждения перед добавлением в корзину. Сокращение времени подбора — цель проекта; количественный эффект пока не измерялся.

Текущий интерфейс работает без модели: введённый текст используется для поиска по названию или артикулу, ответы формируются по данным API. В backend отдельно реализован чат с OpenAI, но интерфейс /assistant его пока не вызывает. Отдельный Python ML-сервис для основного сценария не нужен.

Локальное демо после запуска: ассистент · корзина. Подтверждённой публичной версии пока нет.

Что реализовано

ВозможностьТекущее состояние
Главная страницаReact-страница с переходом к ассистенту
КаталогИмпорт из EKT, сохранение в PostgreSQL, поиск по названию и артикулу, пагинация
Карточка товараИзображение, описание, цена, свойства, склады, ссылка на ekt.kz
Качество данныхВыявление расхождений номинального тока между названием, описанием и свойствами; нормализация единиц и пробелов, сохранение источников. Неизвестные значения не подменяются нулём
АналогиПредварительные кандидаты с объяснением; подтверждённая совместимость не заявляется
СессииВременный Bearer-токен, серверное состояние; токен интерфейса хранится в sessionStorage вкладки
КорзинаПредложение → подтверждение → добавление; проверка цены и остатка, защита от повторного подтверждения
Страница корзины/cart, актуальное состояние через API
Условия покупкиAPI и загрузка файла условий; при отсутствии утверждённого источника показывается соответствующее сообщение
СертификатыИзвлечение потенциальных сведений из свойств товара; реальный образец документа пока не подтверждён
Backend-чат, отдельно от UIOpenAI Responses API, выбор разрешённого действия и контекст диалога; требуется отдельная настройка

Ассистент адаптирован для desktop и мобильного экрана. Обработаны пустая выдача, ошибка соединения и истёкшая сессия. Запоздавший ответ поиска не заменяет результаты нового запроса.

Как работает решение

  1. Покупатель открывает /assistant и вводит название или артикул.
  2. Frontend запрашивает поиск в локальном каталоге PostgreSQL через Spring Boot.
  3. Покупатель выбирает карточку: подробности запрашиваются у EKT, интерфейс показывает характеристики, остатки и предупреждения.
  4. Кнопка «В корзину» создаёт предложение с товаром, количеством и ценой. Сама корзина ещё не изменяется.
  5. После явного подтверждения backend повторно проверяет данные. Если условия изменились, требуется новое подтверждение.
  6. Страница /cart показывает добавленные позиции в той же сессии.

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

Технологии

КомпонентТехнологии
FrontendReact 19, TypeScript 6, Vite 8, CSS, Fetch API
BackendJava 21, Spring Boot 3.5.6, Spring Web, RestClient, Validation, Actuator
ДанныеPostgreSQL 16, Spring Data JPA, JdbcTemplate, Flyway; JSONB для исходных свойств
Внешний каталогEKT HTTP API, Basic Auth только на сервере
Опциональная LLMOpenAI Responses API; модель выбирается через LLM_MODEL, фиксированного значения в публичной конфигурации нет
ПроверкиJUnit, Mockito, Testcontainers, H2 для быстрых тестов, Postman, TypeScript, Oxlint
Локальное окружениеDocker Compose для PostgreSQL, Maven Wrapper, npm

В ml/ сохранён отдельный пример на Python, FastAPI и scikit-learn с endpoint предсказания. Он не участвует в текущем каталоге и корзине; его запуск описан в ml/README.md.

Архитектура

Браузер: React /assistant и /cart
                 │ HTTP /api; Bearer-токен для корзины
                 ▼
        Vite proxy → Spring Boot :8080
                       ├── поиск / сессии / корзина → PostgreSQL
                       ├── карточки и проверка предложения → EKT API
                       ├── условия покупки → локальный утверждённый JSON
                       └── /api/chat → OpenAI (опционально, отдельно от UI)

Отдельный процесс catalog-sync → EKT API → PostgreSQL
frontend/src/assistant/     API-клиент, состояние и компоненты ассистента
frontend/src/components/   Компоненты главной страницы
backend/src/main/java/     Контроллеры, каталог, синхронизация, чат и корзина
backend/src/main/resources/db/  Миграции Flyway
backend/docs/              Контракт API, ограничения и доказательства проверок
backend/postman/           Коллекции для проверки API
ml/                        Отдельный пример ML-сервиса
docker-compose.yml         PostgreSQL с сохраняемым volume

Установка и запуск

Требования

  • Git, JDK 21 и запущенный Docker с Compose.
  • Node.js 24 для воспроизводимого запуска frontend. Требование установленного Vite: ^20.19.0 || >=22.12.0.
  • Доступ к интернету для первой установки зависимостей и обращений к EKT.
  • Учётные данные EKT от партнёра для импорта, свежих карточек и подтверждения корзины.

Команды ниже предназначены для bash/zsh на macOS/Linux. Все шаги начинаются из корня репозитория, если не указано иначе. На Windows используйте mvnw.cmd и задайте переменные окружения через IDE или PowerShell.

1. Получить проект и настроить окружение

git clone https://github.com/BAITC-Hacks/hack-beb0181c-powerpuff.git
cd hack-beb0181c-powerpuff

Для закрытого репозитория требуется доступ участника. Убедитесь, что выбрана ветка с объединёнными frontend и backend; если папки frontend/src/assistant нет, уточните актуальную ветку у команды.

Если локального .env ещё нет:

cp .env.example .env

Отредактируйте .env:

ПеременнаяЧто указать
DB_URLjdbc:postgresql://localhost:5432/hackdb
DB_USERNAME, DB_PASSWORDЛокальные доступы PostgreSQL; замените пароль-заглушку
DB_PORT5432; при изменении также исправьте порт в DB_URL
EKT_ENABLEDtrue для реальной интеграции
EKT_BASE_URLhttps://ekt.kz
EKT_USERNAME, EKT_PASSWORDДоступы, выданные партнёром
FRONTEND_BASE_URLhttp://localhost:5174 — для ссылки на корзину
CORS_ORIGINShttp://localhost:5174; сохраните другие нужные origins через запятую
LLM_API_KEY, LLM_MODELМожно оставить пустыми для текущего интерфейса без LLM
PURCHASE_TERMS_FILEПуть к утверждённому JSON либо пустое значение

Не коммитьте .env и не помещайте секреты в переменные VITE_*: они доступны браузеру. Пример условий находится в backend/docs/purchase-terms.example.json; пример не является утверждёнными условиями партнёра.

2. Запустить PostgreSQL

docker compose up -d --wait db
docker compose ps db

Данные сохраняются в volume pgdata. Изменение пароля в .env не меняет пароль пользователя уже созданной базы. Для старой базы с несовместимой схемой используйте инструкцию backend/README.md, не удаляйте volume с нужными данными.

3. Запустить backend

В отдельном терминале, из корня, загрузите свой доверенный .env в окружение:

set -a
. ./.env
set +a
cd backend
./mvnw spring-boot:run

JAVA_HOME должен указывать на JDK 21. Flyway применяет миграции при старте. Maven устанавливать отдельно не требуется: используется Wrapper.

Альтернатива: импортировать backend/pom.xml в IDE, выбрать Java 21, передать переменные из .env в Environment variables и запустить com.powerpuff.backend.BackendApplication. Spring Boot сам .env не читает. Не запускайте второй экземпляр поверх работающего сервера.

Проверка:

curl http://localhost:8080/api/health
curl http://localhost:8080/actuator/health

Первый endpoint возвращает {"status":"ok"}, второй проверяет в том числе подключение к БД.

4. Загрузить каталог в новую базу

Поиск использует PostgreSQL: после первого запуска база ещё не содержит каталог EKT. В новом терминале из корня выполните:

set -a
. ./.env
set +a
cd backend
./mvnw spring-boot:run \
  -Dspring-boot.run.jvmArguments="-Dspring.devtools.restart.enabled=false" \
  -Dspring-boot.run.profiles=catalog-sync

Профиль не поднимает HTTP-сервер, работает параллельно основному backend и завершается после обработки. По умолчанию обрабатывается одна страница и до 20 подробных карточек. Повтор продолжает незавершённый запуск. Для получения демонстрационного товара может потребоваться несколько запусков.

Проверьте результат:

curl http://localhost:8080/api/catalog/status

totalProducts показывает число сохранённых товаров, productsWithDetails — число карточек с деталями, run.complete — завершённость всего запуска. Пробная загрузка с STOPPED / PAGE_LIMIT ожидаема и не означает ошибку. Не запускайте два импортера одновременно.

Параметры полного импорта, возобновления и ограничения нагрузки: отчёт об импорте. В наблюдаемом EKT API после последней страницы повторяется первая; обработка этого поведения включается отдельно через catalog.sync.accept-short-page-wrap. Наличие товаров списка не означает, что все подробные карточки уже загружены.

5. Запустить frontend

В новом терминале из корня:

cd frontend
npm ci
npm run dev -- --host 127.0.0.1 --port 5174 --strictPort

Откройте:

Все страницы открывайте через один hostname: localhost и 127.0.0.1 имеют разное хранилище сессии. Порт 5174 используется для этого демо; при выборе другого обновите FRONTEND_BASE_URL и CORS_ORIGINS.

По умолчанию Vite проксирует /api на http://localhost:8080. Дополнительные настройки — в frontend/.env.example: BACKEND_PROXY_TARGET меняет адрес proxy, VITE_API_BASE_URL задаёт прямой адрес API. При прямом подключении необходим корректный CORS. Перезапускайте процессы после изменения окружения.

Как проверить решение — сценарий для жюри

Перед демонстрацией запустите PostgreSQL, backend и frontend, загрузите хотя бы пробную выборку. Для свежей карточки и подтверждения корзины необходим работающий доступ к EKT. Используйте одну вкладку и один hostname.

ШагДействиеОжидаемый результат
1Откройте /api/health, /actuator/health, /api/catalog/statusПриложение отвечает ok, БД — UP, в каталоге есть товары
2В ассистенте введите Legrand или артикул 030300435_ из ранее проверенной первой страницыРезультаты поиска из PostgreSQL; если состав каталога изменился, используйте артикул видимой карточки
3Откройте найденный товар и нажмите «Проверить наличие»Карточка с исходными характеристиками и остатками по складам
4Выберите товар с известной ценой и положительным остатком, задайте количество с учётом кратности и нажмите «В корзину»Появляется предложение с количеством и ценой; корзина ещё не меняется
5Нажмите «Подтвердить добавление», затем откройте /cartТовар появляется в локальной корзине. При изменении цены или остатка сначала требуется повторное подтверждение новых условий
6Откройте «Доставка и оплата»Без утверждённого файла показан статус «не подтверждено»

Дополнительные сценарии для подготовленного каталога:

  • Конфликт характеристик: артикул 200300285_, ID 515291. Если исходные сведения не изменились, карточка показывает расхождение 160/250 А с источниками, не выбирая правильное значение.
  • Отсутствующий товар: артикул 310100024_, ID 21182. На момент проверки остаток был нулевым; «Подобрать аналог» возвращал сообщение об отсутствии подтверждённой замены. Похожий товар не должен называться совместимым без проверки характеристик.
  • Нет связи: остановите свой backend и повторите открытие карточки. Интерфейс должен показать ошибку и возможность повторить загрузку. Затем снова запустите backend.

Эти ID могут отсутствовать в пробной выборке: импортируйте дополнительные страницы или используйте подготовленную базу команды. Цены, остатки и содержимое внешнего каталога могут измениться; указанные примеры не являются фиксированными тестовыми данными.

Полный HTTP-сценарий доступен в коллекции Postman. Отдельный сценарий отсутствующего товара: коллекция. Настройка переменных и порядок запуска: backend/postman/README.md.

Основные API

МетодМаршрутНазначение
GET/api/products/search?q=Legrand&page=0&size=20Поиск в PostgreSQL
GET/api/catalog/products?page=0&size=20Сохранённый список
GET/api/catalog/products/{id}Сохранённая карточка и сведения об обновлении
GET/api/products/{id}Свежая карточка EKT
GET/api/catalog/statusПрогресс импорта
GET/api/products/{id}/analogsПредварительные кандидаты
GET/api/purchase-termsУсловия и статус подтверждения
POST/api/sessionСоздание сессии без тела запроса
GET/api/cartКорзина текущей сессии
POST/api/cart/proposalsПредложение без изменения корзины
POST/api/cart/proposals/{id}/confirmЯвное подтверждение
POST/api/chatОпциональный backend-чат с LLM

Корзина и чат требуют Authorization: Bearer <токен из /api/session>. Токен сессии не является ключом OpenAI. Полный формат запросов и ошибок: контракт frontend API.

Проверки и доказательства

Команды backend из backend/:

./mvnw test
# Отдельная тестовая PostgreSQL через Testcontainers; требуется Docker:
./mvnw -DpostgresTests=true test

Команды frontend из frontend/:

npm run build
npm run lint

В отчёте backend зафиксировано 58 прошедших автоматических тестов и HTTP-прогоны с явно пропущенными проверками утверждённых условий и подтверждённой замены. Отчёт frontend описывает сборку, lint и браузерные проверки на desktop и mobile. Это результаты зафиксированных прогонов, а не гарантия текущего состояния внешнего API; при проверке новой версии команды следует выполнить заново.

Данные и интеграции

  • EKT API: /api/products?page=N и /api/products/detail?id=ID, серверная Basic Auth. Артикулы сохраняются строками, включая ведущие нули и _.
  • PostgreSQL: товары, исходные свойства JSONB, склады, журнал импорта, сессии, предложения и корзина. Ошибка обновления не обнуляет прежний остаток.
  • OpenAI: опциональный серверный клиент с store=false; отправляет текст и ограниченный контекст для выбора действия. В текущем UI внешний LLM-вызов не выполняется. store=false не означает отсутствия всех форм хранения у провайдера.
  • Условия партнёра: внешний утверждённый JSON через PURCHASE_TERMS_FILE; тестовый пример не заменяет согласование.
  • Тесты: используют контролируемые fixtures и отдельные тестовые БД. Снимки реальных HTTP-ответов находятся в backend/docs/demo/.

Сохранённый снимок HTTP-проверки на 23.09.2026, 11:38 UTC содержит 15 037 товаров списка и 1 017 подробных карточек, run.complete=false. Это исторический снимок, не текущий счётчик. Актуальную полноту проверяйте через /api/catalog/status.

Ограничения текущей версии

  • Интерфейс без LLM ищет по названию/артикулу, не интерпретирует произвольные вопросы. Backend-чат подключается отдельно через серверные настройки.
  • Без доступов EKT работают health и поиск по ранее сохранённым данным; импорт, свежие карточки и подтверждение корзины требуют интеграции с поставщиком. На новой пустой базе поиска ещё нет.
  • Диагностика конфликтов сейчас реализована для номинального тока; это не универсальная проверка всех характеристик товара.
  • Корзина локальная: без оформления заказа, резервирования, оплаты и синхронизации с корзиной ekt.kz.
  • Подтверждённая реальная пара «отсутствующий товар → совместимый доступный аналог» пока не найдена в проверенных данных. Правила совместимости требуют согласования с партнёром.
  • Валюта, единицы количества, правила кратности, доступные для продажи склады и резервы требуют уточнения. Остаток поставщика не равен подтверждённой возможности отгрузки.
  • Утверждённые условия оплаты, доставки и минимального заказа не предоставлены.
  • Полнота импорта должна проверяться по статусу; список может быть загружен раньше подробных карточек.
  • Реальный сертификат в проверенных данных не подтверждён; универсальное получение документов по ID не реализовано.
  • Загрузка и распознавание Excel, Word, PDF и изображений не реализованы.
  • Казахский язык, аккаунты с постоянной историей и реальная передача обращения менеджеру не реализованы.
  • Гарантия ответа за единицы секунд и нагрузочные характеристики не установлены.
  • Development proxy Vite не входит в production-сборку. Для размещения нужен reverse proxy /api и fallback /assistant и /cart на index.html.

Развёрнутая версия

Подтверждённая публичная deployed-ссылка в репозитории не указана. Для демонстрации используйте локальный запуск выше. Docker Compose в этом проекте поднимает только PostgreSQL.

Дополнительные материалы

Команда хакатона: Powerpuff. Партнёр кейса: ТОО «Электрокомплект» (ekt.kz).

Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
18
Авторы
4
Файлы
60
Ветки
3

Авторы коммитов

Nurseiit Zhuzbay 7kuatsaparali 5Symbat 5Kuat 2

История коммитов

  • 2026-09-23 17:57feat: integrate frontend and backend, improve cartkuatsaparali
  • 2026-09-23 17:50Update README.mdSymbat
  • 2026-09-23 17:48Update README.mdSymbat
  • 2026-09-23 17:46Update README.mdKuat
  • 2026-09-23 17:44Update README.mdKuat
  • 2026-09-23 17:41READMESymbat
  • 2026-09-23 17:26docs: add Russian project READMEkuatsaparali
  • 2026-09-23 16:43Merge remote-tracking branch 'origin/feature/frontend' into backendkuatsaparali
Все коммиты (18)
  • 2026-09-23 16:35Document AI retrieval architectureNurseiit Zhuzbay
  • 2026-09-23 16:25Главная страница ekt.kz на Reactsymbat
  • 2026-09-23 16:16feat: add EKT catalog, chat and confirmed cart workflowkuatsaparali
  • 2026-09-23 15:40Add two-stage product retrieval pipelineNurseiit Zhuzbay
  • 2026-09-23 15:17Add normalized hybrid product searchNurseiit Zhuzbay
  • 2026-09-23 14:48Add query parser and retrieval test queriesNurseiit Zhuzbay
  • 2026-09-23 14:36Add product retrieval and embedding pipelineNurseiit Zhuzbay
  • 2026-09-23 14:10feat: configure backend with PostgreSQL and Flywaykuatsaparali
  • 2026-09-23 12:39Python with virtual environment folder addedNurseiit Zhuzbay
  • 2026-09-23 12:28Project skeleton Spring Boot + React TSNurseiit Zhuzbay
Структура репозитория (60 файлов)
.gitignore
README.md
backend/.env.example
backend/pom.xml
backend/src/main/java/com/powerpuff/backend/BackendApplication.java
backend/src/main/java/com/powerpuff/backend/config/CorsConfig.java
backend/src/main/java/com/powerpuff/backend/controller/HealthController.java
backend/src/main/java/com/powerpuff/backend/controller/ItemController.java
backend/src/main/java/com/powerpuff/backend/dto/CreateItemRequest.java
backend/src/main/java/com/powerpuff/backend/dto/ItemDto.java
backend/src/main/java/com/powerpuff/backend/exception/GlobalExceptionHandler.java
backend/src/main/java/com/powerpuff/backend/exception/NotFoundException.java
backend/src/main/java/com/powerpuff/backend/ml/MlClient.java
backend/src/main/java/com/powerpuff/backend/ml/PredictController.java
backend/src/main/java/com/powerpuff/backend/ml/PredictRequest.java
backend/src/main/java/com/powerpuff/backend/ml/PredictResponse.java
backend/src/main/java/com/powerpuff/backend/model/Item.java
backend/src/main/java/com/powerpuff/backend/repository/ItemRepository.java
backend/src/main/java/com/powerpuff/backend/service/ItemService.java
backend/src/main/resources/application-postgres.yml
backend/src/main/resources/application.yml
backend/src/test/java/com/powerpuff/backend/BackendApplicationTests.java
docker-compose.yml
frontend/.env.example
frontend/.gitignore
frontend/.oxlintrc.json
frontend/index.html
frontend/package-lock.json
frontend/package.json
frontend/public/favicon.svg
frontend/src/App.tsx
frontend/src/api/client.ts
frontend/src/api/items.ts
frontend/src/components/ItemForm.tsx
frontend/src/index.css
frontend/src/main.tsx
frontend/src/pages/HomePage.tsx
frontend/src/types/item.ts
frontend/tsconfig.app.json
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
ml/.env.example
ml/.gitignore
ml/README.md
ml/add_test_product.py
ml/analyze_properties.py
ml/build_embeddings.py
ml/build_normalized_embeddings.py
ml/detail_client.py
ml/download_details.py
ml/download_products.py
ml/hybrid_search.py
ml/normalizer.py
ml/query_parser.py
ml/search.py
ml/semantic_search.py
ml/stage1_search.py
ml/stage2_search.py
ml/tests/test_queries.json
Ветки (3)

Обсуждение 0 комментариев

Войдите, чтобы комментировать

Пока нет комментариев — будьте первым.