add bank holiday shifts

This commit is contained in:
Ross
2021-06-15 22:39:09 +01:00
parent ffb63a3978
commit 01005176a5
6 changed files with 143 additions and 159 deletions
+74 -55
View File
@@ -50,6 +50,7 @@ class SingleShift(object):
assign_as_block=False,
force_as_block=False,
hard_constrain_shift=True,
bank_holidays_only=False,
constraints=[],
):
self.site = sites
@@ -71,11 +72,13 @@ class SingleShift(object):
self.assign_as_block = assign_as_block
self.force_as_block = force_as_block
self.constraints = constraints
self.bank_holidays_only = bank_holidays_only
self.total_shifts = length * len(days) * len(shift_days)
#self.total_shifts = length * len(days) * len(shift_days)
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}, 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}"#, total shifts: {self.total_shifts}"
def get_shift_summary(self):
"""
@@ -456,7 +459,7 @@ class RotaBuilder(object):
workers_required,
site_required,
) in self.get_required_workers_and_site_combinations():
print(week, day, shift, workers_required, site_required)
#print(week, day, shift, workers_required, site_required)
self.model.constraints.add(workers_required == sum(
self.model.works[worker.id, week, day, shift]
for worker in self.workers if worker.site in site_required))
@@ -487,24 +490,26 @@ class RotaBuilder(object):
# Define a maximum number hours that can be worked per week
# NOTE: this works on a calander week not a 7 day period
def maxHoursPerWeekRule(model, wid, week):
return 72 >= sum(
shift.length * model.works[wid, week, day, shift.name]
for day, shift in self.get_day_shiftclass_combinations())
# TODO: consider fixing
#def maxHoursPerWeekRule(model, wid, week):
# return 72 >= sum(
# shift.length * model.works[wid, week, day, shift.name]
# for day, shift in self.get_day_shiftclass_combinations())
self.model.max_hours_per_week_constraint = Constraint(
[worker.id for worker in self.workers],
self.weeks,
rule=maxHoursPerWeekRule)
#self.model.max_hours_per_week_constraint = Constraint(
# [worker.id for worker in self.workers],
# self.weeks,
# rule=maxHoursPerWeekRule)
# Define an upper limit for hours worked over the rota period
def maxHoursRule(model, wid):
return 8000 >= sum(
shift.length * model.works[wid, week, day, shift.name]
for week, day, shift in self.get_all_shiftclass_combinations())
#def maxHoursRule(model, wid):
# return 8000 >= sum(
# shift.length * model.works[wid, week, day, shift.name]
# for week, day, shift in self.get_all_shiftclass_combinations())
self.model.max_hours_constraint = Constraint(
[worker.id for worker in self.workers], rule=maxHoursRule)
#self.model.max_hours_constraint = Constraint(
# [worker.id for worker in self.workers], rule=maxHoursRule)
# Limit to 1 ST2 (or below) on a night shift
def nightShiftMaxSTRule(model, week, shift):
@@ -643,8 +648,8 @@ class RotaBuilder(object):
): # Each site specfies which sites self.workers can fullfill it
# calaculate the total number of shifts that need assigning over the rota
total_shifts = (len(self.weeks) * len(shift.shift_days) *
shift.workers_required)
#total_shifts = (len(self.weeks) * len(shift.shift_days) *
total_shifts = self.shift_counts[shift] * shift.workers_required
full_time_equivalent_joined = sum([
self.full_time_equivalent_sites[i] for i in shift.site
@@ -659,7 +664,7 @@ class RotaBuilder(object):
worked, allocated = worker.previous_shifts[shift.name]
target_shifts = target_shifts + float(allocated) - float(worked)
print(worker.name, shift.name, target_shifts)
worker.shift_target_number[shift.name] = target_shifts
if shift.hard_constrain_shift:
@@ -1022,17 +1027,17 @@ class RotaBuilder(object):
n3week, n3day = weeks_days[n]
n3 = 0
if self.get_shift_names_by_day(day):
if self.get_shift_names_by_week_day(week, day):
# Unable to work (hard constraint not preference)
self.model.constraints.add(
self.model.available[worker.id, week, day] >= sum(
self.model.works[worker.id, week, day, shift]
for shift in self.get_shift_names_by_day(day)))
for shift in self.get_shift_names_by_week_day(week, day)))
# single shift per day
self.model.constraints.add(1 >= sum(
self.model.works[worker.id, week, day, shift]
for shift in self.get_shift_names_by_day(day)))
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
@@ -1050,7 +1055,7 @@ class RotaBuilder(object):
constraint_shift.name] +
sum(n1 *
self.model.works[worker.id, nweek, nday, shift]
for shift in self.get_shift_names_by_day(nday)
for shift in self.get_shift_names_by_week_day(nweek, nday)
if shift != constraint_shift.name))
self.model.constraints.add(
@@ -1058,28 +1063,28 @@ class RotaBuilder(object):
constraint_shift.name] +
sum(n2 * self.model.works[worker.id, n2week, n2day,
shift]
for shift in self.get_shift_names_by_day(n2day)
for shift in self.get_shift_names_by_week_day(n2week, n2day)
if shift != constraint_shift.name))
# self.model.constraints.add(
# 1 >= self.model.works[worker.id, week, day,
# constraint_shift.name] +
# sum(n3 * self.model.works[worker.id, n3week, n3day,
# shift]
# for shift in self.get_shift_names_by_day(n3day)
# for shift in self.get_shift_names_by_week_day(week, n3day)
# if shift != constraint_shift.name))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(p1 *
self.model.works[worker.id, pweek, pday, shift]
for shift in self.get_shift_names_by_day(pday)
for shift in self.get_shift_names_by_week_day(pweek, pday)
if shift != constraint_shift.name))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(p2 * self.model.works[worker.id, p2week, p2day,
shift]
for shift in self.get_shift_names_by_day(p2day)
for shift in self.get_shift_names_by_week_day(p2week, p2day)
if shift != constraint_shift.name))
self.define_objectives()
@@ -1227,14 +1232,14 @@ class RotaBuilder(object):
"""
self.shifts.extend(shifts)
def get_shift_names_by_day(self, day: DayStr):
def get_shift_names_by_week_day(self, week, day: DayStr):
"""Returns the shifts required for a specific day
Returns:
set: Set of ShiftName
"""
return self.day_shifts_dict[day]
return self.week_day_shifts_dict[(week, day)]
def get_shift_by_name(self, name):
"""
@@ -1260,28 +1265,42 @@ class RotaBuilder(object):
self.shifts_by_name = {}
self.shift_names = [] # type: List[ShiftName]
self.day_shifts_dict = {day: set() for day in days}
#self.week_day_shifts_dict = {(week, day): set() for week in self.weeks for day in days}
self.week_day_shifts_dict = {}
self.sites = set()
self.week_day_shift_product = []
self.week_day_shiftclass_product = []
self.shift_counts = defaultdict(int)
for week, day in self.weeks_days_product:
self.week_day_shifts_dict[(week, day)] = set()
for s in self.shifts:
if s.bank_holidays_only:
if self.week_day_date_map[(week, day)] in bank_holiday_map:
self.week_day_shift_product.append((week, day, s.name))
self.week_day_shiftclass_product.append((week, day, s))
self.week_day_shifts_dict[(week, day)].add(s.name)
self.shift_counts[s] = self.shift_counts[s] + 1
else:
if day in s.shift_days:
self.week_day_shift_product.append((week, day, s.name))
self.week_day_shiftclass_product.append((week, day, s))
self.week_day_shifts_dict[(week, day)].add(s.name)
self.shift_counts[s] = self.shift_counts[s] + 1
#print(self.week_day_shift_product)
for s in self.shifts:
self.shifts_by_name[s.name] = s
self.shift_names.append(s.name)
for day in s.shift_days:
self.day_shifts_dict[day].add(s.name)
#for day in s.shift_days:
# self.week_day_shifts_dict[(week, day)].add(s.name)
for site in s.site:
self.sites.add(site)
self.week_day_shift_product = []
self.week_day_shiftclass_product = []
for week, day in self.weeks_days_product:
for s in self.shifts:
if day in s.shift_days:
self.week_day_shift_product.append((week, day, s.name))
self.week_day_shiftclass_product.append((week, day, s))
self.sites.add(site)
# Todo replace with week_day.....
self.day_shift_product = []
self.day_shiftclass_product = []
for s in self.shifts:
@@ -1384,7 +1403,7 @@ class RotaBuilder(object):
"""
l = []
for week, day, shift in self.week_day_shiftclass_product:
if day in shift.shift_days:
#if day in shift.shift_days:
l.append((week, day, shift.name, shift.workers_required,
shift.site))
@@ -1429,7 +1448,7 @@ class RotaBuilder(object):
w = defaultdict(list)
for worker in self.workers:
print(worker)
#print(worker)
w[worker.site].append(worker)
l = []
@@ -1474,7 +1493,7 @@ class RotaResults(object):
for worker in self.rota.workers:
i = ""
for shift in self.rota.get_shift_names_by_day(day):
for shift in self.rota.get_shift_names_by_week_day(week, day):
if works[worker.id, week, day, shift].value == 1:
i = shift
@@ -1531,7 +1550,7 @@ class RotaResults(object):
w = ["{:20}".format(worker.name)]
for week, day in self.rota.get_week_day_combinations():
a = "-"
for shift in self.rota.get_shift_names_by_day(day):
for shift in self.rota.get_shift_names_by_week_day(week, day):
if model.works[worker.id, week, day, shift].value == 1:
shifts.append(shift)
a = shift[0]
@@ -1624,7 +1643,7 @@ class RotaResults(object):
a = "-"
shift_name = ""
for shift in self.rota.get_shift_names_by_day(day):
for shift in self.rota.get_shift_names_by_week_day(week, day):
if model.works[worker.id, week, day, shift].value == 1:
shifts.append(shift)
a = shift[0]
@@ -1673,13 +1692,13 @@ class RotaResults(object):
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.name,
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.grade,
worker.weekend_shift_target_number, worker.night_at_derriford, worker.name, worker.name, worker.grade,
worker.fte)
print(shift_count_dict)
#print(shift_count_dict)
shift_count = shift_count + "#weekends_worked: {}\\#".format(
model.worker_weekend_count[worker.id].value)
@@ -1687,7 +1706,7 @@ class RotaResults(object):
bank_holiday_count = model.bank_holiday_count[worker.id].value
bank_holiday_count_w = model.bank_holiday_count_w[
worker.id].value - 1
print(worker.name, bank_holiday_count, bank_holiday_count_w)
#print(worker.name, bank_holiday_count, bank_holiday_count_w)
timetable.append("<tr class='worker-row'>{}{}</tr>".format(
worker_td, "".join(shift_tds)))
#timetable.append("<tr>{}</tr>".format("".join(w), shift_count))
@@ -1837,14 +1856,13 @@ class RotaResults(object):
site].value,
self.rota.model.night_per_site_t2[week, block,
site].value)
"""
def get_night_details(self):
for worker in self.rota.workers:
print(
"{:20}".format(worker.name),
"worked: {},".format(
self.rota.model.night_shift_count[(worker.id)].value),
"target: {},".format(
worker.shift_target_number["night_weekday"] +
worker.shift_target_number["night_weekend"]),
"target_diff: {},".format(
@@ -1854,6 +1872,7 @@ class RotaResults(object):
"balance: {},".format(
self.rota.model.night_shift_count_w[worker.id].value),
)
"""
def get_weekend_details(self):
for worker in self.rota.workers: