Хакатон MAX 2026 · трек «Эффективный бизнес» · техническое задание для команды

Накладная в кармане

Как за оставшиеся восемь дней собрать в MAX продукт, который ведёт электронную транспортную накладную от отгрузки в учётной системе до подписанного четвёртого титула, что в нём будет настоящим, что моделью, и что понадобится после хакатона, чтобы закрыть задачу целиком.

Составлено 22 сентября 2026 Сдача 30 сентября, осталось 8 дней Опирается на разбор «ЭПД с 1 сентября» и Задание трека
3 роли
диспетчер отправителя, водитель, грузополучатель — те, где цепочка рвётся по опросам
Т1 → Т4
четыре обязательных титула, QR после второго, таймеры на каждом состоянии
живое
учётная система по API, бот и мини-приложение MAX, подпись в «Госключе» через бота MAX
модель
оператор ЭПД и ГИС ЭПД: эмулятор в Docker с настоящими именами титулов и статусов
6 сервисов
bot, api, web, erp-emulator, epd-emulator, postgres — одна команда compose

1. Что значит «закрыть задачу полностью»

Полностью означает три уровня, и честная презентация показывает все три: что работает на хакатоне, что включается на пилоте и что требует внешних сторон. Жюри ставит баллы за первый уровень и за понимание двух других.

ВозможностьMVP хакатона, 30.09Пилот на заводеПродакшен
Отгрузка из учётной системыживое: МойСклад по API или 1С по OData, плюс эмуляторERP клиента через тот же адаптерлюбая система через интерфейс адаптера
Черновик Т1 и проверка реквизитовживое: правила формата ФНС, ИНН, масса, адреса, доверенностьто жеполная XSD-валидация формата ЕД-7-26/1065@
Передача титулов оператору и ГИС ЭПДмодель: эмулятор операторатестовый контур Диадока или песочница Астрала по договорубоевой оператор из реестра Минтранса, роуминг
Подпись Т1 от организациимодель: заглушка подписи в эмулятореУКЭП компании через криптопровайдер или облачную подпись оператора, МЧД диспетчерато же плюс ротация сертификатов
Подпись водителя и получателяживое: PDF титула подписывается в «Госключе» через бота MAX; в эмуляторе фиксируется фактПЭП или УНЭП «Госключа» в приложении оператора по app-to-appинтеграция с «Госключом» по API ЕПГУ и СМЭВ, 2–4 недели с Минцифры
QR-код для проверки на дорогемодель: картинка с идентификатором эмуляторанастоящий QR из приложения оператораQR из ГИС ЭПД через API оператора (событие с вложением QR)
Уведомления, эскалации, сводкаживоеживоеживое
«Нужна ли накладная»живое: дерево по разъяснениям Минтранса и приказу № 262то жеобновление при выходе новых приказов
Правило честности из Задания: всё из колонки «модель» называется модельной интеграцией в README, на слайде архитектуры и в самом интерфейсе (плашка «демо-оператор»). Это не минус, а условие допуска: ограничение 10 и принцип «не имитируйте интеграции».

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

