О проекте
Команда: Strikers Club
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Доведённый продукт
Задача кейса: AI-консультант по каталогу ekt.kz: товары, аналоги, цены, остатки, корзина
Суть
Встраиваемый React-виджет AI-консультанта для ekt.kz. Ищет товары в живом каталоге EKT, подбирает аналоги, разбирает фото и спецификации (PDF/DOCX/XLSX) и после явного подтверждения добавляет товар в демо-корзину.
Как устроено
React-виджет общается с Node BFF, который хранит API-ключ на сервере и пропускает только разрешённые маршруты. BFF проксирует запросы в FastAPI. Бэкенд вызывает OpenAI Responses API с инструментами чата (chat_tools), обращается к EKT API за карточками, ценами и остатками, а гостевые сессии держит в Redis с TTL. Вложения проверяются через ClamAV и разбираются в список распознанных позиций с уверенностью. Корзина меняется только после подтверждения серверного предложения по уникальному operation_id, повтор идемпотентен.
Ключевые решения
- OpenAI Responses API function calling
- подбор аналогов с совпадениями и отличиями
- извлечение позиций из фото и документов
- идемпотентное подтверждение корзины по operation_id
- BFF-прокси
- ClamAV INSTREAM
Особенности
Продуманная безопасность: ключи не попадают в браузер, BFF ограничивает маршруты, размеры тел и origin, вложения сканирует антивирус. Есть двухшаговое подтверждение корзины с защитой от повторов и явный demo-режим без backend. В README заявлено 147 фронтенд-тестов, в репозитории лежат тестовые спецификации и накладные.
Стек
React 19 · TypeScript · Vite · Material UI · Redux Toolkit · TanStack Query · Node.js BFF · Python · FastAPI · Pydantic · Redis · ClamAV · Docker Compose · Vitest · Pytest
Модели
OpenAI (Responses API)
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-e3d8b0d1-strikers-club.
README
README.mdEKT x Kochshi
ИИ-ассистент по электротехнике
Полноценный AI-консультант для сайта ekt.kz. Ассистент понимает запрос обычным языком, ищет электротехнические товары, сравнивает аналоги, разбирает фотографии и спецификации и безопасно добавляет выбранную позицию в демонстрационную корзину.
Репозиторий содержит весь проект:
- React-виджет и презентационную страницу;
- same-origin BFF на Node.js;
- backend на FastAPI;
- Redis для временных сессий;
- профиль ClamAV для проверки вложений;
- frontend- и backend-тесты.
Локальная папка
backend/игнорируется Git: это может быть дополнительный checkout для разработки. Backend проекта уже находится в корневых папкахapp/,tests/и файлах Docker.
Проект за 30 секунд
Проблема
В каталоге электротехники тысячи похожих товаров и технических параметров. Покупателю приходится знать точный артикул, вручную сравнивать характеристики и проверять остатки. Ошибка в номинале или совместимости приводит к потере времени.
Решение
Strikers Club превращает поиск в диалог:
- Пользователь описывает задачу или прикладывает документ.
- AI находит товары в реальном каталоге EKT.
- Интерфейс показывает цену, остаток, параметры и отличия аналогов.
- Перед изменением корзины пользователь видит точное предложение.
- Товар добавляется только после явного подтверждения.
Польза
| Покупателю | Бизнесу | Команде |
|---|---|---|
| Поиск без знания каталога | Меньше потерянных обращений | Встраиваемый React-виджет |
| Понятное сравнение аналогов | Быстрее путь от вопроса к товару | Строгий API-контракт |
| Работа с фото и документами | Консультация 24/7 | Ключи остаются на сервере |
| Контроль перед корзиной | Безопасный UX без случайной покупки | Полный набор автотестов |
Возможности
- поиск по названию, артикулу и техническому описанию;
- свежая карточка товара с ценой, остатками и характеристиками;
- подбор аналогов с совпадениями, отличиями и неизвестными параметрами;
- внешние варианты с обязательной пометкой «требует проверки»;
- JPEG, PNG, PDF, DOCX и XLSX до 20 МБ;
- отображение распознанных позиций и уверенности извлечения;
- серверное предложение на добавление товара;
- подтверждение по уникальному
operation_id; - идемпотентный повтор после потери сетевого ответа;
- временная гостевая сессия с TTL;
- понятное восстановление после истечения сессии;
- автономный demo-режим для презентации без backend.
Демонстрация для хакатона
Откройте http://localhost:5173 и нажмите круглую кнопку чата в правом нижнем углу.
Сценарий на 2–3 минуты:
- Спросите: «Найди кабель ВВГнг 3×2,5».
- Покажите карточку и откройте «Подробнее».
- Попросите подобрать аналог и покажите явные отличия.
- Напишите: «Добавь 1 единицу товара с ID 515291 в корзину».
- Покажите, что до подтверждения корзина не изменилась.
- Подтвердите предложение и откройте внутреннюю демо-корзину.
- Приложите фото или спецификацию при запущенном ClamAV.
Корзина демонстрационная: заказ и резерв на ekt.kz не создаются.
Архитектура
flowchart LR
U[Пользователь] --> UI[React-виджет]
UI --> BFF[Node BFF<br/>/backend/api]
BFF -->|X-API-Key только на сервере| API[FastAPI]
API --> AI[OpenAI]
API --> EKT[Каталог EKT]
API --> R[(Redis TTL)]
API --> AV[ClamAV]
Зачем нужен BFF
Браузер не получает серверный API_KEY. Запросы идут на тот же origin через
/backend/api/*, а BFF:
- добавляет backend-ключ;
- передаёт токен гостевой сессии;
- разрешает только известные маршруты и методы;
- проверяет origin и Content-Type;
- ограничивает JSON до 16 КиБ и файл до 20 МиБ;
- устанавливает timeout 120 секунд;
- возвращает
request_idдля диагностики; - запрещает кеширование API.
Session token хранится только в памяти клиента. Он не записывается в Redux,
localStorage, URL или логи.
Технологии
| Слой | Технологии |
|---|---|
| Frontend | React 19, TypeScript, Vite, Material UI, Emotion |
| State | Redux Toolkit, TanStack Query |
| BFF | Node.js HTTP server, Vite middleware |
| Backend | Python 3.11+, FastAPI, Pydantic |
| AI и каталог | OpenAI Responses API, EKT API |
| Временные данные | Redis с TTL |
| Вложения | ClamAV INSTREAM |
| Тесты | Vitest, Testing Library, Pytest |
| Качество | ESLint, Prettier, Ruff, MyPy |
Быстрый запуск
Требования
- Node.js 22.12+;
- npm;
- Docker и Docker Compose;
- ключ OpenAI;
- доступ к EKT API.
1. Настройте окружение
Linux/macOS:
cp .env.example .env
Windows PowerShell:
Copy-Item .env.example .env
Заполните в .env:
API_KEY=случайный_секрет_длиной_не_менее_32_символов
OPENAI_API_KEY=ваш_openai_key
EKT_API_USER=логин_ekt
EKT_API_PASSWORD=пароль_ekt
VITE_API_MODE=live
VITE_API_BASE_URL=/backend/api
BACKEND_URL=http://127.0.0.1:8000
BACKEND_API_PREFIX=/api
Для генерации API_KEY:
python -c "import secrets; print(secrets.token_urlsafe(48))"
Не добавляйте секреты в переменные с префиксом VITE_: они доступны браузеру.
.env и .env.local исключены из Git.
2. Запустите backend
С поддержкой файлов:
docker compose --profile uploads up --build -d
Только текстовый чат:
docker compose up --build -d
Проверьте readiness:
curl http://127.0.0.1:8000/api/health/ready
Ожидаемый ответ:
{ "status": "ok", "redis": "ok" }
3. Запустите frontend
npm install
npm run dev
Откройте http://localhost:5173.
Vite читает корневой .env, но передаёт в браузер только VITE_*. Значение
API_KEY использует серверная часть BFF.
Demo без backend
Если для презентации нет внешних ключей, создайте .env.local:
VITE_API_MODE=demo
Затем:
npm install
npm run dev
Demo использует только синтетические данные. При сбое live-backend приложение не переключается в demo автоматически, поэтому проблемы интеграции остаются видимыми.
Адреса
| Сервис | Адрес |
|---|---|
| Frontend | http://localhost:5173 |
| BFF readiness | http://localhost:5173/backend/api/health/ready |
| Backend | http://127.0.0.1:8000 |
| Backend readiness | http://127.0.0.1:8000/api/health/ready |
| Swagger в development | http://localhost:8000/docs |
Swagger использует backend API_KEY. В production Swagger и OpenAPI отключены.
Основные переменные окружения
| Переменная | Назначение |
|---|---|
API_KEY | Авторизация BFF в FastAPI |
OPENAI_API_KEY | Доступ к OpenAI |
EKT_API_USER, EKT_API_PASSWORD | Доступ к каталогу |
REDIS_URL | Хранилище временных сессий |
UPLOAD_SCAN_HOST | Host ClamAV; для Compose — clamav |
VITE_API_MODE | live или явный demo |
VITE_API_BASE_URL | Путь браузера к BFF |
BACKEND_URL | Origin FastAPI для BFF |
BACKEND_API_PREFIX | Обычно /api |
BACKEND_API_KEY | Отдельный BFF-ключ; пустое значение использует API_KEY |
PUBLIC_ORIGIN | Точный origin за reverse proxy |
Полный список и безопасные значения по умолчанию находятся в .env.example.
HTTP API
| Метод | Путь | Назначение |
|---|---|---|
| POST | /api/chat/sessions | Создать гостевую сессию |
| POST | /api/chat | Отправить текст |
| POST | /api/chat/upload?session_id=… | Отправить файл сырым телом |
| GET | /api/products/{product_id} | Получить свежую карточку |
| GET | /api/cart?session_id=… | Получить демо-корзину |
| POST | /api/cart/items | Подтвердить предложение |
| GET | /api/health/live | Проверить процесс |
| GET | /api/health/ready | Проверить FastAPI и Redis |
Подробные контракты:
Безопасное подтверждение корзины
sequenceDiagram
participant U as Пользователь
participant UI as Виджет
participant API as Backend
U->>UI: «Добавь 2 единицы»
UI->>API: POST /chat
API-->>UI: pending_cart_action + operation_id
UI-->>U: Цена, остаток и срок
U->>UI: Явное подтверждение
UI->>API: POST /cart/items
API-->>UI: added или reconfirmation_required
- Новое предложение заменяет старое.
- Отмена отправляется backend сообщением «отмена».
- При изменении цены или остатка нужен новый клик.
- Повтор одного
operation_idне добавляет товар второй раз. cart_url— защищённый API-путь, а не checkout-ссылка.- Корзина всегда отмечена как демонстрационная.
Структура
.
├── app/ # FastAPI backend
├── tests/ # backend-тесты
├── docs/ # API и техническая документация
├── src/
│ ├── api/ # TypeScript-клиент и runtime-валидация
│ ├── components/ # чат, карточки и диалоги
│ ├── hooks/ # сессия, сообщения, файлы и корзина
│ ├── state/ # Redux state
│ └── styles/ # стили виджета и preview
├── server/ # Node BFF и static server
├── docker-compose.yml
├── Dockerfile
├── package.json
└── .env.example
Локальная дублирующая папка backend/ намеренно исключена из Git.
Команды frontend
| Команда | Назначение |
|---|---|
npm run dev | Vite development server с BFF |
npm test | Все frontend- и BFF-тесты |
npm run typecheck | TypeScript |
npm run lint | ESLint |
npm run format:check | Prettier |
npm run build | Preview + widget + Node server |
npm start | Production Node BFF/static server |
Команды backend
docker compose --profile uploads up --build -d
docker compose logs -f api
docker compose down
pytest
ruff check app tests
mypy app
Production-сборка frontend
npm ci
npm run build
npm start
Сборка создаёт:
dist/preview— презентационную страницу;dist/widget— встраиваемый виджет и CSS;dist/server— Node BFF и static server.
За HTTPS reverse proxy направьте /backend/api/* на Node-сервер, разрешите
тела до 20 МиБ и установите upstream timeout не меньше 120 секунд.
Встраивание виджета
import AssistantWidget from './dist/widget/ekt-assistant.js';
import './dist/widget/ekt-ai-assistant.css';
export function App() {
return <AssistantWidget mode="live" apiBaseUrl="/backend/api" initiallyOpen={false} />;
}
Хост-приложение должно предоставить BFF по указанному same-origin пути.
Качество и проверки
Frontend проверяет критические интеграционные сценарии:
- сессия создаётся один раз;
- upload и текст выполняются последовательно;
- успешный upload не повторяется после частичного сбоя;
- старое предложение нельзя подтвердить после замены;
- двойной клик не создаёт две операции;
- потерянное подтверждение повторяется тем же payload;
cart: nullне стирает корзину;- decimal-строки отображаются без потери точности;
- BFF проверяет ключ, token, origin, маршрут, размер и timeout.
Последняя frontend-проверка:
19 test files passed
147 tests passed
TypeScript passed
ESLint passed
Prettier passed
Production build passed
Полная проверка frontend:
npm run typecheck
npm run lint
npm run format:check
npm test
npm run build
Backend имеет отдельный набор Pytest-тестов с mock внешних интеграций.
Ограничения прототипа
- Корзина не создаёт заказ и резерв на ekt.kz.
- Сессия, история и корзина исчезают после TTL или перезапуска Redis.
- Вложения не сохраняются backend на диск.
- Для файлов требуется готовый ClamAV.
- Healthcheck не гарантирует доступность OpenAI, EKT и сканера.
- Внешние варианты нельзя подтверждать как каталожный товар.
Ограничения явно показаны в интерфейсе. Live-ошибки не маскируются автоматическим переключением на demo.
Статус
Полный сценарий проверен с реальным backend: создание сессии, получение товара, предложение, подтверждение демонстрационной корзины и загрузка файла через ClamAV.
Авторы коммитов
История коммитов
- 2026-09-23 17:58finalaldick
- 2026-09-23 17:23Add EKT AI assistant frontend and BFFGalm
- 2026-09-23 17:19Initial commit with all filesGalm
- 2026-09-23 17:19testGalm
- 2026-09-23 16:44final version of the backendaldick
- 2026-09-23 15:20backend technical documentation 2.0 createdaldick
- 2026-09-23 15:06backend technical documentation createdaldick
- 2026-09-23 13:03testGalm
Все коммиты (9)
- 2026-09-23 12:42Initial commit with all filesGalm
Структура репозитория (169 файлов)
.dockerignore .env.example .github/workflows/ci.yml .gitignore .prettierignore .prettierrc.json Dockerfile README.md app/__init__.py app/api/__init__.py app/api/errors.py app/api/middleware.py app/api/routes/__init__.py app/api/routes/cart.py app/api/routes/chat.py app/api/routes/health.py app/api/routes/products.py app/api/uploads.py app/clients/__init__.py app/clients/catalog.py app/clients/openai.py app/core/__init__.py app/core/config.py app/core/exceptions.py app/core/logging.py app/core/redis.py app/core/security.py app/data/category_profiles.json app/data/purchase_conditions.json app/main.py app/models/__init__.py app/models/chat_session.py app/models/products.py app/repositories/__init__.py app/repositories/chat_sessions.py app/schemas/__init__.py app/schemas/chat.py app/schemas/errors.py app/services/__init__.py app/services/analogs.py app/services/cart.py app/services/catalog.py app/services/chat.py app/services/chat_tools.py app/services/uploads.py docker-compose.yml docs/BACKEND_TECHNICAL_SPECIFICATION.md docs/CHAT_API.md docs/FRONTEND_INTEGRATION.md eslint.config.js index.html package-lock.json package.json pyproject.toml requirements-dev.lock requirements.lock server/body.ts server/config.ts server/errors.ts server/headers.ts server/index.ts server/proxy.test.ts server/proxy.ts server/routes.ts server/security.test.ts server/security.ts server/static.ts server/vitePlugin.ts src/AssistantWidget.tsx src/api/catalog.ts src/api/client.test.ts src/api/client.ts src/api/demo.test.ts src/api/demo.ts src/api/demoCart.ts src/api/errors.ts src/api/fixtures.ts src/api/http.test.ts src/api/http.ts src/api/normalize.test.ts src/api/normalize.ts src/api/normalizeCart.ts src/api/normalizeProducts.ts src/api/queryKeys.ts src/api/requests.test.ts src/api/requests.ts src/api/types.ts src/api/validation.ts src/components/Chat.attachments.test.tsx src/components/Chat.test.tsx src/components/Chat.tsx src/components/ProductArt.tsx src/components/ProductCard.test.tsx src/components/ProductCard.tsx src/components/QuantityControl.tsx src/components/chat/ChatHeader.tsx src/components/chat/ChatLauncher.tsx src/components/chat/Composer.tsx src/components/chat/MessageList.tsx src/components/chat/RecognizedProducts.tsx src/components/chat/SessionNotice.tsx src/components/chat/StructuredResults.test.tsx src/components/chat/StructuredResults.tsx src/components/chat/Welcome.tsx src/components/dialogs/CartDialog.tsx src/components/dialogs/CartDialogs.test.tsx src/components/dialogs/ConfirmDialog.tsx src/components/dialogs/ProductDialog.test.tsx src/components/dialogs/ProductDialog.tsx src/components/dialogs/ResetDialog.tsx src/hooks/cartActions.test-utils.tsx src/hooks/useCartActions.races.test.tsx src/hooks/useCartActions.test.tsx src/hooks/useCartActions.ts src/hooks/useChatWindow.ts src/hooks/useMessaging.test.tsx src/hooks/useMessaging.ts src/hooks/useResponses.ts src/hooks/useSession.ts src/lib/attachments.test.ts src/lib/attachments.ts src/lib/errorMessage.ts src/lib/format.test.ts src/lib/format.ts src/main.tsx src/preview.css src/preview/circuit.css src/preview/page.css src/state/hooks.ts src/state/store.test.ts src/state/store.ts src/styles.css src/styles/compact.css src/styles/composer.css src/styles/frame.css src/styles/messages.css src/styles/overlays.css src/styles/responsive.css src/styles/welcome.css src/test/chatHarness.tsx src/test/uiFixtures.ts src/theme.ts src/widget.tsx tests/01_specification.xlsx tests/02_mixed_request.xlsx tests/03_purchase_request.docx tests/04_delivery_note.docx tests/05_specification.pdf tests/06_scanned_delivery_note.pdf tests/07_breaker_photo.jpeg tests/08_relay_photo.jpeg tests/__init__.py tests/conftest.py tests/ekt-test-scenarios.pdf tests/test_analogs.py tests/test_api.py tests/test_cart.py tests/test_catalog.py tests/test_chat_api.py tests/test_chat_upload.py tests/test_core_redis.py tests/test_openai_client.py tests/test_sessions.py tests/test_uploads.py tests_results.md tsconfig.json tsconfig.server.json vite.config.ts vite.widget.config.ts
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.