О проекте
Команда: NoLimits
Кейс HackAlem.ai: Логистика — Электрокомплект · ekt.kz
Стадия: Доведённый продукт
Задача кейса: Автоматический расчёт заказов поставщикам для пополнения склада, с утверждением менеджером
Суть
Объяснимая система пополнения складов ekt.kz: по продажам, остаткам, товарам в пути, lead time, MOQ и дефицитам рассчитывает заказы поставщикам с бюджетом в тенге, закупщик правит и утверждает, есть журнал движений и экспорт в Excel.
Как устроено
Excel/CSV (продажи, остатки, транзит, stockouts, поставщики) валидируются и нормализуются (включая русские заголовки), история разворачивается в календарный спрос. Выбросы отсекаются Robust MAD (modified Z) с сохранением в Audit Trail, упущенный спрос в дни дефицита восстанавливается по медиане, учитываются недельная и месячная сезонность и тренд. Потребность = спрос за lead time + safety stock (Z=1.65·σ·√LT) − остаток − транзит, прибывающий в пределах LT; затем применяются MOQ, кратность упаковки и минимальная сумма заказа. FastAPI отдаёт рекомендации с объяснениями, React-дашборд даёт проверку, правку и утверждение (SQLite, версии утверждений архивируются при изменении данных).
Ключевые решения
- Safety Stock (Z=1.65)
- Robust MAD / modified Z-score
- stockout compensation
- сезонность и тренд
- MOQ и округление до кратности упаковки
- стресс-тест на аномалиях
Особенности
Сильная доменная математика без LLM: восстановление упущенного спроса без двойного счёта пересекающихся дефицитов, учёт только транзита, успевающего к концу lead time, сверка сторно с исходной накладной; стресс-датасет на 20 294 транзакции с 8 типами реальных аномалий (тендеры, скачки меди, ошибки 1С) и независимый QA-набор приёмки.
Стек
Python · FastAPI · SQLite · React · TypeScript · Vite · pytest · Docker Compose
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-110c2392-nolimits.
README
README.mdStockPilot — Explainable Warehouse Replenishment OS
Система автоматического и объяснимого расчёта заказов поставщикам для пополнения распределительных складов дистрибьютора электротехники ТОО «Электрокомплект» (ekt.kz).
StockPilot объединяет историю продаж, текущие остатки, товары в пути, логистические сроки (Lead Time), минимальные партии (MOQ), кванты упаковки и периоды дефицита (stockouts) для ответа на ключевой вопрос закупщика: что, сколько и почему нужно заказать прямо сейчас?
⚡️ Быстрый запуск в 1 команду (Docker)
docker compose up --build
- Frontend Dashboard: http://localhost:5173
- Backend Swagger API: http://localhost:8000/docs
- Health Check: http://localhost:8000/health
🛠 Локальный запуск для разработки
1. Backend (FastAPI + Python 3.12/3.13)
cd backend
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
2. Frontend (React + Vite + TypeScript)
cd frontend
npm install
npm run dev
Откройте http://localhost:5173. В приложении доступны:
- Кнопка «⚡️ ekt.kz Dataset (Казахстан)» — мгновенная загрузка реального датасета ТОО «Электрокомплект» с расчётом заказов и бюджета в тенге (₸).
- Кнопка «💥 Аномалии (Stress Test)» — стресс-тест на 20 294 транзакциях с 8 реальными типами коммерческих аномалий (тендеры, скачки цен меди, ЧС/шторм, ошибки операторов 1С, дефициты, неликвиды, каннибализация).
- Кнопка «↻ Synthetic demo» — синтетический эталонный сценарий (105 SKU, 3 склада, 5 поставщиков).
- Раздел «Data Intake» — загрузка файлов по отдельности или книги Excel целиком (
ekt_sales_and_stock_history.xlsxилиekt_extreme_anomalies_sales_and_stock.xlsx, 5 листов в 1 клик). - Раздел «Data editor» — операции через сайт: закуп, продажа, поступление, перемещение, корректировка и возврат. Несколько товаров проводятся одним документом; вкладка «Склад» показывает все SKU из истории проекта, включая позиции с нулевым остатком. «Справочники» сохраняют точечное редактирование таблиц и SQLite-черновик.
Операции с товарами без повторного Excel
- Загрузите книгу через Data intake, проверьте и утвердите рекомендации.
- В Data editor → Закуп добавьте утверждённые рекомендации или вручную наберите несколько SKU, укажите поставщика, склад и дату поставки. Заказ записывается в журнал и увеличивает товары в пути, но ещё не физический остаток.
- В Движения товаров → Поступление примите фактически доставленное количество: оно уйдёт из «в пути» на склад. Частичное поступление допустимо; принять больше заказанного/импортированного количества нельзя.
- В Продажа пробейте несколько товаров в одном документе. Сервис проверит доступный остаток, уменьшит его и добавит продажи в историю для следующего прогноза. Аналогично доступны перемещение между складами, корректировка (+/−) и возврат.
- Нажмите «Скачать обновлённый Excel»: книга содержит
products,sales,stock,transit,stockouts,suppliersиmovements. Журнал и каталог переживают перезапуск. Повторная загрузка книги Excel считается новым авторитетным срезом остатков; прежние движения остаются в журнале.
Документ с несколькими позициями проверяется целиком: недостаток товара в одной строке отклоняет все строки без частичного списания. Повторная отправка того же client_request_id не создаёт вторую операцию.
📐 Архитектура и математический аппарат
StockPilot строго следует пяти ключевым критериям Must Have ТЗ хакатона:
flowchart LR
A[Excel / CSV: продажи, остатки, пути, дефицит, поставщики] --> B[Валидация и нормализация]
B --> C[Календарный спрос]
C --> D[Возвраты, прямые отгрузки, выбросы, lost demand]
D --> E[Сезонный прогноз и тренд]
E --> F[Lead Time + Safety Stock - остаток - своевременный inbound]
F --> G[MOQ + кратность + минимальная сумма]
G --> H[FastAPI: рекомендации и объяснения]
H --> I[React: проверка и правка закупщиком]
I --> J[SQLite: утверждение и история версий]
J --> K[Закуп / приёмка / продажа / перемещение]
K --> L[SQLite: журнал движений + исторический каталог]
L --> B
L --> M[Excel: актуальные таблицы и журнал]
Верхняя панель показывает дату последней продажи в загруженных данных, а не дату запуска приложения: это помогает заметить устаревший срез до утверждения заказа.
1. Формула расчёта потребности
$$\text{Demand during Lead Time} = \text{Lead Time (days)} \times \text{Average Daily Demand} \times \text{Seasonality} \times (1 + \text{Trend})$$ $$\text{Safety Stock} = Z \times \sigma_{\text{daily}} \times \sqrt{\text{Lead Time}} \quad (Z = 1.65 \text{ для 95% уровня сервиса})$$ $$\text{Net Requirement} = \max(0, \text{Demand during Lead Time} + \text{Safety Stock} - \text{Available Stock} - \text{Inbound Arriving within Lead Time})$$
2. Учёт ограничений поставщиков (MOQ и квант упаковки)
- Если $\text{Net Requirement} > 0$ и задан $\text{MOQ}$: $\text{Order} = \max(\text{Net Requirement}, \text{MOQ})$.
- Любой положительный заказ (в том числе при
Pack = 1) округляется вверх: $\text{Order} = \lceil \frac{\max(\text{Net Requirement}, \text{MOQ})}{\text{Package Size}} \rceil \times \text{Package Size}$. - Если поставщик задаёт минимальную сумму заказа, перед округлением применяется также нижняя граница
Minimum Order Value / Unit Cost. Закупщик не может утвердить итог ниже этой суммы.
3. Фильтрация разовых выбросов (Robust MAD)
Алгоритм использует медианное абсолютное отклонение (Median Absolute Deviation, MAD) вместо чувствительного к выбросам стандартного отклонения:
$$\text{MAD} = \text{median}(|x_i - \text{median}(X)|), \quad \text{Modified Z} = 0.6745 \times \frac{x_i - \text{median}(X)}{\text{MAD}}$$
Сначала агрегируются все строки одного клиента за день, SKU и склад. Это выявляет и раздробленный заказ на 4800 м. Разовые оптовые партии (в стресс-датасете — 8500 м от CLNT-BI-GROUP-TENDER) исключаются из регулярного прогноза, но сохраняются в журнале аномалий (Audit Trail).
4. Реконструкция упущенного спроса (Stockout Compensation)
История разворачивается в календарные дни: отсутствие строки продаж в день дефицита означает ноль наблюдаемых продаж, а не отсутствие даты. Пересекающиеся периоды объединяются логической маской, и упущенный спрос оценивается по медиане дней без дефицита без двойного счёта.
5. Сезонность и тренд
- Выявление внутринедельной и помесячной сезонности (например, пик светотехники +65% в зимние месяцы и строительный пик кабельной продукции летом).
- Оценка тренда после удаления месячной сезонной волны; прогноз зимнего пика не гасится ошибочным «падением» после прошлого зимнего сезона.
6. Учёт товаров в пути (In-Transit) и бюджета (KZT)
- Из потребности вычитаются только товары с ожидаемым прибытием не позже конца Lead Time. Поставка через год не покрывает ближайшую потребность.
- Риск дефицита до прибытия ближайшей поставки оценивается по физическому доступному остатку: входящая партия не маскирует сегодняшнюю пустую полку в индикаторе
CRITICAL. - Для каждой строки и поставщика рассчитывается общая сумма закупки в казахстанских тенге (₸ KZT).
- Стресс-данные: сторно/возврат сверяется с исходной накладной того же клиента за последние 7 дней; транзитные отгрузки напрямую клиенту исключаются из спроса склада.
- Утверждение ручной правки проверяет MOQ и Pack; статус, окончательное количество и бюджет сохраняются при пересчёте и перезапуске. При изменении исходных данных прежнее утверждение архивируется, а новая рекомендация становится черновиком.
🧪 Тестирование
Комплексный набор автоматических тестов проверяет математику и валидацию:
cd backend
pytest
Дополнительная независимая приёмка (формула, граничные случаи, API, импорт и жизненный цикл): python qa/run.py из корня репозитория. Стресс-книга и паспорт аномалий находятся в backend/data/.
Покрытие тестов (tests/test_replenishment.py):
- Влияние текущих остатков на снижение объёма закупки.
- Корректное вычитание товаров в пути (In-Transit).
- Изоляция выбросов с сохранением в реестре аудита.
- Гарантия неотрицательности рекомендаций.
- Соблюдение квантов упаковки и минимальной партии (MOQ).
- Восстановление упущенного спроса при дефиците.
- Нормализация русскоязычных заголовков партнёра (Артикул, Количество, Склад и т.д.).
- Корректный расчёт бюджета в KZT.
🔌 API Endpoints
| Метод | Путь | Описание |
|---|---|---|
GET | /health | Проверка жизнеспособности сервиса |
POST | /api/data/load-ekt | Загрузка и расчёт реального датасета ТОО «Электрокомплект» |
POST | /api/data/load-anomalies | Загрузка и расчёт датасета с экстремальными аномалиями (48 аномалий, 20 тыс. строк) |
POST | /api/data/upload-workbook | Загрузка единой 5-листовой книги Excel (.xlsx) |
POST | /api/data/upload/{dataset} | Загрузка отдельного датасета (sales, stock, transit, suppliers, stockouts) |
GET | /api/editor/{dataset} | Постраничный просмотр и поиск редактируемых данных |
POST/PATCH/DELETE | /api/editor/{dataset}/rows | Создание, изменение и удаление строки редактора |
GET/POST/DELETE | /api/editor/draft | Автосохранение и восстановление незавершённого черновика |
GET | /api/editor/{dataset}/export | Выгрузка отредактированной таблицы в CSV/XLSX |
GET | /api/inventory/catalog | Исторический каталог товаров, в том числе с нулевым остатком |
GET | /api/inventory/stock | Остатки и товары в пути по SKU и складу |
GET/POST | /api/inventory/movements | Журнал и проведение многострочного документа |
GET | /api/inventory/export | Полная актуальная книга Excel с журналом движений |
POST | /api/data/demo | Загрузка синтетического сценария |
POST | /api/recommendations/calculate | Перерасчёт с настраиваемыми параметрами (Z, threshold, safety days) |
GET | /api/recommendations | Получение списка рекомендаций с фильтрами |
GET | /api/analytics/{sku} | Детализированные точки спроса, прогноз и дефицит для графика |
GET | /api/outliers | Реестр выявленных аномалий и выбросов |
POST | /api/orders/{id}/adjust | Ручная корректировка количества закупщиком |
POST | /api/orders/{id}/approve | Утверждение заказа в производство/закупку |
GET | /api/orders/history | Архив прежних решений после изменения исходных данных |
GET | /api/orders/export | Экспорт итогового плана закупок в Excel / CSV |
Авторы коммитов
История коммитов
- 2026-09-23 17:58docs: clarify local startup and API environment overrideKlamsii
- 2026-09-23 17:54feat: add guided partner import and show data quality limitsmolya002
- 2026-09-23 17:54docs: clarify data provenance and verify noisy forecast scenariosmolya002
- 2026-09-23 17:54fix: protect inventory persistence and import original partner archivesmolya002
- 2026-09-23 17:45fix: complete dashboard localization and clarify procurement decisionsmolya002
- 2026-09-23 17:43fix: serialize inventory edits and restore empty sales datasetsKlamsii
- 2026-09-23 17:42fix: reconcile demand calendars and delivery shortage riskmolya002
- 2026-09-23 17:34fix: preserve inventory drafts and prevent mobile overflowmolya002
Все коммиты (64)
- 2026-09-23 17:34fix: preserve inventory workbook data and validate import assumptionsmolya002
- 2026-09-23 17:31fix: harden inventory imports, retries and spreadsheet exportsKlamsii
- 2026-09-23 17:29docs: record audit failures and remediation acceptance checksmolya002
- 2026-09-23 17:08Merge remote-tracking branch 'origin/main' into fix/replenishment-qaKlamsii
- 2026-09-23 17:07fix: improve inventory editor layout and price entryAmirBro0
- 2026-09-23 17:04feat: add Basqar logo to navigation and faviconKlamsii
- 2026-09-23 16:48Merge remote-tracking branch 'origin/main' into fix/replenishment-qaKlamsii
- 2026-09-23 16:47fix: move sidebar toggle to bottomAmirBro0
- 2026-09-23 16:41merge: sync classic dashboard and finalize repository setupKlamsii
- 2026-09-23 16:35merge: integrate localization with repository cleanupKlamsii
- 2026-09-23 16:31Merge pull request #6 from BAITC-Hacks/feat/classic-dashboardmolya002
- 2026-09-23 16:31merge: preserve Basqar settings and localization in classic dashboardmolya002
- 2026-09-23 16:30chore: streamline repository and add reproducible CIKlamsii
- 2026-09-23 16:22feat: add basqar localization and settingsAmirBro0
- 2026-09-23 16:18merge: sync latest inbound receipt fix from teammatemolya002
- 2026-09-23 16:18feat: adapt inventory workflows and order archive to classic dashboardmolya002
- 2026-09-23 16:16fix: consume nearest inbound first and verify purchase forecastKlamsii
- 2026-09-23 16:12merge: integrate teammate inventory operations and QA fixesmolya002
- 2026-09-23 16:12feat: implement classic responsive procurement dashboardmolya002
- 2026-09-23 15:58feat: manage purchasing sales and stock movements in data editorKlamsii
- 2026-09-23 15:45Merge remote-tracking branch 'origin/main' into fix/replenishment-qaKlamsii
- 2026-09-23 15:39Merge pull request #5 from BAITC-Hacks/docs/design-molya002molya002
- 2026-09-23 15:39merge: include latest main QA acceptance suiteKlamsii
- 2026-09-23 15:38fix: keep order approvals across editor draft reloadsKlamsii
- 2026-09-23 15:35merge: integrate persistent editor with QA fixesKlamsii
- 2026-09-23 15:35Merge pull request #4 from BAITC-Hacks/qa/acceptance-checksBakytzhan
- 2026-09-23 15:35docs: add green redesign proposal and implementation guidemolya002
- 2026-09-23 15:30feat: add persistent data editor and draftsAmirBro0
- 2026-09-23 15:27fix(ui): restore green theme and unify filter text typographyCodex
- 2026-09-23 15:26fix: harden Docker import flow and surface data freshnessKlamsii
- 2026-09-23 15:22docs(design): propose green redesign and implementation blueprintCodex
- 2026-09-23 15:18feat(ui): refresh workspace styling and add mobile order cardsCodex
- 2026-09-23 15:11merge: integrate latest workflow and supplier order constraintsKlamsii
- 2026-09-23 15:10merge: integrate latest main while preserving mobile fixesCodex
- 2026-09-23 15:06fix: archive approvals when source data changesKlamsii
- 2026-09-23 15:03Merge remote-tracking branch 'origin/main' into fix/replenishment-qaKlamsii
- 2026-09-23 15:02merge: sync teammate dataset and preserve workflow fixesAmirBro0
- 2026-09-23 15:00fix: reconcile demand, inventory timing and order approvalKlamsii
- 2026-09-23 14:59feat: improve analytics and dataset workflowsAmirBro0
- 2026-09-23 14:55fix: repair mobile navigation and anomaly layoutCodex
- 2026-09-23 14:51feat: integrate extreme anomalies dataset with API loader endpoint, UI one-click trigger and full repo data assetsKlamsii
- 2026-09-23 14:50test: audit latest main against EKT data and acceptance casesKlamsii
- 2026-09-23 14:50docs: update QA audit for main 402426aKlamsii
- 2026-09-23 14:50test: add independent replenishment acceptance suite and QA baselineKlamsii
- 2026-09-23 14:42feat(data): add extreme real-world business anomalies dataset and blueprint documentationKlamsii
- 2026-09-23 14:39docs: update QA audit for main 402426aKlamsii
- 2026-09-23 14:34test: audit latest main against EKT data and acceptance casesKlamsii
- 2026-09-23 14:33test: expand API validation coverageAmirBro0
- 2026-09-23 14:24test: cover API startup and demo flowsAmirBro0
- 2026-09-23 14:18test: add independent replenishment acceptance suite and QA baselineKlamsii
- 2026-09-23 14:15fix: harden import validation and add edge case testsAmirBro0
- 2026-09-23 14:10feat: add ekt.kz Kazakhstan partner dataset support, multi-sheet workbook parser, KZT budget tracking and monthly seasonalityKlamsii
- 2026-09-23 14:01fix: stabilize demo startup and outlier detectionAmirBro0
- 2026-09-23 13:55feat: add supplier grouping and demand signal markersAmirBro0
- 2026-09-23 13:45feat: add StockPilot replenishment MVPAmirBro0
- 2026-09-23 13:34Fix README description typotestAmir
Структура репозитория (99 файлов)
.gitignore README.md backend/.dockerignore backend/Dockerfile backend/app/__init__.py backend/app/main.py backend/app/repositories/__init__.py backend/app/repositories/database.py backend/app/schemas.py backend/app/services/__init__.py backend/app/services/demo.py backend/app/services/forecasting.py backend/app/services/inventory.py backend/app/services/replenishment.py backend/app/services/validation.py backend/data/README_ANOMALIES.md backend/data/README_DATASET.md backend/data/current_stock.csv backend/data/ekt_extreme_anomalies_sales_and_stock.xlsx backend/data/ekt_sales_and_stock_history.xlsx backend/data/generate_dataset.py backend/data/sales_extreme_anomalies.csv backend/data/sales_history.csv backend/data/stockouts.csv backend/data/suppliers.csv backend/requirements.txt backend/tests/test_api.py backend/tests/test_replenishment.py backend/tests/test_validation.py data/README_ANOMALIES.md data/README_DATASET.md data/current_stock.csv data/ekt_extreme_anomalies_sales_and_stock.xlsx data/ekt_sales_and_stock_history.xlsx data/generate_dataset.py data/sales_extreme_anomalies.csv data/sales_history.csv data/stockouts.csv data/suppliers.csv design/01_PRODUCT_AUDIT.md design/02_INFORMATION_ARCHITECTURE.md design/03_VISUAL_SYSTEM.md design/04_SCREEN_SPECIFICATIONS.md design/05_COMPONENTS.md design/06_DATA_AND_STATES.md design/07_IDEAS_AND_PRIORITIES.md design/08_IMPLEMENTATION.md design/09_ACCEPTANCE.md design/CLASSIC_DASHBOARD.md design/DECISIONS.md design/README.md design/VERIFICATION.md design/prototype/app.js design/prototype/fixtures.js design/prototype/index.html design/prototype/styles.css design/screen-manifest.json design/tokens/tokens.css design/tokens/tokens.json docker-compose.yml frontend/.dockerignore frontend/Dockerfile frontend/index.html frontend/package-lock.json frontend/package.json frontend/src/App.tsx frontend/src/DataEditor.tsx frontend/src/DataPages.tsx frontend/src/ItemDetail.tsx frontend/src/OrderHistory.tsx frontend/src/Procurement.tsx frontend/src/RawDataEditor.tsx frontend/src/Settings.tsx frontend/src/api.ts frontend/src/chart.ts frontend/src/extra.css frontend/src/i18n.tsx frontend/src/main.tsx frontend/src/styles.css frontend/src/types.ts frontend/src/ui.tsx frontend/src/vite-env.d.ts frontend/tsconfig.json frontend/tsconfig.tsbuildinfo frontend/vite.config.ts pytest.ini qa/.gitignore qa/ACCEPTANCE.md qa/BASELINE.md qa/CURRENT_MAIN_AUDIT.md qa/FIX_VERIFICATION.md qa/README.md qa/conftest.py qa/make_fixtures.py qa/run.py qa/test_acceptance_api.py qa/test_acceptance_validation.py qa/test_engine.py qa/test_inventory_operations.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.