AAYMA
RU
Войти

ЕКТ — автозаказ поставщикам

Локальный детерминированный расчёт рекомендаций закупки у Systeme Electric и ИЭК по выгрузкам 1С.

Рабочий MVP
Команда «SAQ» 9💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: SAQ
Кейс HackAlem.ai: Логистика — Электрокомплект · ekt.kz
Стадия: Рабочий MVP

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

Суть

Локальный детерминированный расчёт рекомендаций закупки у Systeme Electric и ИЭК по выгрузкам 1С. Результат — CSV с количеством, причиной и срочностью, который утверждает менеджер.

Как устроено

Адаптеры se.py и iek.py приводят xlsx из 1С к общей схеме через безопасный загрузчик (data_only, defusedxml). Далее движок: отрицательные продажи обрезаются, разовые всплески исключаются (строка от 10 медиан SKU, если паттерн не регулярный), спрос в месяцы stockout восстанавливается по медиане. Затем строятся сезонные коэффициенты SKU и прогноз на горизонт «срок поставки + 14 дней», добавляется страховой запас по ABC и разбросу, вычитаются остаток и поставки с ETA в горизонте. Итог округляется по MOQ и кратности, к нему прилагаются коды причин и waterfall.

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

  • исключение разовых заказов по медианному правилу
  • восстановление спроса при stockout
  • сезонные коэффициенты
  • страховой запас по ABC
  • округление по MOQ и кратности
  • waterfall-объяснение
  • объяснения на ru/kk

Метрики

На реальных выгрузках: 3 909 строк CSV (724 SE, 3 185 ИЭК), из них 892 SKU без истории с нулём и причиной

Особенности

Подробная формула замены выброса «нормальным» объёмом того же календарного месяца, а не простое ограничение. Приёмочные тесты MH1–MH5 на синтетике проверяют направление реакции, сезонность, stockout, разовые заказы и то, что сумма waterfall равна итоговому количеству. Генеративный AI в расчёте не участвует.

Стек

Python 3.12 · openpyxl · defusedxml · pytest · uv


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

README

README.md

ЕКТ — автозаказ поставщикам

Локальный расчёт рекомендаций по закупкам для ТОО «Электрокомплект»: сколько заказать у Systeme Electric и ИЭК и почему. Проект подготовлен для кейса HACKALEM AI. Его пользователь — менеджер по закупкам, которому нужно свести выгрузки из 1С, учесть дефицит товара и не принять разовую крупную покупку за обычный спрос. Результат — CSV для проверки человеком, не автоматическая отправка заказа.

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

  • Адаптеры se.py и iek.py загружают Excel-выгрузки двух поставщиков в общую схему: помесячные продажи, строки расходных накладных, начальные и текущие остатки, товар в пути, номенклатуру и условия минимального заказа/кратности. Файлы читаются через защищённый загрузчик xlsx_safe.py с data_only=True; коды 1С сохраняются строками.
  • Детерминированный движок очищает отрицательные месячные продажи, исключает подтверждённые разовые всплески, восстанавливает спрос в месяцы отсутствия товара, строит помесячный сезонный прогноз и рассчитывает пополнение с учётом срока поставки, страхового запаса, остатков, поставок с ETA и кратности.
  • Для рассчитанных позиций доступны коды причин, краткое объяснение на русском и казахском и числовой waterfall; приёмочный тест проверяет, что сумма его шагов равна рекомендованному количеству.
  • run_real.py обрабатывает обоих поставщиков и записывает var/recommendations_real.csv. В консоли он показывает только агрегаты, без значений продаж и остатков. Позиции без истории не исчезают: они попадают в CSV с нулевым количеством и явной причиной.
  • Есть синтетический набор данных и пять приёмочных тестов MH1–MH5: реакция на входные факторы, сезонность, восстановление спроса, разовые заказы и объяснения.

Как работает решение

Локальные xlsx из 1С → адаптеры SE/ИЭК → канонические таблицы
    → очистка → разовые заказы → stockout → сезонный прогноз
    → политика пополнения → объяснения → CSV → проверка закупщиком

Менеджер размещает выгрузки в локальном каталоге, проверяет сводку загрузки командой load, затем запускает расчёт. Программа выводит число SKU, рекомендаций, исключённых разовых строк и позиций с восстановленным спросом по каждому поставщику. CSV содержит sku, supplier, qty_recommended, reason, urgency; подробный waterfall доступен в результате движка, но в текущий CSV не экспортируется. Заказ утверждает и передаёт поставщику сотрудник.

Методология расчёта потребности

