This commit is contained in:
Ross
2021-06-24 21:02:18 +01:00
parent 01005176a5
commit ab73ead7c1
8 changed files with 689 additions and 545 deletions
+71 -26
View File
@@ -132,6 +132,7 @@ class RotaBuilder(object):
self.work_requests_map = {}
self.workers = []
self.worker_pairs = []
self.night_blocks = ["weekday", "weekend", "none"]
@@ -156,11 +157,13 @@ class RotaBuilder(object):
"balance_shifts": True,
"balance_weekends": True,
"max_weekends": False,
"max_shifts_per_week": 5,
"prevent_monday_after_full_weekends": [],
"prevent_monday_and_tuesday_after_full_weekends": [],
"prevent_fridays_before_full_weekends": [],
"prevent_thursdays_before_full_weekends": [],
"avoid_st2_first_month": False,
"hard_constrain_pair_separation": False,
}
# Generate a map for week, day combinations to a given date
@@ -843,10 +846,13 @@ class RotaBuilder(object):
# Ensure worker is not allocated shifts on non working days
if worker.nwd:
for week, day, shift in self.get_all_shiftclass_combinations():
if not shift.rota_on_nwds and day in worker.nwd:
self.model.constraints.add(
0 == self.model.works[worker.id, week, day,
shift.name])
for n, start, end in worker.nwd:
if not shift.rota_on_nwds and day == n:
print(start, self.week_day_date_map[(week, day)], end)
if start <= self.week_day_date_map[(week, day)] <= end:
self.model.constraints.add(
0 == self.model.works[worker.id, week, day,
shift.name])
if self.constraint_options["balance_blocks"]:
for week_blocks in self.get_week_block_iterator(
@@ -869,8 +875,13 @@ class RotaBuilder(object):
for week in week_blocks))
for week in self.weeks:
self.model.constraints.add(self.constraint_options["max_shifts_per_week"] >= sum(
self.model.works[worker.id, week, day, shift.name]
for day in self.days for shift in self.get_shifts()))
for shift in self.get_shifts_with_constraint(
"max_2_consecutive_shifts_per_week"):
"max_2_shifts_per_week"):
self.model.constraints.add(2 >= sum(
self.model.works[worker.id, week, day, shift.name]
for day in self.days))
@@ -1039,8 +1050,12 @@ class RotaBuilder(object):
self.model.works[worker.id, week, day, shift]
for shift in self.get_shift_names_by_week_day(week, day)))
# if working a night ensure preceeding (1) or subsequent (2) shifts can only be nights
if self.constraint_options["hard_constrain_pair_separation"]:
for worker_pairs in self.worker_pairs:
if worker_pairs[0] == worker:
self.model.constraints.add(1 >= sum(self.model.works[w.id, week, day, shift] for shift in self.get_shift_names_by_week_day(week, day) for w in worker_pairs))
# if working a night ensure preceeding (1) or subsequent (2) shifts can only be nights
if self.constraint_options["constrain_time_off_after_nights"]:
for constraint_shift in self.get_shifts_with_constraint(
"night"):
@@ -1050,21 +1065,31 @@ class RotaBuilder(object):
self.model.works[worker.id, pweek, pday,
constraint_shift.name])
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(n1 *
self.model.works[worker.id, nweek, nday, shift]
for shift in self.get_shift_names_by_week_day(nweek, nday)
if shift != constraint_shift.name))
# IF paired we check the following against both workers
workers = [worker]
if self.constraint_options["hard_constrain_pair_separation"]:
for worker_pairs in self.worker_pairs:
if worker_pairs[0] == worker:
workers = worker_pairs
print("Workers", workers)
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(n2 * self.model.works[worker.id, n2week, n2day,
sum(n1 *
self.model.works[w.id, nweek, nday, shift]
for shift in self.get_shift_names_by_week_day(nweek, nday)
if shift != constraint_shift.name for w in workers))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(n2 * self.model.works[w.id, n2week, n2day,
shift]
for shift in self.get_shift_names_by_week_day(n2week, n2day)
if shift != constraint_shift.name))
if shift != constraint_shift.name for w in workers))
# self.model.constraints.add(
# 1 >= self.model.works[worker.id, week, day,
# constraint_shift.name] +
@@ -1076,16 +1101,16 @@ class RotaBuilder(object):
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(p1 *
self.model.works[worker.id, pweek, pday, shift]
self.model.works[w.id, pweek, pday, shift]
for shift in self.get_shift_names_by_week_day(pweek, pday)
if shift != constraint_shift.name))
if shift != constraint_shift.name for w in workers))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(p2 * self.model.works[worker.id, p2week, p2day,
sum(p2 * self.model.works[w.id, p2week, p2day,
shift]
for shift in self.get_shift_names_by_week_day(p2week, p2day)
if shift != constraint_shift.name))
if shift != constraint_shift.name for w in workers))
self.define_objectives()
@@ -1173,6 +1198,16 @@ class RotaBuilder(object):
else:
blocks_balancing = 0
# Quadratic :(
#worker_pairs_balancing = 0
#worker_pairs_constant = 100000
#if self.worker_pairs:
# for worker_a, worker_b in self.worker_pairs:
# print(worker_a, worker_b)
# for week, day in self.get_week_day_combinations():
# worker_pairs_balancing = worker_pairs_balancing + sum(self.model.works[worker_a.id, week, day, shift] for shift in self.get_shift_names_by_week_day(week, day)) * sum(self.model.works[worker_b.id, week, day, shift] for shift in self.get_shift_names_by_week_day(week, day)) * worker_pairs_constant
#return weekend_shift_balancing + blocks_balancing
#return bank_holiday_balancing
#return shift_balancing + preferences + blocks_balancing
@@ -1216,6 +1251,15 @@ class RotaBuilder(object):
self.full_time_equivalent_sites[site] = sum(
[w.fte_adj for w in self.workers if w.site == site])
pairs = defaultdict(list)
for w in self.workers:
if w.pair > 0:
pairs[w.pair].append(w)
for p in pairs:
self.worker_pairs.append(tuple(pairs[p]))
def add_shift(self, shift):
"""Add a shift to the collection
@@ -1625,7 +1669,8 @@ class RotaResults(object):
shifts = []
if worker.nwd is not None:
nwds = ", ".join(worker.nwd)
# TODO: limit to dates
nwds = ", ".join([i[0] for i in worker.nwd])
else:
nwds = None
@@ -1691,12 +1736,12 @@ class RotaResults(object):
shift_count_dict[s] = c
shift_count = shift_count + "{}: {}, ".format(s, c)
worker_td = "<td title='Site: {}' class='worker {}' data-nwds='{}' data-site='{}' data-worker='{}' data-fte='{}' data-fte_adj='{}' data-end_date='{}' data-worker-targets='{}' data-shift-counts='{}' data-weekend-target='{}' data-night-at-derriford='{}'><span class='name' title='{}'>{}</span> ({}) [{}]</td>".format(
worker.site, worker.site, nwds, worker.site, worker.name,
worker.fte, worker.fte_adj, worker.end_date, worker_targets,
json.dumps(shift_count_dict),
worker.weekend_shift_target_number, worker.night_at_derriford, worker.name, worker.name, worker.grade,
worker.fte)
worker_td = "<td title='Site: {site}' class='worker {site}' data-nwds='{nwds}' data-site='{site}' data-worker='{name}' data-fte='{fte}' data-fte_adj='{fte_adj}' data-end_date='{end_date}' data-worker-targets='{targets}' data-shift-counts='{shift_counts}' data-weekend-target='{weekend_target}' data-night-at-derriford='{nights_at_derriford}' data-pair='{pair}'><span class='name' title='{name}'>{name}</span> ({grade}) [{fte}]</td>".format(
site=worker.site, nwds=nwds, name=worker.name,
fte=worker.fte, fte_adj=worker.fte_adj, end_date=worker.end_date, targets=worker_targets,
shift_counts=json.dumps(shift_count_dict),
weekend_target=worker.weekend_shift_target_number, nights_at_derriford=worker.night_at_derriford, grade=worker.grade, pair=worker.pair
)
#print(shift_count_dict)