AAYMA
RU
Войти

EKT Assistant

AI-консультант по каталогу ekt.kz: поиск по артикулу и описанию, карточка с ценой и остатком, подбор аналогов для отсутствующих товаров, разбор…

Доведённый продукт
Команда «Bakdaulet-lab» 9💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: Bakdaulet-lab
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Доведённый продукт

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

Суть

AI-консультант по каталогу ekt.kz: поиск по артикулу и описанию, карточка с ценой и остатком, подбор аналогов для отсутствующих товаров, разбор спецификаций из файлов и добавление в корзину после подтверждения.

Как устроено

Next.js-сервер держит локальный индекс каталога в SQLite. В live-режиме актуальную карточку перечитывает из EKT REST API, в demo работает на синтетике. Аналоги ищет по критичным характеристикам с положительным остатком. Спецификации XLSX, DOCX и PDF разбираются через SheetJS, mammoth и pdf-parse, фото и сканы через OCR структурированным JSON в OpenAI. Чат на OpenAI Responses API с серверными инструментами может только подготовить предложение (живёт 10 минут). Корзину меняет отдельное подтверждение пользователя с повторной проверкой цены и остатка в транзакции SQLite.

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

  • function calling
  • OCR via LLM
  • analog matching by attributes
  • Zod validation
  • proposal/confirm pattern

Особенности

Жёсткая граница доверия: у модели нет инструмента подтверждения, корзину меняет только серверная проверка после явного согласия. Повторное подтверждение не удваивает количество. Проверено на реальном EKT API, есть загрузка спецификаций с OCR.

Стек

TypeScript · Next.js · React · SQLite · better-sqlite3 · Zod · Vitest · Playwright · Docker · Docker Compose

Модели

gpt-6-sol · OpenAI Responses API


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

README

README.md

EKT Assistant

Помощник по подбору электротоваров и подготовке корзины на основе каталога EKT.

О проекте

Покупателю электротоваров нужно найти нужный артикул, проверить характеристики и наличие, подобрать замену отсутствующей позиции и перенести список покупок в корзину. EKT Assistant объединяет эти действия в одном интерфейсе: покупатель вводит запрос или загружает спецификацию, проверяет предложенные позиции и подтверждает добавление.

Проект предназначен для покупателей и специалистов, собирающих электротовары по списку. Это прототип для хакатона с двумя режимами: demo на синтетических данных и live с чтением каталога через EKT API.

Корзина хранится на сервере прототипа. Интеграция с корзиной ekt.kz, резервирование, оформление заказа и проведение оплаты не реализованы.

Интерфейс EKT Assistant: реальный товар без остатка и найденный кандидат на замену

Мобильный экран · Сценарии демонстрации · Отчёт интеграции

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

ВозможностьЧто получает пользователь
Поиск товараПоиск по артикулу, названию и словам из описания в локальном индексе каталога.
Карточка и наличиеХарактеристики, цена, остаток, источник и время проверки. В live подробные данные перечитываются из EKT API.
Подбор аналоговДля товара без остатка — доступные кандидаты с совпадениями, различиями и оговорками о совместимости. Неизвестные или противоречивые параметры не считаются подтверждёнными совпадениями.
Условия покупкиОтветы об оплате, доставке и самовывозе со ссылкой на официальный источник. Если правило минимальной партии не подтверждено, приложение сообщает об этом.
Загрузка спецификацииИзвлечение строк из XLSX, XLS, DOCX, PDF и JPEG; ручная правка артикула, количества и единицы измерения. Распознавание фото и сканов требует OpenAI API.
Проверка строкПользователь отдельно отмечает проверенные позиции. В предложение входят только подходящие строки; причины исключения остальных видны в интерфейсе.
Предложение и согласиеПредварительный состав и сумма. Добавление происходит после отдельного подтверждения с повторной проверкой цены и остатка. Повторное подтверждение не удваивает количество.
Собственная корзинаСтраница /cart, изменение количества, удаление позиций и сохранение после обновления страницы.
История и сессияЧат и корзина привязаны к cookie-сессии; вход в аккаунт не требуется.
Ссылки на сертификатыОтображаются связанные ссылки, если они присутствуют в данных товара. Реальный применимый сертификат в проверенной выборке не подтверждён.

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

  1. Ввод: покупатель пишет артикул или запрос в чат либо загружает файл со списком позиций.
  2. Поиск и разбор: сервер ищет товары в индексе; из документа извлекает черновик строк. Фото и сканы обрабатываются через OpenAI при настроенном API.
  3. Проверка товара: в live сервер получает актуальную карточку EKT. Для отсутствующего товара ищет кандидатов с подходящими критичными характеристиками и положительным остатком.
  4. Проверка пользователем: покупатель выбирает товар и количество. Для файла он исправляет и отдельно отмечает каждую строку, которую хочет включить.
  5. Предложение: приложение показывает состав и сумму; корзина на этом шаге ещё не меняется.
  6. Подтверждение: кнопка «Подтвердить добавление» или однозначное согласие в чате запускает серверную проверку и запись в корзину. Пользователь получает ссылку на /cart.

