AAYMA
RU
Войти

DAIRN

Чат-ассистент для сайта ekt.kz из трёх сервисов: React-фронтенд, FastAPI-ассистент с worker и Kotlin/Ktor-сервис каталога EKT с нормализацией и…

Рабочий MVP
Команда «DAIRN» 8💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: DAIRN
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Рабочий MVP

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

Суть

Чат-ассистент для сайта ekt.kz из трёх сервисов: React-фронтенд, FastAPI-ассистент с worker и Kotlin/Ktor-сервис каталога EKT с нормализацией и AI-поиском кандидатов.

Как устроено

Kotlin/Ktor-сервис ходит в партнёрский EKT API по Basic Auth, пагинирует и нормализует карточки товаров, отдаёт Catalog API и умеет AI-поиск кандидатов по SearchIntent с пре-валидацией записей через OpenAI structured outputs. FastAPI Assistant хранит сессии, сообщения и задания в SQLite, отдельный worker разбирает сообщения и вложения (например XLS-спецификации), запрашивает каталог и генерирует ответ модели. Точные артикулы обрабатываются быстрым шаблоном без LLM. React+TS фронтенд через Nginx даёт регистрацию, вход и чат.

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

  • нормализация каталога EKT
  • AI-поиск кандидатов (SearchIntent)
  • OpenAI structured outputs prevalidation
  • очередь заданий в SQLite + worker
  • парсинг вложений (XLS)
  • шаблонный ответ для точного артикула
  • алиасы поиска для автоматов

Особенности

Сервисы разделены чётко: только Catalog service держит учётные данные EKT. Ограничения задокументированы честно: охват PARTIAL не значит, что товара нет, операции корзины в общем стеке отключены. Подробные инструкции по развёртыванию на Ubuntu и live smoke-тест.

Стек

React · TypeScript · Nginx · Python · FastAPI · SQLite · Kotlin · Ktor · Gradle · Docker Compose · Playwright · GitHub Actions

Модели

OpenAI (модель через ASSISTANT_MODEL_NAME)


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

README

README.md

DAIRN — ассистент для сайта EKT

Проект команды DAIRN для хакатона: веб-интерфейс с чатом, backend ассистента и отдельный сервис интеграции с каталогом EKT. Условия задачи — в hackathon-task.json.

Состав проекта

КомпонентНазначение
frontend-serviceReact + TypeScript: интерфейс сайта, чат и отображение товаров
assistant-serviceFastAPI: сессии, сообщения, вложения и сценарии корзины; для обработки нужен отдельный worker
ekt-catalog-serviceKotlin/JVM + Ktor: клиент EKT, нормализация, Catalog API, CLI и опциональный AI-поиск кандидатов

Корневой docker-compose.yml объединяет три сервиса и worker в общей Docker-сети. Сервисные Compose-файлы предназначены для независимого запуска.

Запуск всего приложения

Корневой docker-compose.yml запускает frontend, Catalog API, Assistant API и обязательный worker. Нужны Docker Engine, Compose plugin и доступ к EKT API. Node.js, Python и Java на Docker-хосте не требуются: они входят в образы. Все команды ниже выполняются из корня репозитория.

# Создать конфигурацию только при первом запуске; не перезаписывать существующую.
umask 077
if [ ! -f .env ]; then cp .env.example .env; fi
chmod 600 .env
nano .env

# Заполнить EKT_API_USERNAME, EKT_API_PASSWORD и PUBLIC_FRONTEND_URL.
# Для ответов модели также задать ASSISTANT_MODEL_API_KEY и ASSISTANT_MODEL_NAME.
docker compose config --quiet
docker compose up -d --build --wait --wait-timeout 180
docker compose ps

Корневой Compose читает корневой .env. Файлы .env внутри сервисов для этого запуска не нужны. .env.example содержит только пример конфигурации; настоящий .env игнорируется Git. Не помещайте пароли EKT, ключи моделей или bootstrap-токен в frontend, Git, URL репозитория либо документацию.

КомпонентАдрес по умолчанию на локальной машинеАдрес внутри Docker
Frontendhttp://localhost:5173http://frontend:8080
Assistant API / Swaggerhttp://localhost:8000/docshttp://assistant-service:8000
Catalog APIhttp://localhost:8080/api/catalog/products?query=LEDhttp://ekt-catalog-service:8080
Assistant workerОтдельного публичного порта нетОбщая SQLite-база с Assistant API

Браузер обращается к /assistant/ на origin frontend; Nginx передаёт запросы Assistant API. API сохраняет задания в SQLite, worker обрабатывает сообщения и вложения, запрашивает Catalog API и при настройке модели генерирует ответ. Только Catalog service использует Basic Auth для доступа к EKT.

