О проекте
Команда: 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.mdDAIRN — ассистент для сайта EKT
Проект команды DAIRN для хакатона: веб-интерфейс с чатом, backend ассистента и отдельный сервис интеграции с каталогом EKT. Условия задачи — в hackathon-task.json.
Состав проекта
| Компонент | Назначение |
|---|---|
| frontend-service | React + TypeScript: интерфейс сайта, чат и отображение товаров |
| assistant-service | FastAPI: сессии, сообщения, вложения и сценарии корзины; для обработки нужен отдельный worker |
| ekt-catalog-service | Kotlin/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 |
|---|---|---|
| Frontend | http://localhost:5173 | http://frontend:8080 |
| Assistant API / Swagger | http://localhost:8000/docs | http://assistant-service:8000 |
| Catalog API | http://localhost:8080/api/catalog/products?query=LED | http://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:5173 | Origin браузерного 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.kz | HTTPS 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_SESSIONS | production, false |
ASSISTANT_CATALOG_MODE, ASSISTANT_CATALOG_BASE_URL | http, http://ekt-catalog-service:8080 |
ASSISTANT_CATALOG_MOCK_ON_UNAVAILABLE, ASSISTANT_CART_MODE | false, 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, PORT | live, 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}/analogs | HTTP 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.
Документация
- Авторизация и история — пользователи, токены, владение чатами и сроки хранения.
- Frontend ↔ Assistant — HTTP-контракт, загрузки, SSE и обработка ошибок.
- Assistant ↔ Catalog — структуры данных и тестовый fallback.
- Catalog API — маршруты, JSON-контракт, поиск, пагинация, кеш и ошибки.
- Данные EKT — происхождение образца, наблюдаемая схема и план проверки API.
- AI-поиск — протокол поиска кандидатов, запуск, статусы и ограничения.
- Развёртывание каталога — конфигурация, JAR, Docker, health, smoke и rollback; запись результатов локальной проверки.
- Комментарии по документации — ограничения достоверности данных и открытые вопросы.
- Развёртывание приложения — общий Compose для frontend, каталога, ассистента и worker.
Записи о выполненных проверках относятся к указанным в документах ревизиям и датам; они не заменяют проверку текущей сборки или целевого окружения.
Авторы коммитов
История коммитов
- 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
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.