Перейти к основному содержимому

Планирование сотрудников

В данном разделе рассматривается задача составления графика работы медсестёр в учреждении. Для каждого дня и смены необходимо определить, кто из сотрудников будет задействован. При этом важно обеспечить полное покрытие всех смен и организовать равномерное распределение нагрузки между медсёстрами. Такая задача встречается в практике планирования персонала и требует учёта множества ограничений, включая количество смен, продолжительность работы и индивидуальные особенности расписаний.

Задача представлена в двух вариантах: в первом осуществляется базовое распределение смен между медсёстрами, во втором — к распределению добавляются индивидуальные запросы сотрудников на предпочтительные смены.

Постановка задачи

  • Каждый день разделен на три смены по 8 часов;
  • Каждый день за каждой сменой закреплена одна медсестра, и ни одна медсестра не работает более одной смены;
  • Каждая медсестра закреплена как минимум за двумя сменами в течение трехдневного периода;
  • Равномерно распределить смены между медсестрами.

Решение задачи

1. Выбор модели

Для решения задачи планирования сотрудников воспользуемся моделью ограничений. В задаче есть булевы решения «назначен/не назначен» и логические условия покрытия и равномерности — это естественная область для программирования в ограничениях: мы формулируем правила, а решатель подбирает допустимую конфигурацию.

2. Создание модели

Создаём экземпляр модели — контейнер, в котором регистрируются переменные и ограничения.

// Создаем объект модели
Модель = О2
.Модели()
.МодельОграничений()
.СоздатьМодель();

3. Ввод данных

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

// Ввод данных
КоличествоМедсестер = 4;
КоличествоСмен = 3;
КоличествоДней = 3;

4. Регистрация переменных

Для каждой медсестры и каждого дня мы создаём массив булевых переменных по числу смен. Единица означает, что медсестра работает эту смену в этот день, ноль — что не работает. Проще говоря, в каждой клетке такого «расписания» хранится ответ «назначена/не назначена» на конкретную смену.

// Регистрируем переменные: назначение по дням и сменам
СменыМедсестер = Новый Массив(КоличествоМедсестер);
Для Медсестра = 0 По КоличествоМедсестёр - 1 Цикл
СменыМедсестер[Медсестра] = Новый Массив(КоличествоДней);
Для День = 0 По КоличествоДней - 1 Цикл
СменыМедсестер[Медсестра][День] = Модель.МассивБулевыхПеременных(КоличествоСмен);
КонецЦикла;
КонецЦикла;

5. Описание ограничений

Ограничение на смены внутри дня. В один день медсестра может работать не более одной смены.

// Каждая медсестра работает не более одной смены в день
Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
Для День = 0 По КоличествоДней - 1 Цикл
Модель.Ограничения().НиБолееОдного(СменыМедсестер[Медсестра][День]);
КонецЦикла;
КонецЦикла;

Каждая смена назначается ровно одному сотруднику. Для каждой смены в каждом дне выбираем одного исполнителя.

// Каждая смена в каждый день закрыта ровно одной медсестрой
Для День = 0 По КоличествоДней - 1 Цикл
Для Смена = 0 По КоличествоСмен - 1 Цикл
МедсестрыНаСмену = Новый Массив;
Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
МедсестрыНаСмену.Добавить(СменыМедсестер[Медсестра][День][Смена]);
КонецЦикла;
Модель.Ограничения().ТолькоОдин(МедсестрыНаСмену);
КонецЦикла;
КонецЦикла;

Равномерная нагрузка. Общее число смен у каждого примерно одинаковое: у всех одинаковый минимум, при остатке часть получает на одну смену больше.

