Представьте: вы проводите бизнес-тренинг для нефтяной компании. 30 человек за шестью столами, каждый стол — это «компания». Перед ними — стопки распечатанных таблиц, калькуляторы и ведущий, который вручную считает результаты аукционов, проверяет бюджеты и объявляет инциденты.
Аукцион на шесть участков с шестью командами — это десятки ставок, которые нужно фиксировать на доске. Расчёт строительства — это Excel-файл, в который ведущий вбивает данные от каждой команды, пересчитывает формулы и возвращает результаты. Один раунд игры занимает полтора часа. Из них минут сорок — чистая механика: подсчёт, сверка, объявление.
Команды скучают. Ведущий ошибается от усталости. Если поменять параметры игры — переделывать все таблицы. Если команд стало восемь — формулы ломаются.
Мы решили: всю эту механику можно отдать компьютеру. Пусть машина считает бюджеты, проводит аукционы, генерирует инциденты и показывает результаты в реальном времени. А люди занимаются тем, ради чего пришли на тренинг — принимают решения и учатся на последствиях.
Масштаб задачи: пять фаз, каждая со своей механикой
Когда мы начинали в октябре 2025, казалось — сделаем простую веб-форму для ставок и Excel для расчётов. Через неделю стало ясно: игра устроена сложнее, чем кажется.
Фаза 1 — Аукцион земли. Команды торгуются за нефтяные участки. Ставки в реальном времени, блокировка бюджета, автоматическое определение победителей.
Фаза 2 — Тендер подрядчиков. Выбор подрядчиков из каталога с расчётом цены, мощности и рисков по нелинейным кривым. Восемь видов работ, у каждого — свои параметры.
Фаза 3 — Строительство. Назначение подрядчиков на объекты, расчёт сложности с учётом индекса разработки участка, генерация инцидентов по распределению Пуассона.
Фаза 4 — Добыча. Эксплуатация месторождений с расчётом объёмов добычи, затрат на энергию, инцидентов. Три режима работы: бережный, нормальный, агрессивный.
Фаза 5 — Безопасность. Выбор мер КБП (культуры безопасного поведения) с каталогом из десятков позиций, восемью категориями и расчётом эффективности.
За пять месяцев мы написали 349 коммитов, 117 файлов Python на бэкенде, 220 компонентов TypeScript на фронтенде, 112 миграций базы данных и более 130 000 строк кода.
Архитектура: почему именно так
Backend — Python, FastAPI, SQLAlchemy. FastAPI даёт асинхронность из коробки (критично для WebSocket и аукционов), автодокументацию API и встроенную валидацию через Pydantic. SQLAlchemy 2.0 с типизированными моделями — потому что 112 миграций за пять месяцев, и без строгой схемы БД можно утонуть.
Frontend — React, TypeScript, Vite. React 19 с lazy loading — потому что у нас 21 страница админки, 6 фаз игры и десятки модальных окон. Без code splitting всё это грузилось бы мучительно долго.
Никаких CSS-фреймворков. Чистый CSS с custom properties. Тёмная тема с фирменным синим. Для игры с нефтяной тематикой стандартный Material Design выглядит неуместно. Нам нужен был свой визуальный язык — строгий, с ощущением «пульта управления».
Никакого Redux. useState/useEffect, без глобального стейт-менеджера. Каждая фаза изолирована, данные загружаются при входе на экран, обновляются через WebSocket.
Ключевое архитектурное решение — сервисный слой. Роутеры тонкие: принимают запрос, вызывают сервис, возвращают ответ. Вся бизнес-логика — в 22 сервисных файлах.
Один процесс (WORKERS=1) вместо масштабирования. WebSocket хранит подключения в памяти — для 30–50 одновременных пользователей одного процесса достаточно, а сложность с Redis Pub/Sub для координации воркеров не нужна.
Аукционы в реальном времени
Аукцион — сердце первой фазы. Шесть команд одновременно делают ставки на участки. Каждая ставка должна мгновенно отображаться у всех участников.
Блокировка бюджета. Когда команда ставит 50 млн на участок, эти деньги блокируются мгновенно. Иначе команда может одновременно поставить на три участка по 50 млн, имея всего 100 млн. Решение — budget_blocked: при каждой ставке бюджет блокируется, при перебивании — разблокируется. Всё в одной транзакции.
Дублирование ставок. Пользователь нажимает кнопку дважды — уходят два одинаковых запроса. Решение — idempotency keys: каждая ставка имеет уникальный ключ, повторный запрос с тем же ключом игнорируется.
Для фоновых задач — отдельный планировщик аукционов на asyncio. Он автоматически закрывает торги по таймеру, определяет победителей и рассылает уведомления. Redis используется для distributed locks — чтобы при перезапуске сервера планировщик не создал дублирующие задачи.
Карта участков: MapLibre вместо Google Maps
Нефтяные участки расположены по всей России. Нам нужна была интерактивная карта с маркерами, цветовой индикацией статусов и логотипами компаний. Google Maps — платный. Mapbox — тоже. Мы выбрали MapLibre GL — open-source форк Mapbox GL, работающий с бесплатными тайлами.
Неочевидная проблема — загрузка тайлов. На тренинге 30 планшетов одновременно запрашивают карту. Решение — двухуровневый фолбэк. Сначала отрисовываем участки на простом фоне (мгновенно), затем в фоне пытаемся загрузить красивую карту. Если не получилось — пользователь всё равно видит все данные.
Кроме того, мы добавили GZIP-сжатие для всех данных. Геоданные участков — это сотни килобайт JSON. После сжатия — на 60–90% меньше трафика.
Формулы и балансировка: математика за кулисами
Самая трудоёмкая часть — не интерфейс и не инфраструктура, а формулы. За каждым числом в игре стоит расчёт, который должен быть реалистичным, но не слишком сложным для понимания.
Индекс разработки (IR) — один параметр, который влияет на всё: сложность строительства, мощность добычи, стоимость и риски. Один ползунок — и поведение участка меняется кардинально. Это гораздо удобнее, чем настраивать десять параметров по отдельности.
Инциденты генерируются по распределению Пуассона. Вероятность зависит от режима эксплуатации и уровня КБП компании. Компания с высоким КБП и бережным режимом почти не сталкивается с авариями. Компания с низким КБП и агрессивной добычей — получает инциденты каждый раунд. Это не рандом — это обучающая механика.
Сигналы безопасности — предупреждения перед инцидентами. Система генерирует «тревожные звоночки», на которые компания может среагировать или проигнорировать. Если проигнорировали — вероятность серьёзного инцидента возрастает.
Все формулы документированы в файле на 1 330 строк. Без этого каждое изменение баланса превращалось бы в археологические раскопки по коду.
Генерация контента через OpenAI
Ведущий создаёт игру. Ему нужно шесть компаний с названиями, описаниями и логотипами. Придумывать вручную — долго и однообразно. Мы подключили OpenAI API.
ChatGPT генерирует названия и описания компаний по тематике. Ведущий указывает: «нефть и безопасность» — и получает шесть компаний с уникальными названиями, слоганами и описаниями.
DALL-E генерирует логотипы — уникальные изображения 1024×1024 для каждой компании. Если логотип не нравится — кнопка «Перегенерировать».
Для отображения прогресса генерации мы использовали Server-Sent Events (SSE). Генерация шести компаний с логотипами занимает 30–40 секунд. Вместо «подождите» пользователь видит поток: «Генерирую название... Создаю описание... Рисую логотип...».
Деплой: 12 проблем, которые не описаны ни в одном туториале
Первый деплой — 1 октября 2025. За один день — 10 коммитов только с фиксами деплоя.
bcrypt и длинные пароли. Библиотека bcrypt молча обрезает пароли длиннее 72 байт. Решение — SHA-256 pre-hash: сначала хешируем пароль в короткую строку, потом передаём в bcrypt.
Alpine Linux и bash. Docker-образы на Alpine не содержат bash. Наш entrypoint.sh начинался с #!/usr/bin/env bash — контейнер падал при старте.
Два контейнера вместо одного. Первая попытка — запустить фронтенд и бэкенд в одном контейнере. Не работает: nginx и uvicorn конфликтуют за порт, логи смешиваются.
Утечки соединений к базе данных. При высокой нагрузке соединения не возвращались в пул. PostgreSQL отказывался принимать новые подключения. Решение — настройка пула: pool_size=10, max_overflow=10, pool_pre_ping=True.
Все 12 проблем мы задокументировали в DEPLOY-COOLIFY.md — 380 строк с описанием каждой ошибки, её симптомов и точного решения.
Excel как интерфейс для ведущего
Ведущий бизнес-тренинга — это не разработчик. Ему нужно подготовить данные для игры: участки, подрядчики, виды работ, меры безопасности.
Мы сделали двунаправленный импорт/экспорт через Excel. Ведущий скачивает файл с текущими данными, редактирует в привычном Excel, загружает обратно. Система автоматически определяет, что изменилось, и обновляет базу.
Excel-импорт стал одной из самых востребованных функций. Ведущий не будет заполнять веб-формы с 50 полями — он откроет Excel, скопирует из прошлой игры, поправит пару значений, загрузит.
Мобильные устройства: планшеты на тренинге
На тренинге игроки работают с планшетов. Это значит: маленький экран, тач вместо мыши, нестабильный Wi-Fi.
Polling вместо постоянного соединения на нестабильном Wi-Fi. WebSocket отлично работает для ставок аукциона, но для обновления статуса фазы polling каждые 3 секунды проще, надёжнее и не ломается при нестабильном соединении.
Lazy loading и code splitting стали спасением. Без них первая загрузка — 3 МБ JavaScript. С ними — 200–300 КБ для текущей фазы. На медленном Wi-Fi это разница между «открылось за 2 секунды» и «ждём минуту».
Админка: 21 экран для ведущего
Ведущий управляет всем через административную панель. 21 страница админки. Каждая фаза — отдельный экран управления. Ведущий видит действия всех команд одновременно.
QR-регистрация. Вместо логинов и паролей — QR-коды. Ведущий показывает QR на экране, игроки сканируют планшетом — и попадают в свою компанию.
Логи игры. Каждое действие записывается: кто поставил, сколько, на какой участок, в какое время.
Режим просмотра. Для наблюдателей, экспертов и запасных игроков — режим, в котором видны все данные, но нельзя ничего менять.
5 игровых фаз, каждая со своей механикой и интерфейсом. Аукционы в реальном времени через WebSocket. Интерактивная карта на MapLibre GL. Генерация контента через OpenAI. Математическая модель с инцидентами по Пуассону. Импорт/экспорт данных через Excel. 21 экран админки. QR-регистрация. Весь цикл от идеи до продакшна — 5 месяцев (октябрь 2025 — февраль 2026). Один раунд тренинга теперь занимает 40–50 минут вместо полутора часов.
Уроки, которые мы вынесли
-
1
Документируй формулы сразу
Формул в игре — десятки. Без документации через месяц невозможно вспомнить, почему стоимость строительства считается именно так. Файл на 1 330 строк — и ни разу не пожалели.
-
2
Деплой — это отдельный проект
Мы потратили на деплой столько же усилий, сколько на целую фазу игры. 12 задокументированных проблем. Начинайте деплоить с первого дня.
-
3
WebSocket — не серебряная пуля
Для ставок аукциона WebSocket критичен. Для обновления статуса фазы polling каждые 3 секунды проще и надёжнее. Используем оба подхода.
-
4
Excel — лучший интерфейс для не-разработчика
Ведущий не будет заполнять веб-формы с 50 полями. Excel-импорт решил проблему за один день и стал самой востребованной функцией.
-
5
Один параметр, который влияет на всё — ключ к балансировке
Индекс разработки (IR) управляет сложностью строительства, мощностью добычи, стоимостью и рисками. Один ползунок вместо россыпи настроек.
-
6
Прозрачность для пользователя — обязательство
Прогресс генерации через SSE. Polling с обновлением каждые 3 секунды. Детальный changelog. Логи всех действий. «Чёрный ящик» — это тревога.
-
7
Адаптация под устройство — не «потом», а «сразу»
На первом тренинге половина команд пришла с планшетами. Интерфейс для десктопа на 10-дюймовом экране выглядел ужасно. Переделка заняла две недели.
349 коммитов. 5 фаз. 112 миграций. 130 000 строк кода. Один продукт, который превращает полуторачасовой раунд с ручным подсчётом в 40 минут осмысленных решений.