Клиент MAX Чат с ботом кнопки, deep link, контакт Мини-приложение React + MAX UI, Bridge Бот «Госключ» @goskey_bot Бэкенд «Накладной» Bot Gateway webhook, callbacks, /answers API мини-приложения initData HMAC, роли Ядро: перевозка и титулы Т1–Т8 машина состояний · маршрутизатор событий · таймеры эскалаций очередь исходящих с повтором · журнал Адаптер учётной системы МойСклад · 1С OData · эмулятор Адаптер оператора ЭПД эмулятор · Диадок · Такском PostgreSQL: компании, пользователи, перевозки, титулы, события Учётная система отгрузки, контрагенты, товары Оператор ЭПД → ГИС ЭПД в MVP: эмулятор в Docker «Госключ» (Минцифры) УНЭП / УКЭП физлица webhook + секрет initData, HMAC-SHA256 API, вебхуки Т1–Т4, статусы, QR PDF титула на подпись, подписанный файл обратно
Клиент MAX говорит только с нашим бэкендом. Ядро одно, а всё внешнее спрятано за двумя адаптерами: учётная система живая, оператор ЭПД в MVP эмулируется (пунктир). Подпись водителя и получателя идёт через бота «Госключ» в самом MAX.
СтекTypeScript везде: бот и API на Node 20 (Fastify), общий пакет типов, мини-приложение на React + Vite + @maxhub/max-ui, PostgreSQL 16, миграции через Drizzle или Prisma. Официальный TypeScript SDK бота из репозитория max-messenger/max-bot-api-client-ts. Никаких закрытых библиотек, все зависимости с версиями в package-lock.
РазвёртываниеЛокально: docker compose up поднимает шесть сервисов. Наружу нужен HTTPS: бот получает вебхуки, мини-приложение открывается по URL. На хакатон свой сервер с nginx или Caddy, домен вида epd.<домен>, сертификат Let's Encrypt. Держать до 14 октября.
ГраницыЯдро не знает ни про MAX, ни про МойСклад: события входят через интерфейсы, кнопки и тексты собираются в Bot Gateway. Это и есть «ядро / переменная часть» из Задания, и это же ответ на критерий архитектуры.

3. Сквозной сценарий по ролям

  1. Диспетчер подключает компанию. В мини-приложении вводит токен учётной системы (или выбирает эмулятор), бот проверяет доступ и подписывает вебхук на отгрузки. Сотрудники привязываются по номеру телефона: кнопка request_contact присылает вложение с полем hash, по нему сервер сверяет, что контакт настоящий.
  2. Отгрузка становится накладной. Вебхук «отгрузка создана» превращается в черновик Т1. Ядро проверяет реквизиты: ИНН сторон, масса и места, адреса погрузки и выгрузки, водитель и машина, срок доверенности подписанта. Диспетчер получает карточку с кнопками «Отправить перевозчику», «Исправить», «Не нужна накладная» (с объяснением, почему).
  3. Водитель получает рейс. Deep link https://max.ru/<бот>?start=trip_<id> приходит в bot_started.payload, или диспетчер выбирает водителя из привязанных. Карточка: адреса, груз, контакт получателя, чек-лист «QR должен быть до выезда». Кнопка «Погружено» с request_geo_location (параметр quick) фиксирует точку и время, эмулятор оператора формирует Т2 и QR.
  4. Подпись водителя. Бот присылает PDF титула и кнопку «Подписать в Госключе»: openMaxLink ведёт в @goskey_bot, водитель отправляет файл, выбирает УНЭП, получает подписанный файл и пересылает его нашему боту. Бот принимает вложение file, сохраняет и переводит титул в «подписан водителем». В продакшене этот шаг делает приложение оператора по app-to-app.
  5. Получатель без своего ЭДО. Перевозчик жмёт «Отправить получателю ссылку», получатель открывает карточку груза, отмечает «принято без расхождений» или «расхождения» с фото, подписывает Т3 тем же путём через «Госключ».
  6. Ничего не зависает. Таймеры ядра: Т2 без QR дольше 20 минут после «погружено», Т3 не подписан 24 часа после выгрузки, доверенность диспетчера истекает через 5 дней, оператор недоступен и очередь копится. Каждый таймер шлёт сообщение ответственному с кнопкой действия. Мини-приложение показывает перевозки по состояниям и сводку «висит на нас / на перевозчике / на получателе».

4. Модель данных и состояния

