О проекте
Команда: TryHackMe
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Доведённый продукт
Задача кейса: AI-консультант по каталогу ekt.kz: товары, аналоги, цены, остатки, корзина
Суть
Чат-помощник покупателя ekt.kz на лендинге Next.js: находит товары и остатки, подбирает аналоги для отсутствующих позиций, разбирает закупочные спецификации из файлов и добавляет позиции в серверную корзину после отдельного согласия.
Как устроено
FastAPI-бэкенд через OpenAI Responses API со Structured Outputs извлекает намерение, позиции и количества из сообщения или файла. DOCX разбирается python-docx, XLSX python-calamine, PDF и изображения идут в модель как визуальный ввод. Каждая позиция сверяется с каталогом ekt.kz (публичный поиск и Basic Auth API). Корзина вынесена за CartAdapter с циклом prepare → confirm, повторной проверкой цены, остатка и кратности, CSRF и HttpOnly-cookie. Условия оплаты и доставки и контакты менеджера парсятся с официальных страниц сайта.
Ключевые решения
- OpenAI Structured Outputs
- мультимодальное извлечение позиций из PDF/JPEG
- python-docx
- python-calamine
- CartAdapter с отдельным подтверждением
- проверка спецификации по позициям
- CSRF/Origin защита
Особенности
Сценарий «спецификация → проверка каждой позиции → выбор строк → согласие → корзина»: неоднозначные совпадения, нехватка остатка и неверная кратность видны до добавления. Изменение цены или остатка отменяет всю партию. В репозитории есть готовые входные файлы (docx/xlsx/pdf/jpg/csv) для пяти Must-have сценариев.
Стек
Python · FastAPI · Pydantic · uv · Next.js 16 · React · Tailwind · shadcn/ui · Playwright · Docker Compose · GitHub Actions
Модели
gpt-4o-mini (OpenAI Responses API)
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-728d5982-tryhackme.
README
README.mdHackAlem AI — помощник покупателя ekt.kz
Адаптивный лендинг на Next.js 16, React, Tailwind и shadcn/ui: тёмная тема, сдержанные RGB-линии и одно окно чата. Проверенный чат из index.html переиспользуется внутри Next.js; файлы, карточки, аналоги, подтверждение и корзина сохраняют прежнее поведение. Word ТЗ — первичный источник требований.
Практический результат: спецификация → проверка каждой распознанной позиции → выбор строк → отдельное согласие → корзина. Неизвестные товары, неоднозначные совпадения и нехватка остатка видны до добавления. Польза и развитие продукта, план улучшений и пошаговый пример описывают доступные возможности и следующие шаги.
По уточнению заказчика корзина принадлежит нашему приложению. CartAdapter отделяет её от чата и правил подтверждения, чтобы впоследствии подключить корзину ekt.kz. Заказ, оплата и резерв на стороне магазина сейчас не создаются.

