О проекте
Команда: 27
Кейс HackAlem.ai: Логистика — Электрокомплект · ekt.kz
Стадия: Доведённый продукт
Задача кейса: Автоматический расчёт заказов поставщикам для пополнения склада, с утверждением менеджером
Суть
Streamlit-сервис для менеджера закупа: рассчитывает объяснимые рекомендуемые заказы поставщикам IEK и Systeme Electric по Excel-выгрузкам, допускает правки менеджера и выдаёт CSV только после утверждения.
Как устроено
Importer читает шесть типов Excel-источников в Pydantic-модели. Детерминированный engine учитывает продажи, сезонность, рост, свободный остаток, поставки в пути, компенсацию stockout, исключение разовых крупных покупок (в том числе нескольких накладных одного клиента), MOQ и кратность. Review привязывает утверждение к отпечатку версии и сбрасывает его при любых изменениях. Опциональный AI через собственный ограниченный цикл вызовов инструментов (get_dataset_status, calculate_replenishment, prepare_order) только поясняет позицию и готовит проект заказа; утверждать или отправлять заказ он не может. Backtest оценивает ошибку прогноза продаж.
Ключевые решения
- детерминированный расчёт пополнения
- исключение выбросов/разовых продаж
- компенсация stockout
- MOQ/кратность
- function calling
- backtest прогноза продаж
Метрики
180 тестов passed; синтетические сценарии: заказ 85 шт. (контроль), 135 со stockout, 140 с сезонностью/ростом, 45 с учётом остатка и пути
Особенности
Воспроизводимые синтетические сценарии для жюри с ожидаемыми числами. Утверждение привязано к отпечатку версии заказа. AI отделён от числовой рекомендации и не может утверждать. Данные партнёра во внешний API не отправляются.
Стек
Python · Streamlit · pandas · openpyxl · Pydantic · pytest · OpenAI SDK
Модели
gpt-6-luna · gpt-6-sol (OpenAI, через конфиг)
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-b9ec458c-27.
README
README.mdЗаказ без разовых всплесков
Сервис для менеджера закупа: показывает рекомендуемый заказ и объясняет каждое количество исходными продажами, сезонностью, ростом, остатком и поставками в пути. Кейс ТОО «Электрокомплект», HackAlem AI. Поддерживаются выданные форматы IEK и Systeme Electric. Интерфейс и весь расчёт работают локально без API-ключа.
Менеджер сейчас объединяет продажи, остатки и поставки вручную в Excel. Решение автоматизирует подготовку объяснимого проекта закупки, оставляя проверку человеку. Автоматической отправки заказа поставщику нет.
Что реализовано
- Чтение шести типов локальных Excel-источников по каждому поставщику с валидацией.
- Расчёт по продажам, сезонности, росту, свободному запасу и датам поставок в пути.
- Отбор по поставщикам/категориям и отдельные периоды закупки по категориям.
- Компенсация stockout при заданных днях отсутствия и консервативное исключение разовых крупных покупок, включая несколько накладных одного обезличенного клиента.
- Минимальные партии, кратность, срочность, обоснования и ссылки на ячейки источников.
- Корректировки менеджера и CSV только после утверждения текущей версии заказа.
- Отдельные учебные сценарии, историческая оценка ошибки прогноза и опциональный AI.
Неполные данные не превращаются в числовой заказ. В таблицах закупки показаны только положительные рекомендации, расчёты остальных корректных позиций доступны для просмотра. Наличие механизма не означает полный охват реальных данных — ограничения указаны ниже.
Основной пользовательский сценарий
- Выбрать учебный сценарий или загрузить локальные файлы партнёра.
- Указать дату, период закупки и срок поставки; при необходимости задать фильтры и подтверждённые уточнения менеджера.
- Нажать «Рассчитать рекомендации»: получить рассчитанные позиции и отдельно заблокированные строки с причинами.
- Проверить заказ по поставщику, открыть слагаемые и источники, при необходимости изменить количество и указать причину.
- Утвердить текущую версию и скачать CSV. Изменение входов/количества или отклонение снимает утверждение; поставщику ничего не отправляется.
Технологии
| Компонент | Технология |
|---|---|
| Расчёт и приложение | Python; основной текущий прогон на Python 3.14.7 / Windows |
| Интерфейс | Streamlit 1.64.0 |
| Таблицы и чтение XLSX | pandas 3.0.6, openpyxl 3.1.5 |
| Контракты и валидация | Pydantic 2.13.5 |
| Тесты | pytest 9.1.1, Streamlit AppTest |
| Опциональный AI-адаптер | openai 3.19.0, Chat Completions и function calling |
Версии закреплены в requirements.txt и requirements-agent.txt. Для ядра не нужны Node.js, внешний API, отдельная база данных, векторное хранилище или отдельный HTTP backend.
Архитектура
Excel → importer → Dataset / Pydantic → engine → Calculation
Учебный генератор ────────┘ ↓
Streamlit: проверка и правки
↓
Утверждение версии → review → CSV
app.py— интерфейс, состояние сессии и явные действия менеджера.replenishment/importer.py,models.py— загрузка данных и контракты.engine.py— детерминированные вычисления;review.py— проверка количеств, привязка утверждения к отпечатку версии и экспорт.demo.py,scenarios.py— полностью синтетические данные;backtest.py— историческая оценка прогноза продаж, не создание исторических заказов.agent.py,agent_client.py— необязательное пояснение и проект одной позиции через инструментыget_dataset_status,calculate_replenishment,prepare_order.
AI не становится источником числовой рекомендации и не имеет инструмента утверждения или отправки заказа. Реализован собственный ограниченный цикл вызовов инструментов, не OpenAI Agents SDK. Состояние UI хранится в Streamlit-сессии, не в постоянной БД.
Установка и запуск
Нужны Git и Python; основной текущий прогон проверен на Python 3.14.7 / Windows. Учебные данные создаются кодом, поэтому для первого запуска не требуются закрытые Excel и API-ключ. В PowerShell:
git clone https://github.com/BAITC-Hacks/hack-b9ec458c-27.git
cd hack-b9ec458c-27
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe -m streamlit run app.py
Если проект уже скачан, перейдите в его корень и начните с создания окружения.
Активация .venv не нужна: команды вызывают её Python напрямую.
На Linux/macOS создайте окружение через python3 -m venv .venv и используйте
.venv/bin/python вместо .venv\Scripts\python.exe. Эти команды приведены для
соответствующей ОС; зафиксированный тестовый прогон выполнен на Windows.
Откройте адрес, указанный Streamlit (обычно http://localhost:8501).
Переменные окружения и Node.js для этого MVP не нужны. .env.example не содержит ключей.
Необязательное подключение AI
В Streamlit подключён опциональный AI-помощник по одной выбранной позиции. После
расчёта введите вопрос и нажмите «Запросить AI-пояснение». Checkbox подготовки заказа
создаёт отдельный проект, который требует собственного Approve; Reject снимает его.
Для адаптера установите .\.venv\Scripts\python.exe -m pip install -r requirements-agent.txt и задайте
AGENT_API_KEY в окружении запуска. Простое пояснение использует gpt-6-luna;
«Усиленное AI-пояснение» и подготовка проекта используют gpt-6-sol. Модели можно
переопределить через AGENT_MODEL_LIGHT и AGENT_MODEL_COMPLEX (старый AGENT_MODEL
задаёт одну для обеих задач). Это значения конфигурации проекта, а не гарантия
доступности моделей в любом аккаунте.
AGENT_BASE_URL по умолчанию — https://api.openai.com/v1. .env автоматически
не читается. Без ключа сохраняется локальный расчёт, AI сообщает model_unavailable.
Переменные задаются в окружении процесса, из которого запускается Streamlit.
После их изменения перезапустите приложение. Не вставляйте ключ в исходники.
Пример скрытого ввода для PowerShell с поддержкой -MaskInput:
$env:AGENT_API_KEY = Read-Host "API-ключ" -MaskInput
.\.venv\Scripts\python.exe -m streamlit run app.py
Список безопасных переменных — .env.example. В других оболочках
задайте их средствами локального окружения. Внешний API может тарифицировать запросы;
для проверки основного сценария он не нужен.
AI в текущем UI доступен только для SYNTHETIC/TRAINING; данные партнёра во внешний API
не отправляются. Контракт: docs/AGENT_CONTRACT.md.
Живой запрос через Streamlit проверен на SYNTHETIC/TRAINING: пояснение и
подготовка проекта до pending_approval. Approve открывает CSV, Reject снимает
утверждение. Автотесты дополнительно проверяют ошибки и повторные запуски.
Запускайте Streamlit из корня репозитория: .streamlit/config.toml скрывает встроенную
выгрузку таблиц. Утверждённый CSV доступен через кнопки приложения после Approve.
Это управление экспортом в интерфейсе; показанные данные остаются доступны пользователю.
Как проверить решение
Повторяемый сценарий для жюри
- Оставьте режим «Учебный пример» → «Базовый пример», явно помеченный SYNTHETIC/TRAINING.
- Дата 22.09.2026, демонстрационные параметры: 30 дней между закупками, 7 дней поставки.
- Нажмите «Рассчитать рекомендации». У пары REGULAR/SPIKE одинаковая потребность: заказ 85 шт., у SPIKE отдельно видна исключённая разовая часть в 5 000 единиц. Для этих чисел отдельные правила категорий должны быть выключены.
- Раскройте историю и слагаемые. Отредактируйте количество при необходимости (соблюдая кратность), укажите причину и утвердите текущую версию.
- Скачайте CSV. Изменение параметров, данных или количества снимает утверждение. «Отклонить» также снимает его. Сервис ничего не отправляет поставщику.
Другие сценарии выбираются в поле «Учебный сценарий». При тех же параметрах:
| Сценарий | Что сравнить |
|---|---|
| Один клиент — несколько накладных | Один синтетический ID, три накладные в один день; разовая часть 5000 исключена, заказ 85, как у контроля |
| Несколько разовых клиентов | Два разных синтетических клиента; исключено 8000, заказ 85, как у контроля |
| Сценарные периоды stockout | У DEMO-SCENARIO отсутствие 06–15 июля и 05–19 августа 2026; заказ 135 против 85 без компенсации |
| Сезонность и устойчивый рост | Учебный сезонный пик ×1,5 осенью и рост продаж 2026 года на 50%; заказ 140 против контрольных 85 |
| Изменение остатка и товаров в пути | Дополнительные 20 шт. запаса и 20 шт. поставки уменьшают заказ с 85 до 45 |
Все ID, продажи и периоды этих сценариев полностью синтетические. Они не добавляются
в данные партнёра. Смена сценария сбрасывает старый расчёт, ручные уточнения и утверждение.
В «Период закупки по категориям» можно явно задать отдельный горизонт: для DEMO
вместо 30 выбрать 60 дней, пересчитать и сравнить количество. Это правило менеджера,
не выдуманная расшифровка категории: в базовом примере получится 175 вместо 85 шт.
CSV отражает фактически использованный период.
Подробный маршрут демонстрации: docs/DEMO.md.
CLI для независимой проверки без браузера:
.\.venv\Scripts\python.exe -m replenishment --demo --as-of 2026-09-22 --horizon 30 --lead 7
.\.venv\Scripts\python.exe -m pytest -v
.\.venv\Scripts\python.exe -m pip check
Проверка перед публикацией README и текущего кода: 180 passed in 50.59s. Тесты проверяют импорт, расчёт, сценарии, UI, ошибки, approval и повторные запуски; проверки AI с подменённой моделью не требуют ключа. Фактические прогоны и границы проверок описаны в docs/STATUS.md.
Данные и интеграции
В режиме «Файлы партнёра» укажите две локальные папки с выданными Excel, по одной на строку, и нажмите «Загрузить источники». Они читаются без изменений. При первоначальном чтении больших файлов возможна задержка; далее используется кэш, который инвалидируется при изменении пути, размера или времени изменения файла. Кнопка расчёта требует явно введённых горизонта и срока новой поставки — их нет в справочнике поставщика.
.\.venv\Scripts\python.exe -m replenishment --source "ПУТЬ_К_IEK" --source "ПУТЬ_К_SYSTEME" --as-of 2026-09-22 --horizon 30 --lead 7
В этом примере 30 и 7 — сценарные параметры, а не подтверждённые условия компании. Исходные файлы не включены в Git из соображений приватности. Жюри может проверить алгоритм на автономном учебном примере или подключить выданные данные.
| Источник | Использование |
|---|---|
| Ежемесячные продажи | Основной ряд по коду 1С; пропуски сохраняются, итоги не суммируются повторно |
| Динамика продаж | Накладные и signed-количества, поиск аномалий, сверка месячных итогов |
| Ежемесячные остатки | Исторические наблюдения и предупреждения о возможном stockout |
| Сезонность | 12 переданных коэффициентов из колонки «СЕЗОННОСТЬ» |
| Товары в пути | Количества и сроки; в Systeme Electric также свободный остаток, категории, годовой прирост |
| MOQ | Минимум отгрузки IEK; кратность Systeme Electric |
Дополнительные листы с повторной сезонностью в книге продаж/поставок не складываются
с отдельным источником сезонности. Они не являются независимыми продажами.
Пустая ячейка количества внутри перечисленной поставки остаётся неизвестной, а не
нулём: для такого товара полнота поступлений не подтверждена и заказ блокируется.
Отсутствие товара в источнике поставок также оставляет transit_known=False.
В «Подтверждённых уточнениях менеджера» товар выбирается по названию, поставщику,
артикулу и коду. Если есть исторический остаток, показываются его дата и источник.
Остаток IEK на начало сентября не является текущим свободным остатком 22 сентября
и не подставляется в расчёт автоматически; поставки от 22.09 не делают его актуальным.
Выходной CSV: разделитель ;, кодировка UTF-8 BOM, поставщик, код 1С, артикул,
единица, количество, срочность, обоснование, источники и параметры расчёта.
Прямого подключения к 1С нет, импорт в конкретную конфигурацию не проверен.
Зафиксированный прогон реальных файлов на срезе 22.09.2026: 12 источников, 3909 объединённых позиций; 378 рассчитаны, 3459 требуют данных, 72 содержат ошибки. 23 рассчитанные позиции имеют положительное количество. Это охват, не доказанная точность закупки на всём ассортименте. Исходные файлы не изменялись.
Методика
- Точный ключ — поставщик + строковый код 1С. Не удаляем ведущие нули.
- По умолчанию анализируются 12 завершённых месяцев. Неполный текущий месяц
исключается; пустые месяцы не превращаются в нули. При недостатке истории —
needs_data. - Положительные операции группируются по анонимному клиенту/дню, если ID передан; иначе по товару/дню. Номер накладной не является ID клиента. Данные заказов покупателей не складываются с накладными.
- Кандидат во всплески должен превышать оба порога:
4 × медианаимедиана + 6 × MAD, где MAD — медиана абсолютных отклонений. Эти два параметра доступны в интерфейсе. Нужны минимум 6 обычных событий и минимум 3 обычных на каждое крупное. Исключение допускается, если у клиента только одно крупное событие в окне, а соседние наблюдаемые дни содержат обычные продажи. Это позволяет обработать несколько разных разовых клиентов. Повторные крупные покупки одного клиента, соседние высокие дни и несколько всплесков при неполных клиентских ID сохраняются для проверки менеджером. Вычитается превышение над обычной медианой, сама продажа остаётся в истории. Это консервативная эвристика, не доказательство намерения покупателя или полной устойчивости ко всем аномалиям. - Исключение возможно, только если подписанные количества накладных за месяц совпадают с месячным итогом. При расхождении автоматическое исключение блокируется. Возвраты сохраняют знак; отрицательный чистый месячный спрос ограничивается нулём с предупреждением, а не преобразуется в положительные продажи.
- Снимаем сезонность, получаем дневную потребность. При подтверждённых днях stockout делим продажи на дни наличия. Для полного stockout используем медиану наблюдаемых очищенных дневных значений других месяцев. При отсутствии опоры расчёт блокируется.
- База — среднее очищенных, скорректированных дневных значений последних трёх известных завершённых месяцев. Если это старые месяцы, интерфейс предупреждает.
- Устойчивый годовой тренд — медиана трёх отношений к соответствующим месяцам прошлого года, только если все три направлены одинаково. Переданный коэффициент роста Systeme Electric имеет приоритет как явно обозначенная входная оценка; ручной сценарий менеджера — выше него. Поправки не перемножаются между собой. Если исключён всплеск, исходный коэффициент пересчитывается по очищенной истории: иначе выброс мог бы вернуться в прогноз через коэффициент роста.
- На каждый день
срок поставки + период между закупкамиприменяем сезонность календарного месяца и годовой рост в степенидни/365. Рост не накладывается сразу за целый год на короткий горизонт. max(0, прогноз − свободный запас − поступления внутри горизонта), затем минимум партии и округление вверх по кратности. Для штук сохраняется целочисленность. Поздние и просроченные поступления не выдаются за доступный товар.
Срочность определяется первой датой отрицательного прогнозного баланса, а не баллами. Если дефицит наступает раньше новой поставки, требуется отдельное решение менеджера. Категории используются для отбора и явно заданных в интерфейсе периодов закупки; коммерческая расшифровка кодов не выдумывается. Без отдельного правила действует общий период. Смена правила снимает прежнее утверждение.
Историческая проверка прогноза
В раскрываемом блоке «Проверка прогноза на прошлых месяцах» выберите товар и нажмите «Проверить прогноз». Запуск только по кнопке, повторная отрисовка не пересчитывает отчёт. Показываются прогноз и наблюдаемые продажи за три завершённых месяца, MAE в единицах товара и WAPE; рядом — простое сравнение с дневным темпом предыдущего месяца. Нулевая сумма продаж делает WAPE неопределённой, а не равной нулю.
Полный read-only прогон по всем позициям:
.\.venv\Scripts\python.exe -m replenishment.backtest --demo --as-of 2026-09-22 --months 3
.\.venv\Scripts\python.exe -m replenishment.backtest --source "ПУТЬ_К_IEK" --source "ПУТЬ_К_SYSTEME" --as-of 2026-09-22 --months 3
Для каждого месяца доступны только предшествующие продажи и операции. Текущие остатки, поставки, рост и сезонные коэффициенты из файлов не используются как исторические факты. Сезонность оценивается по 24 последовательным предыдущим месяцам; при нехватке истории явно используется нейтральный коэффициент 1. Пропуски, отрицательные месяцы и ошибки отражаются в охвате, не превращаются в нули. Метрики считаются отдельно по каждой позиции; штуки и метры не складываются в общую ошибку.
Это оценка продаж, включая непредсказуемые разовые покупки, а не доказательство точности регулярного спроса, компенсации stockout или экономии. Даты появления и исправления исходных файлов неизвестны: полного архива на прошлые даты у нас нет. Проверка использует расчётную часть движка с нейтральным запасом только для получения прогноза; она не создаёт и не утверждает исторические заказы.
Зафиксированная оценка реальных продаж июня–августа 2026: 865 товаров, 2284 проверенных товаро-месяца и 9443 пропуска с причинами. Медиана per-SKU WAPE — 62,57%, у простого сравнения — 64,26%; MAE лучше у 465 товаров и хуже у 400. Ошибка остаётся высокой. Эти показатели не означают качество скрытого спроса при stockout или гарантированную экономию. Для воспроизведения нужен тот же набор файлов.
Ограничения текущей версии
- В реальных детализациях нет клиентского ID. Анализ одного клиента проверен на подписанных синтетических тестах; на реальных файлах доступен уровень товара/дня с источниками-накладными, не идентификация клиента.
- Нулевой начальный/месячный остаток не задаёт длительность stockout. Автоматической численной компенсации по такому признаку нет. Ввести подтверждённые дни можно в «Уточнениях менеджера». Это ограничение реального Must have №3, не полный PASS.
- Для IEK не найден актуальный свободный остаток. Исторический срез не подменяет его: нужен подтверждённый ввод на дату расчёта. При этом все его источники читаются.
- Данные склада и общие остатки имеют разную детализацию. MVP рассчитывает общий переданный контур с фильтром категории, не придумывая распределение по складам.
- Значения «бухты/метры», дубликаты кодов, нечисловые значения, отсутствующие даты и неизвестные единицы требуют уточнения; конкретные позиции блокируются.
- Excel читается по сохранённым значениям формул. Сам Excel не пересчитывается; при отсутствии сохранённого результата поле остаётся неизвестным.
- CSV — проверяемый табличный экспорт. Импорт в конкретную конфигурацию 1С не проверен.
- Нет авторизации сотрудников, постоянного журнала утверждений и многопользовательской БД. Состояние текущего проекта заказа хранится в Streamlit-сессии.
- AI в текущем интерфейсе работает только с учебными данными. Свободный текст модели может ошибаться; отсутствие ключа/API отключает пояснение, но не локальный расчёт.
- Добавлена историческая оценка ошибки прогноза наблюдаемых продаж; её результаты и охват не равны точности заказа или доказанной экономии. Экономический эффект не измерен. Тесты проверяют свойства алгоритма, а не обещают снижение затрат.
Доступ к приложению
Публичная deployed-версия в репозитории не указана. Проверяемый способ запуска —
локальный Streamlit по инструкции выше. http://localhost:8501 — адрес на вашем
компьютере, а не публичная ссылка. Производственное развёртывание не заявляется.
Репозиторий: BAITC-Hacks/hack-b9ec458c-27.
Авторы коммитов
История коммитов
- 2026-09-23 17:56Complete hackathon scenarios, validation, and jury READMETemirlan
- 2026-09-23 16:56Record integrated UI and live agent verificationTemirlan
- 2026-09-23 16:52Merge final UI review with data validationTemirlan
- 2026-09-23 16:35Show complete calculation overview at top of demo UIAuen
- 2026-09-23 16:31Verify replenishment inputs and group supplier CSV outputTemirlan
- 2026-09-23 16:23Document final task split and verified partner data runTemirlan
- 2026-09-23 15:55Report safe API status on agent failureTemirlan
- 2026-09-23 15:41Allow enhanced explanations to use stronger modelTemirlan
Все коммиты (20)
- 2026-09-23 15:39Route simple explanations and order drafts to task-sized modelsTemirlan
- 2026-09-23 15:32Clarify agent approval status in StreamlitTemirlan
- 2026-09-23 15:04Fix integration docs and hide built-in draft CSV downloadsTemirlan
- 2026-09-23 14:53Connect agent backend to Streamlit with separate approvalAuen
- 2026-09-23 14:48Merge remote-tracking branch 'origin/agent-integration' into codex/interface-demoAuen
- 2026-09-23 14:35Add bounded replenishment agent backend and integration contractTemirlan
- 2026-09-23 14:29Improve replenishment demo interfaceAuen
- 2026-09-23 14:18Clarify agent roadmap and developer ownershipTemirlan
- 2026-09-23 14:10Add two-developer handoff and work planTemirlan
- 2026-09-23 14:06Remove team workflow section from READMETemirlan
- 2026-09-23 14:04Merge remote-tracking branch 'origin/main'Temirlan
- 2026-09-23 14:04Implement explainable replenishment MVPTemirlan
Структура репозитория (39 файлов)
.agents/skills/logistics-hackathon-mvp/SKILL.md .agents/skills/logistics-hackathon-mvp/agents/openai.yaml .agents/skills/logistics-hackathon-mvp/references/case-analysis.md .agents/skills/logistics-hackathon-mvp/references/implementation.md .agents/skills/logistics-hackathon-mvp/references/logistics-patterns.md .env.example .gitignore .streamlit/config.toml README.md app.py docs/AGENT_CONTRACT.md docs/ARCHITECTURE.md docs/CONTRACT.md docs/DEMO.md docs/DEV_A_TASK.md docs/DEV_B_TASK.md docs/HANDOFF_DEV_B_CODEX.md docs/STATUS.md replenishment/__init__.py replenishment/__main__.py replenishment/agent.py replenishment/agent_client.py replenishment/backtest.py replenishment/demo.py replenishment/engine.py replenishment/importer.py replenishment/models.py replenishment/review.py replenishment/scenarios.py requirements-agent.txt requirements.txt tests/test_agent.py tests/test_agent_client.py tests/test_app.py tests/test_backtest.py tests/test_engine.py tests/test_importer.py tests/test_review.py tests/test_scenarios.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.