AAYMA
RU
Войти

Qor

Сервис для закупщика «Электрокомплект»: по Excel-выгрузкам 1С прогнозирует спрос CatBoost и формирует рекомендуемые заказы поставщикам со…

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

О проекте

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

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

Суть

Сервис для закупщика «Электрокомплект»: по Excel-выгрузкам 1С прогнозирует спрос CatBoost и формирует рекомендуемые заказы поставщикам со срочностью; руководитель утверждает или возвращает заказ в мобильном приложении.

Как устроено

pipeline.py читает шесть Excel из 1С, prepare_dataset_v2 строит признаки (лаги 1/2/3/6/12, скользящие среднее и σ, сезонность, month_sin/cos), чистит выбросы (скользящая медиана + 6×MAD) и добавляет упущенный спрос в месяцы без остатка. CatBoost прогнозирует продажи следующего месяца, рекомендация = прогноз + 0,5σ − остаток − в пути с округлением до MOQ, срочность по покрытию. Next.js-сайт показывает таблицу заказов и шаги расчёта, Flutter-приложение даёт руководителю утверждение, FastAPI хранит статусы; отдельный AI-ассистент с tool calling отвечает только по данным расчёта.

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

  • CatBoost
  • MAD-детекция выбросов
  • коррекция stockout/упущенного спроса
  • страховой запас 0,5σ
  • округление до MOQ
  • walk-forward бэктест
  • function calling

Особенности

Полный цикл «расчёт → правка закупщиком → утверждение руководителем на телефоне» с ролями и 403/409 на API; прозрачная формула заказа и очистка спроса от разовых отгрузок и пустых складов.

Стек

Python · FastAPI · CatBoost · pandas · Next.js 15 · React 19 · TypeScript · Tailwind CSS · Flutter · Docker Compose · Playwright · Jest

Модели

OpenAI


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

README

README.md

Qor — заказы поставщикам для ekt.kz

Qor (қаз. қор — запас). Сервис для менеджера закупа ТОО «Электрокомплект»: по выгрузке из 1С считает, что пополнить на складе, в каком количестве и насколько срочно.

Сейчас это делают вручную в Excel и редко. Из-за этого по одним позициям копится лишний запас, по другим возникает дефицит, а разовая крупная отгрузка искажает регулярную потребность. Qor собирает рекомендацию по каждому артикулу и не отправляет заказ поставщику сам: человек проверяет количество и отдельным шагом утверждает.

Два клиента, один расчёт:

  • Сайт — рабочее место закупщика: загрузка выгрузки, таблица заказов, правка количества, карточка артикула с шагами расчёта. В правом нижнем углу чат: он отвечает только цифрами этого расчёта.
  • Телефон — согласование. Руководитель видит тот же заказ, график продаж за 12 месяцев, спрашивает «почему столько» и нажимает «Утвердить» или «Вернуть». Поставщику ничего не уходит: после утверждения менеджер выгружает заказ в 1С на сайте.

Как считается заказ

Рабочий расчёт — backend/qor/pipeline.py. Он читает Excel, строит признаки скриптом scripts/prepare_dataset_v2.py и прогоняет модель ml/models/demand_model_v2.cbm (CatBoost, цель — продажи следующего месяца).

На каждый артикул:

рекомендация = прогноз + 0,5 × σ за 6 месяцев − остаток − в пути

Результат округляется вверх до минимальной партии (MOQ). Срочность — покрытие остатком и товаром в пути: меньше 0,5 месяца спроса — «Критично», меньше 1 — «Скоро», иначе «Норма». У каждой строки есть шаги: прогноз, страховой запас, остаток, в пути, округление.

Признаки модели: лаги очищенного спроса 1/2/3/6/12, скользящие среднее и σ, рост за 3 месяца, остаток, календарь (month_sin / month_cos), индекс сезонности, а также лаги выброса, пустого склада и упущенного спроса. Клиентов в расчёте нет: в выгрузке нет персонального идентификатора, крупную отгрузку можно увидеть только по номеру документа.

Та же модель обучена с поправкой на выбросы и пустые месяцы (scripts/prepare_dataset_v2.py, scripts/train_model_v2.py):

  • Разовая крупная продажа. По каждому артикулу порог = скользящая медиана за 6 месяцев + 6 × MAD. Если MAD равен нулю, порог = max(4 × медиана, медиана + 10). Месяц выше порога заменяется медианой и не входит в регулярный спрос.
  • Упущенный спрос. Месяц без остатка не считается нулевым спросом: оценка потерянных продаж добавляется к очищенному ряду (demand_adjusted).

