javascript

Теория вероятностей в действии 2.0

  • пятница, 17 января 2025 г. в 00:00:07
https://habr.com/ru/articles/874226/

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

Ссылка на оригинал статьи с объяснением принципа Доказательного Планирования (в оригинале Evidence Based Scheduling - далее будет фигурировать как EBS)

Версия 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: {
  value: 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({
    value: 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-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].value === 'number') {
      sensed.counter += 1
      sensed.speedValues.push((deltas[i].value as number))
    }
  }
  else
    deltas[i].isSensed = false
}

// NOTE: 4. Все! Теперь наиболее вероятная дата финиша
// может быть расчитана в соответствии с наиболее вероятной скоростью:
sensed.averageSpeed = getArithmeticalMean(sensed.speedValues)

Попробовать живьем