В общем стеке включены live-каталог и production-режим Assistant. Гостевые сессии, синтетическая подмена каталога и операции корзины отключены. Пользователь регистрируется или входит через frontend. Локальные аккаунты ещё не связаны с аккаунтами и корзиной сайта EKT. Независимый деморежим описан ниже в разделе локальной разработки.

Развёртывание на Ubuntu

Основа инструкции — deploy.md. Установите Docker Engine и Compose plugin по официальной инструкции для Ubuntu. Если репозитория ещё нет, клонируйте его через настроенный Git-доступ:

git clone https://github.com/BAITC-Hacks/hack-e4fbf0e4-dairn.git
cd hack-e4fbf0e4-dairn
git branch --show-current

Перед сборкой выберите согласованную ветку развёртывания. Не считайте, что новые изменения рабочей ветки уже включены в main. Для существующего checkout перейдите в его фактический каталог и используйте команды обновления ниже.

Создайте .env и запустите стек командами из быстрого старта. Задайте PUBLIC_FRONTEND_URL=http://SERVER_IP:5173 либо реальный origin домена, без завершающего / и пути. В браузере используйте этот адрес, а не localhost сервера. При изменении FRONTEND_PORT обновите порт и в PUBLIC_FRONTEND_URL.

На небольшом сервере можно последовательно собрать образы, чтобы снизить пиковое потребление памяти. Assistant API и worker используют один образ:

docker compose config --quiet
docker compose build frontend
docker compose build assistant-service
docker compose build ekt-catalog-service
docker compose up -d --no-build --wait --wait-timeout 180

До запуска проверьте занятые порты. FRONTEND_PORT, ASSISTANT_PORT и CATALOG_PORT меняют опубликованные порты хоста; внутренние Docker-адреса остаются прежними. Например, CATALOG_PORT=8081 освобождает конфликт с другим приложением на 8080. Сервисные Compose-стеки не нужно запускать одновременно с корневым на тех же портах.

По умолчанию порты публикуются на всех IPv4-интерфейсах (BIND_ADDRESS=0.0.0.0). Разрешите нужные TCP-порты в firewall сервера/провайдера. Docker-публикация портов может обходить правила UFW; настройка firewall описана в инструкции Docker выше. Для доступа только с хоста или через внешний reverse proxy используйте BIND_ADDRESS=127.0.0.1. Compose сам не выпускает TLS-сертификаты: перед передачей реальных учётных данных через Интернет настройте HTTPS на внешнем reverse proxy. Прямой Catalog API пока не требует аутентификации.

Проверка после запуска

При нестандартных портах замените их в командах:

curl --fail http://localhost:5173/health/live
curl --fail http://localhost:5173/assistant/health/live
curl --fail http://localhost:8000/health/live
curl --fail http://localhost:8080/health/ready
curl --fail 'http://localhost:8080/api/catalog/products?query=LED'
docker compose ps
docker compose logs --tail=100

healthy и /health/ready не подтверждают правильность credentials EKT или доступность upstream: проверьте товарный запрос и metadata ответа. Пустой список с охватом PARTIAL сам по себе не является ошибкой связи и не доказывает отсутствие товара в полном каталоге. Затем откройте frontend, зарегистрируйтесь и отправьте в чат артикул из полученного ответа. При настроенной модели естественный вопрос должен вернуть answer_mode: model; точный артикул может обрабатываться быстрым шаблоном без вызова модели.

Дополнительная проверка реального стека выполняет платные обращения к модели, создаёт временные аккаунты/чаты и удаляет созданные ею данные:

docker compose exec -T -e LIVE_STACK_TEST=1 assistant-service python - \
  < assistant-service/tests/live_stack_smoke.py

Для неё нужны доступные EKT/OpenAI и товар с артикулом в результате поиска LED. Это smoke-проверка, а не нагрузочный тест или гарантия p95 задержки.

Обновление и данные

git status --short
git branch --show-current
git pull --ff-only
docker compose config --quiet
docker compose up -d --build --wait --wait-timeout 180
docker compose ps

Сохраните локальные изменения до обновления; --ff-only останавливается при расхождении веток. Существующий .env при обновлении не копируют заново из примера: добавляйте только новые необходимые параметры. Изменение настроек требует пересоздания контейнеров через up -d; изменения VITE_CERTIFICATE_ORIGINS требуют также пересборки frontend. Для нового домена frontend пересобирать не нужно: он использует proxy на своём origin.

Контейнеры автоматически перезапускаются, логи ротируются. SQLite и загрузки Assistant находятся в /data общего named volume assistant-data (для проекта ekt обычно ekt_assistant-data). Перед резервным копированием остановите оба процесса — API и worker — и скопируйте весь /data, затем запустите их снова. Храните резервную копию отдельно от Docker-хоста. Для отката сохраните ревизию, образы и совместимую копию данных перед обновлением.

