Раз в несколько лет возвращаюсь к задаче создания алгоритма для получения наиболее вероятного прогноза какого-либо события на основании ошибок предыдущих прогнозов. В этот раз попробую обойтись минимумом текста (ссылка на демо в конце).
Ссылка на оригинал статьи с объяснением принципа Доказательного Планирования (в оригинале Evidence Based Scheduling) - советую ознакомиться, чтоб быть в контексте: Joel Spolsky Evidence Based Scheduling / Перевод на русский
Для меня лично эта задача имеет небольшую историю:
Версия 1.0 - При желании, можно ознакомиться с первым вариантом увлекательного процесса поиска истины
Версия 2.0 [Вы здесь]
😠 Версия 1.0: Первый блин в коме (2019)
Как я видел решение этой задачи 5 лет назад (здесь очевидная ошибка): Берем самый худший вариант из предыдущего опыта и проецируем на текущую задачу - это и будет мой прогноз для Продукт Менеджера как наиболее худший сценарий. Дёшево и сердито.
🚀 Версия 2.0: Кажется, это успех (2024)
К примеру, у разработчика (исполнителя) есть несколько решенных задач с параметрами:
Дата начала выполнения задачи (start)
Дата его оценки финиша (estimation)
Дата реального финиша (finish)
Итак, добавлена новая задача, получен estimation от разраба. Теперь вопрос - насколько он ошибся в этот раз?
🧐 Теперь поговорим про алгоритм определения наиболее вероятного сценария: Думаю, всем очевидно, что брать скорость "среднюю по больнице" - это неточно. Поэтому я предлагаю следующую логику: Человек работает, как правило (плюс-минус), с одной комфортной для него скоростью - алгоритм должен ее "ощутить". Как именно: Если по имеющимся результатам для анализа среди возможных вариантов скоростей его работы встречаются два с минимальным отклонением (Δ) - это повод искать ещё варианты с примерно таким же отклонением (но чуть больше на константу погрешности). В итоге, найденные варианты образуют "скопления", среднее (или медианное - это уже детали) значение которых можно учитывать как наиболее вероятное. Поправьте в комментариях, если я неправ.
// Код чисто для понимания, // на императивщину не смотрите, // вариант на ООП более многословен, а здесь нужна краткость // NOTE: 1. Отсортированный список скоростей выполнения задач const sortedSpeeds: { v: number; }[] = speeds.sort((e1, e2) => e1.v - e2.v) // NOTE: 2. Список отклонений между этими скоростями const deltas: { speed: number | null; delta: number | null; prev: number | null; next: number | null; isSensed: boolean; // Это тот самый показатель "чувствительности" }[] = [] let minDelta = 1000000 let maxDelta = 0 for (let i = 0, max = sortedSpeeds.length; i < max; i++) { const prevValue = !!sortedSpeeds[i - 1] ? sortedSpeeds[i - 1] : null const nextValue = !!sortedSpeeds[i + 1] ? sortedSpeeds[i + 1] : null const currentValue = sortedSpeeds[i] const delta = typeof prevValue?.v === 'number' ? currentValue.v - (prevValue.v) : null deltas.push({ speed: sortedSpeeds[i].v, delta, next: nextValue?.v || null, prev: prevValue?.v || null, isSensed: false, // By default }) if (typeof delta === 'number') { if (minDelta >= delta) minDelta = delta if (maxDelta <= delta) maxDelta = delta } } // NOTE: 3. Оставим только скорости с минимальным отклонением друг от друга // Коэфф sensibility нужен для большего охвата задач, когда их немного (я беру 4) // По мере роста задач коэфф. sensibility можно уменьшать до значения 1.5-2 const sensed: { counter: number; speedValues: number[]; averageSpeed: number; } = { counter: 0, speedValues: [], averageSpeed: 0, } for (let i = 0, max = deltas.length; i< max; i++) { if ((deltas[i].delta as number) <= sensibility * minDelta) { deltas[i].isSensed = true if (typeof deltas[i].speed === 'number') { sensed.counter += 1 sensed.speedValues.push((deltas[i].speed as number)) } } else deltas[i].isSensed = false } // NOTE: 4. Все! Теперь наиболее вероятная дата финиша // может быть расчитана в соответствии с наиболее вероятной скоростью: sensed.averageSpeed = getArithmeticalMean(sensed.speedValues) // Этот момент AI предложила заменить на расчет медианы // этих скоростей - на проде так и работает сейчас
👉 Попробовать живьем (версия использует локальное хранилище браузера - решил не усложнять проект)

Где тут Теория вероятностей? [спросите Вы]
Существует понятие Функция распределения вероятностей. Закон распределения вероятностей получил свое название из-за его способности разбросать вероятность между значениями случайной величины чтобы в итоге она составила единицу.
Используя библиотеку react-google-charts, я получил (в грубом виде) кривую (в данном случае она прямая, но это не важно) распределения вероятностей, основанную на данных предыдущих опытов:



Итак, мой вывод в двух тезисах:
Скорость с которой обычно работает конкретный человек с задачами, сложность которых он может для себя определить - можно считать наиболее вероятной (она же идеально комфортная для данного исполнителя)
Приблизиться к определению комфортной скорости можно, сделав N опытов, уменьшая погрешность с каждым новым опытом (этот тезис учитывает личный прогресс разработчика)
Надо заметить, комфортная скорость конкретного человека может разниться в зависимости от сложности задачи - простые планировать легко, средние чуть сложнее и т.д. - именно по этой причине нужно не забывать уточнять грейд задачи на старте и на финише (в процессе возможна переоценка с соответствующим пересчетом вероятностей).
В споре рождается истина. Спорьте со мной в комментах ) Здесь периодически пишу про обновления и новые фичи.
Обновления 2025.06
Улучшена навигация по проектам и исполнителям (Стратегия дизайна Mobile First)
Добавлена возможность создавать чек-листы внутри задачи
Обновления 2025.03
Теперь задачам можно присвоить "родителя". Таким образом любая задача может стать проектом и делать суммарный расчет потраченного времени по "задачам-детям";
Резюме по выполненной задаче;
Теперь "ощущаемый" прогноз по задаче может быть пересчитан в экспериментальном режиме как "Если бы она перешла на следующий грейд сложности..." (см. кликабельные морды в открывшемся сайдбаре); Лично мне эта функция нужна чтоб прикинуть дату релиза сложных задач в рамках телефонного разговора (при достаточном количестве решённых задач аналогичной сложности);
Business Time - можно извлечь только рабочее время (планирую добавить json-редактор для настроек конфигурации пользовательского рабочего времени);



