AAYMA
RU
Войти

Apollon (EKT SupplyAI)

Веб-сервис для закупщика «Электрокомплекта»: загружает Excel-выгрузки IEK и Systeme Electric, прогнозирует спрос и выдаёт рекомендованные заказы по…

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

О проекте

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

Задача кейса: Автоматический расчёт заказов поставщикам для пополнения склада, с утверждением менеджером

Суть

Веб-сервис для закупщика «Электрокомплекта»: загружает Excel-выгрузки IEK и Systeme Electric, прогнозирует спрос и выдаёт рекомендованные заказы по поставщикам с объяснением «Почему». Менеджер утверждает заказ, после чего выгружает XLSX, CSV или черновик письма; автоматически ничего не отправляется.

Как устроено

Детерминированный движок на TypeScript (lib/engine). Разовые крупные заказы отсекаются правилом max(median+6·MAD, Q3+3·IQR, 3× сезонная норма). Упущенный спрос = ожидаемый × (1 − доступность). Сезонный профиль сжимается к групповому, тренд Theil–Sen ограничен ±50%. Страховой запас — эмпирический квантиль ошибок × √(H/30). Потребность = спрос_H + страховой запас − остаток − поступления по ETA, затем MOQ и кратность, ABC/XYZ. Бэктест против двух базовых моделей по MAE и WAPE. ИИ-помощник на OpenAI Agents SDK объясняет расчёт через те же сервисы, но утвердить заказ не может.

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

  • Theil–Sen trend
  • MAD/IQR outlier detection
  • lost-demand estimation
  • safety stock quantile
  • ABC/XYZ
  • backtest MAE/WAPE
  • OpenAI Agents SDK

Особенности

Самая глубокая методика пополнения в батче: бэктест на срезах против базовых моделей, эндпоинт /api/quick-test проверяет все Must-have-требования кейса на реальных данных, утверждённая ревизия замораживается, а любая правка снимает утверждение.

Стек

TypeScript · Next.js · React · Prisma · PostgreSQL · Docker · Railway · Vitest

Модели

OpenAI (Agents SDK)


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

README

README.md

Соло-команда Apollon — автоматический расчёт заказов поставщикам

🔑 Вход: логин admin, пароль admin

Живая версия и локальный запуск закрыты паролем. Браузер спросит логин и пароль: введите admin / admin. Для curl: curl -u admin:admin ...

Живая версия: https://apollon-production-59ea.up.railway.app · проверка состояния: /api/health

Кейс HACKALEM AI от ТОО «Электрокомплект» (ekt.kz): «Автоматизация формирования заказов поставщикам».

Быстрая проверка за 2 минуты (живая версия)

  1. Откройте https://apollon-production-59ea.up.railway.app и выберите набор «IEK и Systeme Electric — исходные данные кейса».
  2. Выберите поставщика и нажмите «Рассчитать заказ». Затем откройте «Почему» у любой строки, утвердите заказ и скачайте XLSX / CSV / письмо.
  3. Проверка расчёта одним запросом (только чтение, ничего не сохраняет) — расчёт, проверка 5 обязательных требований и топ-50 срочных позиций с объяснениями: https://apollon-production-59ea.up.railway.app/api/quick-test?limit=50 (логин/пароль admin / admin)

Запуск из репозитория: docker compose up --build → http://localhost:3000 (синтетические данные загружаются автоматически). Подробнее — в разделе 7.

1. Проблема и для кого

Менеджер отдела закупа «Электрокомплекта» сейчас считает пополнение склада вручную в Excel. Поэтому расчёт делают редко. По одним позициям копятся излишки, по другим возникает дефицит. Кроме того, разовые крупные продажи искажают оценку регулярного спроса.

Apollon — веб-сервис для менеджера по закупкам. Сервис:

  1. Загружает выгрузки поставщиков IEK и Systeme Electric в исходном формате .xlsx.
  2. Прогнозирует спрос с учётом сезонности, устойчивого роста, упущенного спроса и разовых выбросов.
  3. Выдаёт список рекомендованных заказов, сгруппированный по поставщикам. У каждой позиции есть объяснение и срочность.
  4. Позволяет скорректировать количество, утвердить заказ с указанием ответственного и выгрузить черновик заказа для поставщика.

Заказы никогда не отправляются автоматически. Сервис только готовит черновик. Решение принимает человек, как и требуют ограничения кейса.

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

Обязательные требования кейса (Must have)