docker compose down сохраняет named volume; docker compose down -v удаляет пользователей и историю вместе с ним. Смена имени Compose-проекта или переход с отдельного Compose-файла сервиса на корневой может создать другой volume: данные автоматически не мигрируют. Этот вариант рассчитан на один хост; масштабирование API/worker требует отдельной проверки работы SQLite.

Переменные окружения

Таблица описывает все параметры, которые корневой Compose читает из .env. Значения по умолчанию сверены с .env.example и docker-compose.yml; параметры отдельных сервисов могут отличаться. Секреты в примере оставлены пустыми.

ПеременнаяОбязательностьПо умолчаниюНазначение
PUBLIC_FRONTEND_URLУказать для сервераhttp://localhost:5173Origin браузерного frontend без пути и завершающего /; задаёт разрешённый CORS origin и базу ссылок Assistant.
BIND_ADDRESSНет0.0.0.0Интерфейс для опубликованных портов; 127.0.0.1 ограничивает доступ хостом.
FRONTEND_PORTНет5173Порт frontend на хосте; согласовать с PUBLIC_FRONTEND_URL.
ASSISTANT_PORTНет8000Порт Assistant API на хосте.
CATALOG_PORTНет8080Порт Catalog API на хосте; Docker-взаимодействие остаётся на 8080.
IMAGE_TAGНетlocalТег общего образа ekt-assistant для API и worker.
EKT_API_BASE_URLНетhttps://ekt.kzHTTPS origin источника EKT, без /api; используется Catalog service.
EKT_API_USERNAMEДаПустоЛогин Basic Auth партнёрского EKT API. Compose не запускается с пустым значением.
EKT_API_PASSWORDДаПустоПароль Basic Auth партнёрского EKT API. Compose не запускается с пустым значением.
CATALOG_MAX_PRODUCTSНет100Максимум загружаемых товаров, от 1 до 1000; не подтверждает полный охват каталога.
CATALOG_CACHE_TTL_SECONDSНет60Срок кеша нормализованной выборки в секундах, от 1 до 3600.
CATALOG_LOAD_TIMEOUT_MSНет25000Общий таймаут загрузки каталога в миллисекундах, от 1 до 120000.
ASSISTANT_CATALOG_TIMEOUT_SECONDSНет30Таймаут HTTP-запроса Assistant к Catalog, больше 0 и не больше 30 секунд. Согласуйте с таймаутом загрузки.
ASSISTANT_MODEL_API_KEYДля живых AI-ответовПустоКлюч OpenAI только для Assistant; без ключа используются ответы-шаблоны по данным каталога.
ASSISTANT_MODEL_NAMEЕсли задан ключ AssistantПустоЯвное имя модели Assistant; автоматического выбора модели нет.
ASSISTANT_BOOTSTRAP_TOKENНетПустоСекрет для доверенного website backend; регистрация и вход пользователей работают без него. Во frontend не передавать.
ASSISTANT_AUTH_TOKEN_TTL_SECONDSНет86400Срок account bearer token в секундах, от 60 до 2592000; logout отзывает текущий токен.
ASSISTANT_USER_HISTORY_TTL_DAYSНет30Срок хранения чата пользователя от создания, от 1 до 365 дней; не продлевает срок хранения вложений.
OPENAI_SEARCH_ENABLEDНетfalseВключает отдельный AI-поиск кандидатов в Catalog API; обычный GET-поиск не зависит от этого флага.
OPENAI_PREVALIDATION_ENABLEDНетfalseФлаг отдельной AI-предвалидации; не включает AI-поиск и не подключает её автоматически ко всему pipeline.
OPENAI_API_KEYДля AI-функций каталогаПустоКлюч OpenAI для Catalog service; независим от ASSISTANT_MODEL_API_KEY.
OPENAI_MODELДля AI-функций каталогаПустоЯвная модель Catalog AI; независима от модели Assistant.
OPENAI_TIMEOUT_MSНет20000Таймаут AI-запроса Catalog в миллисекундах, от 1 до 120000.
OPENAI_MAX_OUTPUT_TOKENSНет2048Ограничение токенов ответа Catalog AI, от 256 до 8192.
VITE_CERTIFICATE_ORIGINSНетПустоДополнительные доверенные origin ссылок сертификатов через запятую. Публичный build-параметр; после изменения пересобрать frontend.

Два набора ключей модели относятся к разным микросервисам. Настройка Assistant AI не включает Catalog AI, и наоборот. Для обычного чата с поиском достаточно ключа и модели Assistant; отдельный AI-поиск каталога по умолчанию выключен.

Настройки, заданные самим Compose

Добавление произвольной переменной в корневой .env не передаёт её автоматически в контейнер. Например, эти настройки зафиксированы в Compose или получаются из других переменных:

