О проекте
Команда: 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-чат, отдельно от UI | OpenAI Responses API, выбор разрешённого действия и контекст диалога; требуется отдельная настройка |
Ассистент адаптирован для desktop и мобильного экрана. Обработаны пустая выдача, ошибка соединения и истёкшая сессия. Запоздавший ответ поиска не заменяет результаты нового запроса.
Как работает решение
- Покупатель открывает
/assistantи вводит название или артикул. - Frontend запрашивает поиск в локальном каталоге PostgreSQL через Spring Boot.
- Покупатель выбирает карточку: подробности запрашиваются у EKT, интерфейс показывает характеристики, остатки и предупреждения.
- Кнопка «В корзину» создаёт предложение с товаром, количеством и ценой. Сама корзина ещё не изменяется.
- После явного подтверждения backend повторно проверяет данные. Если условия изменились, требуется новое подтверждение.
- Страница
/cartпоказывает добавленные позиции в той же сессии.
В интерфейсе доступны быстрые действия для наличия, кандидатов на замену, документов и условий покупки. Это сценарии по данным API, а не свободный разговор с моделью.
Технологии
| Компонент | Технологии |
|---|---|
| Frontend | React 19, TypeScript 6, Vite 8, CSS, Fetch API |
| Backend | Java 21, Spring Boot 3.5.6, Spring Web, RestClient, Validation, Actuator |
| Данные | PostgreSQL 16, Spring Data JPA, JdbcTemplate, Flyway; JSONB для исходных свойств |
| Внешний каталог | EKT HTTP API, Basic Auth только на сервере |
| Опциональная LLM | OpenAI 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_URL | jdbc:postgresql://localhost:5432/hackdb |
DB_USERNAME, DB_PASSWORD | Локальные доступы PostgreSQL; замените пароль-заглушку |
DB_PORT | 5432; при изменении также исправьте порт в DB_URL |
EKT_ENABLED | true для реальной интеграции |
EKT_BASE_URL | https://ekt.kz |
EKT_USERNAME, EKT_PASSWORD | Доступы, выданные партнёром |
FRONTEND_BASE_URL | http://localhost:5174 — для ссылки на корзину |
CORS_ORIGINS | http://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
Откройте:
- Главная: http://localhost:5174/
- Ассистент: http://localhost:5174/assistant
- Корзина: http://localhost:5174/cart
Все страницы открывайте через один 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_, ID515291. Если исходные сведения не изменились, карточка показывает расхождение 160/250 А с источниками, не выбирая правильное значение. - Отсутствующий товар: артикул
310100024_, ID21182. На момент проверки остаток был нулевым; «Подобрать аналог» возвращал сообщение об отсутствии подтверждённой замены. Похожий товар не должен называться совместимым без проверки характеристик. - Нет связи: остановите свой 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.
Дополнительные материалы
- Backend: конфигурация, импорт и тесты
- Frontend: запуск и браузерные проверки
- Контракт API
- Правила характеристик, аналогов и вопросы партнёру
- Postman
Команда хакатона: Powerpuff. Партнёр кейса: ТОО «Электрокомплект» (ekt.kz).
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.