AAYMA
RU
Войти

Агент закупок

Сервис расчёта заказов производителям (IEK, Systeme Electric) по выгрузкам 1С с объяснением каждой цифры, AI-агентом с tool calling, утверждением…

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

О проекте

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

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

Суть

Сервис расчёта заказов производителям (IEK, Systeme Electric) по выгрузкам 1С с объяснением каждой цифры, AI-агентом с tool calling, утверждением менеджером и экспортом CSV/xlsx для 1С.

Как устроено

xlsx-выгрузки 1С (продажи, накладные, остатки, сезонность, товар в пути, MOQ) парсятся по заголовкам. Детерминированное ядро lib/core.js: исключение разовых строк накладных (порог max(Q3+3·IQR, 5×медиана…)) и фильтр Хампеля по месяцам, восстановление упущенного спроса по доступности, сезонность артикул/группа/производитель, устойчивый тренд, страховой запас z·σ·√t по ABC-классу, округление до кратности, срочность. OpenAI-агент (≤6 итераций) вызывает инструменты list_skus → analyze_sku → build_supplier_orders → review_order и пишет сводку; шаги агента видны в UI.

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

  • IQR outlier detection
  • Hampel filter (MAD)
  • ABC-классификация
  • safety stock z·σ·√t
  • сезонность в 3 уровнях
  • OpenAI tool calling
  • эксперименты чувствительности

Метрики

Демо ЩРН-24: заказ с фильтром разовых продаж 45 шт, без фильтра 202 шт, после инъекции искусственной разовой продажи — 45

Особенности

Встроенные «эксперименты из ТЗ» по каждому артикулу: как меняется заказ без фильтра разовых продаж, без учёта дефицита, без сезонности/тренда, при инъекции разовой продажи или +100 шт в пути. LLM не считает цифры, а самопроверка агента review_order помечает аномалии.

Стек

Node.js · JavaScript · HTML · CSS · Material Design 3 · xlsx

Модели

OpenAI


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

README

README.md

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

Кейс HackAlem AI от ТОО «Электрокомплект» (ekt.kz): «Автоматизация формирования заказов поставщикам».

1. Кратко: проблема и для кого

Для кого: менеджер отдела закупа дистрибьютора электротоваров. Электрокомплект закупает товар у производителей (IEK, Systeme Electric), держит склад в Алматы и продаёт своим клиентам.

Проблема: сейчас потребность в пополнении склада считается вручную в Excel. Это трудоёмко, поэтому делается редко. В итоге одни позиции лежат в избытке, других нет. Отдельная проблема: разовые крупные сделки (например, партия под один строительный объект) выглядят в истории как рост спроса и раздувают следующий заказ.

Решение: сервис по нажатию одной кнопки считает рекомендуемый заказ по каждому артикулу производителя и объясняет каждую цифру. Менеджер проверяет результат, при необходимости правит количество, утверждает заказ и получает CSV. Отправить заказ производителю сама система не может: решение всегда остаётся за человеком (так требует ТЗ).

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

  • Чтение выгрузок 1С как есть (xlsx). Используются месячные продажи, продажи по накладным, месячные остатки, сезонность, товар в пути и минимальная партия (MOQ) или кратность. Колонки распознаются по заголовкам. Строки «Итого» и возвраты отбрасываются.
  • Расчёт потребности по каждому артикулу учитывает все источники: историю продаж, остатки, товар в пути, товарную группу, сезонность, устойчивый тренд и прогноз прироста (%, вводит менеджер). Изменение любого входа меняет результат.
  • Поиск и исключение разовых крупных продаж по отдельным строкам накладных (алгоритм описан ниже).
  • Оценка упущенного спроса: за месяцы, когда товара не было на складе, продажи восстанавливаются.
  • Сезонность в трёх уровнях: артикул, товарная группа, производитель. Для уровня производителя используются коэффициенты партнёра из его файла «Сезонность».
  • Страховой запас по классу ABC, округление до кратности, срочность (высокая, средняя, низкая).
  • Обоснование в каждой строке с разложением: прогноз + страховой запас − остаток − в пути → заказ.
  • AI-агент (OpenAI, tool calling, не больше 6 итераций) сам вызывает расчётные инструменты, разбирает рисковые позиции и пишет менеджеру сводку. Каждый шаг агента виден в интерфейсе.
  • Встроенные проверки из ТЗ («эксперименты»). Для любого артикула можно посмотреть, как изменится заказ без фильтра разовых продаж, без учёта периодов отсутствия товара, без сезонности и без тренда, если добавить искусственную разовую продажу или увеличить товар в пути на 100 шт.
  • Самопроверка заказа агентом (review_order): подозрительные позиции помечаются «Проверить» с причиной.
  • Отчёт о проверке входных данных: при загрузке выводятся найденные файлы, счётчики совпадения кодов и предупреждения (не найден файл или колонка, низкое совпадение кодов) вместо молчаливых нулей.
  • Утверждение заказа и выгрузка в CSV (UTF-8 с BOM) и в xlsx с листом «Для загрузки в 1С» (Код, Количество).
  • Интерфейс в стиле Material Design 3: главное на первом экране, полный заказ раскрывается кнопкой, индикатор прогресса, окно с шагами агента.
  • Демо-режим без ключа: расчёт полностью настоящий, заранее заготовлен только текст ответа LLM.