НастройкаЗначение в общем стеке
ASSISTANT_ENVIRONMENT, ASSISTANT_ALLOW_DEMO_SESSIONSproduction, false
ASSISTANT_CATALOG_MODE, ASSISTANT_CATALOG_BASE_URLhttp, http://ekt-catalog-service:8080
ASSISTANT_CATALOG_MOCK_ON_UNAVAILABLE, ASSISTANT_CART_MODEfalse, disabled
ASSISTANT_DATA_DIR, ASSISTANT_POLICY_FILE/data, /config/policies.json
ASSISTANT_FRONTEND_BASE_URL, ASSISTANT_CORS_ORIGINSФормируются из PUBLIC_FRONTEND_URL.
CATALOG_SOURCE, CATALOG_HOST, PORTlive, 0.0.0.0, 8080
CATALOG_CORS_ORIGINSФормируется из PUBLIC_FRONTEND_URL.
VITE_ASSISTANT_API_URL/assistant при сборке frontend.

CATALOG_MAX_PAGES не передаётся корневым Compose; действует значение JVM по умолчанию — 10. Дополнительные параметры Python/JVM, например ASSISTANT_MODEL_TIMEOUT_SECONDS или CATALOG_MAX_PAGES, требуют изменения environment в Compose/override либо самостоятельного запуска сервиса. Они не начнут действовать только от добавления строки в корневой .env.

Быстрый старт каталога без credentials

Нужен JDK 21. Gradle 8.8 закреплён в wrapper; при первой сборке нужен доступ к дистрибутиву Gradle и зависимостям. Из корня репозитория:

cd ekt-catalog-service
sh ./gradlew --no-daemon test serverDist
python3 scripts/verify-local.py

Для HTTP-проверки нужен Python 3. Скрипт временно запускает собранный JAR, проверяет API на сохранённом образце и обработку ошибок, затем останавливает сервер. Чтобы оставить сервер запущенным:

# Из ekt-catalog-service/
CATALOG_SOURCE=snapshot \
CATALOG_SNAPSHOT_PATH=src/test/resources/ekt/products-page-1.json \
sh ./gradlew runServer

В другом терминале:

curl -i 'http://127.0.0.1:8080/health/live'
curl -i 'http://127.0.0.1:8080/api/catalog/products/45357'
curl -i 'http://127.0.0.1:8080/api/catalog/products?query=310100080_'

Это офлайн-запуск на сохранённой странице из 20 товаров. Он не подтверждает доступность EKT или актуальность цен. Задача runServer запускает HTTP-сервис, а run — CLI. Для запуска собранного JAR сохраняйте рядом каталог build/server/lib/.

Возможности Catalog API

Метод и маршрутТекущее поведение
GET /api/catalog/products/{id}Краткая карточка из загруженных страниц списка
GET /api/catalog/products?query={query}Поиск по артикулу и названию в загруженной выборке
GET /api/catalog/products/{id}/availabilityНаличие UNKNOWN, количество null для изученной схемы
GET /api/catalog/products/{id}/analogsHTTP 422 для загруженного товара: недостаточно проверенных данных
POST /api/catalog/assist/searchОпциональный разбор текста и предложение кандидатов через OpenAI
GET /health/liveПроверка доступности процесса
GET /health/readyПроверка наличия настроенного адаптера; не проверяет upstream

id и артикул — разные значения: для артикула используйте query. Поиск не учитывает регистр, точное совпадение артикула идёт первым. Узкие подсказки для запросов «автомат» и «дифавтомат» учитывают сокращения АВ DRX… и Диф.авт. в названиях. Это не проверка технической совместимости.

Ответ поиска содержит items, matchedProducts, warnings и metadata. Предупреждение PARTIAL_CATALOG_SEARCH возвращается и при найденных совпадениях. Пустой результат не доказывает отсутствие товара во всём каталоге; отсутствующий в выборке ID возвращает PRODUCT_NOT_IN_LOADED_SAMPLE. Ошибка источника возвращается явно, а не превращается в успешный пустой список.

Полный контракт, примеры JSON и коды ошибок — в docs/API.md.

Источники и конфигурация каталога

При самостоятельном запуске JVM источник по умолчанию выключен (CATALOG_SOURCE=disabled). Режим snapshot читает одну сохранённую страницу без сети. Режим live требует EKT_API_BASE_URL (HTTPS origin без /api), EKT_API_USERNAME и EKT_API_PASSWORD. При сбое live-источника автоматической подмены на snapshot нет.

Live-адаптер последовательно читает страницы начиная с первой. Загрузка ограничена числом товаров, страниц и общим таймаутом; пустая страница завершает обход. По умолчанию CATALOG_MAX_PRODUCTS=100, CATALOG_MAX_PAGES=10, CATALOG_LOAD_TIMEOUT_MS=35000, CATALOG_CACHE_TTL_SECONDS=60. Корневой Compose задаёт некоторые значения отдельно. Загрузка выполняется лениво, результат кешируется; ошибки страниц отклоняют всю загрузку.

