О проекте
Команда: Ronan
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Рабочий MVP
Задача кейса: AI-консультант по каталогу ekt.kz: товары, аналоги, цены, остатки, корзина
Суть
Чат-ассистент по каталогу ekt.kz: ищет товар по артикулу или тексту, показывает цену, остатки и предупреждения о противоречиях в данных. Добавление в демо-корзину на PostgreSQL проходит только после явного подтверждения.
Как устроено
React-фронт обращается к Node/Fastify, который берёт кандидатов из каталога (fixtures или EKT-адаптер). Python-сервис ml_core (regex, difflib) разбирает запрос и ранжирует кандидатов. Node проверяет ограничения запроса (тип, ток) и возвращает только факты из каталога, при противоречиях помечая SPEC_CONFLICT. Покупка идёт в два шага: сервер создаёт предложение, а корзина меняется только после явного подтверждения с проверкой сессии, CSRF и Idempotency-Key. Опционально OpenAI Responses API формулирует ответ по уже проверенным карточкам.
Ключевые решения
- difflib fuzzy matching
- regex query parsing
- two-phase cart confirmation
- CSRF
- Idempotency-Key
- SQL migrations
- spec conflict detection
Особенности
Корзина меняется в два шага: предложение, затем явное подтверждение, и Python-ML не может менять цену или разрешать покупку. Противоречия в спецификациях помечаются как SPEC_CONFLICT. Если точного совпадения нет, ассистент честно отказывает и не подсовывает автомат на 80 А вместо запрошенного на 160 А.
Стек
Node.js · TypeScript · Fastify · Zod · PostgreSQL · React · Vite · Python · Vitest · Playwright · pytest · Docker Compose
Модели
OpenAI Responses API
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-25dafc95-ronan.
README
README.mdЭКТ Ассистент — demo-MVP для HackAlem AI
Помощник для покупателей и специалистов по закупке электротехнических товаров: помогает найти позицию по артикулу или текстовому запросу, изучить доступные характеристики и подготовить добавление в корзину. Цель проекта — сократить ручной поиск и сделать проверку товара перед покупкой прозрачной.
В репозитории объединены React-интерфейс, Node.js backend, Python-сервис разбора запросов и PostgreSQL. Рабочий сценарий использует снимки каталога и отдельную демо-корзину. Записи в корзину магазина EKT, оформления заказа и резервирования товара нет.
Что реализовано
| Возможность | Текущее поведение |
|---|---|
| Поиск | По ID, точному артикулу и тексту; сохраняет ведущие нули и подчёркивания. При запросе по типу и току Node проверяет ограничения по данным каталога. |
| Карточка товара | Название, артикулы, опубликованная цена, доступные складские данные, источник и предупреждения о противоречиях. Неизвестные значения остаются неизвестными. |
| Чат | Node принимает запрос, Python разбирает текст и ранжирует переданные кандидаты. При недоступном Python работает локальный поиск. |
| Предложение покупки | Сервер создаёт предложение с условиями и версией корзины. Корзина до отдельного подтверждения не меняется. |
| Подтверждение | Node проверяет сессию, CSRF, актуальность предложения и товарные условия; повтор того же подтверждения не удваивает количество. |
| Отмена | Кнопка вызывает серверный cancel endpoint; успех показывается после ответа сервера. |
| Корзина | Хранится в PostgreSQL, привязана к cookie-сессии и доступна после обновления страницы. Чужая сессия не может подтвердить предложение. |
| Диагностика | Health endpoint, OpenAPI, единый формат ошибок и автоматические проверки. |
OpenAI подключается опционально на Node. По умолчанию AI_MODE=stub сохраняет автономный сценарий. При AI_MODE=openai Node вызывает OpenAI Responses API только для формулировки ответа по уже проверенным карточкам; поиск, цены, остатки и корзина остаются на Node. При сбое модели используется шаблонный ответ. Python по-прежнему использует правила и difflib: ml_status=ok относится к Python, а llm_status=ok — к отдельному вызову LLM.
В интерфейсе есть два режима: API для работы с Node и отдельное локальное Демо с подготовленными примерами. Примеры аналогов, доставки и обработки файлов в локальном режиме не подтверждают наличие этих интеграций в backend.
Как работает решение
- Пользователь выбирает режим API и вводит артикул или запрос, например «Нужен автомат на 16 А».
- Frontend обращается к Node через Vite proxy. Node получает кандидатов из каталога и может передать их Python для ранжирования.
- Node проверяет ограничения исходного запроса и возвращает факты только из каталога. Ответ Python не может подменить цену, остаток или разрешить покупку.
- Пользователь выбирает товар и количество. Сервер создаёт предложение; корзина остаётся прежней.
- После явного подтверждения Node повторно проверяет условия и атомарно добавляет товар в демо-корзину.
- Пользователь открывает
/#/cart. После reload остаётся та же серверная корзина. Отмена предложения запрещает его последующее подтверждение.
POST /api/chat сам по себе не изменяет корзину. Python не получает cookie, CSRF-токен и право подтверждать предложения.
Технологии и архитектура
- Backend: Node.js 24 LTS, TypeScript strict, Fastify, Zod, PostgreSQL (
pg), SQL-миграции, OpenAPI. - Frontend: React 19, TypeScript, Vite 6, Lucide React.
- Python: Python 3.12, HTTP-сервис на стандартной библиотеке,
re,difflib,python-dotenv. - Проверки: Vitest, ESLint для backend, TypeScript, pytest, Playwright/Chromium.
- Организация: npm workspaces для backend; frontend устанавливается отдельно. В репозитории есть Docker Compose, описанный ниже проверенный запуск обходится без Docker.
flowchart LR
U[Пользователь] --> F[React: режим API]
F -->|Vite proxy /api| N[Node / Fastify]
N -->|HTTP/JSON: запрос и кандидаты| M[Python ml_core]
M -->|Разбор и порядок ID| N
N --> C[Каталог: fixtures или EKT adapter]
N --> P[(PostgreSQL)]
S[db:seed-fixtures] -->|Исходный JSON и проекция| P
P -->|Сессии, предложения, демо-корзины| N
Node остаётся источником решений о покупке. Режимы каталога (CATALOG_MODE), корзины (CART_MODE) и AI (AI_MODE) независимы. Ошибка live-каталога не включает fixtures автоматически.
apps/api/ Node API, каталог, корзина, ML-клиент и тесты
front/ интерфейс, API-адаптер и браузерные тесты
ml_core/ Python-код и HTTP-сервис
tests/ Python-тесты
samples/ исходные снимки и отдельные synthetic-данные
docs/ требования, планы и контракты команды
Подробные схемы: API_CONTRACT.md, ML_CORE_CONTRACT.md. Машинная схема работающего Node: GET /api/openapi.json.
Установка и запуск без Docker
Проверено локально на Windows, Node.js 24.21.0, Python 3.12.14 и PostgreSQL 18. Нужны установленный PostgreSQL и три терминала. Команды ниже — для PowerShell; если запуск npm.ps1 запрещён политикой системы, используйте npm.cmd вместо npm.
1. Подготовить базу и Node
В pgAdmin создайте базу ekt_assistant. Host, port и username доступны в свойствах подключения сервера, на вкладке Connection.
Из корня репозитория:
npm ci
if (!(Test-Path .env)) { Copy-Item .env.example .env }
Задайте в локальном корневом .env:
CATALOG_MODE=fixture
CART_MODE=demo
AI_MODE=stub
PORT=8000
DATABASE_URL=postgresql://<local-user>@127.0.0.1:5432/ekt_assistant
ML_CORE_URL=http://127.0.0.1:8100
ALLOWED_ORIGIN=http://127.0.0.1:5173
Замените <local-user> на локального пользователя PostgreSQL. Если подключение требует пароль, задайте его только локально в URL с корректным URL-кодированием или в окружении процесса. .env исключён из Git. Для fixture-сценария в режиме stub внешние ключи не нужны.
Чтобы включить OpenAI, задайте только в локальном корневом .env:
AI_MODE=openai
OPENAI_API_KEY=<ваш ключ>
OPENAI_MODEL=<доступный вашему API-проекту model ID>
# OPENAI_TIMEOUT_MS=8000
После перезапуска Node поле llm_status в /api/chat показывает ok, unavailable или disabled. Ключ не возвращается API и не передаётся frontend или Python.
npm run db:migrate
npm run db:seed-fixtures
npm run dev:api
Node читает корневой .env. Health: http://127.0.0.1:8000/api/health. Оставьте процесс работающим.
2. Запустить Python
В отдельном терминале, из корня:
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt pytest
.\.venv\Scripts\python.exe -m ml_core.service
Сервис по умолчанию слушает 127.0.0.1:8100. На Linux/macOS путь к Python в виртуальном окружении — .venv/bin/python. Без ML_CORE_URL Node использует локальный поиск; для полного сценария с Python переменная должна быть задана.
3. Запустить frontend
В третьем терминале, из корня:
cd front
npm ci
npm run build
npm run preview
Откройте http://127.0.0.1:5173 и выберите режим API в верхней панели. Это существенно: локальное «Демо» использует другие данные и не проверяет backend.
Для разработки вместо preview можно выполнить npm run dev. Обе команды используют строго порт 5173; при занятом порте завершаются с ошибкой. Не запускайте их одновременно. Vite проксирует /api на Node 127.0.0.1:8000; ALLOWED_ORIGIN должен совпадать с адресом браузера, включая host и port. Для приведённых команд используйте 127.0.0.1, а не localhost.
Сценарий для жюри
Начните в новом приватном окне браузера, чтобы получить пустую сессию. Все шаги выполняются в режиме API.
- Введите
027228. Откройте карточку 515291: внутренний артикул200300285_, артикул поставщика027228. Видно предупреждение SPEC_CONFLICT: в названии/описании указано 160 А, в свойстве — 250 А. - В карточке опубликована цена 64920 с пояснением об неизвестной валюте. Покупка этой позиции недоступна: подтверждённых правил продажи нет.
- Введите «Нужен автомат Legrand на 160 А». На текущих данных система сообщает об отсутствии подтверждённого совпадения; автомат на 80 А не выдаётся как подходящий.
- Введите
DEMO_001или «Нужен автомат на 16 А». Выберите синтетический товар 900000001 — он специально подготовлен для проверки корзины. - Создайте предложение добавления. До подтверждения корзина пуста. Одно слово «да» в чате не подтверждает покупку; используйте кнопку явного подтверждения.
- Подтвердите, откройте корзину
/#/cartи обновите страницу. Позиция остаётся в той же серверной корзине. - Создайте ещё одно предложение и нажмите «Отмена». Успех появляется после ответа сервера; количество в корзине не меняется.
- Откройте вторую вкладку того же браузера и включите API. Подтверждение в первой вкладке продолжает работать после однократного обновления устаревшего CSRF-токена.
Повтор одного confirm с тем же Idempotency-Key, запрет позднего confirm после отмены и защита чужой сессии проверяются автоматическими тестами. Отдельная проверочная страница Node — http://127.0.0.1:8000/cart.
Проверки и фактические результаты
Из корня:
npm run lint
npm run typecheck
npm test
npm run build
.\.venv\Scripts\python.exe -m pytest
Для PostgreSQL-тестов создайте отдельную базу ekt_demo_mvp_test, примените к ней миграции и задайте TEST_DATABASE_URL в окружении тестового процесса. Не используйте рабочую базу: тесты создают сессии, предложения и корзины.
$env:TEST_DATABASE_URL = 'postgresql://<local-user>@127.0.0.1:5432/ekt_demo_mvp_test'
$env:DATABASE_URL = $env:TEST_DATABASE_URL
npm run db:migrate
Remove-Item Env:DATABASE_URL
npm run test:integration
Frontend, из каталога front:
npm test
npm run build
npx playwright install chromium
# Node, Python и frontend preview уже должны быть запущены:
$env:E2E_EXTERNAL = '1'
$env:E2E_API = '1'
npx playwright test e2e/api-integration.spec.ts
Фактический финальный прогон:
| Проверка | Результат |
|---|---|
| Backend lint, typecheck, build | Пройдены |
| Backend Vitest без внешних ключей | 17 пройдены; 2 DB-теста пропущены в этом запуске |
| PostgreSQL integration в отдельной базе | 2 пройдены |
| Python pytest | 23 пройдены, дополнительно 5 subtests |
| Frontend unit | 17 пройдены |
| Frontend build, включая TypeScript | Пройден |
| Playwright: основной UI и редактирование локального демо | 6 пройдены |
| Playwright API: реальные frontend + Node + Python + PostgreSQL | 4 пройдены |
Четыре API-теста проверяют полный путь до корзины и reload, отсутствие изменений до confirm, отсутствие удвоения при повторе, ограничения поиска и ml_status=ok, серверную отмену и две вкладки с CSRF. Сессия создаётся через настоящий Vite proxy. Frontend mock-режим в этих четырёх тестах не используется.
У frontend нет отдельного lint script; TypeScript проверяется сборкой. В проверочной Windows-среде автоматическое завершение preview из Playwright зависало, поэтому API-тесты выполнены с отдельно запущенным preview и E2E_EXTERNAL=1. Live EKT, реальный вызов OpenAI с ключом и публичное размещение не проверялись.
Данные и интеграции
samples/содержит предоставленные снимки EKT: страницу из 20 позиций и detail-карточку. Это не текущие данные сервера и не полный каталог;count=20не считается его размером.samples/synthetic/demo-products.jsonсодержит отдельный тестовый товар сsource=synthetic, демо-ценой, единицей продажи и остатком. Только для него подтверждены условия текущего сценария покупки.- Команда
db:seed-fixturesсохраняет исходный JSON отдельно от нормализованной проекции PostgreSQL. Во время работы fixture-каталог читаетsamples/; автоматической записи live-ответов в БД нет. Артикулы остаются строками; отсутствующийquantityне превращается в нулевой остаток. - У 515291 сохранены оба значения тока и их источники.
RECOMMENDне трактуется как доказанный список аналогов. - В Node есть
EktCatalogProviderдляCATALOG_MODE=live. Он требует серверныеEKT_API_USERиEKT_API_PASSWORD; live-подключение в этом прогоне не проверялось. Отдельный Python-клиент каталога читаетEKT_API_PASS; он не участвует в описанном HTTP-пути ранжирования. - Python работает по HTTP/JSON-контракту. Недоступность Python не блокирует обычный поиск товаров.
Ключи и пароли задаются только в локальном серверном окружении. Они не нужны frontend и не должны попадать в Git.
Ограничения текущей версии
- OpenAI используется только для текста ответа и не имеет tools для изменения корзины. История диалога, embeddings, семантический поиск и NVIDIA API не подключены.
- Поиск использует ограниченные правила и доступную часть каталога. Совпадение типа и тока не является инженерной гарантией совместимости; при недостаточных данных система не обещает подбор.
- Реальная корзина EKT не интегрирована:
CART_MODE=ektвозвращаетINTEGRATION_NOT_CONFIGURED. Демо-корзина не резервирует товар, не принимает оплату и не оформляет заказ. - Не подтверждены валюта, единицы и правила продажи EKT, назначение складов, условия доставки/оплаты, сертификаты и аналоги. Backend их не выдумывает.
- В API-режиме добавляется только подготовленный synthetic-товар; изменение и удаление строк корзины через frontend API пока отключены.
- Серверная обработка пользовательских вложений, OCR и полноценная авторизация покупателей не реализованы.
Размещение
Подтверждённой ссылки на deployed-версию в репозитории нет. Проверенный адрес локального демо — http://127.0.0.1:5173.
В front/netlify.toml есть настройки статического frontend, но они не разворачивают Node, Python и PostgreSQL. Vite proxy относится к локальному dev/preview; для публичного API-сценария потребуется отдельная серверная конфигурация. В рамках финальной проверки deploy не выполнялся.
Авторы коммитов
История коммитов
- 2026-09-23 17:59Document optional OpenAI environment variables without secretsdakonya
- 2026-09-23 17:54Add optional OpenAI responses providerdakonya
- 2026-09-23 17:43Fix final demo search and cart integration; document verified setupdakonya
- 2026-09-23 17:05Align explicit cart consent across frontend and ML gatedakonya
- 2026-09-23 16:34Connect frontend and Python ranking to demo APIdakonya
- 2026-09-23 16:17Merge ML work while preserving backend and frontenddakonya
- 2026-09-23 16:07Merge frontend into backend demo MVPdakonya
- 2026-09-23 16:01Build EKT catalog and safe demo cart MVPdakonya
Все коммиты (19)
- 2026-09-23 15:37Merge pull request #1 from BAITC-Hacks/feat/chat-cart-uiDias
- 2026-09-23 15:32Add files via uploadDias
- 2026-09-23 16:57Restrict confirmation to exact frontend phrase listsyngyskadyr
- 2026-09-23 16:55Restrict confirmation to exact frontend phrase listsyngyskadyr
- 2026-09-23 14:14Delete feat directoryDias
- 2026-09-23 16:12Add gitignore, env example, requirements, and README updatessyngyskadyr
- 2026-09-23 16:10Merge branch 'main' of https://github.com/BAITC-Hacks/hack-25dafc95-ronansyngyskadyr
- 2026-09-23 16:09Fix pagination to respect limit parameter; add concurrency and progress outputsyngyskadyr
- 2026-09-23 14:02Create chat-cart-uiDias
- 2026-09-23 13:28Fix gitignore for env secretssyngyskadyr
- 2026-09-23 13:25Add gitignore for env secretssyngyskadyr
Структура репозитория (92 файлов)
.env.example .gitignore AGENTS.md README.md apps/api/eslint.config.js apps/api/migrations/001_catalog.sql apps/api/migrations/002_demo_cart.sql apps/api/package.json apps/api/src/app.ts apps/api/src/cart/cart.integration.test.ts apps/api/src/cart/routes.test.ts apps/api/src/cart/routes.ts apps/api/src/cart/store.ts apps/api/src/catalog/catalog.test.ts apps/api/src/catalog/ekt.ts apps/api/src/catalog/errors.ts apps/api/src/catalog/fixture.ts apps/api/src/catalog/normalize.ts apps/api/src/catalog/types.ts apps/api/src/config.test.ts apps/api/src/config.ts apps/api/src/db/migrate.ts apps/api/src/db/seed-fixtures.ts apps/api/src/llm/provider.test.ts apps/api/src/llm/provider.ts apps/api/src/local-env.ts apps/api/src/ml/chat.test.ts apps/api/src/ml/client.ts apps/api/src/ml/search.ts apps/api/src/schemas.ts apps/api/src/server.ts apps/api/tsconfig.json compose.yaml docs/ACCEPTANCE_TESTS.md docs/API_CONTRACT.md docs/CODEX_PROMPT.md docs/ML_CORE_CONTRACT.md docs/PLAN.md docs/REQUIREMENTS.md docs/SOURCES.md front/QA.md front/README.md front/e2e/api-integration.spec.ts front/e2e/editing.spec.ts front/e2e/flow.spec.ts front/e2e/harness.html front/e2e/harness.tsx front/e2e/network.spec.ts front/index.html front/netlify.toml front/package-lock.json front/package.json front/playwright.config.ts front/public/demo-certificate.html front/qa/cart-desktop.png front/qa/desktop.png front/qa/mobile-cart.png front/qa/mobile.png front/src/App.tsx front/src/Assistant.tsx front/src/apiSource.test.ts front/src/apiSource.ts front/src/assistant.css front/src/cartEngine.test.ts front/src/cartEngine.ts front/src/catalog.ts front/src/components.tsx front/src/data.ts front/src/env.d.ts front/src/main.tsx front/src/products.css front/src/site.css front/src/types.ts front/src/useAssistant.tsx front/tsconfig.json front/vite.config.ts front/vite.harness.config.ts ml_core/__init__.py ml_core/catalog.py ml_core/models.py ml_core/pending_actions.py ml_core/service.py package-lock.json package.json requirements.txt samples/ekt-product-515291.json samples/ekt-products-page2.json samples/manifest.json samples/synthetic/demo-products.json tests/test_catalog.py tests/test_http_service.py tests/test_pending_actions.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.