#Требование кейсаКак реализованоГде в коде
1Базовая потребность по каждому артикулу с учётом всех источников данныхГоризонт = срок поставки + период пересмотра. В расчёте участвуют прогноз спроса, страховой запас, свободный остаток, поставки в пути (по дате ETA), категория (уровень сервиса) и коэффициент роста. Затем применяются MOQ и кратность упаковки.lib/engine/index.ts
2Сезонность и устойчивый ростДля каждого товара строится сезонный профиль, который сжимается к групповому профилю. Тренд Theil–Sen оценивается на сезонно скорректированных данных, годовой рост ограничен ±50%. Коэффициент роста от поставщика или менеджера заменяет оценённый тренд.lib/engine/index.ts, lib/engine/math.ts
3Оценка и компенсация упущенного спроса при stockoutДоступность товара оценивается по пустым остаткам и продажам ниже нормы. Подтверждённые интервалы дефицита имеют приоритет. Формула: упущенный спрос = ожидаемый × (1 − доступность). Для оценки выводится диапазон ±0,25.lib/engine/index.ts
4Выявление и исключение разовых крупных заказовСчёт считается выбросом, если превышает max(median + 6·MAD·1.4826, Q3 + 3·IQR, 3 × сезонная норма) и занимает не менее 30% месяца. Устойчивый рост не отбрасывается. Вычитание разрешено, только если сумма счетов сходится с месячным отчётом (допуск 5%). Для обезличенного customerId есть проверка концентрации на одного клиента за 60 дней.lib/engine/index.ts
5Итоговый список по поставщикам с обоснованиемТаблица сгруппирована по поставщикам. У каждой строки есть кнопка «Почему», которая показывает спрос, остаток, поступления, округление и допущения.components/order-workspace.tsx, components/sku-detail.tsx

Опциональные пункты кейса и дополнительные возможности

  • Приоритизация по риску дефицита: срочность строки, дата первого дефицита и число дней покрытия.
  • MOQ и условия поставщика: минимальная партия, кратность упаковки и пересчёт единиц (например, метры в бухты).
  • Тренды спроса по категориям: отдельный дашборд и GET /api/trends.
  • Выгрузка заказа: XLSX, CSV (разделитель ;, BOM для Excel) и текстовый черновик письма поставщику. Автоматической рассылки нет, это сделано намеренно.
  • Утверждение с ревизиями. Утверждённая версия замораживается, а любая правка снимает утверждение. Экспорт всегда строится из замороженной ревизии.
  • Историческая проверка (backtest): прогноз заново строится на срезах 31.05, 30.06 и 31.07.2026 и сравнивается с двумя базовыми моделями: «тот же месяц прошлого года» и «среднее за 12 месяцев». Метрики: MAE, WAPE и средняя ошибка со знаком.
  • Встроенные проверки кейса: диалог «Проверка расчёта» и POST /api/checks проверяют каждое Must-have-требование на реальных данных.
  • ИИ-помощник на OpenAI Agents SDK. Помощник объясняет расчёт и вызывает те же серверные сервисы, что и интерфейс. Утвердить или отправить заказ он не может (подробности в разделе 6).

3. Как работает решение: сценарий пользователя

Excel-выгрузки → проверка и архив в PostgreSQL → расчёт → обзор «Почему» → корректировка → утверждение → экспорт XLSX/CSV/письмо
  1. Выбор данных (/). Менеджер загружает до шести отчётов поставщика: продажи, остатки, товары в пути, MOQ/кратность, динамика продаж и сезонность. Можно также выбрать уже сохранённый набор данных кейса. Файлы распределяются по слотам по имени. Нераспознанные файлы и файлы не в формате .xlsx явно отклоняются.
  2. Проверка (/datasets/<id>). Показываются покрытие данных, предупреждения импорта и отсутствующие отчёты. Менеджер выбирает поставщиков и, при необходимости, одну категорию товаров (расчёт по категории, как в кейсе), а также меняет политику в расширенных настройках: срок поставки, уровень сервиса и так далее.
  3. Расчёт. Нажатие «Рассчитать заказ» запускает детерминированный движок. Результат сохраняется как отдельный расчёт (/runs/<id>).
  4. Обзор. Рекомендации сгруппированы по поставщику и выводятся по 100 строк на страницу. В каждой строке есть артикул, количество, срочность, уверенность и кнопка «Почему». Объяснение показывает прогноз, остаток, поступления, исключённые выбросы, упущенный спрос и округление. Неизвестные факты помечаются явно, а не подменяются.
  5. Корректировка и утверждение. Менеджер меняет количества, указывает ответственного и подтверждает допущения. После этого создаётся замороженная ревизия.
  6. Экспорт. Менеджер скачивает XLSX, CSV или черновик письма поставщику.

У каждого состояния есть свой URL (?sku=<key> открывает объяснение), поэтому ссылкой можно поделиться, а кнопки браузера «Назад» и «Вперёд» работают.

Методика расчёта

