add bank holiday shifts
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user