Предложение действует 10 минут. Сервер проверяет его версию и принадлежность сессии. Модель может подготовить предложение, но инструмента подтверждения или прямого изменения корзины у неё нет.

Технологии

Версии и зависимости зафиксированы в package.json и package-lock.json.

КомпонентТехнологии
Язык и интерфейсTypeScript 5.9, React 19, CSS.
Приложение и серверNext.js 16, App Router и Route Handlers в Node.js runtime.
ХранениеSQLite через better-sqlite3: каталог, сессии, история, предложения, корзины и черновики файлов.
AIOpenAI SDK 6, Responses API, вызовы серверных инструментов; структурированный JSON для OCR. Модель задаётся через OPENAI_MODEL.
ВалидацияZod 4 для входных данных и ответов AI.
Разбор документовSheetJS xlsx 0.20.3, mammoth для DOCX, pdf-parse для PDF.
Интеграция с каталогомEKT REST API, HTTP Basic Auth.
ПроверкиVitest 4, ESLint 9, TypeScript, браузерные сценарии через playwright-core и Microsoft Edge.
КонтейнеризацияDockerfile на Node.js 22 и Docker Compose с томом для SQLite.

В сохранённых проверках от 23.09.2026 использовалась модель gpt-6-sol: подтверждены вызов инструмента и OCR на синтетических файлах. Автоматического выбора модели нет; доступность указанного имени зависит от API-аккаунта. Подробности — в отчёте интеграции и отчёте OCR.

Архитектура

flowchart TD
    UI["Браузер: чат, проверка файлов, корзина"] --> API["Next.js: серверные API"]
    API --> CORE["Поиск, аналоги, правила корзины"]
    API --> FILES["Разбор файлов и проверка строк"]
    API --> AI["AI-консультация с инструментами"]
    AI --> CORE
    CORE <--> DB[("SQLite")]
    FILES --> DB
    CORE --> EKT["EKT API в live"]
    CORE --> DEMO["Синтетический каталог в demo"]
    AI <--> OPENAI["OpenAI Responses API"]
    FILES -->|"Фото и сканы"| OPENAI
РасположениеНазначение
src/app/Страницы приложения и серверные маршруты чата, каталога, файлов и корзины.
src/components/Интерфейс чата, карточки товаров, проверка строк спецификации и корзина.
src/lib/ekt.ts, catalog.tsДоступ к EKT, нормализация карточек, локальный индекс и получение свежих деталей.
src/lib/analogs.ts, inventory.tsСравнение характеристик и проверка доступного количества.
src/lib/chat.ts, openai.tsСценарии консультации и инструменты AI. Ответы о товарах формируются из серверных данных.
src/lib/uploads.tsПарсеры документов, OCR и сопоставление строк с каталогом.
src/lib/cart.ts, db.ts, session.tsPrototypeCartAdapter, транзакции SQLite и изоляция сессий.
src/lib/policy.ts, certificates.tsСправка об условиях покупки и извлечение ссылок на сертификаты.
scripts/, tests/, docs/Синхронизация, автоматические проверки, тестовые файлы, сценарии и отчёты.

Индекс помогает найти кандидатов, а данные карточки и предложения в live перечитываются через detail API. Ключи внешних сервисов используются на сервере. Корзина меняется через серверные проверки и транзакции SQLite.

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

1. Подготовить окружение

Нужны Git, npm и Node.js 22.x от 22.12 либо 20.x от 20.19. Локальные проверки проекта выполнены на Node.js 20.20.0. Для установки зависимостей нужен доступ к сети.

