ЕКТ — автозаказ поставщикам
Локальный детерминированный расчёт рекомендаций закупки у Systeme Electric и ИЭК по выгрузкам 1С.
О проекте
Команда: 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 расчёт идёт так:
- Отрицательные значения месячных продаж заменяются нулём с кодом причины
RETURNS_CLIPPED. Подтверждённый избыток разовых покупок вычитается из истории (алгоритм ниже). - Месяцы с нулевым начальным и следующим остатком и низкими продажами считаются полным stockout; переход через нулевой остаток — частичным. Спрос в такие месяцы восстанавливается по медиане доступных месяцев или корректируется на долю доступности с верхним ограничением.
- По очищенной истории вычисляются сезонные коэффициенты SKU. Средний десезонализированный спрос за последние 12 месяцев умножается на коэффициент будущего месяца; при переданном
growth_overridesприменяется дополнительный множитель прироста. Автоматический тренд текущая реализация не оценивает. - Прогноз суммируется по дням горизонта
срок поставки + 14 дней пересмотра: по умолчанию 45 дней для SE и 24 дня для ИЭК. К нему добавляется страховой запас по ABC-категории и разбросу истории. Вычитаются доступный остаток и только те поставки, чья ETA попадает в горизонт. - Отрицательная потребность ограничивается нулём; положительная округляется с учётом минимального заказа и кратности. Движок добавляет к результату коды причин и шаги
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-версии нет: решение запускается локально.
Дополнительные материалы: спецификация данных, спецификация алгоритма, проверка исходных данных.
Авторы коммитов
История коммитов
- 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
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.