Черновик реквизиты проверены Т1 подписан отправитель Т2 подписан есть QR В пути Т7/Т8 возможны Т3 подписан получатель Т4 подписан перевозчик Закрыта Отказ в Т2 завершает оборот Т7 / Т8 адрес, водитель, ТС ⏱ Т1 не отправлен 2 ч до погрузки ⏱ нет QR 20 мин после «погружено» ⏱ Т3 не подписан 24 ч ⏱ Т4 не подписан 24 ч Каждое состояние хранит: кто подписал, когда, каким видом подписи, идентификатор оператора и mt-id ГИС ЭПД, ссылку на файл титула
Машина состояний перевозки. Зелёное состояние — точка, после которой водителю можно ехать. Оранжевые таймеры — эскалации, которые бот шлёт ответственному. Пунктир — ветки, которые в MVP показываются как сценарии эмулятора.
ТаблицаКлючевые поля
companyid, name, inn, kpp, role (shipper | carrier | consignee), erp_kind (moysklad | odata | emulator), erp_credentials (шифрованы), epd_operator_kind (emulator | diadoc | taxcom)
personid, company_id, max_user_id, phone (из вложения контакта), role (dispatcher | driver | receiver | admin), poa_valid_to (срок МЧД), driver_license
shipmentid, company_id, erp_id, erp_number, consignee_inn, loading_address, unloading_address, planned_at, cargo (места, масса брутто, описание), vehicle (регномер, тип), driver_id, raw (снимок из ERP)
waybillid, shipment_id, number, date, state (см. схему), operator_doc_id, mt_id, qr_file_id, kl_id, requires_waybill (результат дерева), reason
titleid, waybill_id, index (1–8), function (reception | delivery | cost | readdress | relay), status (draft | sent | signed | rejected), signed_by_person_id, signature_kind (ukep | unep_goskey | pep | emulated), signed_at, xml_file_id, signed_file_id, file_name (ON_TRNACLGROT_… по формату)
eventid, waybill_id, kind, payload, source (erp | operator | bot | timer), created_at — журнал, из него строится лента в мини-приложении
outboxid, target (max | erp | operator), payload, attempts, next_try_at, last_error — очередь с экспоненциальным повтором, идемпотентность по ключу события
timerid, waybill_id, kind, fire_at, fired — эскалации; обрабатывает воркер раз в минуту

5. Интеграции по одной

5.1 Учётная система

Интерфейс адаптера один для всех: listShipments(since), getShipment(id), subscribe(webhookUrl), writeBack(shipmentId, status, comment). Три реализации.

5.2 Оператор ЭПД

Интерфейс адаптера повторяет реальный поток титулов, чтобы боевой оператор потом встал без переделки ядра: createT1(waybill) → operatorDocId, signAndSend(titleIndex, xml, signature), onDriverConfirm(cb), getStatus(docId) → {stage, mt_id, qr}, reject(docId, reason), readdress(T7), relay(T8).

Эмулятор в MVPХранит титулы в своей базе, принимает XML, выдаёт статусы sent → signed → registered, генерирует mt-id и PNG с QR, в котором закодирован идентификатор. Имена файлов по формату ФНС: ON_TRNACLGROT_… для Т1, ON_TRNACLPPRIN_… для Т2, ON_TRNACLGRPO_… для Т3, ON_TRNACLPVYN_… для Т4. Ручки «хаоса»: задержка QR на N минут, недоступность на M минут, отказ в Т2, чтобы показать обработку ошибок.
Диадок API, следующий шагТестовый контур diadoc-api-test.kontur.ru, два тестовых ящика через форму easyregistration, ключ разработчика и тестовый сертификат по заявке. Методы: GenerateTitleXml с documentTypeNamedId=LogisticsWaybill, функции reception, delivery, cost, версия kl_trn_mt_05_01; PostMessage V3 для Т1 и Т3, PostMessagePatch V4 для Т2, Т4, Т6; событие KlDriverConfirm с данными водителя; QR через GetEntityContent V4 по вложению с NamedId = QR; поиск по mt-id в SearchDocflows. Отказ: GenerateSignatureRejectionXml.
Такском и АстралТакском: документация Web API открыта (invoice.taxcom.ru/DevHelp), идентификатор интегратора выдаёт оператор, есть 1С-ЭПД. Астрал: бесплатная песочница с тестовыми подписями, но по договору, внедрение до четырёх недель, оплата только с промышленной эксплуатации. Для хакатона ни один не успеет, для пилота подходят оба.