3. Как работает: сценарий пользователя

  1. Менеджер открывает страницу, выбирает производителя, горизонт покрытия (по умолчанию 30 дней) и при желании прогноз прироста в %. Нажимает «Сформировать заказ».

  2. Пока агент работает (обычно 15–30 с с ключом, 1–2 с в демо), видна полоса прогресса с секундомером. Агент по шагам:

    • list_skus: сводка по производителю и самые рисковые позиции;
    • analyze_sku: разбирает 3–5 рисковых позиций (разовые продажи, отсутствие товара, сезон, тренд);
    • build_supplier_orders: собирает итоговый заказ по срочности;
    • review_order: проверяет готовый заказ на аномалии (заказ на много месяцев вперёд, сильное влияние фильтра разовых продаж, товар в пути больше потребности, нет заказа при нулевом остатке).
  3. Простой режим (по умолчанию):

    • «Итог»: сколько позиций, единиц и срочных, короткая сводка агента, блок «Проверьте перед утверждением»;
    • «Требует внимания»: позиции, где нужно решение менеджера, — срочные классов A и B (≈95% объёма продаж) и всё, где самопроверка агента нашла замечание; карточками, с причиной и полем количества. Срочная мелочь класса C уже включена в заказ и свёрнута в одну строку (видна по кнопке «Показать полный заказ»).

    Кнопка «Как агент пришёл к результату» открывает окно с шагами агента.

  4. Полный заказ (кнопка «Показать полный заказ» внизу блока «Требует внимания»): полная таблица заказа со всеми колонками и сводные счётчики. По клику на строку или карточку открывается разбор артикула: история за 12 месяцев, прогноз по месяцам, исключённые разовые продажи, упущенный спрос и «Проверки ТЗ».

  5. Менеджер правит количества и нажимает «Утвердить и выгрузить CSV» или «Скачать xlsx для 1С». Файл сохраняется в exports/ и скачивается. Производителю ничего не отправляется.

4. Методология расчёта

Все расчёты детерминированные и выполняются в lib/core.js. LLM цифры не считает. Так расчёт остаётся объяснимым и воспроизводимым, а агент только управляет инструментами и объясняет результат.

Дата расчёта — последняя дата продажи в выгрузке. История — 24 последних полных месяца (текущий неполный месяц в истории не участвует).

  1. Очистка от разовых продаж (см. раздел 5): получаем «очищенные» месячные продажи.
  2. Доступность товара в месяце определяется по остаткам на начало месяца и на начало следующего:
    • оба больше нуля — товар был весь месяц (1);
    • оба равны нулю — товара не было (0; если при этом были продажи — 0,5: товар приходил и продавался внутри месяца);
    • иначе — был часть месяца (0,5).
  3. Упущенный спрос. Уровень спроса оценивается по месяцам, когда товар был в наличии. Для месяцев без товара продажи достраиваются до ожидаемых с учётом сезона. Прогноз может только вырасти, но не уменьшиться.
  4. Сезонность. Берётся 50% собственной сезонности артикула (только если товар продавался ≥20 из 24 месяцев и объём ≥200 шт) и 50% сезонности его товарной группы. Если группа мала, используется сезонность производителя. Коэффициенты ограничены диапазоном [0,5; 2]. Товарная группа — колонка категории из данных (например, «Категория 2026» у Systeme Electric, «Категория» в демо), а если её нет — первое слово наименования («Светильник», «Розетка»…). Влияние категории видно в эксперименте «Без категории (сезонность производителя)».
  5. Уровень спроса — взвешенное среднее последних 6 месяцев без сезонности (свежие месяцы весят больше).
  6. Устойчивый тренд учитывается, только если и последние 6 месяцев к предыдущим 6, и последние 12 к предыдущим 12 изменились в одну сторону больше чем на 5%. Тренд ограничен ±5% в месяц. Сверху применяется прогноз прироста менеджера (growthPct).
  7. Прогноз считается по дням на период «срок поставки + покрытие» с сезонным коэффициентом каждого месяца.
  8. Страховой запас = z · σ · √(период/30), где σ — разброс месячного спроса без сезонности, z зависит от класса ABC: A = 1,65 (95%), B = 1,28 (90%), C = 0,84 (80%).
  9. Заказ = прогноз + страховой запас − текущий остаток − товар в пути. Если результат ≤ 0, заказ не нужен. Иначе он округляется вверх до кратности или MOQ.
  10. Срочность. Считается, на сколько дней хватит остатка и товара в пути:
    • высокая — товара нет или его не хватит до прихода новой поставки;
    • средняя — не хватит на период покрытия;
    • низкая — хватит. Высокая срочность понижается до средней, если остаток не известен точно (оценён как 0) или позиция нерегулярная (товара не было на складе ≥6 из 12 мес). Так «Требует внимания» не засоряется позициями, где проблема в данных, а не в запасе.