Запуск
Для текущего интерфейса нужны Python 3.12+, uv, Node.js ≥ 20.9, интернет и серверные доступы OpenAI/ekt.kz. В чистом клоне, если backend/.env ещё нет, создайте его из backend/.env.example и заполните выданные доступы.
Основной интерфейс запускается в двух терминалах после установки зависимостей (make install) и настройки .env:
make dev # терминал 1: FastAPI на 8765
make dev-frontend # терминал 2: Next.js на 3000
Откройте http://localhost:3000/. Запросы /api и корзина /cart остаются на этом адресе; внутренний адрес backend задаётся ASSISTANT_API_URL в frontend/.env.local, по умолчанию http://127.0.0.1:8765. Секреты OpenAI и каталога frontend не получает.
Отдельный запуск FastAPI с исходным HTML:
cd backend
uv sync
# Заполните локальный .env; значения секретов не публикуйте.
uv run uvicorn app.landing:app --host 127.0.0.1 --port 8765
Откройте http://127.0.0.1:8765/. Сервер автоматически читает backend/.env, затем корневой .env; существующие переменные окружения имеют приоритет. Нужны:
OPENAI_API_KEY=ваш_ключ
AI_MODEL=gpt-4o-mini
EKT_API_USER=логин_партнёра
EKT_API_PASSWORD=пароль_партнёра
Модель берётся из вашего .env; она должна поддерживать Responses API, Structured Outputs и изображения. Ключ OpenAI и Basic Auth остаются на сервере. Используйте один worker: сессии и корзины хранятся в памяти процесса, переживают обновление страницы, но очищаются после часа бездействия или перезапуска сервера.
Открытие index.html без backend включает явно обозначенный деморежим: встроенные 40 товаров и одна детальная карточка из JSON партнёра, локальная корзина вкладки, без OpenAI. Это отдельный режим и не доказательство живой интеграции.
Готовые сценарии и файлы для демонстрации
Начните с out/README.md: там находятся пять сценариев строго по Must have Word ТЗ, готовые сообщения, закупочные Word/Excel/PDF/JPEG и дополнительные TXT/CSV, ожидаемые ответы и последовательность подтверждения корзины. Формы и количества смоделированы, коды взяты из каталога; фото товара сохранено с сайта без изменений. Реальные ответы проверяются по текущему каталогу. out/ документирует использование приложения и не служит источником готовых ответов для LLM.
На лендинге раздел «Начните со своей задачи» открывает тот же чат: пример артикула, выбор файла или запрос отсутствующего товара. Прикрепление только готовит вложение; пользователь отправляет запрос, проверяет результат и отдельно подтверждает корзину. Для знакомства с приложением используйте одну историю из demo-day.md, а не семь копий спецификации в одном заказе.
Что работает
| Сценарий | Реальная обработка и результат |
|---|---|
| UC-01: товар | OpenAI извлекает запрос/артикул → поиск по известным товарам и публичному поиску ekt.kz → детальная карточка через Basic Auth API. Показываются характеристики, склады, цена источника, время чтения и документы, когда есть ссылка. |
| UC-02: отсутствие | Для каждой найденной позиции с нулевым остатком автоматически ищутся аналоги, в том числе в запросе на добавление. При пустом узком поиске проверяются тип товара и раздел каталога. Кандидаты имеют проверенный остаток, объяснение совпадений и отличий; аксессуары и известные несовместимые электрические параметры отсеиваются. |
| UC-03: покупка | Оплата и доставка читаются с опубликованной страницы магазина и отображаются разделами, абзацами и списками. Повторные блоки и навигация сайта не попадают в ответ; разные версии условий отмечаются. Кратность выбранного товара показывается в разделе минимальной партии. |
| UC-04: подтверждение | Сначала предложение с артикулами, количеством и ценой. Кнопка «Да, добавить» либо отдельное «да, добавь» запускают повторную проверку цены, остатка и кратности. «Ок», вложения и ответы модели не разрешают изменение. |
| UC-05: корзина | После добавления выдаётся /cart. Ссылка открывает актуальную серверную корзину текущего посетителя в том же окне чата, в том числе после обновления страницы. Удаление тоже требует подтверждения. |
Кнопка корзины повторно загружает её состав и показывает позиции в видимой части чата без фокуса на поле ввода. Запрос «Покажи корзину» выводит состав сразу. Товары из спецификации появляются здесь после отдельного подтверждения; исходные файлы в корзине не хранятся.
Примеры: Есть ли 027005? Покажи характеристики и сертификат, Есть ли ярп4520? Если нет, предложи аналог, Как оплатить и получить доставку?, Добавь 2 шт 027005, затем да, добавь.
Проверка спецификации перед корзиной
В серверном режиме у каждой распознанной позиции есть запрос, количество, соответствие каталогу и статус. Отдельно отмечаются неоднозначность, неизвестный код, отсутствие количества, нехватка остатка и неверная кратность. Повторы учитываются суммарно вместе с уже добавленным количеством. Модель может пропустить строку: разбор нужно сверить с исходным файлом.
Готовые строки можно снять с выбора; счётчик показывает выбранные и пропущенные. «Проверить выбранное» повторно читает каталог и готовит предложение. Только «Да, добавить» изменяет корзину. Изменившийся остаток или цена отменяют добавление всей партии. Неизвестные строки и аналоги автоматически в партию не попадают.
Как обрабатываются файлы
- Прикрепление подготавливает файл в браузере; передача начинается при отправке сообщения. До отправки вложение можно удалить.
- Backend проверяет размер, формат и структуру. DOCX разбирается через
python-docx(абзацы и таблицы), XLS/XLSX — черезpython-calamine(все листы, заголовки и значения, включая сохранённый результат формулы). CSV разбирается по столбцам с учётом разделителя, TXT — как UTF-8. Модель получает текст и структуру строк; внутренние ERP-коды не подменяют код производителя. - JPEG/PNG передаются в OpenAI как изображение, PDF — как файл с визуальным содержимым, включая сканы. Модель извлекает коды, признаки и количества. Старый бинарный DOC направляется как документ OpenAI; отдельный живой прогон этого формата пока не зафиксирован, предпочтителен DOCX.
- Распознанные позиции показываются в чате; цена, остаток и характеристики всё равно проверяются по каталогу. Если точного соответствия нет, требуется уточнение. Ни текст файла, ни распознанная инструкция не подтверждают корзину.
Лимиты: 5 файлов по 8 МБ; общий base64-пакет до 24 млн символов; PDF до 10 страниц без пароля; фото до 20 Мп; Excel до 5 листов, 300 строк и 80 столбцов на лист; извлечённый текст документа до 24 000 символов; до 20 товарных позиций за запрос. Превышение и повреждённые файлы возвращают объяснимую ошибку.
Файлы не записываются на диск или в БД приложения. В памяти сессии остаются последние 12 сообщений с названиями файлов, без их содержимого. Запросы OpenAI используют store=False; это не обещание нулевого хранения на стороне провайдера. Не отправляйте платёжные данные. Известные шаблоны номера карты/CVV блокируются в сообщениях и извлечённом офисном тексте; это не универсальный детектор содержимого изображений. Подробности — в docs/file-processing.md.
Помощь и связь с менеджером
«Как заказать?» (в том числе с опечатками) возвращает конкретные шаги, «С чем можешь помочь?» — возможности и следующий шаг. При обработке вложений чат показывает, что читает файлы и сопоставляет позиции; при долгом запросе сообщает об ожидании.
«Связаться с менеджером» доступно в том же окне и без ответа языковой модели. Сервер читает телефон/WhatsApp с официальной страницы контактов и готовит редактируемый текст обращения. Покупатель копирует его и сам открывает канал магазина. Сообщения, история и документы автоматически не отправляются. Это контакт с магазином, а не подключение оператора к нашей сессии или создание CRM-заявки.
Кнопка также предлагается при сбое источника/модели, повторно неудачном поиске, отсутствии нужного сертификата, конфликте характеристик или невозможности подтвердить замену. Если контакты получить не удалось, остаётся ссылка на исходный сайт. Корзина меняется только после своего отдельного подтверждения.
Архитектура и смена корзины
Next.js / React / Tailwind / shadcn — лендинг
→ общий DOM/JS чат из index.html, подключённый внутри страницы
→ Next.js /api → app.landing /api/chat
→ OpenAI Responses: намерение, позиции, количества
→ ekt.kz: поиск + проверенный каталог + условия
→ /api/cart/prepare → отдельное согласие → /api/cart/confirm
→ CartAdapter → ServerCart
→ /cart → та же Next.js страница + актуальная сессия
backend/app/landing.py содержит Pydantic-контракты, обработку файлов, поиск, проверки и адаптер. LLM не имеет инструмента прямого изменения корзины. HttpOnly/SameSite cookie, CSRF-токен и проверка Origin защищают сессию; предложение связано с её версией, одноразовое и действует 3 минуты. Проверяется весь набор позиций до изменения; повторное подтверждение отклоняется.
Для будущего переключения реализуется CartAdapter с авторизованной сессией покупателя ekt.kz и заменяется cart_adapter = ServerCart(). Логика явного согласия остаётся перед адаптером. Нужен контракт магазина для сопоставления пользователя и идемпотентного применения операции; передача наших cookie или Basic Auth каталога вместо авторизации корзины недопустима. Для размещения виджета на другом сайте предусмотрите маршрутизацию /api и /cart на этот backend через тот же origin.
contracts/landing.openapi.json генерируется из Pydantic. В index.html обычный JavaScript, отдельной ручной копии типов нет. Генератор проверяется локально и в CI.
Проверки
# Из backend/
uv run python generate_landing_openapi.py --check
uv run python generate_ekt_catalog_openapi.py --check
uv run ruff check app tests generate_ekt_catalog_openapi.py generate_landing_openapi.py
uv run pytest
# Из frontend/; нужны установленный Chromium и его системные библиотеки
npm run test:landing # автономный режим, без внешнего AI
npm run test:landing:live # живые API и расходы OpenAI из .env
# Новый интерфейс: сначала npm run build
npm run test:next # production Next.js, без внешнего AI
npm run test:next:live # Next.js → FastAPI → OpenAI / ekt.kz
После переноса в Next.js: production build, ESLint, TypeScript и 24 автономных браузерных проверки прошли. В отдельных живых запусках прошли 9 из 10 сценариев; полный desktop UC дважды остановился на сообщении о сбое обработки модели без товаров. Файлы, помощь и защита прокси проверены на обоих экранах, полная живая цепочка UC-01…05 — на mobile. Подробности и оставшаяся проверка — ROADMAP, M13.
Предыдущий этап M12: 41 backend-тест, 22 автономных браузерных и 8 живых сценариев; это отдельные результаты до переноса. Команда из корня make test-files-live загружает все восемь входов с реальными OpenAI/ekt.kz, сравнивает позиции/количества и факты, проверяет объяснённый аналог и подтверждённую корзину из 6 единиц. Результаты, модель, длительности, хеши и неуспешные предыдущие попытки — out/verification.json. JPEG накладной и фото аппарата проверяются отдельно: мелкая маркировка требует уточнения кода, это не успешное автоматическое распознавание модели аппарата.
Границы данных и прототипа
- В API нет валюты; сумма показывается без придуманного обозначения. Цена API может отличаться от цены интернет-магазина, окончательная цена заказа здесь не обещается.
- Для
027228обнаружено расхождение: название 160 А, свойство 250 А. Оно показано пользователю и переносится в пояснение к кандидатам; полная взаимозаменяемость не утверждается. Замены не добавляются автоматически. - В проверенных карточках нет сертификата: отображается отсутствие данных. Положительный путь показа ссылки реализован, но живой товар с доступным сертификатом пока не найден.
- Поиск использует выборку и публичный поиск магазина, а не гарантированно полный локальный индекс. Его HTML и опубликованные условия могут измениться; при сбое факты не подменяются выдуманными.
- Критерий UC-02 — минимум один релевантный доступный аналог. Он проверяется на реальных отсутствующих позициях, но для всего каталога ещё не доказан: при отсутствии подходящего кандидата после расширенного поиска показывается ограничение. Нерелевантный товар не подставляется ради количества ответов.
- Перед публичной эксплуатацией нужны постоянное хранилище сессий/корзин, HTTPS и ограничения нагрузки. Размещение на действующем домене ekt.kz не выполнялось.
Перенос frontend без переписывания чата
React-лендинг живёт в frontend/src/app/page.tsx. Сборка автоматически извлекает CSS/HTML/JS чата из index.html: это один источник поведения, изолированный от React и оформленный в общей теме. Так сохраняются проверенные сценарии, а полный перенос сообщений на React можно выполнить отдельно. Для изменений чата после редактирования HTML перезапустите frontend. Подробнее — frontend/README.md.
make test-next-ui проверяет production Next.js без внешних API; make test-next-live — живую цепочку через прокси, файлы и корзину на desktop/mobile. Старые команды HTML-набора сохранены. Новых зависимостей этот перенос не добавляет.
Команды проекта и Docker
Из корня: make install, make dev, make check, make test, make test-ui, make test-live. Команды, архитектура и проверки описаны в docs/architecture.md и docs/testing.md.
Для контейнера: docker compose up --build. Используется один backend на http://127.0.0.1:8765/, секреты из локального backend/.env; дополнительные сервисы не запускаются. Файлы .env и документы с доступом не включаются в образ. Сессии остаются в памяти процесса.
Существующий Docker-образ обслуживает самостоятельный HTML на FastAPI. Новый Next.js запускается отдельно; production-сборка проверяется командой make build-frontend.
Авторы коммитов
История коммитов
- 2026-09-23 18:04docs: add img demomm
- 2026-09-23 18:03fix(frontend): chat colors in dark mode and testsmm
- 2026-09-23 18:00fix(frontend): chat colors in dark modemm
- 2026-09-23 17:55feat(frontend): migrate landing to Next.js with neon UImm
- 2026-09-23 17:34feat(assistant): add procurement inputs and manager contact flowmm
- 2026-09-23 17:07feat(assistant): add specification review before cart confirmationmm
- 2026-09-23 16:37fix(assistant): repair cart navigation, terms and analog searchmm
- 2026-09-23 16:13refactor(repo): trim template to HackAlem assistant scopemm
Все коммиты (19)
- 2026-09-23 15:58docs(demo): add HackAlem use cases and input examplesmm
- 2026-09-23 15:57feat(landing): connect OpenAI assistant and protected session cartmm
- 2026-09-23 15:26feat: update landingmm
- 2026-09-23 14:22feat: landing htmlmm
- 2026-09-23 14:17docs(contracts): move catalog API contracts into dedicated directorymm
- 2026-09-23 14:11docs: align use cases with HackAlem Word source of truthmm
- 2026-09-23 14:01docs: openapi generatedmm
- 2026-09-23 13:39clean docsmm
- 2026-09-23 13:38docs: formalize ekt.kz requirements and use-case roadmapmm
- 2026-09-23 13:12docs: add HackAlem AI hackathon materials and update agent guidelinesmm
- 2026-09-23 11:08chore: initialize bootstrap scaffoldmm
Структура репозитория (87 файлов)
.dockerignore .github/workflows/ci.yml .gitignore AGENTS.md CHANGELOG.md Makefile README.md ROADMAP.md TMP_real_out_files.md backend/.env.example backend/Dockerfile backend/app/__init__.py backend/app/landing.py backend/app/schemas/__init__.py backend/app/schemas/ekt_catalog.py backend/generate_ekt_catalog_openapi.py backend/generate_landing_openapi.py backend/pyproject.toml backend/tests/test_landing.py backend/uv.lock contracts/README.md contracts/ekt-catalog-api.md contracts/ekt-catalog.openapi.json contracts/landing.openapi.json docker-compose.yml docs/architecture.md docs/file-processing.md docs/product-value.md docs/source of truth/PRD.md docs/source of truth/assets/HackAlem AI_ ИИ-ассистент для чата на сайте ekt.kz.docx docs/source of truth/assets/detail.json docs/source of truth/assets/products _page_2.json docs/source of truth/assets/products.json docs/source of truth/assets/ИИ-ассистент для чата на сайте ekt.kz ДАННЫЕ.docx docs/source of truth/plan.md docs/source of truth/source of truth.md docs/source of truth/use-cases.md docs/testing.md frontend/.editorconfig frontend/.env.example frontend/.gitignore frontend/.prettierignore frontend/.prettierrc frontend/README.md frontend/components.json frontend/e2e/landing.live.spec.ts frontend/e2e/landing.spec.ts frontend/e2e/next.live.spec.ts frontend/e2e/next.spec.ts frontend/eslint.config.mjs frontend/next.config.ts frontend/package-lock.json frontend/package.json frontend/playwright.landing.config.ts frontend/playwright.next.config.ts frontend/postcss.config.mjs frontend/scripts/sync-assistant.mjs frontend/src/app/api/[...path]/route.ts frontend/src/app/cart/page.tsx frontend/src/app/globals.css frontend/src/app/layout.tsx frontend/src/app/page.tsx frontend/src/components/assistant/action.tsx frontend/src/components/assistant/widget-theme.css frontend/src/components/assistant/widget.tsx frontend/src/components/ui/button.tsx frontend/src/lib/utils.ts frontend/tsconfig.json image.png index.html out/README.md out/demo-day.md out/expected-results.md out/generate_examples.py out/inputs/delivery_note_image.jpg out/inputs/delivery_note_scan.pdf out/inputs/electrician_request.txt out/inputs/erp_export.csv out/inputs/procurement_request.docx out/inputs/product_photo.jpg out/inputs/project_specification.pdf out/inputs/purchase_request.xlsx out/manifest.json out/observed-session.json out/use-cases.md out/verification.json out/verify_live.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.