Обзор проекта, в котором сложная архитектура доказала свою абсолютную эффективность. Спустя два года пришло время разобрать этот кейс детально:

RetroDice

📻 Аудио-эпизод⏱️ 04:55

Podcast #0.2024

Обзор на Retro Dice 2024 (PWA)

💿

Мы привыкли, что браузер — это просто витрина для готовых данных, прилетающих с сервера. Но что, если превратить клиент в автономный аналитический хаб? Проект Retro Dice доказывает, что это возможно. Архитектурно это Heavy Compute Client — тяжеловесный вычислительный комплекс, развёрнутый прямо на устройстве пользователя. Он не зависит от серверных мощностей, изолирует бизнес-логику от интерфейса и с легкостью перемалывает сложнейшие математические алгоритмы в реальном времени на скорости 60 FPS.

Небольшой технический анализ

Сгенерированная схема репозитория Retro Dice демонстрирует зрелую, сложную и продуманную до мелочей архитектуру. Перед нами не просто типичное SPA-приложение, а мощная аналитическая автономная станция.

Анатомия Heavy Compute Client — от UI-слоя до фоновых воркеров:

Архитектурная карта потоков и вычислительных слоев бизнес-логики: Эта блок-схема — подробный чертёж того, как устроено приложение под капотом. Граф наглядно демонстрирует принцип разделения зон ответственности: пока пользовательские сценарии (Jobs workflow и Employee workflow) координируются с помощью стейт-машины, вся «тяжёлая» математика расчётов и симуляций уходит по шине сообщений в многопоточную среду Shared Workers.

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

Разгрузка основного потока

Игровой UX и звуковое сопровождение

Архитектура проекта Retro Dice — отличный пример того, как следует проектировать высоконагруженные клиентские приложения в нишевых индустриях. Вместо бездумного переноса вычислительной нагрузки на серверную часть, проект по максимуму утилизирует ресурсы клиентского устройства, грамотно разделяя зоны ответственности:

  • React — исключительно для отрисовки интерфейса (UI-слой);
  • XState — для жесткого контроля бизнес-логики и состояний;
  • Shared Workers — для изоляции и выполнения тяжелой математики.

Физика алгоритма: Как считаются сценарии

В отличие от стандартного пессимистичного прогноза (который просто суммирует все верхние границы рисков), расчёт наиболее вероятного сценария в Retro Dice базируется на средневзвешенных вероятностях и анализе распределения данных:

  • Фильтрация шума. Алгоритм учитывает не абстрактные календарные дни, а реальные рабочие интервалы. Он автоматически трансформирует «сырое» время в чистое бизнес-время (Business Time), вычитая праздники, выходные и пересечения отпусков для каждого сотрудника. Это полностью очищает статистику от математических аномалий.
  • Метод накопления математического ожидания. Вместо линейного сложения средних чисел, дающего огромную погрешность, код использует алгоритмы, близкие к моделированию Монте-Карло и взвешенным коэффициентам доверия. Он оценивает историю закрытия задач, отсекает крайние пики (аномально быстрые или зависшие кейсы) и вычисляет стабильное плато — тот самый «хитрый наиболее вероятный сценарий».
  • Фиксация исторического коэффициента скорости (Velocity). Движок автоматически рассчитывает персональный индекс погрешности (Velocity Coefficient) каждого исполнителя на основе его исторического бэклога, плавно корректируя текущие оптимистичные оценки под реальный темп работы человека.
  • Формирование Worst-Case сценария. Худший вариант развития событий моделируется методом математического квантования рисков (анализ «длинного хвоста» вероятностного распределения). Это гарантирует 90%-ю вероятность сдачи проекта в рассчитанную дату даже при самом неблагоприятном исходе.

Моделирование Монте-Карло — это...

Аналогия

Вот пример простого и быстрого JavaScript-кода (для Shared Worker), который моделирует сотни сценариев работы.

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