5. Алгоритм исключения разовых крупных заказов

В выгрузке нет ID клиента, поэтому «крупная продажа одному клиенту» определяется на уровне одной строки расходной накладной. Код — detectOneOffs в lib/core.js. Проверки два слоя.

Слой 1: аномальные строки накладных.

  1. Для артикула берутся строки накладных за окно анализа (24 мес). Самая крупная строка исключается из опорной выборки, чтобы подозрительная продажа не поднимала собственный порог. По остальным строкам считаются квартили Q1, Q3 и медиана, а также типичный месяц.
  2. Порог = max(5, Q3 + 3·IQR, 5 × медиана строки, min(типичный месяц, 2 × медианный месяц)).
  3. Строка считается разовой, если она выше порога или одна даёт больше 25% всего объёма остальных продаж (и при этом больше 5 шт и больше 2 медиан).
  4. Разовая строка исключается целиком: даже частичный остаток такой продажи у редких товаров искажает уровень спроса, разброс и тренд.

Слой 2: аномальные месяцы. Месячные итоги после первого слоя проверяются правилом Хампеля: если месяц больше медиана + 4·1,4826·MAD и больше 2 × медиана, он обрезается до этого порога. Так ловится ситуация, когда разовый объём разбит на несколько накладных.

В обоснование строки заказа пишется, сколько накладных и штук исключено.

Проверка из ТЗ встроена в analyze_sku.experiments.injectedOneOff: в историю добавляется искусственная разовая продажа (10 средних месяцев), и видно, что заказ не меняется. Эксперимент меняет историю одного артикула; ABC-класс и сезонность группы при этом не пересчитываются. Если добавить такую продажу прямо в исходные файлы, ABC-класс может сдвинуться и заказ вырастет на несколько штук (на демо-Щите — на единицы, а не в разы). На демо-товаре «Щит распределительный ЩРН-24» заказ с фильтром 45 шт, без фильтра 202 шт, а после добавления искусственной продажи остаётся 45. Агрегатная проверка по всем артикулам: node scripts/check-oneoff.js "Производитель А (демо)" (печатает только перцентили «утечки»; без аргумента проверяется производитель IEK — он есть только в локальных данных).

6. Технологии

  • Node.js 20+ (проверено на 24), Express 5: сервер и REST API.
  • Статический HTML, CSS и JavaScript без фреймворков: интерфейс.
  • openai SDK: модель gpt-4o-mini (задаётся через OPENAI_MODEL), Chat Completions с tool calling.
  • xlsx (SheetJS 0.18.5): чтение выгрузок 1С и генерация демо-данных.
  • dotenv: переменные окружения.

7. Архитектура

public/ (index.html, app.js, style.css)       интерфейс менеджера
        │  fetch
        ▼
server.js                                      Express API + агентный цикл
  GET  /api/suppliers   производители и источник данных (demo/local)
  POST /api/plan        агент: цикл ≤6 итераций, tool calling, trace шагов
  GET  /api/sku         разбор артикула и эксперименты
  POST /api/approve     утверждение → CSV в exports/
        │  вызов инструментов
        ▼
lib/core.js    расчётное ядро: list_skus, analyze_sku, calc_order, build_supplier_orders
lib/review.js  самопроверка готового заказа агентом: review_order
lib/export.js  выгрузка xlsx с листом «Для загрузки в 1С»
        │
        ▼
lib/data.js    парсер xlsx-выгрузок 1С → нормализованные данные (кэш в памяти)
        │
        ▼
заказчик/ (реальные данные, только локально) или data/demo/ (синтетика в репозитории)

Где работает AI, а где чистый расчёт

ЗадачаКто выполняетПочему
Все числа: прогноз, страховой запас, заказ, срочностьДетерминированное ядро lib/core.jsОбъяснимость и воспроизводимость, в числах LLM может ошибиться
Какие артикулы разобрать подробно (3–5 из сотен)LLM-агент (tool calling)Смотрит на сводку и флаги риска и сам выбирает, что важно
Сводка для менеджера: итог, ключевые позиции, что проверитьLLM-агентТекст по результатам инструментов, цифры берутся только из них
Утверждение и выгрузка заказаТолько человекТребование ТЗ

