Планирование сотрудников
В данном разделе рассматривается задача составления графика работы медсестёр в учреждении. Для каждого дня и смены необходимо определить, кто из сотрудников будет задействован. При этом важно обеспечить полное покрытие всех смен и организовать равномерное распределение нагрузки между медсёстрами. Такая задача встречается в практике планирования персонала и требует учёта множества ограничений, включая количество смен, продолжительность работы и индивидуальные особенности расписаний.
Задача представлена в двух вариантах: в первом осуществляется базовое распределение смен между медсёстрами, во втором — к распределению добавляются индивидуальные запросы сотрудников на предпочтительные смены.
Постановка задачи
- Каждый день разделен на три смены по 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,
КоличествоСменМедсестры,
Символы.ПС
);
КонецЦикла;
Сообщить(Сообщение);