Расчёт детерминирован: чистые функции TypeScript в lib/engine, без LLM и без БД. Один и тот же вход всегда даёт один и тот же результат. Обучение использует только законченные месяцы до даты среза; текущий неполный месяц не экстраполируется. Возвраты (отрицательные продажи) сохраняются в истории и обнуляются только для обучения.

  1. Очистка от разовых заказов — см. следующий раздел.
  2. Доступность и упущенный спрос. Пустой остаток считается нулём только между первым и последним известным остатком товара. Подтверждённые интервалы дефицита имеют приоритет. Иначе, если продажи ниже 80% сезонной нормы при нулевом остатке, доступность оценивается по последнему дню продажи или по отношению факта к ожиданию. Упущенный спрос = ожидаемый сезонный спрос × (1 − доступность), восстановленный объём ограничен двумя нормами. Выводится сценарный диапазон при доступности ±0,25.
  3. Сезонность. Профиль товара — отношения к среднему объёму года (минимум 9 наблюдаемых месяцев в году), среднее индексов = 1. Собственный профиль сжимается к групповому (в тех же единицах) с весом полные годы / (полные годы + 1).
  4. Рост. Уровень и тренд Theil–Sen по последним 12 сезонно скорректированным месяцам, годовой тренд ограничен ±50%. Коэффициент роста от поставщика или менеджера заменяет оценённый тренд. Прогноз месяца: уровень × сезонность × (1 + рост)^(месяцы от последнего законченного / 12).
  5. Горизонт и страховой запас. H = срок поставки + период пересмотра. Ошибки одношагового прогноза считаются последовательно (только по прошлому). При ≥12 ошибках страховой запас — эмпирический квантиль уровня сервиса × √(H/30), иначе z × σ × √(H/30). Нижняя граница — спрос за safetyDays. Категория меняет уровень сервиса.
  6. Потребность. Остатки и поставки в пути проецируются по дням: поставка добавляется только в день ETA, просроченная неподтверждённая ETA к остатку не добавляется и требует проверки. Потребность = max(0, спрос_H + страховой запас − свободный остаток − поступления внутри H). Затем MOQ и округление вверх до кратности; при пересчёте единиц (метры → бухты) MOQ и кратность сначала переводятся в единицы спроса.
  7. Срочность и классы. Срочность — по дате первого прогнозного дефицита. ABC — по восстановленному спросу за 12 месяцев (A/B/C: 80%/95%), XYZ — по коэффициенту вариации (X ≤ 0,5, Y ≤ 1).

Параметры по умолчанию (редактируются в «Расширенных настройках» перед расчётом, DEFAULT_POLICY в lib/contracts/engine.ts): срок поставки 45 дней, период пересмотра 30 дней, уровень сервиса 95%, минимум запаса 7 дней, уровни сервиса по категориям 1 → 98%, 2 → 95%, 3 → 92%, 5 → 90%, 7 → 85%.

Алгоритм исключения разовых крупных заказов и выбросов

Константы — ENGINE_THRESHOLDS в lib/engine/index.ts.

  1. Строки продаж объединяются по товару и номеру счёта (возвраты сохраняются). Номер счёта — это заказ, а не клиент.
  2. Проверка счёта. Нужно минимум 6 более ранних положительных счетов. Счёт помечается как разовый, если одновременно:
    • он больше max(median + 6 × MAD × 1.4826, Q3 + 3 × IQR, 3 × типичный сезонный месячный объём);
    • он составляет не менее 30% продаж своего месяца.
  3. Защита устойчивого роста. Если хотя бы 3 из следующих 6 месяцев тоже повышены, это рост, а не выброс: ничего не исключается.
  4. Вычитание. Счёт вычитается из месячного ряда, только если сумма счетов сходится с месячным отчётом с точностью 5%. Иначе работает резервный месячный фильтр на сезонно скорректированном ряду: выброс заменяется типичным сезонным объёмом.
  5. Один клиент. Если есть обезличенный customerId, дополнительно проверяется сумма покупок одного клиента за скользящие 60 дней по тому же порогу.
  6. Прерывистый спрос. Для рядов, где больше 40% месяцев нулевые, автоматические фильтры отключены (иначе любая продажа выглядела бы выбросом).
  7. Каждое исключение видно в объяснении товара, и менеджер может его отменить; тогда товар пересчитывается.

Проверка: тест в tests/engine/engine.test.ts добавляет заказ в 50 месячных норм, и базовый спрос меняется меньше чем на 5%.

Формат экспорта и совместимость с 1С