Даже если модель ошибётся в тексте, таблица заказа от этого не изменится: её строки приходят из ядра, а не из ответа модели. В инструкции агенту прямо запрещено придумывать и пересчитывать числа.

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

Нужен Node.js 20 или новее.

git clone https://github.com/BAITC-Hacks/hack-dae58240-go.git
cd hack-dae58240-go
npm install
cp .env.example .env     # можно оставить пустым: запустится демо-режим
npm run dev

Открыть http://localhost:3000.

В консоли сервер печатает, в каком режиме он запущен ([DEMO MODE] или [LIVE]) и откуда взяты данные ([DATA] ДЕМО (синтетика data/demo) или локальные данные).

После изменения .env сервер нужно перезапустить: переменные читаются только при старте.

Запуск на своих данных (выгрузки 1С)

Положите xlsx-выгрузки в папку заказчик/ в корне проекта (она в .gitignore и в репозиторий не попадает): одна подпапка на производителя, имя подпапки становится именем производителя.

заказчик/
  IEK/
    Динамика продаж_2025-2026.xlsx                 ← накладные (обязательно)
    Ежемесячные продажи в количественном ….xlsx    ← продажи по месяцам (обязательно)
    Ежемесячные остатки ….xlsx                     ← остатки на начало месяца (обязательно)
    Сезонность ….xlsx                              ← необязательно
    Путь ИЭК ….xlsx / Товар в пути ….xlsx          ← необязательно
    MOQ ….xlsx                                     ← необязательно
  Systeme electric/
    …

Файлы распознаются по словам в имени: «динамика», «продажи в кол», «остатки», «сезонность», «путь»/«пути», «moq». Колонки — по заголовкам (формат — как в data/demo/). Другую папку можно указать через DATA_DIR=/путь/к/папке. Перезапустите сервер: в консоли появится [DATA] локальные данные … и отчёт [DATA CHECK] с найденными файлами и предупреждениями, а на странице — плашка «ИСТОЧНИК: ЛОКАЛЬНЫЕ ДАННЫЕ». Если папки заказчик/ нет, используются демо-данные.

Если порт 3000 занят, запустите на другом: PORT=3001 npm run dev и откройте http://localhost:3001.

9. Переменные окружения

ПеременнаяОбязательнаНазначение
OPENAI_API_KEYнетКлюч OpenAI. Без него включается демо-режим
OPENAI_MODELнетМодель, по умолчанию gpt-4o-mini
OPENAI_BASE_URLнетАльтернативный OpenAI-совместимый адрес
PORTнетПорт, по умолчанию 3000
DATA_DIRнетПапка с данными, по умолчанию заказчик/, а если её нет — data/demo/
LEAD_TIME_IEKнетСрок поставки IEK в днях, по умолчанию 30
LEAD_TIME_DEFAULTнетСрок поставки остальных производителей, по умолчанию 45
NVIDIA_API_KEY, NVIDIA_BASE_URLнетЗарезервировано, в текущей версии не используется

Демо-режим (без ключа): расчёт, таблица, эксперименты и CSV работают полностью. Агентные шаги выполняются по фиксированному сценарию: те же инструменты и тот же формат лога. Заготовлен только итоговый текст, он помечен [DEMO], а в интерфейсе висит плашка «ДЕМО-РЕЖИМ».

С ключом: инструменты выбирает сама модель OpenAI, текст ответа генерируется, а в логе шагов видны реальные вызовы и расход токенов (usage).

10. Как проверить решение

Сценарий на демо-данных (без ключа, из чистого клона):

  1. npm install && npm run dev, открыть http://localhost:3000. В консоли — [DEMO MODE] и [DATA] ДЕМО (синтетика data/demo), на странице плашка «ИСТОЧНИК: ДЕМО-ДАННЫЕ».
  2. Выбрать «Производитель А (демо)» и нажать «Сформировать заказ». Появятся «Итог», «Требует внимания» и «Проверьте перед утверждением». Кнопка «Как агент пришёл к результату» показывает шаги агента.
  3. Нажать «Показать полный заказ» внизу блока «Требует внимания»: откроется полная таблица. Клик по строке открывает разбор артикула, внизу — таблица «Проверки ТЗ»:
    • Must have 1 (все источники): строка «Товар в пути +100» уменьшает заказ; строка «Без категории (сезонность производителя)» у «Автоматический выключатель 1P 16А» даёт другой заказ (240 против 276); изменение «Прогноз прироста, %» и повторный расчёт меняют заказ.
    • Must have 2 (сезонность): «Кабель силовой ВВГнг» — прогноз по месяцам следует сезону, «Без сезонности» даёт другое число.
    • Must have 3 (stockout): выбрать «Производитель Б (демо)» → «Розетка 2-местная с заземлением»: в истории видны месяцы без остатка и восстановленный спрос; заказ 10 шт, «Без учёта stockout» — 0 шт (прогноз 175 против 29). У производителя А та же розетка в заказ не попадает (хватает остатка), её разбор доступен через API: curl -sG localhost:3000/api/sku --data-urlencode 'supplier=Производитель А (демо)' --data-urlencode 'sku=DA0004_'.
    • Must have 4 (разовые заказы): «Щит распределительный ЩРН-24» — разовая продажа на 180 шт исключена; «Без фильтра разовых продаж» даёт заметно больший заказ, «С добавленной разовой продажей» его не меняет.
    • Must have 5 (группировка и обоснование): заказ собирается по выбранному производителю, у каждой строки есть обоснование; «Утвердить» выгружает CSV, «Скачать xlsx для 1С» — xlsx.
  4. С ключом OpenAI в .env (после перезапуска сервера) шаги выбирает сама модель, а в окне шагов виден расход токенов.