// Равномерное распределение смен
ВсегоСмен = КоличествоСмен * КоличествоДней;
МинимальноСменНаМедсестру = Цел(ВсегоСмен / КоличествоМедсестер);
Остаток = ВсегоСмен % КоличествоМедсестер;
МаксимальноСменНаМедсестру = ?(Остаток = 0, МинимальноСменНаМедсестру, МинимальноСменНаМедсестру + 1);

Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
ВсеСмены = Новый Массив;
Для День = 0 По КоличествоДней - 1 Цикл
Для Смена = 0 По КоличествоСмен - 1 Цикл
ВсеСмены.Добавить(СменыМедсестер[Медсестра][День][Смена]);
КонецЦикла;
КонецЦикла;

СуммаСмен = Модель.Выражения().Сумма(ВсеСмены);
Модель.Ограничения().ЗначениеБольшеИлиРавно(СуммаСмен, МинимальноСменНаМедсестру);
Модель.Ограничения().ЗначениеМеньшеИлиРавно(СуммаСмен, МаксимальноСменНаМедсестру);
КонецЦикла;

6. Решение модели

Выполняется поиск решения. Целевая функция не задаётся — это задача выполнимости: требуется любое расписание, удовлетворяющее всем ограничениям.

// Решение модели
Решение = Модель.Решить();

7. Вывод результатов

Печатается расписание по дням и сменам, затем суммарная нагрузка по каждой медсестре.

// Вывод результатов
Если Не Решение.РешениеНайдено() Тогда
Сообщить("Задача не имеет решения!");
Возврат;
КонецЕсли;

Сообщение = "Распределение медсестер по сменам" + Символы.ПС + Символы.ПС;

Для День = 0 По КоличествоДней - 1 Цикл
Сообщение = Сообщение + СтрШаблон(
"День %1:%2",
День + 1,
Символы.ПС
);

Для Смена = 0 По КоличествоСмен - 1 Цикл
Назначенная = -1;
Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
Если Решение.ЗначениеПеременной(СменыМедсестер[Медсестра][День][Смена]) = 1 Тогда
Назначенная = Медсестра;
Прервать;
КонецЕсли;
КонецЦикла;

Если Назначенная >= 0 Тогда
Сообщение = Сообщение + СтрШаблон(
" Смена %1: Медсестра №%2%3",
Смена + 1,
Назначенная + 1,
Символы.ПС
);
Иначе
Сообщение = Сообщение + СтрШаблон(
" Смена %1: Не назначена!%2",
Смена + 1,
Символы.ПС
);
КонецЕсли;
КонецЦикла;

Сообщение = Сообщение + Символы.ПС;
КонецЦикла;

Сообщение = Сообщение + "Нагрузка медсестер" + Символы.ПС;

Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
КоличествоСменМедсестры = 0;
Для День = 0 По КоличествоДней - 1 Цикл
Для Смена = 0 По КоличествоСмен - 1 Цикл
Если Решение.ЗначениеПеременной(СменыМедсестер[Медсестра][День][Смена]) = 1 Тогда
КоличествоСменМедсестры = КоличествоСменМедсестры + 1;
КонецЕсли;
КонецЦикла;
КонецЦикла;

Сообщение = Сообщение + СтрШаблон(
"Медсестра №%1: %2 смен%3",
Медсестра + 1,
КоличествоСменМедсестры,
Символы.ПС
);
КонецЦикла;

Сообщить(Сообщение);

Полный код решения задачи

Ниже представлен полный код решения. Вы также можете скачать пример в виде готовой обработки.

// Создаем объект модели
Модель = О2
.Модели()
.МодельОграничений()
.СоздатьМодель();

// Вводим данные
КоличествоМедсестер = 4;
КоличествоСмен = 3;
КоличествоДней = 3;

// Регистрируем переменные: назначение медсестёр по дням и сменам
СменыМедсестер = Новый Массив(КоличествоМедсестер);
Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
СменыМедсестер[Медсестра] = Новый Массив(КоличествоДней);
Для День = 0 По КоличествоДней - 1 Цикл
// Булевы переменные на все смены этого дня для данной медсестры
СменыМедсестер[Медсестра][День] = Модель.МассивБулевыхПеременных(КоличествоСмен);
КонецЦикла;
КонецЦикла;