Реализация находится в src/ekt_engine/; параметры — в src/ekt_core/config.py. Для каждого SKU расчёт идёт так:

  1. Отрицательные значения месячных продаж заменяются нулём с кодом причины RETURNS_CLIPPED. Подтверждённый избыток разовых покупок вычитается из истории (алгоритм ниже).
  2. Месяцы с нулевым начальным и следующим остатком и низкими продажами считаются полным stockout; переход через нулевой остаток — частичным. Спрос в такие месяцы восстанавливается по медиане доступных месяцев или корректируется на долю доступности с верхним ограничением.
  3. По очищенной истории вычисляются сезонные коэффициенты SKU. Средний десезонализированный спрос за последние 12 месяцев умножается на коэффициент будущего месяца; при переданном growth_overrides применяется дополнительный множитель прироста. Автоматический тренд текущая реализация не оценивает.
  4. Прогноз суммируется по дням горизонта срок поставки + 14 дней пересмотра: по умолчанию 45 дней для SE и 24 дня для ИЭК. К нему добавляется страховой запас по ABC-категории и разбросу истории. Вычитаются доступный остаток и только те поставки, чья ETA попадает в горизонт.
  5. Отрицательная потребность ограничивается нулём; положительная округляется с учётом минимального заказа и кратности. Движок добавляет к результату коды причин и шаги waterfall, включая округление.

До округления базовая формула выглядит так:

Q_raw = max(0, спрос на горизонте + страховой запас
               − доступный остаток − поставки с ETA в горизонте)

Генеративный ИИ в вычислении количества не участвует.

Как исключаются разовые крупные заказы

Алгоритм src/ekt_engine/oneoff.py рассматривает только положительные строки расходных накладных. Строка становится кандидатом, если её количество не меньше 10 медиан положительных строк этого SKU. Если крупные строки встречаются не менее чем в четырёх разных месяцах или один обезличенный клиент покупал в трёх месяцах, паттерн считается регулярным и не исключается.

Для месяца с разовой строкой берётся медиана продаж в нормальных месяцах того же календарного месяца, то есть без строк-кандидатов; если таких месяцев нет — медиана всех нормальных месяцев SKU. Если нормальных месяцев нет вообще, исключения нет. Кандидат заменяется обычным объёмом, который ещё не покрыт другими строками месяца:

replacement = min(candidate_total, max(0, baseline − regular_lines_total))
excess      = candidate_total − replacement
removed     = min(excess, max(0, monthly_sales − (transaction_total − excess)))
clean_month = max(0, monthly_sales − removed)

Последняя граница не позволяет вычесть из месячного отчёта объём, которого в нём нет. Причины ONE_OFF_EXCLUDED, ONE_OFF_NOT_IN_MONTHLY и REGULAR_LARGE_BUYER различают исключённый избыток, расхождение с месячным отчётом и повторяющиеся крупные покупки. Это именно замена выброса нормальным уровнем, а не ограничение строки величиной 3 × median.

Технологии и архитектура

Python 3.12+, openpyxl и defusedxml для чтения xlsx, стандартные библиотеки Python для расчёта и CSV, pytest для тестов; зависимости перечислены в pyproject.toml. Пакеты разделены по ролям: ekt_adapters читает и нормализует выгрузки, ekt_core задаёт параметры и синтетические данные, ekt_engine рассчитывает рекомендации и экспортирует их. Текущему запуску не нужны облачные API, внешние сервисы или AI-модель. Веб-интерфейс и API в этой версии отсутствуют.

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

Нужны Python 3.12+ и uv. Из корня репозитория:

uv sync --extra test
uv run python -m pytest tests/acceptance -q

Для локальных выгрузок партнёра передайте каталог как аргумент (данные не входят в репозиторий):

uv run python -m ekt_adapters.cli load "C:\ekt-data"
uv run python -m ekt_adapters.run_real "C:\ekt-data"

Вторая команда создаёт var/recommendations_real.csv; путь можно изменить через --output. Не публикуйте этот файл: var/ и табличные выгрузки исключены из Git.

Как проверить решение без данных партнёра

Приёмочные тесты используют только ekt_core.synthetic.make_dataset, поэтому жюри может повторить проверку без закрытых xlsx:

uv run python -m pytest tests/acceptance -q
uv run python -m pytest -q

Тест MH1 меняет остаток, поставку, историю, категорию, прирост и срок поставки и проверяет направление изменения рекомендации. MH2 проверяет сезонность, MH3 — восстановление спроса, MH4 — исключение одиночной строки при сохранении повторяющихся крупных покупок, MH5 — объяснение и равенство суммы waterfall итоговому количеству. Тест адаптера отдельно проверяет, что SKU без истории остаётся в CSV с нулём и причиной NO_HISTORY_INSUFFICIENT_DATA.

В выполненном локальном прогоне на выгрузках двух поставщиков получено 3 909 строк CSV: 724 SE и 3 185 ИЭК. Это число включает все загруженные SKU, в том числе 892 без истории; сами выгрузки и итоговый CSV не публикуются.

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