Проверено из чистого клона (23.09.2026, без ключа): npm install ставит 79 пакетов; сервер стартует в [DEMO MODE] на демо-данных. На «Производитель А (демо)» получается 8 позиций, 3479 единиц, 3 срочных, 13 шагов агента и замечание самопроверки по Щиту. Проверки Must have: Щит — 45 шт с фильтром, 202 без фильтра, 45 после добавления разовой продажи; Кабель — прогноз 2013 шт с сезонностью против 2899 без неё; Розетка (Производитель Б) — заказ 10 шт с учётом stockout против 0 без него; Автоматический выключатель — заказ 276 с сезонностью категории против 240 с сезонностью производителя; товар в пути +100 уменьшает заказ. CSV и xlsx выгружаются, некорректный запрос даёт 400.

То же через API:

curl -s localhost:3000/api/suppliers
curl -s -X POST localhost:3000/api/plan -H 'Content-Type: application/json' \
  -d '{"supplier":"Производитель А (демо)","horizonDays":30}'

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

Источники данных

Данные партнёра выданы организаторами для работы. Это выгрузки по двум производителям, IEK и Systeme Electric, по 6 файлов xlsx на каждого. Они не хранятся в репозитории (папка заказчик/ в .gitignore) и читаются только локально. В репозитории лежит синтетика того же формата (data/demo/, генератор scripts/make-demo-data.js). В неё специально заложены сценарии для проверки: сезонный товар, рост, отсутствие товара на складе, разовая крупная продажа, большой товар в пути, кратность.

ФайлПроисхождение (по структуре)Что берём в расчётЧто есть, но не используется
Динамика продажОтчёт 1С, строки расходных накладныхдата, номер и тип документа, код, количествоединица измерения, склад (только Алматы)
Ежемесячные продажиОтчёт 1Скод, наименование, количество по месяцамартикул, кратность (берём из MOQ), «Итого»
Ежемесячные остаткиОтчёт 1С, «нач. остаток»остаток на начало каждого месяцаединица измерения
СезонностьРасчёт в Excelготовая колонка партнёра «СЕЗОННОСТЬ»; иначе таблица «год × месяц»«Поправка 2026/2025», доли по годам
Товар в путиIEK: сводка заказов поставщику; SE: рабочая таблица менеджераколичества в пути; у SE — «Свободный остаток» как текущий остаток и «Категория 2026» как товарная группадаты поступления (читаются), «Зарезервировано», коэффициенты и «Запас» менеджера
MOQСправочник«Мин. разр. к отгр.» (IEK) или «Кратность» (SE)артикул поставщика

Как парсер переносит изменения в таблицах. Колонки ищутся по названию заголовка, а строка заголовка — поиском, поэтому добавленные или переставленные колонки и лишние строки сверху не мешают. Исключение — таблица «год × месяц» в файле сезонности: год читается из первой колонки, месяцы — из следующих двенадцати; если её структура другая, сезонность считается по продажам. Строки «Итого» пропускаются, отрицательные значения (возвраты) обнуляются. Файлы распознаются по ключевым словам в имени («продажи в кол», «остатки», «динамика», «сезонность», «пут», «moq»). Если переименовать заголовок или файл, данные не найдутся. Без обязательных файлов (продажи, остатки, динамика) сервер останавливается с понятной ошибкой.

Экспорт и API

После проверки менеджер утверждает проект и скачивает CSV или XLSX. В XLSX лист «Заказ» содержит пояснения, а лист «Для загрузки в 1С» — только колонки «Код» и «Количество». В типовых конфигурациях УТ 11/ERP этот лист можно использовать через загрузку табличной части из файла в документе «Заказ поставщику»; соответствие полей следует проверить в конкретной 1С.