git clone https://github.com/BAITC-Hacks/hack-d3504b88-bakdaulet-lab.git
cd hack-d3504b88-bakdaulet-lab
npm ci

Если репозиторий уже склонирован, перейдите в его папку и выполните npm ci.

2. Создать локальную конфигурацию

Создайте .env из .env.example, если файла ещё нет.

Windows PowerShell:

if (-not (Test-Path .env)) { Copy-Item .env.example .env }

Linux / macOS:

test -f .env || cp .env.example .env

Существующий .env сохраните. Для демонстрационного запуска задайте в нём:

DATA_MODE=demo
CART_MODE=prototype
DATABASE_PATH=./data/demo.sqlite

Ключи EKT и OpenAI для основного demo-сценария не нужны. Без OpenAI доступны поиск, условия покупки, корзина и разбор Excel, DOCX и текстового PDF. Фото и сканы требуют настроенного OpenAI API даже в demo.

Смена режима на той же БД очищает каталог, сессии, историю, предложения и корзины. Для demo и live используйте разные DATABASE_PATH, например ./data/demo.sqlite и ./data/live.sqlite. Перед изменением режима остановите сервер. При повторном запуске уже настроенного проекта сохраняйте его существующий путь к БД.

Основные параметры:

ПеременнаяНазначение / значение в примере
DATA_MODEdemo — синтетические товары; live — EKT API.
DATABASE_PATHПуть к SQLite; в .env.example — ./data/app.sqlite. База создаётся автоматически.
CART_MODEprototype; реализована собственная корзина.
EKT_API_BASE_URLhttps://ekt.kz/api.
EKT_API_USERNAME, EKT_API_PASSWORDУчётные данные EKT для live.
OPENAI_API_KEY, OPENAI_MODELКлюч и модель для AI-консультации и OCR; по умолчанию пустые.
CATALOG_MAX_PAGESМаксимум страниц при синхронизации; по умолчанию 25.
CATALOG_DETAIL_PAGESСколько начальных страниц дополнить деталями при синхронизации; по умолчанию 2.
UPLOAD_MAX_MBМаксимальный размер файла; по умолчанию 10, допустимый диапазон 1–20 МБ.

.env и локальные SQLite в data/ исключены из Git. Заполняйте секреты только локально.

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

npm run catalog:sync
npm run dev

Откройте http://localhost:3000. Статус доступен по http://localhost:3000/api/health: режим demo, четыре товара, готовность каталога.

Для другого порта: npm run dev -- -p 3001.

4. Подключить реальные данные — при наличии доступа

Остановите сервер. В локальном .env задайте DATA_MODE=live, отдельный DATABASE_PATH=./data/live.sqlite и заполните EKT_API_USERNAME / EKT_API_PASSWORD. Для AI и OCR также заполните OPENAI_API_KEY и OPENAI_MODEL; проверенное в отчётах значение — gpt-6-sol.

npm run catalog:sync
npm run dev

Для проверки обеих интеграций и поиска актуальной пары «нет в наличии → доступный аналог» выполните в другом терминале:

npm run live:check
npm run analogs:discover

live:check требует одновременно EKT и OpenAI: получает каталог и карточку, затем выполняет реальный цикл вызова инструмента модели. Запросы OpenAI могут расходовать средства API-аккаунта. analogs:discover использует EKT и частичный индекс; отсутствие найденной пары не означает отсутствия аналогов во всём магазине.

5. Запустить production-сборку локально

Остановите dev-сервер и выполните с выбранным .env:

npm run build
npm run start

Приложение будет доступно на http://localhost:3000. Не запускайте сборку и dev-сервер одновременно в одной папке: они используют общий каталог .next.

Docker Compose

Подготовлены Dockerfile и docker-compose.yml. После настройки .env, при работающем Docker Engine:

docker compose -p ekt-demo config --quiet
docker compose -p ekt-demo up --build

Перед стартом сервера выполняется синхронизация каталога. Порт по умолчанию — 3000; переменная APP_PORT позволяет изменить внешний порт. SQLite хранится в томе ekt-demo_ekt_data по пути /app/storage/app.sqlite; Compose переопределяет DATABASE_PATH из .env.

Для live используйте отдельное имя проекта, например -p ekt-live, чтобы получить отдельный том. Для остановки с сохранением данных: docker compose -p ekt-demo down.

