.
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user