REST API позволяет подключить другой интерфейс или учётную систему:

  • GET /api/suppliers — производители и источник данных.
  • POST /api/plan — расчёт; тело, например, {"supplier":"Производитель А (демо)","horizonDays":30,"growthPct":0}; ответ содержит orders, trace, answer.
  • GET /api/sku?supplier=...&sku=... — подробный разбор артикула.
  • POST /api/approve — утверждённые строки {supplier,orders} → CSV.
  • POST /api/approve/xlsx — те же строки, необязательные asOf, horizonDays, growthPct → XLSX с листом для 1С.

Что уходит во внешний сервис (OpenAI)

Внешний сервис один: OpenAI API, и только если задан ключ. В модель отправляются результаты инструментов, специально урезанные: сводные счётчики по производителю, 3–5 разобранных артикулов (код, наименование, прогноз, количество, флаги, текст обоснования, прогноз по месяцам, эксперименты) и итог заказа с несколькими примерами строк. Каждый ответ инструмента ограничен 4000 символами.

Не отправляются: строки накладных, номера документов, цены, полная таблица заказа. Данных о клиентах в выгрузках нет вовсе.

Персональных данных в запросах нет, но наименования товаров и объёмы — коммерческая информация дистрибьютора. Для чувствительной среды провайдер меняется без правки кода: OPENAI_BASE_URL указывает на любой OpenAI-совместимый сервис (корпоративный шлюз, провайдер без хранения данных, локальная модель). Расчёт заказа от LLM не зависит и работает без неё.

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

  • Сроков поставки в данных нет. Приняты допущения IEK = 30 дней, остальные = 45 дней; их можно переопределить через env.
  • ID клиента в данных нет, поэтому «крупная продажа одному клиенту» определяется по строке накладной.
  • Материальной ведомости 1С и цен по артикулам нет. Цена для расчёта количества не нужна.
  • Склад в данных один (Алматы). Расчёт ведётся по нему, разбивки по складам нет.
  • Остатки известны на начало месяца, поэтому доступность внутри месяца оценивается грубо (0 / 0,5 / 1). Текущий остаток у Systeme Electric берётся из колонки «Свободный остаток». У IEK он оценивается как остаток на начало месяца минус продажи месяца (поступления внутри месяца не видны).
  • Текущий остаток у IEK оценивается (остаток на начало месяца минус продажи месяца): поступления внутри месяца не видны. Если оценка дала 0, а на начало месяца товар был, позиция помечается ❔ «уточните остаток в 1С», и срочность не поднимается до высокой. Позиции, которых не было на складе 6 и более месяцев из 12, помечаются 🕓 как нерегулярные (возможно, под заказ); их срочность тоже не выше средней. Количество к заказу эти правила не меняют.
  • Даты поступления товара в пути читаются, но пока не используются: всё, что в пути, считается пришедшим до следующей поставки.
  • Товарная группа берётся из колонки категории, если она есть (Systeme Electric, демо); у IEK её нет, и группа определяется эвристически, по первому слову наименования. Сокращения («Роз.») и латиница («ВА47-29») попадают в отдельные группы или в «Прочее»; для малых групп тогда берётся сезонность производителя.
  • Фильтр разовых продаж настроен строго, в пользу защиты от раздутых заказов. У товаров, которые регулярно берут крупными партиями, он может недооценить спрос. Пороги стоит откалибровать вместе с менеджером на реальных данных.
  • Слой месячных выбросов работает без учёта сезона и у товаров с резкой сезонностью может немного срезать пиковый месяц.
  • Результаты независимой проверки на реальных данных партнёра (отдельный парсер на Python, пересчёт всех 3 017 SKU). Парсинг подтверждён: суммы месяцев = «Итого», товар в пути 109 517 = 109 517 (IEK) и 50 160 = 50 160 (SE), сезонность и кратность совпадают, формула заказа воспроизводится до штуки. Найдены ограничения методики, которые мы не успели исправить:
    • Накладные и месячные продажи в выгрузках расходятся (28% ненулевых пар «SKU × месяц» у IEK, 60% у SE), в динамике нет положительных продаж 2024 года. Исключённый объём разовых накладных вычитается из месячной таблицы, поэтому у 90 SKU IEK и 139 SKU SE отдельные месяцы обнуляются. Нужно согласовать состав двух отчётов с партнёром и очищать историю на одной базе.
    • Восстановление упущенного спроса меняет тренд и разброс, поэтому у 24 SKU IEK прогноз с поправкой оказывается чуть ниже, чем без неё (у 15 SKU заказ меньше суммарно на 232 шт). Нужна явная монотонность: поправка stockout не должна уменьшать прогноз.
    • Фильтр разовых продаж исключает и повторяющиеся крупные продажи (флаг у 60% SKU IEK и 73% SE; у 20 SKU исключённые строки встречаются в 6 и более разных месяцах). Без ID клиента регулярного оптовика не отличить от разовой сделки; такие случаи нужно оставлять в спросе или отдавать менеджеру на подтверждение.
    • Нет ограничения избыточного покрытия: у 129 SKU IEK заказ больше 6 средних месяцев продаж (у 68 из них это одна минимальная партия); у 133 заказанных SKU IEK кратность в данных не найдена и принята равной 1.
    • Пустые ячейки в остатках трактуются как 0 (отсутствие товара); по файлу нельзя доказать, что каждая пустота — именно stockout.
  • Нет авторизации, базы данных и автоматической отправки заказа (последнее запрещено ТЗ).

