pre repalce

This commit is contained in:
Ross
2022-05-26 15:56:28 +01:00
parent c9ba2a098c
commit 0cebbfddf6
4 changed files with 75 additions and 81 deletions
+38 -44
View File
@@ -3,6 +3,8 @@ import datetime
import itertools import itertools
from typing import Dict, Iterable, List, Sequence, Tuple, Set from typing import Dict, Iterable, List, Sequence, Tuple, Set
from pydantic import BaseModel, Extra
from pathlib import Path from pathlib import Path
import datetime import datetime
@@ -45,7 +47,7 @@ SHIFT_BOUNDS = {
} }
class SingleShift(object): class SingleShift(BaseModel):
"""Class to hold all details for a shift """Class to hold all details for a shift
@@ -68,56 +70,49 @@ class SingleShift(object):
""" """
sites: Iterable[str]
name: str
length: float
shift_days: Sequence[str]
balance_offset: float | None = None,
balance_weighting: float = 1,
workers_required: float = 1,
rota_on_nwds: bool = False,
assign_as_block: bool = False,
force_as_block: bool = False,
force_as_block_unless_nwd: bool = False,
hard_constrain_shift: bool = True,
bank_holidays_only: bool = False,
constraint: Iterable[str] = [],
def __init__( class Config:
self, extra = Extra.allow
sites: Iterable[str],
name: str, def __init__(self, **data: Any):
length: float, super().__init__(**data)
shift_days: Sequence[str], #self.site = sites
# balance_by_site=True, #self.name = name
balance_offset: float | None = None, #self.length = length
balance_weighting: float = 1, #self.shift_days = shift_days
workers_required: float = 1,
rota_on_nwds: bool = False,
assign_as_block: bool = False,
force_as_block: bool = False,
force_as_block_unless_nwd: bool = False,
hard_constrain_shift: bool = True,
bank_holidays_only: bool = False,
constraints: Iterable[str] = [],
):
self.site = sites
self.name = name
self.length = length
self.shift_days = shift_days
# self.balance_by_site = balance_by_site # self.balance_by_site = balance_by_site
# balance_offset defines the max difference in allocated shifts # balance_offset defines the max difference in allocated shifts
# versus target shifts (hard constraint) # versus target shifts (hard constraint)
# if 0 exactly equal shifts must be assigend (unlikely to be possible) # if 0 exactly equal shifts must be assigend (unlikely to be possible)
if balance_offset is None:
self.balance_offset = len(shift_days)
else:
self.balance_offset = balance_offset
# weight the shift for balancing, default is 1
self.balance_weighting = balance_weighting
self.hard_constrain_shift = hard_constrain_shift
self.workers_required = workers_required if self.balance_offset is None:
self.rota_on_nwds = rota_on_nwds self.balance_offset = len(self.shift_days)
self.force_as_block = force_as_block
self.force_as_block_unless_nwd = force_as_block_unless_nwd
if force_as_block_unless_nwd:
if self.force_as_block_unless_nwd:
self.assign_as_block = True self.assign_as_block = True
self.assign_as_block = assign_as_block #self.assign_as_block = assign_as_block
self.constraint_options = {} self.constraint_options = {}
self.constraints = [] self.constraints = []
for c in constraints: for c in self.constraint:
match c: match c:
case (constraint, options): case (constraint, options):
self.constraints.append(constraint) self.constraints.append(constraint)
@@ -125,9 +120,6 @@ class SingleShift(object):
case constraint: case constraint:
self.constraints.append(constraint) self.constraints.append(constraint)
self.bank_holidays_only = bank_holidays_only
# self.total_shifts = length * len(days) * len(shift_days)
def __str__(self): def __str__(self):
return f"name: {self.name}, site: {self.site}, length: {self.length}, balance weighting: {self.balance_weighting}, balance offset: {self.balance_offset}, hard_constrain: {self.hard_constrain_shift}, workers required : {self.workers_required}, rota on nwds {self.rota_on_nwds}, assign as block: {self.assign_as_block}, force as block: {self.force_as_block}, constraints: {self.constraints}, constraint_options: {self.constraint_options}" # , total shifts: {self.total_shifts}" return f"name: {self.name}, site: {self.site}, length: {self.length}, balance weighting: {self.balance_weighting}, balance offset: {self.balance_offset}, hard_constrain: {self.hard_constrain_shift}, workers required : {self.workers_required}, rota on nwds {self.rota_on_nwds}, assign as block: {self.assign_as_block}, force as block: {self.force_as_block}, constraints: {self.constraints}, constraint_options: {self.constraint_options}" # , total shifts: {self.total_shifts}"
@@ -154,6 +146,7 @@ class RotaBuilder(object):
use_previous_shifts: bool = False, use_previous_shifts: bool = False,
use_shift_balance_extra: bool = False, use_shift_balance_extra: bool = False,
use_bank_holiday_extra: bool = False, use_bank_holiday_extra: bool = False,
SHIFT_BOUNDS = SHIFT_BOUNDS
): ):
print(f"Start time: {datetime.datetime.now()}") print(f"Start time: {datetime.datetime.now()}")
@@ -161,6 +154,7 @@ class RotaBuilder(object):
print(f"Use previous shifts {use_previous_shifts}") print(f"Use previous shifts {use_previous_shifts}")
print(f"Use previous shifts (balance_extra) {use_shift_balance_extra}") print(f"Use previous shifts (balance_extra) {use_shift_balance_extra}")
self.SHIFT_BOUNDS = SHIFT_BOUNDS
self.shifts = [] # type List[SingleShift] self.shifts = [] # type List[SingleShift]
self.days = days self.days = days
@@ -341,7 +335,7 @@ class RotaBuilder(object):
((worker.id) for worker in self.workers), ((worker.id) for worker in self.workers),
within=NonNegativeIntegers, within=NonNegativeIntegers,
initialize=0, initialize=0,
bounds=SHIFT_BOUNDS["bank_holiday"], bounds=self.SHIFT_BOUNDS["bank_holiday"],
) )
self.model.bank_holiday_count_w = Var( self.model.bank_holiday_count_w = Var(
((worker.id) for worker in self.workers), ((worker.id) for worker in self.workers),
@@ -428,7 +422,7 @@ class RotaBuilder(object):
), ),
domain=NonNegativeReals, domain=NonNegativeReals,
initialize=0, initialize=0,
bounds=SHIFT_BOUNDS["shift_count"], bounds=self.SHIFT_BOUNDS["shift_count"],
) )
self.model.shift_count_diff = Var( self.model.shift_count_diff = Var(
@@ -501,7 +495,7 @@ class RotaBuilder(object):
((worker.id) for worker in self.workers), ((worker.id) for worker in self.workers),
domain=NonNegativeReals, domain=NonNegativeReals,
initialize=0, initialize=0,
bounds=SHIFT_BOUNDS["night_shift_count"], bounds=self.SHIFT_BOUNDS["night_shift_count"],
) )
self.model.night_shift_count_t1 = Var( self.model.night_shift_count_t1 = Var(
@@ -565,7 +559,7 @@ class RotaBuilder(object):
((worker.id) for worker in self.workers), ((worker.id) for worker in self.workers),
domain=NonNegativeIntegers, domain=NonNegativeIntegers,
initialize=0, initialize=0,
bounds=SHIFT_BOUNDS["weekend_count"], bounds=self.SHIFT_BOUNDS["weekend_count"],
) )
if self.constraint_options["balance_weekends"]: if self.constraint_options["balance_weekends"]:
+5 -5
View File
@@ -75,7 +75,7 @@ class TestBalancing:
balance_weighting=4, balance_weighting=4,
workers_required=1, workers_required=1,
force_as_block=False, force_as_block=False,
constraints=["preclear2", "postclear2"], constraint=["preclear2", "postclear2"],
), ),
SingleShift( SingleShift(
("group1", "group2"), ("group1", "group2"),
@@ -118,7 +118,7 @@ class TestBalancing:
#balance_weighting=4, #balance_weighting=4,
workers_required=1, workers_required=1,
force_as_block=False, force_as_block=False,
#constraints=["preclear2", "postclear2"], #constraint=["preclear2", "postclear2"],
), ),
SingleShift( SingleShift(
("group1", "group2"), ("group1", "group2"),
@@ -163,7 +163,7 @@ class TestBalancing:
#balance_weighting=4, #balance_weighting=4,
workers_required=1, workers_required=1,
force_as_block=False, force_as_block=False,
#constraints=["preclear2", "postclear2"], #constraint=["preclear2", "postclear2"],
), ),
SingleShift( SingleShift(
("group1", "group2"), ("group1", "group2"),
@@ -214,7 +214,7 @@ class TestBalancing:
balance_weighting=8, balance_weighting=8,
workers_required=2, workers_required=2,
force_as_block=False, force_as_block=False,
#constraints=["preclear2", "postclear2"], #constraint=["preclear2", "postclear2"],
), ),
SingleShift( SingleShift(
("group2","group3"), ("group2","group3"),
@@ -255,7 +255,7 @@ class TestBalancing:
#balance_weighting=8, #balance_weighting=8,
workers_required=2, workers_required=2,
force_as_block=False, force_as_block=False,
#constraints=["preclear2", "postclear2"], #constraint=["preclear2", "postclear2"],
), ),
SingleShift( SingleShift(
("group2","group3"), ("group2","group3"),
+5 -5
View File
@@ -37,7 +37,7 @@ class TestRemoteRotas:
workers_required=1, workers_required=1,
balance_offset=100, balance_offset=100,
force_as_block=False, force_as_block=False,
constraints=[("require_remote_site_presence_week", ("group2", 2))], constraint=[("require_remote_site_presence_week", ("group2", 2))],
), ),
) )
@@ -76,7 +76,7 @@ class TestRemoteRotas:
workers_required=1, workers_required=1,
balance_offset=10, balance_offset=10,
force_as_block=True, force_as_block=True,
constraints=[("require_remote_site_presence_week", ("group2", 1))], constraint=[("require_remote_site_presence_week", ("group2", 1))],
), ),
SingleShift( SingleShift(
("group1", "group2"), ("group1", "group2"),
@@ -86,7 +86,7 @@ class TestRemoteRotas:
workers_required=1, workers_required=1,
balance_offset=10, balance_offset=10,
force_as_block=False, force_as_block=False,
#constraints=[("require_remote_site_presence_week", ("group2", 1))], #constraint=[("require_remote_site_presence_week", ("group2", 1))],
#constraint=["balance_shifts_over_workers"] #constraint=["balance_shifts_over_workers"]
), ),
) )
@@ -142,7 +142,7 @@ class TestRemoteRotas:
workers_required=1, workers_required=1,
balance_offset=10, balance_offset=10,
force_as_block=False, force_as_block=False,
constraints=[("require_remote_site_presence", ("group2", 1))], constraint=[("require_remote_site_presence", ("group2", 1))],
), ),
SingleShift( SingleShift(
("group1", "group2"), ("group1", "group2"),
@@ -152,7 +152,7 @@ class TestRemoteRotas:
workers_required=1, workers_required=1,
balance_offset=10, balance_offset=10,
force_as_block=False, force_as_block=False,
#constraints=[("require_remote_site_presence_week", ("group2", 1))], #constraint=[("require_remote_site_presence_week", ("group2", 1))],
#constraint=["balance_shifts_over_workers"] #constraint=["balance_shifts_over_workers"]
), ),
) )
+27 -27
View File
@@ -228,8 +228,8 @@ class TestDemoRotaNights:
12.5, 12.5,
days[:4], days[:4],
workers_required=1, workers_required=1,
constraints=["preclear2", "postclear2"], constraint=["preclear2", "postclear2"],
# constraints=["night"], # constraint=["night"],
force_as_block=True force_as_block=True
# We use a different balance weighting for each shift as otherwise # We use a different balance weighting for each shift as otherwise
# the shifts can be treated equally (and no target diff) # the shifts can be treated equally (and no target diff)
@@ -241,8 +241,8 @@ class TestDemoRotaNights:
12.5, 12.5,
days[4:], days[4:],
# balance_weighting=0.5 # balance_weighting=0.5
constraints=["preclear2", "postclear2"], constraint=["preclear2", "postclear2"],
# constraints=["night"], # constraint=["night"],
force_as_block=True, force_as_block=True,
), ),
SingleShift( SingleShift(
@@ -252,12 +252,12 @@ class TestDemoRotaNights:
days[:5], days[:5],
# balance_weighting=0.5 # balance_weighting=0.5
workers_required=2, workers_required=2,
# constraints=["preclear2", "postclear2"], # constraint=["preclear2", "postclear2"],
), ),
# SingleShift(("group1","group2"), "atwilight", 12.5, days[:5], balance_offset=40, # SingleShift(("group1","group2"), "atwilight", 12.5, days[:5], balance_offset=40,
##balance_weighting=0.5 ##balance_weighting=0.5
# workers_required=2, # workers_required=2,
# #constraints=["postclear2"], # #constraint=["postclear2"],
# ), # ),
) )
@@ -329,7 +329,7 @@ class TestDemoRotaClear:
days[:5], days[:5],
balance_offset=40, balance_offset=40,
workers_required=2, workers_required=2,
constraints=["preclear"], constraint=["preclear"],
), ),
SingleShift( SingleShift(
("group1", "group2"), ("group1", "group2"),
@@ -346,7 +346,7 @@ class TestDemoRotaClear:
days[3], days[3],
balance_offset=40, balance_offset=40,
workers_required=1, workers_required=1,
constraints=["preclear2", "postclear2"], constraint=["preclear2", "postclear2"],
), ),
) )
@@ -486,7 +486,7 @@ class TestDemoRotaBalanceShiftSites:
balance_offset=40, balance_offset=40,
workers_required=2, workers_required=2,
force_as_block=True, force_as_block=True,
constraints=["balance_across_groups"], constraint=["balance_across_groups"],
), ),
) )
self.Rota.constraint_options["balance_nights_across_sites"] = False self.Rota.constraint_options["balance_nights_across_sites"] = False
@@ -522,7 +522,7 @@ class TestDemoRotaBalanceShiftSites:
balance_offset=40, balance_offset=40,
workers_required=3, workers_required=3,
force_as_block=True, force_as_block=True,
constraints=["balance_across_groups"], constraint=["balance_across_groups"],
), ),
) )
self.Rota.constraint_options["balance_nights_across_sites"] = False self.Rota.constraint_options["balance_nights_across_sites"] = False
@@ -556,7 +556,7 @@ class TestDemoRotaBalanceShiftSites:
balance_offset=40, balance_offset=40,
workers_required=3, workers_required=3,
force_as_block=True, force_as_block=True,
constraints=["balance_across_groups"], constraint=["balance_across_groups"],
), ),
) )
self.Rota.constraint_options["balance_nights_across_sites"] = False self.Rota.constraint_options["balance_nights_across_sites"] = False
@@ -614,7 +614,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=4, workers_required=4,
force_as_block=True, force_as_block=True,
constraints=[("limit_grade_number", {2: 1})], constraint=[("limit_grade_number", {2: 1})],
), ),
) )
# self.Rota.constraint_options["balance_nights_across_sites"] = False # self.Rota.constraint_options["balance_nights_across_sites"] = False
@@ -650,7 +650,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=4, workers_required=4,
force_as_block=True, force_as_block=True,
constraints=[("limit_grade_number", {3: 2})], constraint=[("limit_grade_number", {3: 2})],
), ),
) )
# self.Rota.constraint_options["balance_nights_across_sites"] = False # self.Rota.constraint_options["balance_nights_across_sites"] = False
@@ -688,7 +688,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=4, workers_required=4,
force_as_block=True, force_as_block=True,
constraints=[("limit_grade_number", {2: 3, 3: 1})], constraint=[("limit_grade_number", {2: 3, 3: 1})],
), ),
) )
# self.Rota.constraint_options["balance_nights_across_sites"] = False # self.Rota.constraint_options["balance_nights_across_sites"] = False
@@ -726,7 +726,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=4, workers_required=4,
force_as_block=True, force_as_block=True,
constraints=[("limit_grade_number", {2: 4, 3: 0})], constraint=[("limit_grade_number", {2: 4, 3: 0})],
), ),
) )
# self.Rota.constraint_options["balance_nights_across_sites"] = False # self.Rota.constraint_options["balance_nights_across_sites"] = False
@@ -747,7 +747,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=1, workers_required=1,
force_as_block=True, force_as_block=True,
constraints=[("minimum_grade_number", (3, 1))], constraint=[("minimum_grade_number", (3, 1))],
), ),
) )
@@ -783,7 +783,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=4, workers_required=4,
force_as_block=True, force_as_block=True,
constraints=[("minimum_grade_number", (3, 3))], constraint=[("minimum_grade_number", (3, 3))],
), ),
) )
@@ -819,7 +819,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=4, workers_required=4,
force_as_block=True, force_as_block=True,
constraints=[("minimum_grade_number", (3, 0))], constraint=[("minimum_grade_number", (3, 0))],
), ),
) )
@@ -841,7 +841,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=4, workers_required=4,
force_as_block=True, force_as_block=True,
constraints=[("minimum_grade_number", (4, 1))], constraint=[("minimum_grade_number", (4, 1))],
), ),
) )
@@ -860,7 +860,7 @@ class TestLimitConstraints:
balance_offset=40, balance_offset=40,
workers_required=2, workers_required=2,
force_as_block=True, force_as_block=True,
constraints=[("require_remote_site_presence_week", ("group1", 2))], constraint=[("require_remote_site_presence_week", ("group1", 2))],
), ),
) )
self.Rota.build_and_solve(options={"ratio": 0.00}) self.Rota.build_and_solve(options={"ratio": 0.00})
@@ -898,7 +898,7 @@ class TestLimitConstraints:
balance_offset=20, balance_offset=20,
workers_required=3, workers_required=3,
force_as_block=True, force_as_block=True,
constraints=[("require_remote_site_presence_week", ("group2", 2))], constraint=[("require_remote_site_presence_week", ("group2", 2))],
), ),
) )
self.Rota.build_and_solve(options={"ratio": 0.00}) self.Rota.build_and_solve(options={"ratio": 0.00})
@@ -930,7 +930,7 @@ class TestLimitConstraints:
balance_offset=20, balance_offset=20,
workers_required=3, workers_required=3,
force_as_block=True, force_as_block=True,
constraints=[("require_remote_site_presence_week", ("group2", 1))], constraint=[("require_remote_site_presence_week", ("group2", 1))],
), ),
) )
self.Rota.build_and_solve(options={"ratio": 0.00}) self.Rota.build_and_solve(options={"ratio": 0.00})
@@ -995,7 +995,7 @@ class TestNightUnavailable:
# Add a weekday and weekend shift # Add a weekday and weekend shift
self.Rota.add_shifts( self.Rota.add_shifts(
SingleShift( SingleShift(
("group1",), "night_weekend", 12.5, days[5:], constraints=["night"] ("group1",), "night_weekend", 12.5, days[5:], constraint=["night"]
), ),
) )
@@ -1018,7 +1018,7 @@ class TestNightUnavailable:
# Add a weekday and weekend shift # Add a weekday and weekend shift
self.Rota.add_shifts( self.Rota.add_shifts(
SingleShift( SingleShift(
("group1",), "night_weekend", 12.5, days[5:], workers_required=2, constraints=["night"] ("group1",), "night_weekend", 12.5, days[5:], workers_required=2, constraint=["night"]
), ),
) )
@@ -1032,7 +1032,7 @@ class TestNightUnavailable:
self.Rota.add_shifts( self.Rota.add_shifts(
SingleShift( SingleShift(
("group1",), "night_weekend", 12.5, days[5:], workers_required=2, constraints=[] ("group1",), "night_weekend", 12.5, days[5:], workers_required=2, constraint=[]
), ),
) )
@@ -1053,7 +1053,7 @@ class TestNightUnavailable:
self.Rota.add_shifts( self.Rota.add_shifts(
SingleShift( SingleShift(
("group1",), "night_weekend", 12.5, days[5:], workers_required=3, constraints=[] ("group1",), "night_weekend", 12.5, days[5:], workers_required=3, constraint=[]
), ),
) )
@@ -1079,7 +1079,7 @@ class TestNightUnavailable:
# #
# self.Rota.add_shifts( # self.Rota.add_shifts(
# SingleShift( # SingleShift(
# ("group1",), "night_weekend", 12.5, days[5:], workers_required=3, constraints=["night"] # ("group1",), "night_weekend", 12.5, days[5:], workers_required=3, constraint=["night"]
# ), # ),
# ) # )
# #