Сайт и телефон берут заказ из run_workspace.py: он вызывает этот же расчёт и пишет backend/data/workspace.json.

Технологии

ЧастьСтек
СайтNext.js 15 (App Router), React 19, TypeScript, Tailwind CSS
API согласованияPython, FastAPI, uvicorn. Папка backend/
МодельCatBoost, pandas. Файл ml/models/demand_model_v2.cbm, сервис ml
ТелефонFlutter, riverpod, dio. Тот же REST
ДанныеШесть Excel из 1С. Схема полей — docs/06-datasets.md

Роли: aigerim / buyer — менеджер закупа, daniyar / director — руководитель. Пароль для демо совпадает с ролью. Утверждать и возвращать заказ может только руководитель.

Запуск

Модель ml/models/demand_model_v2.cbm уже в репозитории.

docker compose up --build

Три контейнера: сайт, API и CatBoost. Считать заказ умеет только ml. API сохраняет результат и отдаёт его сайту и телефону.

Сайтhttp://localhost:3000
APIhttp://localhost:8000/docs
Входaigerim / buyer, daniyar / director

Модель наружу не публикуется: API ходит к ней по http://ml:8002. Готовый заказ лежит в volume qor-data. Свою выгрузку 1С можно примонтировать в сервис ml как /data/iek.

Чат в углу сайта — четвёртый процесс, нужен OPENAI_API_KEY в .env:

uvicorn api.main:app --host 127.0.0.1 --port 8001