13. Развёрнутая версия

Не развёрнуто. Проект запускается локально по инструкции из раздела 8.

14. Потенциал развития

  1. Чат-помощник по закупкам и продажам. Данные уже разобраны, а инструменты у агента есть. Следующий шаг — диалог: «почему в этом году просели продажи розеток?», «где зоны роста?», «что будет, если IEK задержит поставку на 2 недели?». Агент отвечает, вызывая те же расчётные инструменты и добавляя аналитические: динамика по группам, топ растущих и падающих, сценарии сроков. Все числа при этом по-прежнему берутся из расчёта, а не из «памяти» модели. Чтобы анализировать выручку, а не только штуки, нужны цены по артикулам.
  2. Интеграция с 1С без ручных выгрузок.
    • Этап 1: папка-наблюдатель. Выгрузки кладутся в папку, пересчёт запускается автоматически.
    • Этап 2: прямое чтение через стандартный REST-интерфейс 1С (OData). Продажи, остатки и открытые заказы поставщику, то есть «товар в пути», берутся напрямую. Ручные таблицы (товар в пути, MOQ) исчезают. Конкретные объекты зависят от конфигурации (УТ или ERP) и уточняются с партнёром.
    • Этап 3: утверждённый заказ создаётся в 1С черновиком документа «Заказ поставщику». Отправку производителю по-прежнему делает человек.
  3. Сроки поставки и условия производителей. Справочник сроков, минимальной суммы заказа и графика отгрузок по каждому производителю вместо общего допущения. Учёт дат поступления товара в пути.
  4. Калибровка на истории. Бэктест: «что заказала бы система год назад» против фактических продаж. По нему подбираются пороги фильтра выбросов и уровни сервиса по классам ABC.
  5. Загрузка выгрузок через интерфейс. Сейчас папка с данными задаётся DATA_DIR (по умолчанию заказчик/). Следующий шаг — перетащить файлы в окно, увидеть отчёт о проверке данных и пересчитать.
  6. Агент для исправления данных. По отчёту о проверке (несовпадение кодов, пропавшая колонка, ручные правки в таблицах) агент предполагает причину и предлагает исправление, например сопоставить переименованную колонку. Применяется только после подтверждения менеджера. Для этого агенту достаточно имён файлов и колонок и счётчиков, значения ячеек не нужны.
  7. Сравнение с ручным расчётом менеджера. Показ рядом с коэффициентами и «Запасом» из рабочей таблицы, с объяснением расхождений.

Использованные заготовки и AI

  • Каркас «Express + статический фронт + демо-режим без ключа» подготовлен заранее как пустая заготовка (разрешено п. 5.4.4.2). Вся предметная логика написана во время хакатона.
  • Код писался с помощью AI-ассистентов (Claude Code, OpenAI Codex) и проверен участником.
Звёзды
⭐ 0
Форки
0
Язык
JavaScript
Последний push
в прошлом месяце
Коммиты
41
Авторы
1
Файлы
28
Ветки
1

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

Zhamso Radnaev 41

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

  • 2026-09-23 17:52README: how to run on your own 1C exports (folder layout, file naming)Zhamso Radnaev
  • 2026-09-23 17:48README: add review.js and export.js to architectureZhamso Radnaev
  • 2026-09-23 17:46README: known limitations from independent real-data verificationZhamso Radnaev
  • 2026-09-23 17:39UI: remove mode switch — full order expands via 'Показать полный заказ' button; cards clearly clickable; 10 attention cardsZhamso Radnaev
  • 2026-09-23 17:33UI: single centered column (result as main block, attention below), constant width across modes, mode switch above clockZhamso Radnaev
  • 2026-09-23 17:25Attention block: urgent A/B items and review issues only; urgent C-class collapsed into summary lineZhamso Radnaev
  • 2026-09-23 17:23Re-audit fixes: numeric/type checks for exported fields, working curl example in READMEZhamso Radnaev
  • 2026-09-23 17:15README: final demo numbers incl. category effect and stockout scenario on manufacturer B; UI label for withoutCategoryZhamso Radnaev