Статическая конфигурация проверена. Сборка образа, запуск контейнера, его healthcheck и сохранность корзины после рестарта не проверены: в окружении проверки не было доступного Docker Engine. Подробности — в отчёте Docker.

Как проверить решение

Сценарий для жюри без API-ключей

Запустите приложение в DATA_MODE=demo. Откройте новое приватное окно браузера, чтобы начать с пустой корзины.

  1. Введите в чат DEMO-AV16-OLD. Ожидается карточка товара с нулевым остатком и кандидат на замену DEMO-AV16.
  2. Изучите характеристики и оговорки подбора. Спросите «Условия доставки в Алматы» — ответ должен содержать источник EKT.
  3. У аналога нажмите «Предложить 1 шт.». Проверьте состав предложения.
  4. Откройте корзину в другой вкладке того же окна: до согласия она должна быть пустой.
  5. Вернитесь в чат и нажмите «Подтвердить добавление». Откройте корзину по появившейся ссылке: в ней должна быть одна единица DEMO-AV16.
  6. Обновите страницу — позиция должна сохраниться. Проверьте изменение количества и удаление.

Все четыре demo-товара, их цены, остатки и пример сертификата в fixtures — синтетические данные, не сведения о продаже в EKT.

Сценарий со спецификацией

Начните с новой сессии и пустой корзины в demo:

  1. Кнопкой загрузки рядом с полем чата выберите tests/fixtures/ocr/review.xlsx.
  2. У первой строки DEMO-AV16 / 2 / шт поставьте отметку «Проверено».
  3. У второй строки DEMO-AV25 задайте количество 1, единицу шт, затем отметьте её проверенной.
  4. Третью строку с неизвестным артикулом оставьте исключённой. В сводке должны быть выбраны 2 из 3 строк.
  5. Нажмите «Подготовить предложение из проверенных строк». До отдельного подтверждения корзина должна оставаться пустой.
  6. Подтвердите добавление: в корзине ожидаются DEMO-AV16 × 2 и DEMO-AV25 × 1.

Этот сценарий проверяет разбор Excel и ручное согласование без вызова OCR. Для фото и сканов нужны ключ и модель OpenAI; образцы и результаты находятся в отчёте OCR.

Автоматические проверки

При остановленном dev-сервере:

npm test
npm run lint
npm run typecheck
npm run build

Для HTTP-проверки demo запустите npm run dev в первом терминале, затем во втором:

node scripts/smoke-http.mjs

Сценарий проверяет поиск, чат, предложение, подтверждение, повтор без дублирования, редактирование корзины и загрузку XLSX/PDF. Браузерная проверка demo: node scripts/check-browser.mjs.

Для работающего live-сервера предусмотрены npm run smoke:live и npm run browser:live. Они зависят от реальных API и текущих данных. Браузерные скрипты используют playwright-core и установленный Windows Microsoft Edge по пути C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe.

HTTP- и браузерные скрипты по умолчанию обращаются к http://localhost:3000. Если сервер работает на другом порту, задайте APP_BASE_URL в терминале проверок: $env:APP_BASE_URL='http://localhost:3001' в PowerShell или export APP_BASE_URL=http://localhost:3001 в Bash. Эти Node.js-скрипты не загружают .env автоматически.

Какие результаты зафиксированы

В сохранённых отчётах за 23.09.2026 указаны следующие результаты:

ПроверкаРезультат и границы
Объединённая версияnpm ci, 148/148 тестов, lint, typecheck и production build прошли.
EKT и OpenAIРеальные запросы каталога и карточки; gpt-6-sol выполнил вызов серверного инструмента. Live-индекс содержал 500 товаров и оставался частичным.
Реальный аналог и корзинаТовар 18157 / 310100086_ с остатком 0 → 515262 / 200300265_ с остатком 74 шт. Проверены согласие, повтор, изоляция сессий и очистка тестовой позиции.
БраузерLive-сценарий на desktop 1440 px и mobile 390 px, включая сохранение корзины после обновления. Отдельно прошёл demo-сценарий проверки строк спецификации.
OCRВ ветке OCR прошли три реальных запроса на синтетических JPEG/PDF; при интеграции платные OCR-запросы не повторялись.
DockerCompose config и три локальных теста запуска прошли; два POSIX-теста пропущены на Windows. Контейнерная работа не проверена.