Экспорт строится только из утверждённой (замороженной) ревизии заказа (lib/export/index.ts):

  • CSV (UTF-8 с BOM, разделитель ;, как ожидает русский Excel и загрузка табличных частей в 1С): Код 1с; Артикул поставщика; Наименование; Количество; Ед.; Поставщик. Колонка Код 1с — это код номенклатуры из исходных выгрузок 1С, поэтому строку можно сопоставить с номенклатурой 1С без ручного поиска.
  • XLSX: лист на каждого поставщика с колонками Код 1с, Артикул поставщика, Наименование, Категория 2026, Свободный остаток, В пути, Заказ, Ед., Обоснование, Срочность и лист «Утверждение» (номер заказа, версия, кто и когда утвердил).
  • Письмо поставщику: текстовый черновик. Он не отправляется автоматически.

Текст ячеек защищён от формульных инъекций (=, +, -, @ в начале экранируются). Точный шаблон загрузки в 1С партнёр не предоставил, поэтому совместимость проверена на уровне состава колонок и кодов, а не на реальной загрузке (см. ограничения).

4. Технологии

СлойТехнологии
Язык / средаTypeScript, Node.js 24 (engines: >=22.12 <25)
Веб-приложениеNext.js 16 (App Router, API routes), React 19, Tailwind CSS 4, Recharts, lucide-react
Расчётный движокЧистые функции на TypeScript (lib/engine), без внешних ML-библиотек
База данныхPostgreSQL на Railway, Prisma 7 (@prisma/adapter-pg), миграции в prisma/migrations
Разбор ExcelСобственный потоковый парсер OOXML (jszip + saxes) для импорта без потерь; exceljs для генерации XLSX
ИИOpenAI Agents SDK (@openai/agents): tools, handoff, input/output guardrails; модель задаётся через OPENAI_MODEL
Валидацияzod для всех входов API
Тесты и CIVitest, ESLint 9, GitHub Actions (.github/workflows/ci.yml)
ДеплойDocker (multi-stage, node:24-alpine, непривилегированный пользователь), Railway