Черновик Т1 собирается из тех же блоков, что и в реальном API: сведения о грузоотправителе и грузополучателе, груз с массой и упаковкой, перевозчик, водитель с номером удостоверения и телефоном, машина с регномером, время и адрес погрузки, указания отправителя, подписант. Полная проверка по XSD формата ЕД-7-26/1065@ в MVP заменяется проверкой обязательных полей, XSD берётся из документации оператора на пилоте.

5.3 Подписи

5.4 Платформа MAX

МеханикаКак используемЧто в API
Вебхук ботаВсе обновления в Bot Gateway; секрет в заголовке проверяется на каждом запросеPOST /subscriptions с url, secret (заголовок X-Max-Bot-Api-Secret), update_types; long polling GET /updates только для отладки
КнопкиДействия по накладной, привязка телефона, точка погрузки, открытие мини-приложенияcallback с payload; request_contact; request_geo_location с quick; open_app с web_app (публичное имя бота) и payload до 512 символов; link; до 7 кнопок в ряду
Ответ на нажатиеМеняем сообщение на «согласовано» или шлём одноразовое уведомлениеmessage_callback с callback_id, payload, user; POST /answers с message или notification
Deep linkРейс водителю, ссылка получателю?start= приходит в bot_started.payload до 512 символов; ?startapp= для мини-приложения, читается из initDataUnsafe.start_param
Контакт и геолокацияПроверяем подлинность контакта, фиксируем точку погрузкивложение контакта с hash и max_info; вложение location с latitude, longitude
ФайлыQR-картинка и PDF титула в чатPOST /uploads типов image и file, токен вложения в POST /messages
Мини-приложениеСписки перевозок, карточка, настройки; авторизация без логинаBridge window.WebApp, проверка initData на сервере; requestContact, openMaxLink на @goskey_bot, BackButton, platform для fallback в web

Проверка initData по официальной документации: секрет равен HMAC-SHA256("WebAppData", BOT_TOKEN), из строки запуска исключается hash, значения декодируются, пары сортируются по имени и склеиваются через перевод строки, подпись HMAC-SHA256(secret, строка) сравнивается с hash в hex, а auth_date в секундах Unix проверяется на свежесть. Строку передаём в заголовке X-Max-Init-Data при каждом запросе к API.

const secret = hmacSha256("WebAppData", BOT_TOKEN);
const params = new URLSearchParams(initData);
const hash = params.get("hash"); params.delete("hash");
const line = [...params.entries()].sort(([a],[b]) => a.localeCompare(b))
  .map(([k, v]) => `${k}=${decodeURIComponent(v)}`).join("\n");
const ok = timingSafeEqual(hex(hmacSha256(secret, line)), hash)
  && now() - Number(params.get("auth_date")) < 24 * 3600;
Веб-версия MAX обязательна по Заданию. В web нет шаринга, биометрии и части хранилищ, а «Госключ» это мобильное приложение. Поэтому на десктопе шаг подписи показывает QR-код со ссылкой на бота «Госключ» и инструкцию «подпишите с телефона», а статус обновляется, когда файл пришёл. Никаких тупиков: любой экран в web имеет продолжение.

6. Что проверит жюри и как это воспроизвести

# compose.yaml (скелет)
services:
  postgres:     { image: postgres:16, env_file: .env, volumes: [pg:/var/lib/postgresql/data] }
  api:          { build: ./apps/api, env_file: .env, depends_on: [postgres], ports: ["8080:8080"] }
  bot:          { build: ./apps/bot, env_file: .env, depends_on: [api] }
  web:          { build: ./apps/web, ports: ["8081:80"] }           # статика мини-приложения
  erp-emulator: { build: ./apps/erp-emulator, ports: ["8090:8090"] }
  epd-emulator: { build: ./apps/epd-emulator, ports: ["8091:8091"] }
volumes: { pg: {} }