Это результаты на дату проверки. Цены, остатки и доступность live-пары нужно перепроверять перед показом. Детали: интеграция, матрица приёмки, сценарий демонстрации.

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

ИсточникКак используется
Демонстрационный каталогЧетыре синтетических товара для воспроизводимого запуска без EKT API.
EKT API: https://ekt.kz/apiGET /products?page=N для индекса и GET /products/detail?id=N для карточки. Доступ по HTTP Basic Auth.
Официальные условия покупки EKTИсточник справочных ответов. Текст хранится в src/lib/policy.ts, дата проверки в коде — 23.09.2026; страница не перечитывается при каждом вопросе.
OpenAI Responses APIAI-консультация с инструментами и распознавание фото/сканов. При AI-консультации передаются запрос и результаты инструментов, при OCR — содержимое изображения или PDF.
Файлы пользователяТекстовые документы разбираются серверными библиотеками. В SQLite сохраняется сокращённый черновик строк, привязанный к сессии; исходные файлы не публикуются в public/.

По умолчанию синхронизация ограничена 25 страницами; индекс не объявляется полным каталогом. /api/health показывает фактический режим, число товаров и готовность. Неизвестные данные об остатках, единицах или совместимости не заменяются предположениями модели.

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

  • Корзина прототипа: официальный API корзины EKT не интегрирован; нет оформления заказа, оплаты и резервирования. Наличие при запросе не гарантирует продажу.
  • Подбор аналогов: реализованы правила для автоматов, дифавтоматов и светильников. Подбор УЗО исключён. Монтажная совместимость и аксессуары не подтверждаются; в частичном индексе подходящий товар может отсутствовать.
  • Количество и склады: в одном предложении допускается до 30 строк. Поддерживаются целые положительные количества в штуках без вариантов offers. Мерные товары требуют отдельной реализации. Пересекающиеся остатки общего каталога, города и склада консервативно отклоняются, если их нельзя безопасно распределить.
  • Документы: XLSX, XLS, DOCX, PDF, JPG/JPEG. Старый DOC и PNG не поддержаны. Лимиты — 100 строк, 5 листов Excel, 10 страниц PDF и по умолчанию 10 МБ. Превышение лимита отклоняет документ целиком. Сложная вёрстка и OCR требуют ручной проверки.
  • Проверка OCR: реальное фото клиента не проверено; защищённый паролем PDF проверен только через mock. Успешные OCR-примеры синтетические.
  • Сертификаты: извлечение ссылок реализовано, но в выборке из 23 реальных карточек применимый сертификат не подтверждён. Общий каталог сертификатов не доказывает применимость к конкретному товару. См. отчёт.
  • Условия покупки: справочный текст требует обновления при изменении источника; онлайн-расчёт доставки отсутствует.
  • Развёртывание: SQLite предполагает один сервер с постоянным диском. Горизонтальное масштабирование и Docker runtime не проверены.

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

Ссылка на публично развёрнутое приложение в репозитории не указана. Решение можно проверить локально по инструкции выше: http://localhost:3000.

Как задеплоить проект: пошагово

Ниже — вариант для одного Linux-сервера (VPS), Docker и собственного домена. Приложение работает в контейнере, SQLite сохраняется в отдельном томе, а Caddy принимает запросы по HTTPS. Caddy добавляется этой инструкцией как внешний прокси.

Коротко: подготовить сервер → скопировать проект → заполнить .env → указать домен → запустить Docker Compose.

Это инструкция для развёртывания, а не отчёт об уже работающем сайте: полный запуск Docker и HTTPS на публичном сервере пока не проверен.

Шаг 1. Подготовить сервер и домен

Понадобятся:

  • VPS с Ubuntu 24.04, постоянным диском и доступом по SSH с sudo.
  • Docker Engine и плагин Docker Compose. Если их ещё нет, установите по официальной инструкции для Ubuntu.
  • Домен или поддомен, например ekt.example.com. Это пример: в командах и конфигурации ниже замените его своим адресом.

В панели DNS создайте запись A для выбранного домена со значением публичного IP сервера. Если есть запись AAAA, она тоже должна указывать на этот сервер. Разрешите входящие TCP-порты 80 и 443 в панели провайдера и firewall сервера; сохраните доступ по SSH. Порт приложения 3000 наружу публиковать не требуется.