Охват всегда PARTIAL, общее число товаров неизвестно (totalProducts=null). Ограниченная пагинация проверена синтетическими тестами; поведение следующих страниц реального EKT ещё требует проверки. Для snapshot актуальность неизвестна; время live-загрузки не гарантирует актуальность коммерческих данных поставщика.

Самостоятельный сервер слушает 127.0.0.1:8080; адрес задаётся через CATALOG_HOST, порт — через PORT или CATALOG_PORT (PORT имеет приоритет). Compose загружает .env, JVM и CLI автоматически его не читают. Credentials и ключи моделей должны оставаться в серверном окружении, вне Git и конфигурации frontend. Все параметры — в docs/DEPLOYMENT.md.

AI-поиск и ограничения данных

AI-поиск выключен по умолчанию. Для включения нужны OPENAI_SEARCH_ENABLED=true, OPENAI_API_KEY и явно выбранная OPENAI_MODEL. Обычный GET-поиск не зависит от OpenAI. Флаг OPENAI_PREVALIDATION_ENABLED управляет отдельной функцией и не включает поиск.

Сервис выполняет до двух AI-вызовов, рассматривает до 20 товаров и возвращает до пяти кандидатов. Товарные факты берутся из нормализованного каталога; модель не получает credentials и raw EKT JSON. Результат содержит verifiedAnalogs=false, compatibility=UNVERIFIED, requirementsVerified=false. Живая проверка именно этого сценария Catalog API в документации не подтверждена.

Изученный образец списка не подтверждает валюту, наличие, количество, склады, сертификаты и структурированные характеристики. Отсутствующая цена остаётся null, сумма передаётся точной десятичной строкой, неизвестная валюта не заменяется на KZT. Пустой offers и фильтр сайта «В наличии» не доказывают численный остаток. Описание товара нужно хранить отдельно от проверенных характеристик.

Для настоящего подбора аналогов остаётся проверить detail endpoint, параметры совместимости и актуальное наличие. Внешнее развёртывание каталога, HTTPS, живые запросы EKT и откат на целевом хосте требуют отдельной проверки; локальный smoke-test не подтверждает готовность production.

Assistant API и пользовательские данные

Assistant предоставляет регистрацию, вход, logout, профиль, список чатов, сообщения/историю, загрузки, polling и SSE. Аккаунтный bearer token открывает только чаты своего пользователя; учётные записи и история сохраняются в SQLite между перезапусками. Подробные маршруты и примеры — в AUTHENTICATION.md, FRONTEND_AGENT.md и OpenAPI. Для ручной проверки доступна Postman-коллекция.

Поддерживаются Excel (.xlsx, .xls), Word (.docx, .doc), PDF, JPEG/JPG и PNG. Лимит файла — 10 MiB; фото и сканы обрабатываются OCR, распознавание предметов без читаемой маркировки не реализовано. Бинарные файлы удаляются после обработки; извлечённые данные живут не более 24 часов. История пользователя по умолчанию хранится 30 дней, гостевой чат в отдельном demo-режиме — не более 24 часов. Текст вложения, процитированный в ответе, следует сроку хранения сообщения.

POST сообщения ждёт до 1,5 секунды, затем при незавершённой обработке возвращает 202 со ссылками polling/SSE. Это срок подтверждения приёма, а не обещание готового ответа за 1,5 секунды. SSE передаёт состояния и готовый результат, не отдельные токены модели. Точные SKU могут отвечать без модели; остальные запросы используют до одного вызова генерации ответа Assistant. При ошибке модели есть шаблонный ответ на основании каталога. RAG пока не включён; предусмотрен интерфейс KnowledgeProvider для будущего поиска по знаниям.

Подтверждённые условия оплаты, доставки и минимальной партии задаются в assistant-service/config/policies.json объектами id, text, source, version. Пустой файл означает отсутствие подтверждённых условий. После изменения перезапустите worker. Синтетическая demo-корзина требует явного подтверждения; она не резервирует остатки EKT и не оформляет реальные заказы.

Локальная разработка и проверки

Отдельные Compose-файлы предназначены для независимой разработки; их настройки и volumes отличаются от общего production-стека.

Assistant: demo без внешнего каталога

Из корня репозитория, на свободных портах:

umask 077
if [ ! -f assistant-service/.env ]; then
  cp assistant-service/.env.example assistant-service/.env
fi
chmod 600 assistant-service/.env
docker compose --env-file assistant-service/.env -f assistant-service/compose.yaml up -d --build --wait

Настройки по умолчанию — development, синтетический каталог и demo-корзина. Примеры артикулов: DEMO-C16, DEMO-C16-OLD, DEMO-C16-ALT. Подключение HTTP Catalog и явная тестовая подмена при ошибке соединения описаны в CATALOG_INTEGRATION.md. Такую подмену нельзя включить в production. Partner Basic Auth настраивается в Catalog service, а не в Assistant.