# .env.example
MAX_BOT_TOKEN=            # выдаёт организатор, в репозиторий не класть
MAX_WEBHOOK_SECRET=       # 5–256 символов, проверяется в заголовке
PUBLIC_BASE_URL=https://epd.example.ru
DATABASE_URL=postgres://app:app@postgres:5432/app
ERP_KIND=emulator         # emulator | moysklad | odata
MOYSKLAD_TOKEN=
ODATA_URL= ODATA_USER= ODATA_PASSWORD=
EPD_OPERATOR=emulator     # emulator | diadoc
ENCRYPTION_KEY=           # для токенов учётных систем в базе

README по двенадцати пунктам Задания: назначение; основной сценарий; состав и архитектура (схема из раздела 2); одна команда запуска; параметры и переменные окружения; порты 8080, 8081, 8090, 8091; зависимости; внешние сервисы (MAX, учётная система, эмуляторы); работа с данными и тестовыми данными; пошаговая проверка с ожидаемым поведением; известные ограничения (эмулятор оператора, заглушка УКЭП, без проверки подписи «Госключа»); остановка и перезапуск.

7. План на восемь дней

Три роли в команде: интеграции и ядро (два человека), бот и мини-приложение, продукт и документация. Заморозка функционала вечером 27 сентября.

8. Риски и запасные пути

9. Что нужно после хакатона, чтобы закрыть задачу целиком

  1. Договор с оператором из реестра Минтранса, доступ к тестовому контуру и ключи API; на пилот подходят Диадок, Такском или Астрал.
  2. УКЭП организации для подписи Т1 на сервере или в облаке оператора, МЧД диспетчера в реестре ФНС, контроль сроков сертификатов и доверенностей в самом продукте.
  3. Подпись водителя через приложение оператора по app-to-app с «Госключом» или собственная интеграция с «Госключом» через API ЕПГУ и СМЭВ.
  4. Бот и мини-приложение под верифицированным юрлицом в «MAX для бизнеса»: по правилам хакатона доступ к боту после финала остаётся у ООО «МАХ».
  5. Полная XSD-валидация форматов ФНС, титулы Т5–Т8, электронный путевой лист, роуминг между операторами, действия при недоступности ГИС по постановлению № 931.
  6. Хранение подписанных титулов пять лет, выгрузка архива перевозки, журнал доступа.
  7. Пилот на заводе: метрики «доля рейсов с QR до выезда», «время от отгрузки до QR», «время подписи Т3», «число рейсов с простоем на воротах», сравнение с сентябрём 2026.

Источники

  1. Диадок API: работа с ЭТрН (developer.kontur.ru/doc/diadoc-api/instructions/documents/formal/waybill.html)
  2. Диадок API: документооборот ЭТрН, титулы и имена файлов (docflows/waybill.html)
  3. Диадок API: быстрый старт, тестовый контур, тестовые ящики (howtostart/quickstart.html)
  4. Такском: ЭПД и Web API (taxcom.ru/epd, invoice.taxcom.ru/DevHelp)
  5. Астрал: ЭПД через API, песочница по договору (astral.ru/aj)
  6. MAX для разработчиков: проверка initData (dev.max.ru/docs/webapps/validation)
  7. MAX Bot API, схема OpenAPI (github.com/max-messenger/api-schema): кнопки, callback, /answers, /subscriptions, /uploads
  8. MAX: подключение мини-приложения и Bridge (dev.max.ru/docs/webapps)
  9. MAX UI (dev.max.ru/ui, npm @maxhub/max-ui)
  10. Бот «Госключ» в MAX: лимиты 20 файлов и 100 МБ (help.max.ru, maxofficial.ru)
  11. Интеграция с «Госключом» для организаций (info.gosuslugi.ru, ЕСКС)
  12. МойСклад JSON API 1.2: отгрузки и вебхуки (dev.moysklad.ru)
  13. 1С: стандартный интерфейс OData (1cfresh.com, infostart.ru)
  14. Формат ЭТрН, приказ ФНС ЕД-7-26/1065@ (consultant.ru, nalog.gov.ru)
  15. Минтранс: ответы на вопросы участников грузоперевозок; приказ № 262 от 01.06.2026 (через Астрал.Журнал)
  16. Задание трека «Эффективный бизнес» и правила хакатона, ред. 21.08.2026