Подключитесь к серверу, заменив ubuntu и SERVER_IP своими данными:

ssh ubuntu@SERVER_IP

Дальнейшие команды выполняйте на сервере в Bash:

sudo apt-get update
sudo apt-get install -y git curl nano
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version

Node.js и npm на сервер отдельно устанавливать не нужно: они находятся в образе приложения.

Шаг 2. Скачать проект и настроить .env

mkdir -p ~/ekt-deploy
cd ~/ekt-deploy
git clone https://github.com/BAITC-Hacks/hack-d3504b88-bakdaulet-lab.git app
cd app
test -f .env || cp .env.example .env
chmod 600 .env
nano .env

Для первого запуска оставьте DATA_MODE=demo и CART_MODE=prototype. Так можно проверить сайт без ключей EKT и OpenAI.

Для реального каталога сразу задайте DATA_MODE=live и заполните EKT_API_USERNAME / EKT_API_PASSWORD. Для AI и распознавания фото/сканов также нужны OPENAI_API_KEY и OPENAI_MODEL; значение модели должно быть доступно вашему API-аккаунту. В отчётах проекта проверено gpt-6-sol.

В nano: Ctrl+O → Enter сохраняет файл, Ctrl+X закрывает редактор. Секреты остаются только в серверном .env. Не добавляйте их в Git. Путь SQLite в контейнере задаст конфигурация следующего шага.

Шаг 3. Создать конфигурацию контейнеров

Вернитесь в папку развёртывания и создайте compose.yaml:

cd ~/ekt-deploy
nano compose.yaml

Вставьте целиком:

name: ekt-public

services:
  app:
    build: ./app
    init: true
    restart: unless-stopped
    stop_grace_period: 30s
    env_file:
      - ./app/.env
    environment:
      DATABASE_PATH: /app/storage/app.sqlite
      PORT: "3000"
    volumes:
      - ekt_data:/app/storage

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - app

volumes:
  ekt_data:
  caddy_data:
  caddy_config:

Этот файл расположен рядом с папкой app, отдельно от Git-репозитория. Все дальнейшие команды docker compose запускайте из ~/ekt-deploy: они используют именно эту конфигурацию. Имя ekt-public сохраняйте при обновлениях — от него зависит имя тома с БД. Политика restart запускает контейнеры после перезапуска Docker, если вы не остановили их вручную. См. развёртывание Compose на одном сервере.

Шаг 4. Подключить домен и HTTPS

nano ~/ekt-deploy/Caddyfile

Вставьте конфигурацию, заменив оба вхождения ekt.example.com своим доменом:

ekt.example.com {
    @bad_origin {
        not method GET HEAD OPTIONS
        not header Origin https://ekt.example.com
    }
    respond @bad_origin "Forbidden origin" 403

    reverse_proxy app:3000 {
        header_up X-Forwarded-Proto https
        header_up Origin https://0.0.0.0:3000
    }
}

При правильно настроенном DNS и доступных портах 80/443 Caddy получает и продлевает HTTPS-сертификат автоматически. Сертификаты сохраняются в его отдельном томе. Основание: автоматический HTTPS и образ Caddy.

Зачем здесь проверка Origin: текущий Docker-запуск Next.js использует внутренний адрес 0.0.0.0:3000, с которым приложение сравнивает источник запроса. Поэтому прокси сначала допускает изменяющие запросы только с вашего HTTPS-домена, затем подставляет внутренний Origin. Сохраняйте обе части конфигурации вместе и не публикуйте порт app наружу. Адрес https://0.0.0.0:3000 в header_up менять на домен не нужно. Использованы стандартные правила сопоставления запросов и настройки reverse_proxy.

Шаг 5. Собрать и запустить

cd ~/ekt-deploy
sudo docker compose config --quiet
sudo docker compose run --rm --no-deps caddy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo docker compose up -d --build
sudo docker compose ps
sudo docker compose logs --tail=100 app caddy

Первая сборка скачивает зависимости. Затем приложение синхронизирует каталог и запускает HTTP-сервер; в live это может занять несколько минут. До завершения синхронизации прокси может отвечать 502. Посмотреть запуск в реальном времени: sudo docker compose logs -f app. Ctrl+C закрывает просмотр логов, контейнеры продолжают работать.

Откройте https://ekt.example.com, заменив домен своим. Проверьте готовность:

curl -fsS https://ekt.example.com/api/health