Все коммиты (41)
  • 2026-09-23 17:13Use source categories for seasonal demand; demo category effects A10 B8Zhamso Radnaev
  • 2026-09-23 17:12README: availability rule detail, one-off experiment scope, seasonality table layout limitZhamso Radnaev
  • 2026-09-23 17:11Audit fixes: no invoice details to LLM from calc_order, strict approval validation, JSON errors, calc date in xlsx, monthly forecast in chartZhamso Radnaev
  • 2026-09-23 17:08Urgency: don't mark high on estimated-zero stock or rarely stocked items; demo free stockZhamso Radnaev
  • 2026-09-23 17:04README: alternate port hint and clean-clone verification resultsZhamso Radnaev
  • 2026-09-23 16:50Data check: optional columns reported as info, not warningsZhamso Radnaev
  • 2026-09-23 16:46SKU panel: demand chart (actual sales, cleaned demand, forecast) with trend labelZhamso Radnaev
  • 2026-09-23 16:42UI: single launch panel with form, data source and progress; fix select arrowZhamso Radnaev
  • 2026-09-23 16:37README: final UI scenario, verification steps, review/export features, roadmapZhamso Radnaev
  • 2026-09-23 16:37UI: normal scrolling page, compact launch bar with visible parameters, date/time in headerZhamso Radnaev
  • 2026-09-23 16:31UI: progress indicator, trace in dialog, mode switch, xlsx button; light theme onlyZhamso Radnaev
  • 2026-09-23 16:19Add 1C-ready XLSX export for approved ordersZhamso Radnaev
  • 2026-09-23 16:13Theme switch: light by default, dark via compact MD3 toggle (remembered)Zhamso Radnaev
  • 2026-09-23 16:11Add agent review of supplier ordersZhamso Radnaev
  • 2026-09-23 16:10MD3: fit KPI numbers on phone widthZhamso Radnaev
  • 2026-09-23 16:10Material Design 3 theme and one-screen dashboard layoutZhamso Radnaev
  • 2026-09-23 15:59Fix direct-file preview and simple layoutZhamso Radnaev
  • 2026-09-23 15:51Keep source dates out of data warningsZhamso Radnaev
  • 2026-09-23 15:48Add simple purchase review modeZhamso Radnaev
  • 2026-09-23 15:46Report input data anomalies and parsing fallbacksZhamso Radnaev
  • 2026-09-23 15:37README: AI vs deterministic roles, data sources and parsing, what is sent to LLM, roadmapZhamso Radnaev
  • 2026-09-23 15:34Make agent trace readable for purchase reviewZhamso Radnaev
  • 2026-09-23 15:17Ignore local .claude configZhamso Radnaev
  • 2026-09-23 15:01Sharpen agent prompt, show token usage once per model turn, document new one-off algorithmZhamso Radnaev
  • 2026-09-23 14:45Expand manufacturer planning UI and SKU analysisZhamso Radnaev
  • 2026-09-23 14:42Fix one-off leakage in supplier ordersZhamso Radnaev
  • 2026-09-23 14:35Write README with methodology and outlier algorithm; extend .env.exampleZhamso Radnaev
  • 2026-09-23 14:24Add calculation core, 1C xlsx parser and synthetic demo dataZhamso Radnaev
  • 2026-09-23 14:02Keep CSV export tied to planned supplierZhamso Radnaev
  • 2026-09-23 14:00Build supplier order planning skeletonZhamso Radnaev
  • 2026-09-23 13:42Ignore partner data foldersZhamso Radnaev
  • 2026-09-23 13:27Add .env.example with empty valuesZhamso Radnaev
  • 2026-09-23 13:26Add .gitignoreZhamso Radnaev
Структура репозитория (28 файлов)
.env.example
.gitignore
README.md
data/demo/Производитель А (демо)/MOQ (демо).xlsx
data/demo/Производитель А (демо)/Динамика продаж (демо).xlsx
data/demo/Производитель А (демо)/Ежемесячные остатки (демо).xlsx
data/demo/Производитель А (демо)/Ежемесячные продажи в количественном выражении (демо).xlsx
data/demo/Производитель А (демо)/Сезонность (демо).xlsx
data/demo/Производитель А (демо)/Товар в пути (демо).xlsx
data/demo/Производитель Б (демо)/MOQ (демо).xlsx
data/demo/Производитель Б (демо)/Динамика продаж (демо).xlsx
data/demo/Производитель Б (демо)/Ежемесячные остатки (демо).xlsx
data/demo/Производитель Б (демо)/Ежемесячные продажи в количественном выражении (демо).xlsx
data/demo/Производитель Б (демо)/Сезонность (демо).xlsx
data/demo/Производитель Б (демо)/Товар в пути (демо).xlsx
lib/core.js
lib/data.js
lib/export.js
lib/review.js
package-lock.json
package.json
public/app.js
public/index.html
public/md3.css
public/style.css
scripts/check-oneoff.js
scripts/make-demo-data.js
server.js

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

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

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