Сайт зовёт его через POST /api/chat (QOR_AI_API, по умолчанию http://127.0.0.1:8001). Без этого процесса кнопка чата пишет, что ассистент не запущен.

Без Docker

Нужны Python 3.11+ и Node.js 20+.

pip install -r backend/requirements.txt
cd frontend && npm install
  1. Посчитать заказ. Excel — в IEK_DATA_DIR (по умолчанию /Users/azamatomirtaj/Documents/IEK): динамика продаж, ежемесячные продажи, ежемесячные остатки, путь, MOQ, сезонность.
cd backend && python3 run_workspace.py

Появится backend/data/workspace.json. В git он не коммитится.

  1. API для сайта и телефона: cd backend && uvicorn main:app --host 0.0.0.0 --port 8000.

  2. Сайт: cd frontend && npm run dev — http://localhost:3000 (/import, /orders, /sku/[артикул], /anomalies, /suppliers).

Телефон: cd mobile && flutter pub get && flutter run. Симулятор iOS ходит на http://127.0.0.1:8000, эмулятор Android — на http://10.0.2.2:8000. Реальный телефон в той же сети: flutter run --dart-define=QOR_API=http://<IP компьютера>:8000. Сценарий проверки — mobile/README.md.

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

  1. python3 backend/run_workspace.py завершается без ошибки и пишет число SKU к заказу.
  2. curl -s http://127.0.0.1:8000/health возвращает "ready": true и "model": "demand_model_v2.cbm".
  3. На сайте /orders есть строки с артикулом, остатком, «в пути», рекомендуемым количеством и бейджем срочности. Раскрытие строки показывает шаги расчёта.
  4. Измените количество в файле «Путь» и пересчитайте: рекомендуемое количество по этому артикулу изменится. Так же на результат влияют остаток и история продаж.
  5. Войдите в API как daniyar и откройте GET /v1/bundle — тот же JSON, что на сайте. POST /v1/orders/iek-current/approve переводит заказ в approved. Повторное утверждение — 409. Тем же методом под aigerim — 403.
  6. На телефоне тот же заказ: «Утвердить» или «Вернуть» с комментарием меняет статус, который читает GET /v1/orders/current. Поставщику запрос не уходит.
  7. Выбросы: scripts/prepare_dataset_v2.py помечает outlier_flag и заменяет всплеск медианой. Компенсация пустого склада — колонка demand_adjusted в том же скрипте. Эти колонки входят в признаки demand_model_v2.cbm.
Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
21
Авторы
3
Файлы
211
Ветки
1

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

Azamat Omirtay 12Harry 6bektaszhaksylyk 3

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

  • 2026-09-23 17:59refactor(agent): answer from the same workspace the UI rendersAzamat Omirtay
  • 2026-09-23 17:59fix(engine): stop forecasting zero for chronically out-of-stock SKUsAzamat Omirtay
  • 2026-09-23 17:58fix(web): sanitize uploaded filenames before writing them to diskAzamat Omirtay
  • 2026-09-23 17:54docs: lead the README with Docker and the demo loginsAzamat Omirtay
  • 2026-09-23 17:49feat: run the site, API, and CatBoost model in DockerAzamat Omirtay
  • 2026-09-23 17:12fix(mobile): show monthly sales on the SKU chartAzamat Omirtay
  • 2026-09-23 17:02docs(mobile): add phone check steps for the jurybektaszhaksylyk
  • 2026-09-23 16:54feat: buyer auth, director notifications, v2 model, quieter desk UIAzamat Omirtay
Все коммиты (21)
  • 2026-09-23 16:09fix(agent): enforce dataset-only responsesHarry
  • 2026-09-23 15:59feat(agent): restrict AI responses to QOR datasetHarry
  • 2026-09-23 15:51feat(mobile): talk to the Qor API instead of local databektaszhaksylyk
  • 2026-09-23 15:47feat(agent): add AI procurement assistant with tool callingHarry
  • 2026-09-23 15:33feat(web): split buyer screens out of the route filesAzamat Omirtay
  • 2026-09-23 15:33feat(backend): build supplier orders from the trained demand modelAzamat Omirtay
  • 2026-09-23 15:33feat(api): add buyer and director login for the mobile approval flowAzamat Omirtay
  • 2026-09-23 15:31feat(model): add procurement engine with CatBoost V2 forecastsHarry
  • 2026-09-23 15:23feat(model): add demand forecasting with outlier and stockout correctionHarry
  • 2026-09-23 14:50feat(model): add CatBoost demand forecasting and procurement recommendationsHarry
  • 2026-09-23 14:47feat: split the buyer UI and the order engineAzamat Omirtay
  • 2026-09-23 14:46feat(mobile): add Qor approval app on Flutterbektaszhaksylyk
  • 2026-09-23 13:57feat: add Qor procurement workspace on IEK 1C dumpsAzamat Omirtay
Структура репозитория (211 файлов)
.dockerignore
.gitignore
README.md
api/__init__.py
api/agent.py
api/main.py
backend/Dockerfile
backend/data/README.md
backend/main.py
backend/pytest.ini
backend/qor/__init__.py
backend/qor/auth.py
backend/qor/contract.py
backend/qor/explain.py
backend/qor/ml_server.py
backend/qor/orders.py
backend/qor/pipeline.py
backend/requirements-api.txt
backend/requirements.txt
backend/run_workspace.py
backend/tests/conftest.py
backend/tests/test_api.py
backend/tests/test_auth.py
backend/tests/test_contract.py
backend/tests/test_orders.py
data/processed/backtest_results.csv
data/processed/recommended_orders.csv
data/processed/recommended_orders_v2.csv
data/processed/recommended_orders_v2.json
data/processed/training_dataset.csv
data/processed/training_dataset_v2.csv
data/processed/validation_predictions.csv
data/processed/validation_predictions_v2.csv
data/processed/walk_forward_results.csv
data/processed/walk_forward_v2_results.csv
docker-compose.yml
docs/01-pain-point.md
docs/02-stitch-web-prompt.md
docs/03-stitch-mobile-prompt.md
docs/04-design-references.md
docs/05-tech-stack.md
docs/06-datasets.md
docs/07-roles-and-screens.md
frontend/.dockerignore
frontend/Dockerfile
frontend/__tests__/AssistantChat.test.tsx
frontend/__tests__/DirectorHome.test.tsx
frontend/__tests__/LoginScreen.test.tsx
frontend/__tests__/StatusBadge.test.tsx
frontend/__tests__/catalog.test.ts
frontend/__tests__/import-safeName.test.ts
frontend/app/anomalies/page.tsx
frontend/app/api/auth/login/route.ts
frontend/app/api/auth/logout/route.ts
frontend/app/api/auth/me/route.ts
frontend/app/api/chat/route.ts
frontend/app/api/import/route.ts
frontend/app/api/orders/current/route.ts
frontend/app/api/orders/submit/route.ts
frontend/app/api/workspace/route.ts
frontend/app/globals.css
frontend/app/icon.svg
frontend/app/import/page.tsx
frontend/app/layout.tsx
frontend/app/orders/page.tsx
frontend/app/page.tsx
frontend/app/sku/[code]/page.tsx
frontend/app/suppliers/page.tsx
frontend/components/AppShell.tsx
frontend/components/AssistantChat.tsx
frontend/components/DirectorHome.tsx
frontend/components/ForecastChart.tsx
frontend/components/Icon.tsx
frontend/components/ImportPanel.tsx
frontend/components/KpiCard.tsx
frontend/components/LoginScreen.tsx
frontend/components/Logo.tsx
frontend/components/OrdersWorkspace.tsx
frontend/components/SessionProvider.tsx
frontend/components/SettingsDrawer.tsx
frontend/components/Sidebar.tsx
frontend/components/StatusBadge.tsx
frontend/components/WorkspaceProvider.tsx
frontend/data/catalog.ts
frontend/data/workspace.ts
frontend/e2e/auth.spec.ts
frontend/e2e/director.spec.ts
frontend/e2e/helpers.ts
frontend/e2e/order-flow.spec.ts
frontend/e2e/orders.spec.ts
frontend/jest.config.mjs
frontend/jest.setup.ts
frontend/next-env.d.ts
frontend/next.config.ts
frontend/package-lock.json
frontend/package.json
frontend/playwright.config.ts
frontend/postcss.config.mjs
frontend/screens/Anomalies.tsx
frontend/screens/Dashboard.tsx
frontend/screens/Sku.tsx
frontend/screens/Suppliers.tsx
frontend/server/import.ts
frontend/server/qor.ts
frontend/server/upload-name.ts
frontend/server/workspace.ts
frontend/tailwind.config.ts
frontend/tsconfig.json
ml/Dockerfile
ml/models/demand_model_v2.cbm
mobile/.gitignore
mobile/.metadata
mobile/README.md
mobile/analysis_options.yaml
mobile/android/.gitignore
mobile/android/app/build.gradle.kts
mobile/android/app/src/debug/AndroidManifest.xml
mobile/android/app/src/main/AndroidManifest.xml
mobile/android/app/src/main/kotlin/kz/ekt/qor_mobile/MainActivity.kt
mobile/android/app/src/main/res/drawable-v21/launch_background.xml
mobile/android/app/src/main/res/drawable/launch_background.xml
mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
mobile/android/app/src/main/res/values-night/styles.xml
mobile/android/app/src/main/res/values/styles.xml
mobile/android/app/src/profile/AndroidManifest.xml
mobile/android/build.gradle.kts
mobile/android/gradle.properties
mobile/android/gradle/wrapper/gradle-wrapper.properties
mobile/android/settings.gradle.kts
mobile/ios/.gitignore
mobile/ios/Flutter/AppFrameworkInfo.plist
mobile/ios/Flutter/Debug.xcconfig
mobile/ios/Flutter/Release.xcconfig
mobile/ios/Runner.xcodeproj/project.pbxproj
mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
mobile/ios/Runner.xcworkspace/contents.xcworkspacedata
mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
mobile/ios/Runner/AppDelegate.swift
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/[email protected]
mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/[email protected]
mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/[email protected]
mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard
mobile/ios/Runner/Base.lproj/Main.storyboard
mobile/ios/Runner/Info.plist
mobile/ios/Runner/Runner-Bridging-Header.h
mobile/ios/RunnerTests/RunnerTests.swift
mobile/lib/data/format.dart
mobile/lib/data/models.dart
mobile/lib/data/providers.dart
mobile/lib/data/repository.dart
mobile/lib/data/why.dart
mobile/lib/main.dart
mobile/lib/screens/chat_screen.dart
mobile/lib/screens/home_screen.dart
mobile/lib/screens/lock_screen_push.dart
mobile/lib/screens/login_screen.dart
mobile/lib/screens/notifications_screen.dart
mobile/lib/screens/order_screen.dart
mobile/lib/screens/profile_screen.dart
mobile/lib/screens/sku_screen.dart
mobile/lib/theme/app_theme.dart
mobile/lib/theme/tokens.dart
mobile/lib/widgets/common.dart
mobile/lib/widgets/mini_chart.dart
mobile/lib/widgets/position_card.dart
mobile/pubspec.lock
mobile/pubspec.yaml
mobile/test/fake_repository.dart
mobile/test/live_api_test.dart
mobile/test/screens_smoke_test.dart
package-lock.json
package.json
scripts/backtest.py
scripts/generate_orders.py
scripts/generate_orders_v2.py
scripts/hourly-push.sh
scripts/inspect_data.py
scripts/prepare_dataset.py
scripts/prepare_dataset_v2.py
scripts/train_model.py
scripts/train_model_v2.py
scripts/walk_forward_backtest.py
scripts/walk_forward_v2.py
tests/test_agent.py
tests/test_sku_series.py

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

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

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