Ожидается ready: true, выбранный режим и непустой каталог. В demo — четыре товара. Одного ответа health недостаточно: в браузере выполните сценарий DEMO-AV16-OLD → аналог → предложение → подтверждение → корзина, затем обновите страницу. В live используйте актуальную пару из каталога.

Проверьте также защиту изменяющих запросов: следующий запрос с чужим Origin должен вернуть 403:

curl -sS -o /dev/null -w '%{http_code}\n' \
  -X POST https://ekt.example.com/api/chat \
  -H 'Origin: https://other.example.com' \
  -H 'Content-Type: application/json' \
  --data '{"message":"DEMO-AV16"}'

Для проверки сохранения данных создайте позицию в корзине, выполните sudo docker compose restart app, дождитесь готовности и обновите корзину в той же сессии браузера. Только после этих проверок добавляйте реальный адрес сайта в раздел «Развёрнутая версия» выше.

Шаг 6. Обновлять проект и сохранять данные

Перед обновлением — резервная копия БД. Остановка приложения на время копирования сохраняет согласованность SQLite и её служебных файлов:

cd ~/ekt-deploy
backup_dir="./backups/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir"
chmod 700 ./backups "$backup_dir"
sudo docker compose stop app
sudo docker compose cp app:/app/storage/. "$backup_dir/"
sudo docker compose start app

Сохраните эту папку и серверный .env также вне VPS. Они содержат данные приложения и секреты; в Git их не добавляйте. Если копирование завершилось ошибкой, возобновите приложение командой sudo docker compose start app и разберитесь с ошибкой до обновления.

Обновление кода:

cd ~/ekt-deploy/app
git pull --ff-only origin main
cd ~/ekt-deploy
sudo docker compose up -d --build
sudo docker compose ps
curl -fsS https://ekt.example.com/api/health

После изменения ключей в app/.env пересоздайте контейнер: sudo docker compose up -d --force-recreate app. После изменения Caddyfile: sudo docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile.

Остановка сайта: sudo docker compose down. Тома останутся. Не добавляйте -v: этот флаг удалит тома, включая корзины и сессии. Данные приложения находятся в томе ekt-public_ekt_data, а не в папке Git-репозитория.

При переходе с demo на live: остановите прежний проект командой sudo docker compose down, измените DATA_MODE и ключи в .env, а name в compose.yaml — например, на ekt-public-live. Затем запустите sudo docker compose up -d --build. Новый проект получит отдельную БД; старый том останется сохранённым. Смена режима в прежнем томе очистила бы корзины и сессии.

Если что-то не работает

СимптомЧто проверить
Сайт недоступен или сертификат не выпущенDNS A/AAAA, доступность портов 80/443, отсутствие другого сервера на этих портах; sudo docker compose logs --tail=100 caddy.
502 или приложение перезапускаетсяsudo docker compose logs --tail=100 app: дождитесь синхронизации либо исправьте ошибку доступа к EKT.
403 при отправке сообщения или подтвержденииОткрывайте именно HTTPS-домен из Caddyfile; проверьте оба вхождения домена, блок @bad_origin и header_up.
Каталог не готов в liveУчётные данные EKT и доступ сервера к API; /api/health должен показывать ready: true.
Фото или скан не распознаётсяКлюч и доступность OPENAI_MODEL, ответ сервиса и лимиты загружаемого файла.
Исчезла прежняя корзинаТа же ли cookie-сессия, имя Compose-проекта и том; не менялся ли DATA_MODE в той же БД.

В этом варианте работает один экземпляр приложения с постоянным томом SQLite. Публичную доступность сайта, HTTPS, подтверждение корзины и сохранность после рестарта нужно проверить на вашем сервере; успешный локальный build сам по себе этого не подтверждает.

Звёзды
⭐ 0
Форки
0
Язык
TypeScript
Последний push
в прошлом месяце
Коммиты
17
Авторы
1
Файлы
93
Ветки
1

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