Для запуска без Docker нужны Python 3.12+, Tesseract с rus+eng, Poppler (pdftoppm) и LibreOffice Writer. Docker-образ устанавливает их сам.

cd assistant-service
python3 -m venv .venv
.venv/bin/python -m pip install -e '.[test]'
# Терминал 1:
.venv/bin/uvicorn app.main:app --reload --port 8000
# Терминал 2, тот же каталог данных и окружение:
.venv/bin/python -m app.worker
# Проверки:
.venv/bin/python -m pytest -q

Локальный Python читает экспортированные ASSISTANT_*, но автоматически не загружает .env. Один API без worker не завершит обработку сообщений и файлов. Контейнерная проверка OCR/Office/HTTP использует временные данные без живой модели и выполняется из корня после сборки образа:

docker compose -f assistant-service/compose.yaml run --rm --no-deps -T \
  -v "$PWD/assistant-service/tests:/checks:ro" \
  assistant-service python /checks/container_smoke.py

Frontend

Для разработки используйте Node.js 24; Docker устанавливает его сам.

cd frontend-service
npm ci
npm run dev
# Проверки типов, стиля, unit-тестов и production-сборки:
npm run check
# Детерминированные браузерные тесты:
npx playwright install chromium
npm run test:e2e

При самостоятельной разработке frontend-service/.env.example задаёт публичные параметры, включая VITE_ASSISTANT_API_URL (по умолчанию http://localhost:8000). Backend должен разрешать origin frontend в CORS. Корневой Compose вместо прямого API URL собирает /assistant и использует Nginx; сервисный Compose запускает Vite preview только для локальной проверки.

Docker-проверки из frontend-service/:

docker compose run --build --rm frontend-test
docker compose run --build --rm frontend-e2e

Браузерные тесты с API fixtures не подтверждают живую интеграцию с EKT/OpenAI. Интерфейс поддерживает RU/KZ; это перевод UI, а не текстов товаров или ответов API. Backend пока принимает locale: "ru". Правила frontend — в react-standards.md.

CLI каталога

CLI и HTTP-сервер имеют разные точки входа: run запускает CLI, runServer — Ktor. Из каталога ekt-catalog-service/:

sh ./gradlew --quiet installDist
bash bin/ekt-cli --help
bash bin/ekt-cli list --page 1 --output local-evidence/products-page-1.json
bash bin/ekt-cli list --page 2 --output local-evidence/products-page-2.json
# Вместо ID подставьте идентификатор из списка товаров:
bash bin/ekt-cli detail --id ID --output local-evidence/product-detail.json

Запускник запрашивает логин и скрытый пароль, если они не экспортированы в окружение; по умолчанию адрес источника — https://ekt.kz. Сам JVM/CLI .env не читает. Не передавайте пароль аргументом команды. Для неинтерактивного запуска заранее задайте EKT_API_BASE_URL, EKT_API_USERNAME, EKT_API_PASSWORD в окружении.

list вызывает upstream /api/products, --page добавляет номер страницы, detail --id обращается к /api/products/detail?id=.... Это raw-экспорт для исследования, а не нормализованный контракт Catalog API. --output не заменяет существующий файл: выбирайте новый путь для каждого наблюдения. Успешный JSON без --output выводится в stdout, диагностика — в stderr. Каталог local-evidence/ игнорируется Git; перед публикацией данных проверьте отсутствие секретов.

Коды выхода дистрибутива CLI: 0 — успех/справка, 2 — аргументы или конфигурация, 3 — EKT/сеть/таймаут/JSON, 4 — ошибка экспорта или существующий файл, 1 — непредвиденная ошибка. Gradle может заменить ненулевой код дочернего процесса собственным кодом 1; для автоматизации используйте запускник или установленный дистрибутив, а не gradlew run.

Документация

Записи о выполненных проверках относятся к указанным в документах ревизиям и датам; они не заменяют проверку текущей сборки или целевого окружения.

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

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

Andrey Svininykh 14magickern 10Miramgul Zhangazieva 5meiramgul75-wq 5Dulalet 1

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

  • 2026-09-23 17:52docs: add branch-specific service guidesMiramgul Zhangazieva
  • 2026-09-23 17:47docs: note EKT product card observationsMiramgul Zhangazieva
  • 2026-09-23 17:40fixesmagickern
  • 2026-09-23 17:36docs: sync latest main guidanceMiramgul Zhangazieva
  • 2026-09-23 17:34alphamagickern
  • 2026-09-23 17:25docs: sync project guides with mainMiramgul Zhangazieva
  • 2026-09-23 17:20docs: refresh project README and catalog documentationAndrey Svininykh
  • 2026-09-23 17:17docs: add missing main markdown filesMiramgul Zhangazieva
Все коммиты (35)
  • 2026-09-23 17:14fix(catalog): paginate product loading and match breaker aliasesAndrey Svininykh
  • 2026-09-23 17:00frontend updatesmagickern
  • 2026-09-23 16:57frontend and assistant service auth updates;magickern
  • 2026-09-23 16:50Merge pull request #9 from BAITC-Hacks/assistant-serviceDulalet
  • 2026-09-23 16:49all services mergedmagickern
  • 2026-09-23 16:32big mergemagickern
  • 2026-09-23 16:27Merge pull request #8 from BAITC-Hacks/workAndrey Svininykh
  • 2026-09-23 16:24docs: remove obsolete catalog directory restored by mergeAndrey Svininykh
  • 2026-09-23 16:23Merge main into work, preserving implemented catalog API documentationAndrey Svininykh
  • 2026-09-23 16:03Add catalog documentation comments and project READMEmeiramgul75-wq
  • 2026-09-23 15:58docs: add catalog API JSON response examplesAndrey Svininykh
  • 2026-09-23 15:55Document product descriptions and stock limitationsmeiramgul75-wq
  • 2026-09-23 15:50feat: frontend service; assistant service updatemagickern
  • 2026-09-23 15:30feat: package standalone EKT catalog service for deploymentAndrey Svininykh
  • 2026-09-23 15:15Merge remote-tracking branch 'origin/main' into assistant-servicemagickern
  • 2026-09-23 15:10Clarify characteristics coverage in product cardsmeiramgul75-wq
  • 2026-09-23 15:02feat: add normalized catalog API and OpenAI-assisted searchAndrey Svininykh
  • 2026-09-23 14:55assistant servicemagickern
  • 2026-09-23 14:52Document product description and characteristics variancemeiramgul75-wq
  • 2026-09-23 14:41Add EKT CLI and observed-list normalization for issue #2Andrey Svininykh
  • 2026-09-23 14:38Add catalog API documentation from mainmeiramgul75-wq
  • 2026-09-23 14:30docs: describe observed EKT catalog input dataAndrey Svininykh
  • 2026-09-23 14:28docs: describe observed EKT catalog input dataAndrey Svininykh
  • 2026-09-23 13:53first commit, setup harnessmagickern
  • 2026-09-23 13:51docs: add catalog service READMEAndrey Svininykh
  • 2026-09-23 13:51Add Kotlin EKT transport foundation for issue #2Andrey Svininykh
  • 2026-09-23 13:32docs: add catalog service READMEAndrey Svininykh
Структура репозитория (175 файлов)
.dockerignore
.env.example
.github/workflows/catalog.yml
.gitignore
Dockerfile
README.md
assistant-service/.dockerignore
assistant-service/.env.example
assistant-service/.gitignore
assistant-service/AUTHENTICATION.md
assistant-service/CATALOG_INTEGRATION.md
assistant-service/Dockerfile
assistant-service/FRONTEND_AGENT.md
assistant-service/IMPLEMENTATION_STATUS.md
assistant-service/README.md
assistant-service/app/__init__.py
assistant-service/app/api/__init__.py
assistant-service/app/api/dependencies.py
assistant-service/app/api/health.py
assistant-service/app/api/models.py
assistant-service/app/api/v1/__init__.py
assistant-service/app/api/v1/auth.py
assistant-service/app/api/v1/router.py
assistant-service/app/core/__init__.py
assistant-service/app/core/body_limit.py
assistant-service/app/core/config.py
assistant-service/app/core/errors.py
assistant-service/app/domain/__init__.py
assistant-service/app/domain/attachments/__init__.py
assistant-service/app/domain/attachments/parser.py
assistant-service/app/domain/auth/__init__.py
assistant-service/app/domain/auth/service.py
assistant-service/app/domain/cart/__init__.py
assistant-service/app/domain/cart/service.py
assistant-service/app/domain/chat/__init__.py
assistant-service/app/domain/chat/service.py
assistant-service/app/domain/policies/__init__.py
assistant-service/app/domain/sessions/__init__.py
assistant-service/app/integrations/__init__.py
assistant-service/app/integrations/catalog.py
assistant-service/app/integrations/catalog_schema.py
assistant-service/app/integrations/fixtures/catalog.json
assistant-service/app/integrations/knowledge.py
assistant-service/app/integrations/model.py
assistant-service/app/main.py
assistant-service/app/runtime.py
assistant-service/app/storage/__init__.py
assistant-service/app/storage/store.py
assistant-service/app/worker.py
assistant-service/compose.yaml
assistant-service/config/policies.json
assistant-service/openapi.json
assistant-service/postman/EKT-Assistant.postman_collection.json
assistant-service/pyproject.toml
assistant-service/tests/container_smoke.py
assistant-service/tests/fixtures/spec.xls
assistant-service/tests/live_stack_smoke.py
assistant-service/tests/test_auth_history.py
assistant-service/tests/test_business_api.py
assistant-service/tests/test_catalog_contract.py
assistant-service/tests/test_catalog_live_contract.py
assistant-service/tests/test_chat_live_search.py
catalog-service/README.md
catalog-service/docs/api-findings.md
deploy.md
docker-compose.yml
docs/AI_SEARCH.md
docs/API.md
docs/DEPLOYMENT.md
docs/EKT_DATA.md
docs/WORK_COMMENTS.md
ekt-catalog-service/.dockerignore
ekt-catalog-service/.env.example
ekt-catalog-service/Dockerfile
ekt-catalog-service/README.md
ekt-catalog-service/bin/ekt-cli
ekt-catalog-service/build.gradle.kts
ekt-catalog-service/compose.yaml
ekt-catalog-service/docs/api-findings.md
ekt-catalog-service/docs/prevalidation.md
ekt-catalog-service/gradle/wrapper/gradle-wrapper.jar
ekt-catalog-service/gradle/wrapper/gradle-wrapper.properties
ekt-catalog-service/gradlew
ekt-catalog-service/gradlew.bat
ekt-catalog-service/scripts/deploy.sh
ekt-catalog-service/scripts/smoke.py
ekt-catalog-service/scripts/verify-local.py
ekt-catalog-service/settings.gradle.kts
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/Application.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/assistant/AiSearchService.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/assistant/SearchIntent.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/cli/EktCli.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/ekt/EktConfig.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/ekt/EktException.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/ekt/EktRawClient.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/ekt/EktTransport.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/ekt/NormalizedCatalog.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/model/Product.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/normalization/EktListNormalizer.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/prevalidation/OpenAiConfig.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/prevalidation/OpenAiPrevalidator.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/prevalidation/OpenAiStructuredClient.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/prevalidation/OpenAiTransport.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/prevalidation/RawCatalogRecord.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/prevalidation/ReportContract.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/prevalidation/ValidationReport.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/routes/AssistedSearchRoutes.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/routes/CatalogErrors.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/routes/CatalogJson.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/routes/CatalogRoutes.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/service/CatalogSearchAliases.kt
ekt-catalog-service/src/main/kotlin/kz/hackalem/catalog/service/CatalogService.kt
ekt-catalog-service/src/main/resources/logback.xml
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/CatalogApplicationTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/CatalogDataTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/CatalogSearchPaginationTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/DeploymentTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/assistant/AiSearchTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/cli/EktCliTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/ekt/EktRawClientTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/prevalidation/OpenAiPrevalidatorTest.kt
ekt-catalog-service/src/test/kotlin/kz/hackalem/catalog/prevalidation/OpenAiTransportTest.kt
ekt-catalog-service/src/test/resources/ekt/products-page-1.json
ekt-catalog-service/src/test/resources/prevalidation/synthetic-records.json
frontend-service/.dockerignore
frontend-service/.env.example
frontend-service/.gitignore
frontend-service/.prettierignore
frontend-service/.prettierrc.json
frontend-service/Dockerfile
frontend-service/LIVE_TEST_REPORT.md
frontend-service/README.md
frontend-service/docker-compose.yml
frontend-service/e2e/app.spec.ts
frontend-service/e2e/integration.spec.ts
frontend-service/eslint.config.js
frontend-service/example-page.html
frontend-service/index.html
frontend-service/nginx.conf
frontend-service/package-lock.json
frontend-service/package.json
frontend-service/playwright.config.ts
frontend-service/react-standards.md
frontend-service/src/api/client.ts
frontend-service/src/api/types.ts
frontend-service/src/app/App.test.tsx
frontend-service/src/app/App.tsx
frontend-service/src/app/navigation.tsx
frontend-service/src/components/Icon.tsx
frontend-service/src/features/auth/AccountPanel.css
frontend-service/src/features/auth/AccountPanel.tsx
frontend-service/src/features/auth/AuthProvider.tsx
frontend-service/src/features/cart/CartPage.tsx
frontend-service/src/features/cart/QuoteRows.tsx
frontend-service/src/features/chat/ChatWidget.tsx
frontend-service/src/features/chat/ConversationList.tsx
frontend-service/src/features/chat/ProductCard.tsx
frontend-service/src/features/chat/useAssistant.ts
frontend-service/src/i18n/LocaleProvider.tsx
frontend-service/src/i18n/authMessages.ts
frontend-service/src/i18n/catalogMessages.ts
frontend-service/src/i18n/historyMessages.ts
frontend-service/src/i18n/messages.ts
frontend-service/src/main.tsx
frontend-service/src/styles/global.css
frontend-service/test/setup.ts
frontend-service/tsconfig.json
frontend-service/vite.config.ts
hackathon-task.json
pyproject.toml
tests/test_contracts.py
tests/test_docker_deployment.py
tests/test_ekt_openapi.py
tests/test_ekt_planning.py
tests/test_validator.py
Ветки (6)

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

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

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