// Каждая медсестра работает не более одной смены в день
Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
Для День = 0 По КоличествоДней - 1 Цикл
Модель.Ограничения().НиБолееОдного(СменыМедсестер[Медсестра][День]);
КонецЦикла;
КонецЦикла;

// Каждая смена в каждый день закрыта ровно одной медсестрой
Для День = 0 По КоличествоДней - 1 Цикл
Для Смена = 0 По КоличествоСмен - 1 Цикл
МедсестрыНаСмену = Новый Массив;
Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
МедсестрыНаСмену.Добавить(СменыМедсестер[Медсестра][День][Смена]);
КонецЦикла;
Модель.Ограничения().ТолькоОдин(МедсестрыНаСмену);
КонецЦикла;
КонецЦикла;

// Равномерное распределение смен
ВсегоСмен = КоличествоСмен * КоличествоДней;
МинимальноСменНаМедсестру = Цел(ВсегоСмен / КоличествоМедсестер);
Остаток = ВсегоСмен % КоличествоМедсестер;
МаксимальноСменНаМедсестру = ?(Остаток = 0, МинимальноСменНаМедсестру, МинимальноСменНаМедсестру + 1);

Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
ВсеСмены = Новый Массив;
Для День = 0 По КоличествоДней - 1 Цикл
Для Смена = 0 По КоличествоСмен - 1 Цикл
ВсеСмены.Добавить(СменыМедсестер[Медсестра][День][Смена]);
КонецЦикла;
КонецЦикла;

СуммаСмен = Модель.Выражения().Сумма(ВсеСмены);
Модель.Ограничения().ЗначениеБольшеИлиРавно(СуммаСмен, МинимальноСменНаМедсестру);
Модель.Ограничения().ЗначениеМеньшеИлиРавно(СуммаСмен, МаксимальноСменНаМедсестру);
КонецЦикла;

// Решение модели
Решение = Модель.Решить();

// Вывод результатов
Если Не Решение.РешениеНайдено() Тогда
Сообщить("Задача не имеет решения!");
Возврат;
КонецЕсли;

Сообщение = "Распределение медсестер по сменам" + Символы.ПС + Символы.ПС;

Для День = 0 По КоличествоДней - 1 Цикл
Сообщение = Сообщение + СтрШаблон(
"День %1:%2",
День + 1,
Символы.ПС
);

Для Смена = 0 По КоличествоСмен - 1 Цикл
Назначенная = -1;
Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
Если Решение.ЗначениеПеременной(СменыМедсестер[Медсестра][День][Смена]) = 1 Тогда
Назначенная = Медсестра;
Прервать;
КонецЕсли;
КонецЦикла;

Если Назначенная >= 0 Тогда
Сообщение = Сообщение + СтрШаблон(
" Смена %1: Медсестра №%2%3",
Смена + 1,
Назначенная + 1,
Символы.ПС
);
Иначе
Сообщение = Сообщение + СтрШаблон(
" Смена %1: Не назначена!%2",
Смена + 1,
Символы.ПС
);
КонецЕсли;
КонецЦикла;

Сообщение = Сообщение + Символы.ПС;
КонецЦикла;

Сообщение = Сообщение + "Нагрузка медсестер" + Символы.ПС;

Для Медсестра = 0 По КоличествоМедсестер - 1 Цикл
КоличествоСменМедсестры = 0;
Для День = 0 По КоличествоДней - 1 Цикл
Для Смена = 0 По КоличествоСмен - 1 Цикл
Если Решение.ЗначениеПеременной(СменыМедсестер[Медсестра][День][Смена]) = 1 Тогда
КоличествоСменМедсестры = КоличествоСменМедсестры + 1;
КонецЕсли;
КонецЦикла;
КонецЦикла;

Сообщение = Сообщение + СтрШаблон(
"Медсестра №%1: %2 смен%3",
Медсестра + 1,
КоличествоСменМедсестры,
Символы.ПС
);
КонецЦикла;

Сообщить(Сообщение);
  Скачать пример