AAYMA
RU
Войти

StockPilot

Объяснимая система пополнения складов ekt.kz: по продажам, остаткам, товарам в пути, lead time, MOQ и дефицитам рассчитывает заказы поставщикам с…

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

О проекте

Команда: 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.md

StockPilot — Explainable Warehouse Replenishment OS

Система автоматического и объяснимого расчёта заказов поставщикам для пополнения распределительных складов дистрибьютора электротехники ТОО «Электрокомплект» (ekt.kz).

StockPilot объединяет историю продаж, текущие остатки, товары в пути, логистические сроки (Lead Time), минимальные партии (MOQ), кванты упаковки и периоды дефицита (stockouts) для ответа на ключевой вопрос закупщика: что, сколько и почему нужно заказать прямо сейчас?


⚡️ Быстрый запуск в 1 команду (Docker)

docker compose up --build

🛠 Локальный запуск для разработки

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. В приложении доступны:

  1. Кнопка «⚡️ ekt.kz Dataset (Казахстан)» — мгновенная загрузка реального датасета ТОО «Электрокомплект» с расчётом заказов и бюджета в тенге (₸).
  2. Кнопка «💥 Аномалии (Stress Test)» — стресс-тест на 20 294 транзакциях с 8 реальными типами коммерческих аномалий (тендеры, скачки цен меди, ЧС/шторм, ошибки операторов 1С, дефициты, неликвиды, каннибализация).
  3. Кнопка «↻ Synthetic demo» — синтетический эталонный сценарий (105 SKU, 3 склада, 5 поставщиков).
  4. Раздел «Data Intake» — загрузка файлов по отдельности или книги Excel целиком (ekt_sales_and_stock_history.xlsx или ekt_extreme_anomalies_sales_and_stock.xlsx, 5 листов в 1 клик).
  5. Раздел «Data editor» — операции через сайт: закуп, продажа, поступление, перемещение, корректировка и возврат. Несколько товаров проводятся одним документом; вкладка «Склад» показывает все SKU из истории проекта, включая позиции с нулевым остатком. «Справочники» сохраняют точечное редактирование таблиц и SQLite-черновик.

Операции с товарами без повторного Excel

  1. Загрузите книгу через Data intake, проверьте и утвердите рекомендации.
  2. В Data editor → Закуп добавьте утверждённые рекомендации или вручную наберите несколько SKU, укажите поставщика, склад и дату поставки. Заказ записывается в журнал и увеличивает товары в пути, но ещё не физический остаток.
  3. В Движения товаров → Поступление примите фактически доставленное количество: оно уйдёт из «в пути» на склад. Частичное поступление допустимо; принять больше заказанного/импортированного количества нельзя.
  4. В Продажа пробейте несколько товаров в одном документе. Сервис проверит доступный остаток, уменьшит его и добавит продажи в историю для следующего прогноза. Аналогично доступны перемещение между складами, корректировка (+/−) и возврат.
  5. Нажмите «Скачать обновлённый 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):

  1. Влияние текущих остатков на снижение объёма закупки.
  2. Корректное вычитание товаров в пути (In-Transit).
  3. Изоляция выбросов с сохранением в реестре аудита.
  4. Гарантия неотрицательности рекомендаций.
  5. Соблюдение квантов упаковки и минимальной партии (MOQ).
  6. Восстановление упущенного спроса при дефиците.
  7. Нормализация русскоязычных заголовков партнёра (Артикул, Количество, Склад и т.д.).
  8. Корректный расчёт бюджета в 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
Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
64
Авторы
7
Файлы
99
Ветки
8

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

Klamsii 26molya002 16AmirBro0 12Codex 5Klamsii 3Bakytzhan 1Amir 1

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

  • 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
Ветки (8)

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

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

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