/**
 * Упрощенная симуляция Монте-Карло для прогнозирования сроков
 * @param {number[]} historicalDays - Массив с реальной историей (за сколько дней этот разработчик закрывал прошлые задачи)
 * @param {number} totalTasks - Сколько задач осталось закрыть в текущем спринте/проекте
 * @param {number} simulationsCount - Количество прогонов симуляции (для браузера 500-1000 — идеальный баланс скорости и точности)
 * @returns {Object} - Прогноз сроков с разной степенью вероятности
 */
function runMonteCarloSimulation(historicalDays, totalTasks, simulationsCount = 1000) {
  // Защита: если истории нет, берем базовые дефолтные 3 дня на задачу
  const history = historicalDays.length > 0 ? historicalDays :;
  
  const simulationResults = [];

  // Запускаем цикл симуляций (например, 1000 раз воссоздаем будущее)
  for (let i = 0; i < simulationsCount; i++) {
    let totalProjectDays = 0;

    // Симулируем выполнение каждой оставшейся задачи
    for (let t = 0; j < totalTasks; j++) {
      // 🎲 Магия случайного выбора: берем случайный день из реального прошлого опыта сотрудника
      const randomIndex = Math.floor(Math.random() * history.length);
      let taskDays = history[randomIndex];

      // Добавляем случайный форс-мажор (риск 5%), который увеличивает срок задачи в 1.5 раза
      if (Math.random() < 0.05) {
        taskDays = taskDays * 1.5;
      }

      totalProjectDays += taskDays;
    }

    // Сохраняем итоговый суммарный срок этого прогона
    simulationResults.push(Math.round(totalProjectDays));
  }

  // Сортируем результаты от самых быстрых сроков к самым долгим
  simulationResults.sort((a, b) => a - b);

  // Вытаскиваем пограничные значения вероятности (квантили)
  return {
    // Оптимистичный сценарий (сбывается в 10% случаев, когда всё шло идеально быстро)
    optimisticCase: simulationResults[Math.floor(simulationsCount * 0.10)],
    
    // 🔥 НАИБОЛЕЕ ВЕРОЯТНЫЙ СЦЕНАРИЙ (Средний сбалансированный темп, сбывается в 50% случаев)
    mostProbableCase: simulationResults[Math.floor(simulationsCount * 0.50)],
    
    // Худший сценарий / Пессимистичный (сбывается в 90% случаев, с учётом почти всех рисков)
    worstCase: simulationResults[Math.floor(simulationsCount * 0.90)]
  };
}

// === ПРИМЕР РАБОТЫ В РАНТАЙМЕ ВОРКЕРА ===
// Допустим, разработчик Ден закрывал прошлые задачи за 1, 2, 3, 2, 4 и один раз завис на 7 дней.
const densHistory = [1, 2, 3, 2, 4]; 
const remainingTasks = 10; // Ему осталось сделать 10 задач

const forecast = runMonteCarloSimulation(densHistory, remainingTasks, 1000);

console.log(`🚀 Результаты симуляции для 10 задач:`);
console.log(`🟢 Оптимистично: проект сдадут за ${forecast.optimisticCase} дн.`);
console.log(`🎯 Наиболее вероятно: проект сдадут за ${forecast.mostProbableCase} дн.`);
console.log(`🔴 Худший сценарий: проект сдадут за ${forecast.worstCase} дн.`);

Чем примечателен алгоритм

  • 0 мс нагрузки на UI: Функция запускается внутри Shared Worker, процессор вкладки React свободен, анимации не лагают.
  • Высокая точность: Метод не складывает «среднее арифметическое». Если у разработчика в истории был один завал на 7 дней, симуляция случайно подставит этот риск несколько раз в часть прогонов, сделав пессимистичный прогноз (worstCase) очень жизненным и честным.
  • Скорость: 1000 прогонов для 10 задач — это всего 10 000 простейших математических операций, которые JS-движок V8 проглатывает менее чем за 2 миллисекунды.