.
This commit is contained in:
+55
-22
@@ -1,5 +1,6 @@
|
||||
from calendar import week
|
||||
import datetime
|
||||
from distutils.log import debug
|
||||
import itertools
|
||||
from typing import Dict, Iterable, List, Sequence, Tuple, Set, Any, Type
|
||||
|
||||
@@ -13,6 +14,7 @@ from pyomo.environ import *
|
||||
from pyomo.opt import SolverFactory
|
||||
|
||||
from rota.workers import Worker
|
||||
from rota.console import console
|
||||
|
||||
import json
|
||||
|
||||
@@ -25,6 +27,15 @@ import sys
|
||||
|
||||
from rich.pretty import pprint
|
||||
|
||||
import logging
|
||||
|
||||
logging.basicConfig(
|
||||
filename="rota.log",
|
||||
filemode="w",
|
||||
format="%(name)s - %(levelname)s - %(message)s",
|
||||
level=logging.DEBUG,
|
||||
)
|
||||
|
||||
ShiftName = str
|
||||
DayStr = str
|
||||
WeekInt = int
|
||||
@@ -38,9 +49,12 @@ from govuk_bank_holidays.bank_holidays import BankHolidays
|
||||
|
||||
bank_holidays = BankHolidays()
|
||||
bank_holiday_map = {}
|
||||
logging.debug("Creating bank holiday map")
|
||||
for bank_holiday in bank_holidays.get_holidays(division="england-and-wales"):
|
||||
bank_holiday_map[bank_holiday["date"]] = bank_holiday["title"]
|
||||
|
||||
logging.debug(bank_holiday_map)
|
||||
|
||||
SHIFT_BOUNDS = {
|
||||
"bank_holiday": (0, 9),
|
||||
"shift_count": (0, 400),
|
||||
@@ -253,12 +267,16 @@ class RotaBuilder(object):
|
||||
self.ignore_valid_shifts = False
|
||||
|
||||
def solve_model(
|
||||
self, solver: str = "cbc", use_neos: bool = False, options: dict = {}
|
||||
self,
|
||||
solver: str = "cbc",
|
||||
use_neos: bool = False,
|
||||
options: dict = {},
|
||||
debug_if_fail: bool = False,
|
||||
):
|
||||
print("Setting up solver")
|
||||
console.print("Setting up solver")
|
||||
self.opt = SolverFactory(solver)
|
||||
|
||||
print("Solving")
|
||||
console.print("Solving")
|
||||
if use_neos:
|
||||
solver_manager = SolverManagerFactory("neos") # Solve using neos server
|
||||
# results = solver_manager.solve(Rota.model, opt=opt, logfile="test.log")
|
||||
@@ -278,39 +296,47 @@ class RotaBuilder(object):
|
||||
|
||||
self.results = results
|
||||
|
||||
print(results)
|
||||
results.solver.status
|
||||
console.print(f"Complete - outcome: {results.solver.status}")
|
||||
|
||||
if results.solver.status != "ok" and debug_if_fail:
|
||||
console.print(f"Attempting each shift individually")
|
||||
self.solve_shifts_individually(options)
|
||||
|
||||
if not results.solver.status:
|
||||
sys.exit(0)
|
||||
|
||||
def solve_shifts_individually(self, options, folder="individual_shifts"):
|
||||
shifts = self.shifts
|
||||
|
||||
|
||||
outcomes = {}
|
||||
|
||||
for shift in shifts:
|
||||
console.print(f"Testing shifts: {shift.name}")
|
||||
self.ignore_valid_shifts = True
|
||||
self.clear_shifts()
|
||||
self.add_shift(shift)
|
||||
self.build_and_solve(options)
|
||||
self.export_rota_to_html(filename=shift.name, folder=folder)
|
||||
|
||||
outcomes[shift.name] = (self.results.solver.status, self.results.solver.termination_condition)
|
||||
pprint(outcomes)
|
||||
|
||||
|
||||
pprint(outcomes)
|
||||
outcomes[shift.name] = (
|
||||
self.results.solver.status,
|
||||
self.results.solver.termination_condition,
|
||||
)
|
||||
|
||||
console.print(outcomes)
|
||||
|
||||
def build_and_solve(
|
||||
self, options={"ratio": 0.1, "seconds": 1000, "threads": 10}, solve=True
|
||||
self,
|
||||
options={"ratio": 0.1, "seconds": 1000, "threads": 10},
|
||||
solve=True,
|
||||
debug_if_fail: bool = False,
|
||||
):
|
||||
self.build_shifts()
|
||||
self.build_workers()
|
||||
self.build_model()
|
||||
|
||||
if solve:
|
||||
self.solve_model(options=options)
|
||||
self.solve_model(options=options, debug_if_fail=debug_if_fail)
|
||||
|
||||
def build_model(self):
|
||||
# Initialize model
|
||||
@@ -1078,7 +1104,7 @@ class RotaBuilder(object):
|
||||
set(self.get_shift_names())
|
||||
):
|
||||
if self.ignore_valid_shifts:
|
||||
continue # Skip if we don't want to raise an error
|
||||
continue # Skip if we don't want to raise an error
|
||||
else:
|
||||
raise InvalidShift(
|
||||
f"avoid shifts by grade constraint contains a non existent shift: {invalid_shifts}"
|
||||
@@ -1478,7 +1504,9 @@ class RotaBuilder(object):
|
||||
shift.name, week, worker.id
|
||||
]
|
||||
for week in week_blocks
|
||||
for shift in self.get_shifts_with_constraint("night")
|
||||
for shift in self.get_shifts_with_constraint(
|
||||
"night"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1747,7 +1775,7 @@ class RotaBuilder(object):
|
||||
|
||||
#
|
||||
|
||||
#def get_pre_may(week_days, max_pre)
|
||||
# def get_pre_may(week_days, max_pre)
|
||||
|
||||
for n in range(len(weeks_days)):
|
||||
|
||||
@@ -1955,7 +1983,7 @@ class RotaBuilder(object):
|
||||
)
|
||||
|
||||
for constraint_shift in self.get_shifts_with_constraints(
|
||||
"pre",
|
||||
"pre",
|
||||
):
|
||||
for n in range(0, constraint_shift.constraint_options["pre"]):
|
||||
if day in constraint_shift.days:
|
||||
@@ -1965,7 +1993,10 @@ class RotaBuilder(object):
|
||||
worker.id, week, day, constraint_shift.name
|
||||
]
|
||||
+ sum(
|
||||
pre_map[n][0] * self.model.works[w.id, pre_map[n][1], pre_map[n][2], shiftname]
|
||||
pre_map[n][0]
|
||||
* self.model.works[
|
||||
w.id, pre_map[n][1], pre_map[n][2], shiftname
|
||||
]
|
||||
for shiftname in self.get_shift_names_by_week_day(
|
||||
pre_map[n][1], pre_map[n][2]
|
||||
)
|
||||
@@ -1973,7 +2004,7 @@ class RotaBuilder(object):
|
||||
for w in workers
|
||||
)
|
||||
)
|
||||
#for constraint_shift in self.get_shifts_with_constraint( "pre"):
|
||||
# for constraint_shift in self.get_shifts_with_constraint( "pre"):
|
||||
# to_sum = []
|
||||
# for n in range(
|
||||
# 0, constraint_shift.constraint_options["pre"]
|
||||
@@ -1988,7 +2019,6 @@ class RotaBuilder(object):
|
||||
# w.id, wk, dy, shiftname
|
||||
# ])
|
||||
|
||||
|
||||
# if day in constraint_shift.days:
|
||||
# self.model.constraints.add(
|
||||
# 1
|
||||
@@ -2403,7 +2433,10 @@ class RotaBuilder(object):
|
||||
set: Set of ShiftName
|
||||
|
||||
"""
|
||||
return set((week, day, shift_name) for shift_name in self.week_day_shifts_dict[(week, day)])
|
||||
return set(
|
||||
(week, day, shift_name)
|
||||
for shift_name in self.week_day_shifts_dict[(week, day)]
|
||||
)
|
||||
|
||||
def get_shift_by_name(self, name: str) -> SingleShift:
|
||||
"""
|
||||
@@ -2738,7 +2771,7 @@ class RotaBuilder(object):
|
||||
return self.start_date + datetime.timedelta(weeks=week)
|
||||
|
||||
# RESULTS
|
||||
def export_rota_to_html(self, filename: str = "rota", folder = None):
|
||||
def export_rota_to_html(self, filename: str = "rota", folder=None):
|
||||
if folder is not None:
|
||||
output_file = Path("output", folder, f"{filename}.html")
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user