Вход — локальные xlsx-выгрузки из 1С для Systeme Electric и ИЭК: месячные отчёты продаж и остатков, расходные накладные, MOQ/кратность и рабочие таблицы с товаром в пути. Поддерживаемые структуры описаны в SPEC_DATA.md. Загрузчик не исполняет формулы и отклоняет колонки с сырыми идентификаторами клиентов. Для ИЭК текущий остаток оценивается по начальному остатку месяца и последующим продажам; это не отдельный фактический снимок. Выход — локальный CSV, не прямой обмен с 1С.

Известные ограничения

  • 892 SKU без истории продаж за 32 месяца получают qty_recommended=0 и reason=NO_HISTORY_INSUFFICIENT_DATA вместо расчёта: cold start не реализован.
  • Автоматический тренд, модели прерывистого спроса, отдельная интеграция сезонных файлов и расширенная политика из SPEC_ENGINE.md ещё не реализованы. Ручной прирост поддерживается движком как входной параметр, но текущие адаптеры его не загружают.
  • Доступный CSV содержит краткое объяснение, но не полный waterfall, коды причин и единицы закупки. Перевод между единицами продажи и закупки в текущих адаптерах не настроен по данным партнёра.
  • Оценка текущего остатка ИЭК и восстановление спроса зависят от качества исходных выгрузок. Автоматического утверждения, отправки заказа и импорта результата в 1С нет.
  • Публичной deployed-версии нет: решение запускается локально.

Дополнительные материалы: спецификация данных, спецификация алгоритма, проверка исходных данных.

Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
11
Авторы
3
Файлы
53
Ветки
3

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

Твоё Имя 5Codex 4Raiymbek 2

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

  • 2026-09-23 17:50feat: add local read-only CSV recommendations viewerCodex
  • 2026-09-23 17:43docs: final READMEТвоё Имя
  • 2026-09-23 17:34fix: cold-start SKUs now explicit in output (NO_HISTORY_INSUFFICIENT_DATA)Твоё Имя
  • 2026-09-23 17:31Merge remote-tracking branch 'origin/friend/data-readme'Твоё Имя
  • 2026-09-23 17:29docs: replace root planning README with project overviewCodex
  • 2026-09-23 17:18Merge remote-tracking branch 'origin/friend/data-readme'Твоё Имя
  • 2026-09-23 17:15fix: handle #N/A and numeric-cell codes in IEK adapterТвоё Имя
  • 2026-09-23 17:13docs: reflect Python CSV scope and add submission textCodex
Все коммиты (11)
  • 2026-09-23 17:03docs: fill data check with verified numbersCodex
  • 2026-09-23 15:46docs: add full spec package from teammate (A/B/C plan, contracts, specs)Raiymbek
  • 2026-09-23 15:13chore: init project structureRaiymbek
Структура репозитория (53 файлов)
.gitignore
AGENTS.md
README.md
contracts/api.ts
contracts/examples/lines.json
contracts/examples/run_summary.json
contracts/examples/sku_detail.json
docs/DATA_CHECK.md
docs/DEMO.md
docs/PLAN.md
docs/QUESTIONS.md
docs/README_DRAFT.md
docs/SPEC_AI.md
docs/SPEC_API.md
docs/SPEC_DATA.md
docs/SPEC_ENGINE.md
docs/SPEC_SECURITY.md
docs/SPEC_UI.md
docs/SUBMISSION_TEXT.md
docs/TASKS.md
docs/readme_prompt.md
docs/task.md
pyproject.toml
src/ekt_adapters/__init__.py
src/ekt_adapters/_common.py
src/ekt_adapters/cli.py
src/ekt_adapters/iek.py
src/ekt_adapters/run_real.py
src/ekt_adapters/se.py
src/ekt_adapters/xlsx_safe.py
src/ekt_core/__init__.py
src/ekt_core/config.py
src/ekt_core/synthetic.py
src/ekt_engine/__init__.py
src/ekt_engine/cleaning.py
src/ekt_engine/explain.py
src/ekt_engine/export.py
src/ekt_engine/forecast.py
src/ekt_engine/oneoff.py
src/ekt_engine/pipeline.py
src/ekt_engine/policy.py
src/ekt_engine/seasonality.py
src/ekt_engine/stockout.py
tests/acceptance/test_mh1_sources.py
tests/acceptance/test_mh2_seasonality.py
tests/acceptance/test_mh3_stockout.py
tests/acceptance/test_mh4_oneoff.py
tests/acceptance/test_mh5_explain.py
tests/adapters/test_loaders.py
tests/adapters/test_parsing.py
tests/adapters/test_run_real.py
tests/adapters/test_xlsx_safe.py
web/AGENTS.md
Ветки (3)

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

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

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