Bagdaulet 17

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

  • 2026-09-23 17:54docs: add step-by-step VPS deployment guideBagdaulet
  • 2026-09-23 17:37docs: write Russian project README for hackathon juryBagdaulet
  • 2026-09-23 17:25Verify integrated EKT flows and document resultsBagdaulet
  • 2026-09-23 17:09Merge branch 'parallel/docker'Bagdaulet
  • 2026-09-23 17:09Merge branch 'parallel/certificates'Bagdaulet
  • 2026-09-23 17:09Merge branch 'parallel/ocr'Bagdaulet
  • 2026-09-23 17:07Merge branch 'parallel/core'Bagdaulet
  • 2026-09-23 17:05fix(ocr): preserve uncertain rows and require upload reviewBagdaulet
Все коммиты (17)
  • 2026-09-23 16:57Fix certificate link extraction and record bounded live investigationBagdaulet
  • 2026-09-23 16:56fix(core): enforce cart stock and AI trust boundariesBagdaulet
  • 2026-09-23 16:54fix(docker): supervise startup and isolate persistent dataBagdaulet
  • 2026-09-23 16:28Document parallel EKT worktree workflowBagdaulet
  • 2026-09-23 16:12Verify live EKT and OpenAI flow with real analogBagdaulet
  • 2026-09-23 15:42Add project linting and live catalog statusBagdaulet
  • 2026-09-23 15:34Persist chat state and prepare live analog discoveryBagdaulet
  • 2026-09-23 15:26Harden cart and document workflows with live diagnosticsBagdaulet
  • 2026-09-23 15:12Build EKT assistant prototype with catalog, chat, and cartBagdaulet
Структура репозитория (93 файлов)
.dockerignore
.env.example
.gitignore
AGENTS.md
CLAUDE.md
Dockerfile
README.md
data/fixtures/catalog.json
docker-compose.yml
docs/EKT_CODEX_MASTER_PLAN.md
docs/EKT_PARALLEL_TASKS.md
docs/acceptance.md
docs/demo.md
docs/reports/certificates.md
docs/reports/certificates/live-evidence.json
docs/reports/core.md
docs/reports/core/check-http.mjs
docs/reports/docker.md
docs/reports/integration.md
docs/reports/ocr.md
docs/reports/ocr/browser-results.json
docs/reports/ocr/live-results.json
docs/reports/ocr/review-1440.png
docs/reports/ocr/review-390.png
docs/screenshot-desktop.png
docs/screenshot-live-desktop.png
docs/screenshot-live-mobile.png
docs/screenshot-mobile.png
eslint.config.mjs
next.config.ts
package-lock.json
package.json
scripts/check-browser.mjs
scripts/check-certificates.ts
scripts/check-live-browser.mjs
scripts/check-live.ts
scripts/check-ocr-live.mjs
scripts/discover-analogs.ts
scripts/docker/check-start.mjs
scripts/docker/healthcheck.mjs
scripts/docker/start.mjs
scripts/probe-live.ts
scripts/smoke-http.mjs
scripts/smoke-live-http.mjs
scripts/sync-catalog.ts
src/app/api/cart/confirm/route.ts
src/app/api/cart/proposals/route.ts
src/app/api/cart/route.ts
src/app/api/chat/route.ts
src/app/api/health/route.ts
src/app/api/products/[id]/route.ts
src/app/api/products/search/route.ts
src/app/api/uploads/route.ts
src/app/cart/page.tsx
src/app/globals.css
src/app/layout.tsx
src/app/page.tsx
src/components/CartView.tsx
src/components/CatalogStatus.tsx
src/components/Chat.tsx
src/components/UploadReview.tsx
src/lib/analogs.ts
src/lib/cart.ts
src/lib/catalog.ts
src/lib/certificates.ts
src/lib/chat.ts
src/lib/db.ts
src/lib/ekt.ts
src/lib/http.ts
src/lib/inventory.ts
src/lib/openai.ts
src/lib/policy.ts
src/lib/session.ts
src/lib/types.ts
src/lib/uploads.ts
tests/analogs.test.ts
tests/cart.test.ts
tests/catalog.test.ts
tests/certificates.test.ts
tests/chat.test.ts
tests/core-cart.test.ts
tests/fixtures/ocr/README.md
tests/fixtures/ocr/replacement.xlsx
tests/fixtures/ocr/review.xlsx
tests/fixtures/ocr/scan.pdf
tests/fixtures/ocr/specification.jpg
tests/fixtures/ocr/unclear.jpg
tests/ocr-pdf.test.ts
tests/ocr.test.ts
tests/openai-core.test.ts
tests/session-core.test.ts
tests/uploads.test.ts
tsconfig.json

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

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

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