5. Архитектура

 Браузер (одна страница, адресуемые URL)
   │  components/order-workspace.tsx  ─ загрузка, расчёт, обзор, утверждение, экспорт
   │  components/workspace.tsx        ─ панель ИИ-помощника
   ▼
 Next.js API routes (app/api/*)       ─ zod-валидация, rate limit, защита от cross-origin
   │   import · datasets · runs · orders/[id] · orders/[id]/approve · export · history · jobs
   │   checks · quick-test · backtest · trends · data/* · agent · insights · health
   ├──► lib/ingest     ─ парсинг XLSX без потерь + версионированное сопоставление с моделью планирования
   ├──► lib/engine     ─ детерминированный расчёт: очистка → доступность → сезонность/рост → запас → MOQ
   ├──► lib/export     ─ XLSX / CSV / письмо (с защитой от формульных инъекций)
   ├──► lib/agent      ─ OpenAI Agents SDK: ProcurementCopilot + AnomalyReviewer
   └──► lib/repo + lib/db ─ Prisma → PostgreSQL (Railway)

Слои данных в PostgreSQL (prisma/schema.prisma):

  1. Неизменяемый архив источника: SourceWorkbook, SourceSheet, SourceRow. Здесь хранятся исходные байты файла, SHA-256, каждая физическая строка и ячейка, включая заголовки, итоги и пустые строки. Ничего не фильтруется и не «исправляется».
  2. Версионированное сопоставление: из архива строится набор данных для планирования (Dataset, Product и др.). В манифесте набора записаны ID и хэши исходных книг.
  3. Результаты: расчёты, заказы, ревизии и неизменяемые снимки утверждений.

Приватность и защита API. Все входы проверяются через zod, на изменяющих запросах есть rate limit и проверка Origin. Параметры пагинации проверяются (ошибка 400, а не падение). Обозреватель данных (/api/data/*) не отдаёт ни ID клиентов, ни исходные номера счетов: вместо номера показывается стабильный псевдоним INV-<sha256> (lib/repo/explorer.ts).

Принцип: математика не зависит ни от LLM, ни от БД. calculatePlan() — чистая функция. Поэтому один и тот же вход всегда даёт один и тот же результат, и движок покрыт тестами без базы данных.

6. ИИ-помощник (OpenAI Agents SDK)

Файлы: lib/agent/index.ts, lib/agent/guardrails.ts, app/api/agent/route.ts. В интерфейсе это кнопка «Ассистент». В открытом расчёте она предлагает три вопроса по первым десяти рекомендациям; нажатие отправляет вопрос агенту.

  • Агент ProcurementCopilot имеет 6 инструментов, и все они вызывают настоящие сервисы приложения:
    • inspect_data_quality
    • calculate_replenishment (создаёт только черновик расчёта)
    • explain_sku
    • compare_scenario (реальные дельты движка при изменённой политике)
    • list_anomalies
    • draft_supplier_order (новый черновик, без утверждения)
  • Handoff на AnomalyReviewer: специализированный агент разбирает спорные разовые продажи.
  • Guardrails. Входной guardrail блокирует попытки деанонимизации клиентов. Выходной guardrail блокирует ответы, которые утверждают или отправляют заказ. Ответ сначала буферизуется и отдаётся клиенту только после того, как выходной guardrail его пропустил.
  • Приватность. В модель передаются только агрегаты расчёта: без сырых транзакций, ID клиентов и номеров счетов.
  • Без ключа. Если OPENAI_API_KEY не задан, помощник честно сообщает, что недоступен. Расчёт, проверки, утверждение и экспорт работают без ключа.

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

Основной способ запустить проект из чистого checkout — Docker Compose. Нужен только Docker: аккаунт Railway, доступ к нашей базе и ключ OpenAI не требуются.

A. Полный запуск одной командой (рекомендуется)

git clone <этот репозиторий> apollon && cd apollon
docker compose up --build

Что происходит:

  1. db поднимает PostgreSQL 17 (порт 5433 на хосте, чтобы не конфликтовать с локальным Postgres).
  2. setup применяет миграции (prisma migrate deploy) и запускает npm run seed:sample. Скрипт импортирует 12 синтетических книг из sample-data/ через тот же код, что и загрузка в интерфейсе (parseRawWorkbook → saveRawWorkbook → materializeSourceDataset). Готовых результатов в сиде нет: расчёт выполняется в приложении. Повторный запуск идемпотентен.
  3. app запускается на http://localhost:3000, когда setup успешно завершился.

Откройте http://localhost:3000, выберите набор «Синтетические данные sample-data — IEK и Systeme Electric» и пройдите сценарий из раздела 8.

ИИ-помощник по желанию: OPENAI_API_KEY=sk-... docker compose up --build. Без ключа всё остальное работает.

Остановить: docker compose down. Удалить и данные: docker compose down -v.

B. Без Docker (Node.js 24 + любой PostgreSQL)

npm ci
npm run db:generate
cp .env.example .env
# в .env: DATABASE_URL=postgresql://apollon:apollon@localhost:5433/apollon и APOLLON_LOCAL_DB=1
npm run db:migrate
npm run seed:sample
npx next dev          # http://localhost:3000

База для этого варианта поднимается командой docker compose up -d db либо используется своя. APOLLON_LOCAL_DB=1 явно разрешает подключение к базе вне Railway: без этого флага lib/db.ts принимает только хосты Railway, чтобы команда случайно не работала с чужой базой.

npm run dev — служебный скрипт команды. Он подключается к production-базе через Railway CLI и SSH, поэтому жюри он не нужен.

C. Проверки без базы данных

npm ci && npm run db:generate
npm run typecheck && npm run lint && npm test && npm run build

npm test: все тесты проходят, 9 интеграционных пропускаются без DATABASE_URL. Эти же шаги выполняет CI (.github/workflows/ci.yml) на каждый push.

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

ПеременнаяОбязательнаНазначение
DATABASE_URLда (для UI/API)PostgreSQL. В Docker Compose задана автоматически.
APOLLON_LOCAL_DBдля не-Railway базы1 разрешает локальную/свою базу.
OPENAI_API_KEYнетВключает ИИ-помощника. Используется только на сервере.
OPENAI_MODELнетМодель агента. По умолчанию gpt-5.6-terra.
APOLLON_ACCESS_USER / APOLLON_ACCESS_PASSWORDнетВход по HTTP Basic для всех страниц и API, кроме /api/health. В Docker Compose, .env.example и на живой версии: admin / admin. Если пароль не задан, вход отключён.

8. Как проверить решение (сценарий для жюри)

Быстрая проверка на живой версии

  1. Откройте https://apollon-production-59ea.up.railway.app. Выберите сохранённый набор «IEK и Systeme Electric — исходные данные кейса».
  2. Выберите поставщика (и при желании категорию) и нажмите «Рассчитать заказ».
  3. Откройте «Почему» у любой строки. Вы увидите прогноз по месяцам, сезонные коэффициенты, остаток, поступления в горизонте, исключённые выбросы, упущенный спрос и шаги округления до MOQ и кратности.
  4. Измените количество в строке, введите имя ответственного и утвердите заказ. Затем скачайте XLSX / CSV / письмо. Попробуйте изменить строку после утверждения: утверждение снимется.
  5. Откройте «Проверка расчёта» и посмотрите вкладки проверок кейса, backtest и трендов.

Один запрос — проверка расчёта: GET /api/quick-test

Откройте в браузере или через curl:

При каждом запросе сервер заново загружает набор данных из базы, запускает детерминированный движок (calculatePlan) и проверки кейса (runCaseChecks), а затем возвращает JSON. В базу ничего не сохраняется. Это быстрая проверка движка и обязательных требований; корректировку, утверждение и экспорт проверяйте в интерфейсе (шаги выше). В ответе:

  • mustHave — 5 обязательных требований кейса: passed и доказательство (пример артикула и числа);
  • summary — число рекомендаций, разбивка по поставщикам и срочности, аномалии, упущенный спрос;
  • top — до limit самых срочных позиций: артикул, поставщик, количество, срочность, покрытие в днях и объяснение «почему».

Параметры: limit (1–200, по умолчанию 50), supplier (IEK | SE), datasetId. Некорректные параметры возвращают понятную ошибку 400.

Проверка через API

BASE=https://apollon-production-59ea.up.railway.app   # или http://localhost:3000
curl -s $BASE/api/health
curl -s -u admin:admin $BASE/api/datasets            # список наборов, взять id
curl -s -u admin:admin -X POST $BASE/api/checks -H "Content-Type: application/json" -H "Origin: $BASE" \
  -d '{"datasetId":"<id>"}'

На наборе данных кейса ответ /api/checks на момент написания содержит:

  • 6 из 6 проверок пройдено:
    • влияние остатка и ETA;
    • учёт всех входов;
    • найден сезонный товар;
    • найден пример упущенного спроса;
    • найден пример разовой продажи;
    • у каждой строки есть обоснование.
  • Сводка realChecks:
    • 3 909 рекомендаций;
    • все количества конечные и неотрицательные;
    • все строки с объяснением;
    • 808 помеченных аномалий;
    • 404 товара с оценённым упущенным спросом.

Проверка обязательных требований тестами

npm test прогоняет tests/engine/engine.test.ts и другие тесты:

  • изменение остатка, товаров в пути, роста или категории меняет итог;
  • сезонный товар получает сезонный, а не средний прогноз;
  • stockout увеличивает потребность;
  • искусственно добавленный заказ объёмом в 50 месячных норм меняет базовый спрос менее чем на 5%.

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

  • Данные кейса. Это 12 книг .xlsx (по 6 на IEK и Systeme Electric) в исходных форматах партнёра. Они загружены в PostgreSQL на Railway через импорт без потерь (scripts/import-source.ts): в таблицах SourceWorkbook/SourceSheet/SourceRow хранятся исходные байты, SHA-256 и каждая ячейка. Файлы партнёра в Git не хранятся.

  • sample-data/ содержит синтетические книги с той же структурой листов и русскими заголовками, что у партнёра: 24 вымышленных товара на поставщика, история с января 2024 по сентябрь 2026. Каждый артикул моделирует свой сценарий: сезонность, рост, stockout, разовый счёт в 50 норм, концентрацию на одного клиента, пересчёт метров в бухты, MOQ, возвраты, битые ячейки. Файлы воспроизводимо (байт в байт) генерируются командой npm run demo:generate (scripts/make-demo-data.ts). Сценарии по суффиксу артикула (одинаково для обоих поставщиков):

    СуффиксСценарий
    001_Выраженная летняя сезонность
    002_Устойчивый рост спроса; для SE дополнительно задан рост бизнеса +30%
    003_Провал продаж в апреле–мае 2026 при пустом остатке → оценка упущенного спроса
    004_То же, но с подтверждённым интервалом дефицита
    005_Разовый счёт в 50 месячных норм (февраль 2026)
    006_Десять счетов одного обезличенного клиента в марте 2026
    007_Спрос и остатки в метрах, закупка в бухтах (305 м/бухта)
    008_Малый спрос, округление до MOQ и кратности
    009_ / 010_Большой остаток и ранняя поставка / малый остаток и поставка после горизонта
    011_Возвраты
    012_, 015_, 018_, 021_, 024_Прерывистый спрос и новые товары
    013_Просроченная ETA без подтверждения получения
    021_, 023_Нет строки MOQ / #N/A в MOQ и отрицательный остаток
    022_Битая числовая ячейка в истории

    Также добавлены дубликаты, невыполненный заказ и некорректный счёт, чтобы было видно, как импорт сообщает о проблемах. Отличия от формата партнёра явно подписаны: необязательная колонка anonymized_customer_id и лист Supplements (SYNTHETIC) с интервалами дефицита и коэффициентом метры/бухты.

  • Внешние сервисы:

    • Railway: хостинг и PostgreSQL;
    • OpenAI API через Agents SDK: только для помощника, по желанию.

    Других интеграций нет. Загрузки в 1С и отправки писем тоже нет.

10. Ограничения (известные нам)

  • Нет пользователей и ролей. Весь экземпляр закрыт одним общим входом (admin / admin, proxy.ts). Пароль демонстрационный и опубликован в README для жюри; для реального использования его нужно сменить через переменные окружения. Имя утверждающего — подпись, а не проверенная личность. Не загружайте конфиденциальные данные в публичный экземпляр.
  • Локальный запуск использует синтетические данные из sample-data/: реальные выгрузки партнёра в репозиторий не входят. Реальные данные кейса доступны только в живой версии.
  • В реальных выгрузках нет ID клиентов. Поэтому проверка концентрации продаж на одном клиенте показана только на синтетических данных с anonymized_customer_id. На реальных данных разовые продажи выявляются по счетам и месячным рядам.

11. Документация и ссылки

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

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

Allayar Kdyrbayev 31

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

  • 2026-09-23 17:55README: trim limitations listAllayar Kdyrbayev
  • 2026-09-23 17:46Require admin/admin login, README login note, seed loads .envAllayar Kdyrbayev
  • 2026-09-23 17:41Harden public API, add category scope, consolidate docs into READMEAllayar Kdyrbayev
  • 2026-09-23 17:34feat(ui): AI summaries for data and checks, visible actions, user storyAllayar Kdyrbayev
  • 2026-09-23 17:31Use distinct SKUs in assistant suggestionsAllayar Kdyrbayev
  • 2026-09-23 17:29feat: one-command judge setup, quick-test endpoint, Russian READMEAllayar Kdyrbayev
  • 2026-09-23 17:21Rename assistant UI and suggest run questionsAllayar Kdyrbayev
  • 2026-09-23 17:17feat(ui): AI summary in the SKU report, clearer drawer and tableAllayar Kdyrbayev
Все коммиты (31)
  • 2026-09-23 17:16Improve Copilot chat interaction and draft linksAllayar Kdyrbayev
  • 2026-09-23 16:54feat(ui): addressable URLs, six-slot upload, compact order tableAllayar Kdyrbayev
  • 2026-09-23 16:34fix(dev): forward shutdown signals and clean up Railway relayAllayar Kdyrbayev
  • 2026-09-23 16:34Show review flag only for low-confidence recommendations with SKU assumptionsAllayar Kdyrbayev
  • 2026-09-23 16:24chore: add web design guidelines skillAllayar Kdyrbayev
  • 2026-09-23 16:20fix(dev): connect local app to Railway automaticallyAllayar Kdyrbayev
  • 2026-09-23 16:18feat: connect PostgreSQL case data and simplify procurement flowAllayar Kdyrbayev
  • 2026-09-23 15:52feat(data): preserve supplier workbooks in Railway PostgreSQLAllayar Kdyrbayev
  • 2026-09-23 15:42feat(ui): Vercel/Geist-style redesign; drop monospace fontAllayar Kdyrbayev
  • 2026-09-23 15:33feat(ui): IEK and Systeme Electric logos in supplier rows, data check, dataset cards and drawerAllayar Kdyrbayev
  • 2026-09-23 15:21feat(ui): single-page order workspace; remove dead pages and codeAllayar Kdyrbayev
  • 2026-09-23 15:05fix(deploy): anchor partner-dir ignores so sample-data workbooks reach RailwayAllayar Kdyrbayev
  • 2026-09-23 15:03feat(brand): Электрокомплект logo, favicon and apple icon; ship public/ in imageAllayar Kdyrbayev
  • 2026-09-23 14:53fix(ui): NaN-safe explorer paging; flag partial ₸ totalsAllayar Kdyrbayev
  • 2026-09-23 14:53docs: add English single-page UI UX redesign promptAllayar Kdyrbayev
  • 2026-09-23 14:50feat(ui): EKT SupplyAI manager workspaceAllayar Kdyrbayev
  • 2026-09-23 14:48build: slimmer runtime image, railway/docker ignore rules, CI with PostgresAllayar Kdyrbayev
  • 2026-09-23 14:44fix(security): streaming body caps, headers, CSRF, rate limits, CSV guardAllayar Kdyrbayev
  • 2026-09-23 14:41feat(engine): per-horizon/unit backtest, anomaly-share confidence, category fallback demandAllayar Kdyrbayev
  • 2026-09-23 14:35fix: service-layer order validation, real SDK handoff name; docs syncAllayar Kdyrbayev
  • 2026-09-23 14:18chore: save paused implementation checkpoint and cleanup runbookAllayar Kdyrbayev
  • 2026-09-23 14:11docs: consolidate build plan and add execution runbookAllayar Kdyrbayev
  • 2026-09-23 13:32Add gitignore and hackathon task descriptionAllayar Kdyrbayev
Структура репозитория (125 файлов)
.dockerignore
.env.example
.github/workflows/ci.yml
.gitignore
.railwayignore
AGENTS.md
Dockerfile
README.md
app/[[...path]]/page.tsx
app/api/agent/route.ts
app/api/backtest/route.ts
app/api/checks/route.ts
app/api/data/deliveries/route.ts
app/api/data/products/route.ts
app/api/data/sales/route.ts
app/api/data/stocks/route.ts
app/api/data/transactions/route.ts
app/api/datasets/[id]/route.ts
app/api/datasets/route.ts
app/api/export/route.ts
app/api/health/route.ts
app/api/history/route.ts
app/api/import/route.ts
app/api/insights/route.ts
app/api/jobs/route.ts
app/api/orders/[id]/approve/route.ts
app/api/orders/[id]/route.ts
app/api/quick-test/route.ts
app/api/runs/[id]/route.ts
app/api/runs/[id]/summary/route.ts
app/api/runs/route.ts
app/api/trends/route.ts
app/apple-icon.png
app/error.tsx
app/globals.css
app/icon.png
app/layout.tsx
app/loading.tsx
app/not-found.tsx
case and data/Hackathon_task.md
components/ai-insight.tsx
components/backtest-dashboard.tsx
components/charts.tsx
components/checks-dashboard.tsx
components/data-explorer.tsx
components/order-workspace.tsx
components/sku-detail.tsx
components/trends-dashboard.tsx
components/workspace.tsx
docker-compose.yml
eslint.config.mjs
lib/agent/guardrails.ts
lib/agent/index.ts
lib/agent/summary.ts
lib/assistant-suggestions.ts
lib/contracts/api.ts
lib/contracts/engine.ts
lib/db.ts
lib/engine/backtest.ts
lib/engine/checks.ts
lib/engine/index.ts
lib/engine/math.ts
lib/export/index.ts
lib/http.ts
lib/ingest/index.ts
lib/ingest/raw-workbook.ts
lib/ingest/source-mapping.ts
lib/repo/explorer.ts
lib/repo/index.ts
lib/repo/raw-workbook.ts
lib/repo/source-planning.ts
lib/route.ts
next-env.d.ts
next.config.ts
package-lock.json
package.json
postcss.config.mjs
prisma.config.ts
prisma/migrations/20260923090113_init/migration.sql
prisma/migrations/20260923153000_source_workbooks/migration.sql
prisma/migrations/migration_lock.toml
prisma/schema.prisma
proxy.ts
public/brand/ekt-logo-256.png
public/brand/ekt-logo.png
public/brand/iek-logo.png
public/brand/systeme-electric-logo.png
railway.json
sample-data/IEK/MOQ  ИЭК.xlsx
sample-data/IEK/Динамика продаж_2025-2026.xlsx
sample-data/IEK/Ежемесячные остатки продукции за последние 2 года  ИЭК.xlsx
sample-data/IEK/Ежемесячные продажи в количественном выражении за последние 2 года.xlsx
sample-data/IEK/Путь ИЭК 22.09.2026.xlsx
sample-data/IEK/Сезонность ИЭК.xlsx
sample-data/Systeme electric/MOQ SystemElectric.xlsx
sample-data/Systeme electric/Динамика продаж_Syseme Electric_2025-2026.xlsx
sample-data/Systeme electric/Ежемесячные остатки SystemElectric 2024-2026.xlsx
sample-data/Systeme electric/Ежемесячные продажи в кол-м выражении SystemElectric 2024-2026.xlsx
sample-data/Systeme electric/Сезонность SystemElectric 2024-2026.xlsx
sample-data/Systeme electric/Товар в пути_SystemElectric на 22.09.2026.xlsx
scripts/import-source.ts
scripts/make-demo-data.ts
scripts/map-sources.ts
scripts/railway-production.ts
scripts/seed-sample.ts
scripts/with-production-db.ts
tests/agent/agent.test.ts
tests/agent/summary.test.ts
tests/api/quick-test-schema.test.ts
tests/assistant-suggestions.test.ts
tests/engine/engine.test.ts
tests/export/export.test.ts
tests/fixtures/engine.ts
tests/ingest/import-route.test.ts
tests/ingest/ingest.test.ts
tests/ingest/raw-workbook.test.ts
tests/ingest/source-mapping.test.ts
tests/repo/explorer.test.ts
tests/repo/load-dataset.test.ts
tests/repo/repo.test.ts
tests/repo/source-planning.test.ts
tests/security/http.test.ts
tests/security/proxy.test.ts
tsconfig.json
vitest.config.ts

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

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

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