Составлен план работ + выполнен этап 1
This commit is contained in:
141
models.py
Normal file
141
models.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Модуль для создания и решения модели 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:30–20: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
|
||||
Reference in New Issue
Block a user