Files
OperatorSchedule/models.py

141 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Модуль для создания и решения модели CP-SAT
"""
from ortools.sat.python import cp_model
from typing import List, Dict, Tuple
# Дни работы
def get_days(year: int = 2026, month: int = 4) -> List[str]:
"""Генерирует список дат месяца"""
return [f"{year}-{month:02d}-{i:02d}" for i in range(1, 31)]
# Смены
SHIFTS = [
(6, 14.5), # с 06:00 до 14:30
(8, 16.5), # с 08:00 до 16:30
(11.5, 20), # с 11:30 до 20:00
(12.5, 21) # с 12:30 до 21:00
]
# Операторы
OPERATORS = [
"Иванов",
"Алексеев",
"Сидоров",
"Козобородов",
"Трудяшкина",
"Петрова"
]
# Предпочтения операторов
OPERATOR_PREFERENCES = {
"Иванов": ["2026-04-10", "2026-04-13"],
"Алексеев": [(i, 0) for i in get_days() if (int(i[-2:]) - 1) % 7 == 0], # Понедельники
"Сидоров": ["2026-04-25", "2026-04-26"],
"Козобородов": [(i, 2) for i in get_days()], # Предпочитает смену 11:3020:00
"Трудяшкина": ["2026-04-04"],
"Петрова": ["2026-04-08", "2026-04-29"]
}
def create_model() -> Tuple[cp_model.CpModel, Dict, Dict]:
"""
Создаёт и настраивает CP-модель.
Возвращает:
model: CP-модель
variables: словарь переменных работы
operators_set: множество операторов для удобства
"""
model = cp_model.CpModel()
variables = {}
days = get_days()
# Определяем переменные: works[(day, shift_idx, op)]
for day in days:
for shift_idx in range(len(SHIFTS)):
for op in OPERATORS:
var_name = f"{op}_{day}_shift_{shift_idx}"
variables[(day, shift_idx, op)] = model.NewBoolVar(var_name)
# Добавляем ограничения
# 1. Учёт предпочтений операторов
for op, prefs in OPERATOR_PREFERENCES.items():
if isinstance(prefs, list):
for pref in prefs:
if isinstance(pref, tuple):
day, shift = pref
day_key = f"{day}"
shift_key = shift
# Создаем ключ для переменных, используя стандартный формат дней
# Нужно найти соответствующий день в списке days
for d in days:
if d.endswith(day.split('-')[2]) and d.startswith(f"{day.split('-')[0]}-{day.split('-')[1]}"):
if f"{d}-{shift_key}" in pref:
continue
day, shift_idx_pref = pref
model.AddHint(variables[(day, shift_idx_pref, op)], 1)
break
else:
day = pref
model.Add(sum(variables[(day, shift_idx, op)] for shift_idx in range(len(SHIFTS))) == 0)
# Условие 1: На каждую смену должен быть назначен оператор
for day in days:
for shift_idx in range(len(SHIFTS)):
model.Add(sum(variables[(day, shift_idx, op)] for op in OPERATORS) == 1)
# Условие 2: У каждого оператора должно быть 8 выходных (20 рабочих дней)
for op in OPERATORS:
model.Add(sum(variables[(day, shift_idx, op)] for day in days for shift_idx in range(len(SHIFTS))) == 20)
# Условие 3: Оператор не должен работать больше 5 дней подряд
for op in OPERATORS:
for day_idx in range(len(days) - 4):
model.Add(sum(variables[(days[day_idx + i], shift_idx, op)]
for i in range(5)
for shift_idx in range(len(SHIFTS))) <= 4)
# Условие 4: В день оператор может работать только одну смену
for day in days:
for op in OPERATORS:
model.Add(sum(variables[(day, shift_idx, op)] for shift_idx in range(len(SHIFTS))) <= 1)
return model, variables, set(OPERATORS)
def solve_model():
"""
Решает модель.
Возвращает:
True - если решение найдено
False - если не удалось составить график
"""
model, variables, operators_set = create_model()
solver = cp_model.CpSolver()
status = solver.Solve(model)
return status in (cp_model.OPTIMAL, cp_model.FEASIBLE)
def get_solution():
"""
Получает решение модели.
Возвращает:
Словарь с расписанием: {день: {смена: {оператор: True/False}}}
"""
model, variables, operators_set = create_model()
solver = cp_model.CpSolver()
solver.Solve(model)
schedule = {}
for day in get_days():
schedule[day] = {
shift_idx: {op: solver.Value(variables[(day, shift_idx, op)])
for op in OPERATORS}
for shift_idx in range(len(SHIFTS))
}
return schedule