О проекте
Команда: 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 минуты (живая версия)
- Откройте https://apollon-production-59ea.up.railway.app и выберите набор «IEK и Systeme Electric — исходные данные кейса».
- Выберите поставщика и нажмите «Рассчитать заказ». Затем откройте «Почему» у любой строки, утвердите заказ и скачайте XLSX / CSV / письмо.
- Проверка расчёта одним запросом (только чтение, ничего не сохраняет) — расчёт, проверка 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 — веб-сервис для менеджера по закупкам. Сервис:
- Загружает выгрузки поставщиков IEK и Systeme Electric в исходном формате
.xlsx. - Прогнозирует спрос с учётом сезонности, устойчивого роста, упущенного спроса и разовых выбросов.
- Выдаёт список рекомендованных заказов, сгруппированный по поставщикам. У каждой позиции есть объяснение и срочность.
- Позволяет скорректировать количество, утвердить заказ с указанием ответственного и выгрузить черновик заказа для поставщика.
Заказы никогда не отправляются автоматически. Сервис только готовит черновик. Решение принимает человек, как и требуют ограничения кейса.
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/письмо
- Выбор данных (
/). Менеджер загружает до шести отчётов поставщика: продажи, остатки, товары в пути, MOQ/кратность, динамика продаж и сезонность. Можно также выбрать уже сохранённый набор данных кейса. Файлы распределяются по слотам по имени. Нераспознанные файлы и файлы не в формате.xlsxявно отклоняются. - Проверка (
/datasets/<id>). Показываются покрытие данных, предупреждения импорта и отсутствующие отчёты. Менеджер выбирает поставщиков и, при необходимости, одну категорию товаров (расчёт по категории, как в кейсе), а также меняет политику в расширенных настройках: срок поставки, уровень сервиса и так далее. - Расчёт. Нажатие «Рассчитать заказ» запускает детерминированный движок. Результат сохраняется как отдельный расчёт (
/runs/<id>). - Обзор. Рекомендации сгруппированы по поставщику и выводятся по 100 строк на страницу. В каждой строке есть артикул, количество, срочность, уверенность и кнопка «Почему». Объяснение показывает прогноз, остаток, поступления, исключённые выбросы, упущенный спрос и округление. Неизвестные факты помечаются явно, а не подменяются.
- Корректировка и утверждение. Менеджер меняет количества, указывает ответственного и подтверждает допущения. После этого создаётся замороженная ревизия.
- Экспорт. Менеджер скачивает XLSX, CSV или черновик письма поставщику.
У каждого состояния есть свой URL (?sku=<key> открывает объяснение), поэтому ссылкой можно поделиться, а кнопки браузера «Назад» и «Вперёд» работают.
Методика расчёта
Расчёт детерминирован: чистые функции TypeScript в lib/engine, без LLM и без БД. Один и тот же вход всегда даёт один и тот же результат. Обучение использует только законченные месяцы до даты среза; текущий неполный месяц не экстраполируется. Возвраты (отрицательные продажи) сохраняются в истории и обнуляются только для обучения.
- Очистка от разовых заказов — см. следующий раздел.
- Доступность и упущенный спрос. Пустой остаток считается нулём только между первым и последним известным остатком товара. Подтверждённые интервалы дефицита имеют приоритет. Иначе, если продажи ниже 80% сезонной нормы при нулевом остатке, доступность оценивается по последнему дню продажи или по отношению факта к ожиданию.
Упущенный спрос = ожидаемый сезонный спрос × (1 − доступность), восстановленный объём ограничен двумя нормами. Выводится сценарный диапазон при доступности ±0,25. - Сезонность. Профиль товара — отношения к среднему объёму года (минимум 9 наблюдаемых месяцев в году), среднее индексов = 1. Собственный профиль сжимается к групповому (в тех же единицах) с весом
полные годы / (полные годы + 1). - Рост. Уровень и тренд Theil–Sen по последним 12 сезонно скорректированным месяцам, годовой тренд ограничен ±50%. Коэффициент роста от поставщика или менеджера заменяет оценённый тренд. Прогноз месяца:
уровень × сезонность × (1 + рост)^(месяцы от последнего законченного / 12). - Горизонт и страховой запас.
H = срок поставки + период пересмотра. Ошибки одношагового прогноза считаются последовательно (только по прошлому). При ≥12 ошибках страховой запас — эмпирический квантиль уровня сервиса ×√(H/30), иначеz × σ × √(H/30). Нижняя граница — спрос заsafetyDays. Категория меняет уровень сервиса. - Потребность. Остатки и поставки в пути проецируются по дням: поставка добавляется только в день ETA, просроченная неподтверждённая ETA к остатку не добавляется и требует проверки.
Потребность = max(0, спрос_H + страховой запас − свободный остаток − поступления внутри H). Затем MOQ и округление вверх до кратности; при пересчёте единиц (метры → бухты) MOQ и кратность сначала переводятся в единицы спроса. - Срочность и классы. Срочность — по дате первого прогнозного дефицита. 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.
- Строки продаж объединяются по товару и номеру счёта (возвраты сохраняются). Номер счёта — это заказ, а не клиент.
- Проверка счёта. Нужно минимум 6 более ранних положительных счетов. Счёт помечается как разовый, если одновременно:
- он больше
max(median + 6 × MAD × 1.4826, Q3 + 3 × IQR, 3 × типичный сезонный месячный объём); - он составляет не менее 30% продаж своего месяца.
- он больше
- Защита устойчивого роста. Если хотя бы 3 из следующих 6 месяцев тоже повышены, это рост, а не выброс: ничего не исключается.
- Вычитание. Счёт вычитается из месячного ряда, только если сумма счетов сходится с месячным отчётом с точностью 5%. Иначе работает резервный месячный фильтр на сезонно скорректированном ряду: выброс заменяется типичным сезонным объёмом.
- Один клиент. Если есть обезличенный
customerId, дополнительно проверяется сумма покупок одного клиента за скользящие 60 дней по тому же порогу. - Прерывистый спрос. Для рядов, где больше 40% месяцев нулевые, автоматические фильтры отключены (иначе любая продажа выглядела бы выбросом).
- Каждое исключение видно в объяснении товара, и менеджер может его отменить; тогда товар пересчитывается.
Проверка: тест в 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 |
| Тесты и CI | Vitest, 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):
- Неизменяемый архив источника:
SourceWorkbook,SourceSheet,SourceRow. Здесь хранятся исходные байты файла, SHA-256, каждая физическая строка и ячейка, включая заголовки, итоги и пустые строки. Ничего не фильтруется и не «исправляется». - Версионированное сопоставление: из архива строится набор данных для планирования (
Dataset,Productи др.). В манифесте набора записаны ID и хэши исходных книг. - Результаты: расчёты, заказы, ревизии и неизменяемые снимки утверждений.
Приватность и защита 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_qualitycalculate_replenishment(создаёт только черновик расчёта)explain_skucompare_scenario(реальные дельты движка при изменённой политике)list_anomaliesdraft_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
Что происходит:
dbподнимает PostgreSQL 17 (порт5433на хосте, чтобы не конфликтовать с локальным Postgres).setupприменяет миграции (prisma migrate deploy) и запускаетnpm run seed:sample. Скрипт импортирует 12 синтетических книг изsample-data/через тот же код, что и загрузка в интерфейсе (parseRawWorkbook→saveRawWorkbook→materializeSourceDataset). Готовых результатов в сиде нет: расчёт выполняется в приложении. Повторный запуск идемпотентен.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. Как проверить решение (сценарий для жюри)
Быстрая проверка на живой версии
- Откройте https://apollon-production-59ea.up.railway.app. Выберите сохранённый набор «IEK и Systeme Electric — исходные данные кейса».
- Выберите поставщика (и при желании категорию) и нажмите «Рассчитать заказ».
- Откройте «Почему» у любой строки. Вы увидите прогноз по месяцам, сезонные коэффициенты, остаток, поступления в горизонте, исключённые выбросы, упущенный спрос и шаги округления до MOQ и кратности.
- Измените количество в строке, введите имя ответственного и утвердите заказ. Затем скачайте XLSX / CSV / письмо. Попробуйте изменить строку после утверждения: утверждение снимется.
- Откройте «Проверка расчёта» и посмотрите вкладки проверок кейса, backtest и трендов.
Один запрос — проверка расчёта: GET /api/quick-test
Откройте в браузере или через curl:
- локально: http://localhost:3000/api/quick-test?limit=50
- живая версия: https://apollon-production-59ea.up.railway.app/api/quick-test?limit=50
При каждом запросе сервер заново загружает набор данных из базы, запускает детерминированный движок (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. Документация и ссылки
- Живая версия: https://apollon-production-59ea.up.railway.app
- Методика расчёта и алгоритм исключения выбросов — раздел 3 этого README; константы —
ENGINE_THRESHOLDSвlib/engine/index.tsиDEFAULT_POLICYвlib/contracts/engine.ts. - Текст кейса:
case and data/Hackathon_task.md.
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.