it works?

This commit is contained in:
Ross
2020-05-14 23:40:48 +01:00
parent 0ac1990cfd
commit 12f7b5270c
13 changed files with 1657 additions and 289 deletions
+418 -198
View File
@@ -37,6 +37,7 @@ class SingleShift(object):
rota_on_nwds=False,
assign_as_block=False,
force_as_block=False,
hard_constrain_shift=True,
constraints=[],
):
self.site = sites
@@ -51,6 +52,7 @@ class SingleShift(object):
# 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
self.rota_on_nwds = rota_on_nwds
@@ -75,7 +77,10 @@ class RotaBuilder(object):
def __init__(self,
start_date,
weeks_to_rota=26,
balance_offset_modifier=1):
balance_offset_modifier=1,
ltft_balance_offset=4,
max_night_frequency=2,
max_weekend_frequency=2):
self.shifts = [] # type List[SingleShift]
self.days = days
@@ -99,6 +104,22 @@ class RotaBuilder(object):
self.sites = None
self.balance_offset_modifier = balance_offset_modifier
self.ltft_balance_offset = ltft_balance_offset
# Don't assign multiple shifts every (n) weeks
self.max_night_frequency = max_night_frequency
self.max_weekend_frequency = max_weekend_frequency
self.constraint_options = {
"limit_to_1_st1_on_nights": True,
"ensure_1_st4_plus_on_nights": True,
"balance_nights": True,
"constrain_time_off_after_nights": True,
"balance_nights_across_sites": True,
"balance_blocks": True,
"balance_shifts": True,
"balance_weekends": True,
}
def build_model(self):
# Initialize model
@@ -115,12 +136,12 @@ class RotaBuilder(object):
)
# The nights self.model is used to ensure nights are assigned as a block (and limit the number of workers required)
self.model.nights = Var(
((worker.id, week, block) for worker in self.workers
for week in self.weeks for block in self.night_blocks),
within=Binary,
initialize=0,
)
# self.model.nights = Var(
# ((worker.id, week, block) for worker in self.workers
# for week in self.weeks for block in self.night_blocks),
# within=Binary,
# initialize=0,
# )
# The nights self.model is used to ensure nights are assigned as a block (and limit the number of workers required)
self.model.shift_week_worker_assigned = Var(
@@ -156,26 +177,30 @@ class RotaBuilder(object):
# Used to limit number of workers on night shift per site
# if we force a binary it will in effect hard constrain to < 2
# from a single site
self.model.night_per_site = Var(
((week, block, site) for site in self.sites for week in self.weeks
for block in ["weekday", "weekend"]),
#within=Binary,
within=NonNegativeIntegers,
initialize=0,
)
if self.constraint_options["balance_nights_across_sites"]:
self.model.night_per_site = Var(
((week, shift.name, site) for site in self.sites
for week in self.weeks
for shift in self.get_shifts_with_constraint("night")),
#within=Binary,
within=NonNegativeIntegers,
initialize=0,
)
self.model.night_per_site_t1 = Var(
((week, block, site) for site in self.sites for week in self.weeks
for block in ["weekday", "weekend"]),
domain=NonNegativeIntegers,
initialize=0,
)
self.model.night_per_site_t2 = Var(
((week, block, site) for site in self.sites for week in self.weeks
for block in ["weekday", "weekend"]),
domain=NonNegativeIntegers,
initialize=0,
)
self.model.night_per_site_t1 = Var(
((week, shift.name, site) for site in self.sites
for week in self.weeks
for shift in self.get_shifts_with_constraint("night")),
domain=NonNegativeIntegers,
initialize=0,
)
self.model.night_per_site_t2 = Var(
((week, shift.name, site) for site in self.sites
for week in self.weeks
for shift in self.get_shifts_with_constraint("night")),
domain=NonNegativeIntegers,
initialize=0,
)
self.model.shift_count = Var(
((worker.id, shift) for worker in self.workers
@@ -196,6 +221,84 @@ class RotaBuilder(object):
initialize=0,
)
if self.constraint_options["balance_nights"]:
# We also try to even out the night shifts seperately
self.model.night_shift_count = Var(
((worker.id) for worker in self.workers),
domain=NonNegativeReals,
initialize=0,
)
self.model.night_shift_count_t1 = Var(
((worker.id) for worker in self.workers),
domain=NonNegativeReals,
initialize=0,
)
self.model.night_shift_count_t2 = Var(
((worker.id) for worker in self.workers),
domain=NonNegativeReals,
initialize=0,
)
self.model.worker_weekend_assigned = Var(
((worker.id, week) for worker in self.workers
for week in self.weeks),
domain=NonNegativeReals,
initialize=0,
)
self.model.works_weekend_count = Var(
((worker.id, week) for worker in self.workers
for week in self.weeks),
domain=NonNegativeIntegers,
initialize=0,
)
# self.model.works_saturday = Var(
# ((worker.id, week) for worker in self.workers
# for week in self.weeks),
# domain=Binary,
# initialize=0,
# )
# self.model.works_sunday = Var(
# ((worker.id, week) for worker in self.workers
# for week in self.weeks),
# domain=Binary,
# initialize=0,
# )
# self.model.works_whole_weekend = Var(
# ((worker.id, week) for worker in self.workers
# for week in self.weeks),
# domain=Binary,
# initialize=0,
# )
self.model.works_weekend = Var(
((worker.id, week) for worker in self.workers
for week in self.weeks),
domain=Binary,
initialize=0,
)
self.model.worker_weekend_count = Var(
((worker.id) for worker in self.workers),
domain=NonNegativeIntegers,
initialize=0,
)
# self.model.weekend_count_t1 = Var(
# ((worker.id) for worker in self.workers),
# domain=NonNegativeReals,
# initialize=0,
# )
# self.model.weekend_count_t2 = Var(
# ((worker.id) for worker in self.workers),
# domain=NonNegativeReals,
# initialize=0,
# )
# self.model.unavailable = Var(((worker, week, day) for worker in self.workers for week in weeks for day in days),
# within=Binary, initialize=0)
@@ -295,86 +398,62 @@ class RotaBuilder(object):
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, block):
# """Limits to 1 ST2 (or below) per night"""
# single_workers = [w for w in self.workers if w.grade < 3]
# if not single_workers:
# return Constraint.Skip
# return sum(model.nights[w.id, week, block]
# for w in single_workers) <= 1
# # self.model.night_shifts_max_st_constraint = Constraint(
# # [week for week in self.weeks],
# # [block for block in ["weekday", "weekend"]],
# # rule=nightShiftMaxSTRule,
# # )
# Limit to 1 ST2 (or below) on a night shift
def nightShiftMaxSTRule_new(model, week, day, shift):
def nightShiftMaxSTRule(model, week, shift):
"""Limits to 1 ST2 (or below) per night"""
single_workers = [w for w in self.workers if w.grade < 3]
if not single_workers:
return Constraint.Skip
return sum(model.works[w.id, week, day, shift]
return sum(model.shift_week_worker_assigned[shift, week, w.id]
for w in single_workers) <= 1
self.model.night_shifts_max_st_constraint = Constraint(
[week for week in self.weeks],
[day for day in self.days],
[shift.name for shift in self.get_shifts_with_constraint("night")],
rule=nightShiftMaxSTRule_new,
)
if self.constraint_options["limit_to_1_st1_on_nights"]:
self.model.night_shifts_max_st_constraint = Constraint(
[week for week in self.weeks],
[
shift.name
for shift in self.get_shifts_with_constraint("night")
],
rule=nightShiftMaxSTRule,
)
# Enusre at least 1 ST4+ on a night shift
def nightShiftMinST4Rule(model, week, block):
def nightShiftMinST4Rule(model, week, shift):
single_workers = [w for w in self.workers if w.grade > 3]
if not single_workers:
print(single_workers)
return Constraint.Skip
return sum(model.nights[w.id, week, block]
for w in single_workers) >= 1
return sum(model.shift_week_worker_assigned[shift, week, w.id]
for w in single_workers) >= 2
self.model.night_shifts_min_st4_constraint = Constraint(
[week for week in self.weeks],
[block for block in ["weekday", "weekend"]],
rule=nightShiftMinST4Rule,
)
if self.constraint_options["ensure_1_st4_plus_on_nights"]:
self.model.night_shifts_min_st4_constraint = Constraint(
[week for week in self.weeks],
[
shift.name
for shift in self.get_shifts_with_constraint("night")
],
rule=nightShiftMinST4Rule,
)
# #Constraint (def of self.model.needed)
# for worker in self.workers:
# self.model.constraints.add(
# 10000 * self.model.needed[worker.id] >= sum(
# self.model.works[worker.id, week, day, shift] for week,
# day, shift in self.get_all_shiftname_combinations())
# ) # if any self.model.works[worker, ·, ·] non-zero, self.model.needed[worker] must be one; else is zero to reduce the obj function
# #10000 is to remark, but 5 was enough since max of 40 hours yields max of 5 shifts, the maximum possible sum
# #Constraint (def of self.model.no_pref)
# for worker in self.workers:
# for week in weeks:
# self.model.constraints.add(
# self.model.no_pref[worker.id] >=
# sum(self.model.works[worker.id, week, 'Sat', shift]
# for shift in self.get_shift_names_by_day("Sat")) -
# sum(self.model.works[worker.id, week, 'Sun', shift]
# for shift in self.get_shift_names_by_day("Sun"))
# ) # if not working on sunday but working saturday self.model.needed must be 1; else will be zero to reduce the obj function
# Count the number of workers from each site on each night shift
# As 1 or 0 is optimum we can simply subtract 1 from the number
for site in self.sites:
for block in ("weekend", "weekday"):
for week in self.weeks:
self.model.constraints.add(
self.model.night_per_site[week, block, site] == sum(
self.model.nights[worker.id, week, block]
for worker in self.workers if worker.site == site)
#1 >= sum(self.model.nights[worker.id, week, block] for worker in self.workers if worker.site == site)
)
self.model.constraints.add(
self.model.night_per_site_t1[week, block, site] -
self.model.night_per_site_t2[week, block, site] ==
self.model.night_per_site[week, block, site] - 1)
# # Count the number of workers from each site on each night shift
# # As 1 or 0 is optimum we can simply subtract 1 from the number
if self.constraint_options["balance_nights_across_sites"]:
for site in self.sites:
for shift in self.get_shifts_with_constraint("night"):
block = shift.name
for week in self.weeks:
self.model.constraints.add(
self.model.night_per_site[week, block, site] ==
sum(self.model.shift_week_worker_assigned[
block, week, worker.id]
for worker in self.workers
if worker.site == site)
#1 >= sum(self.model.shift_week_worker_assigned[shift.name, week, worker.id] for worker in self.workers if worker.site == site)
)
self.model.constraints.add(
self.model.night_per_site_t1[week, block, site] -
self.model.night_per_site_t2[week, block, site] ==
self.model.night_per_site[week, block, site] - 1)
weeks_days = self.get_week_day_combinations()
@@ -396,7 +475,6 @@ class RotaBuilder(object):
for worker in self.workers))
if shift.force_as_block:
# Force nights to be assigned in blocks
self.model.constraints.add(
self.model.worker_block_shift_counts[worker.id, week,
shift.name] ==
@@ -412,6 +490,13 @@ class RotaBuilder(object):
# Most of our constraints apply per worker
for worker in self.workers:
# Count number of weekends an worker works
if self.constraint_options["balance_weekends"]:
self.model.constraints.add(
self.model.worker_weekend_count[worker.id] == sum(
self.model.works_weekend[worker.id, week]
for week in self.weeks))
# Balance shifts within required limits (set by balance_offset)
# If balance_offset is too restrictive (for the rota length)
# no solution will be possible
@@ -433,18 +518,26 @@ class RotaBuilder(object):
worker.fte_adj)
worker.shift_target_number[shift.name] = target_shifts
if worker.name == "Jean Sukumar":
print(worker.name, shift.name, target_shifts,
total_shifts, worker.fte_adj)
max_shifts = target_shifts + shift.balance_offset * self.balance_offset_modifier
min_shifts = target_shifts - shift.balance_offset * self.balance_offset_modifier
if shift.hard_constrain_shift:
adjusted_balance_offset = shift.balance_offset
if worker.fte_adj < 100:
adjusted_balance_offset = adjusted_balance_offset * self.ltft_balance_offset
self.model.constraints.add(
inequality(
min_shifts,
sum(self.model.works[worker.id, week, day,
shift.name] for week, day in
self.get_week_day_combinations()),
max_shifts,
))
max_shifts = target_shifts + adjusted_balance_offset * self.balance_offset_modifier
min_shifts = target_shifts - adjusted_balance_offset * self.balance_offset_modifier
self.model.constraints.add(
inequality(
min_shifts,
sum(self.model.works[worker.id, week, day,
shift.name] for week, day
in self.get_week_day_combinations()),
max_shifts,
))
else:
# Make sure shifts aren't assigned to those from other sites
self.model.constraints.add(0 == sum(
@@ -458,6 +551,13 @@ class RotaBuilder(object):
self.model.works[worker.id, week, day, shift.name]
for week, day in self.get_week_day_combinations()))
if self.constraint_options["balance_nights"]:
self.model.constraints.add(
self.model.night_shift_count[worker.id] == sum(
self.model.works[worker.id, week, day, shift.name]
for week, day in self.get_week_day_combinations()
for shift in self.get_shifts_with_constraint("night")))
# Define shift_count_t1 and shift_count_t2 constraints for the object
# Thus bypassing the need for a quadratic solver
# t1-t2 is the target
@@ -469,6 +569,28 @@ class RotaBuilder(object):
(self.model.shift_count[worker.id, shift.name] -
worker.shift_target_number[shift.name]) *
shift.balance_weighting for shift in self.get_shifts()))
#if "night" not in shift.constraints))
if self.constraint_options["balance_nights"]:
night_shift_target_number = sum(
worker.shift_target_number[shift.name]
for shift in self.get_shifts_with_constraint("night"))
min_shifts = night_shift_target_number - 20
max_shifts = night_shift_target_number + 20
self.model.constraints.add(
inequality(
min_shifts,
self.model.night_shift_count[worker.id],
max_shifts,
))
self.model.constraints.add(
self.model.night_shift_count_t1[worker.id] -
self.model.night_shift_count_t2[worker.id] ==
self.model.night_shift_count[worker.id] -
night_shift_target_number)
# Ensure worker is not allocated shifts on non working days
if worker.nwd:
@@ -478,37 +600,74 @@ class RotaBuilder(object):
0 == self.model.works[worker.id, week, day,
shift.name])
if "night" in self.get_shift_names():
for week_blocks in self.get_week_block_iterator(2):
# Prevent nights more than once every n weeks
self.model.constraints.add(
1 >= sum(self.model.nights[worker.id, week, block]
for week in week_blocks
for block in ["weekday", "weekend"]))
# for week_blocks in self.get_week_block_iterator(
# self.max_night_frequency):
# if self.get_shifts_with_constraint("night"):
# # Prevent nights more than once every n weeks
# self.model.constraints.add(1 >= sum(
# self.model.shift_week_worker_assigned[shift.name, week,
# worker.id]
# for week in week_blocks
# for shift in self.get_shifts_with_constraint("night")))
# for week_blocks in self.get_week_block_iterator(
# self.max_weekend_frequency):
# # Prevent weekend shifts more than once every n weeks
# self.model.constraints.add(
# 1 >= sum(self.model.works_weekend[worker.id, week]
# for week in week_blocks))
for week in self.weeks:
# # model.weekend_count stores the number of weekend shifts that a worker is assigned to wor
# # this is used (by the objective) to balance the total number of weekend shifts worked
# self.model.constraints.add(
# # We could use a helper to get required shifts to check
# self.model.works_whole_weekend[worker.id, week] >= sum(
# self.model.works[worker.id, week, day, shift.name]
# for day in self.days[5:]
# for shift in self.get_shifts()) - 1)
# self.model.constraints.add(
# self.model.works_whole_weekend[worker.id, week] <= sum(
# self.model.works[worker.id, week, day, shift.name]
# for day in self.days[5:]
# for shift in self.get_shifts()) / 2)
if self.constraint_options["balance_weekends"]:
self.model.constraints.add(
self.model.works_weekend[worker.id, week] >= sum(
self.model.works[worker.id, week, day, shift.name]
for day in self.days[5:]
for shift in self.get_shifts()) / 2)
self.model.constraints.add(
self.model.works_weekend[worker.id, week] <= sum(
self.model.works[worker.id, week, day, shift.name]
for day in self.days[5:]
for shift in self.get_shifts()))
# self.model.constraints.add(
# self.model.works_saturday[worker.id, week] == sum(
# self.model.works[worker.id, week, "Sat", shift.name]
# for shift in self.get_shifts()))
# self.model.constraints.add(
# self.model.works_sunday[worker.id, week] == sum(
# self.model.works[worker.id, week, "Sun", shift.name]
# for shift in self.get_shifts()))
# self.model.constraints.add(1 == sum(
# self.model.shift_week_worker_assigned[s.name, week, worker.id]
# for s in self.get_shifts_with_constraint("night")))
if "night" in self.get_shift_names():
for shift in self.get_shifts_with_constraint("night"):
# Force nights to be assigned in blocks
self.model.constraints.add(1 == sum(
self.model.nights[worker.id, week, block]
for block in self.night_blocks))
# self.model.constraints.add(8* self.model.shift_week_worker_assigned[shift.name, week, worker.id] >= sum(self.model.works[worker.id, week, day, shift.name] for day in self.days)
# )
# if night block is weekday make sure Mon - Thurs is assigned as nights
# Force night shifts to be assigned in blocks
self.model.constraints.add(
self.model.nights[worker.id, week, "weekday"] *
4 == sum(self.model.works[worker.id, week, day,
"night"]
for day in days[:4]))
self.model.constraints.add(
self.model.nights[worker.id, week, "weekend"] *
3 == sum(self.model.works[worker.id, week, day,
"night"]
for day in days[4:]))
self.model.shift_week_worker_assigned[shift.name, week,
worker.id] *
len(shift.shift_days) == sum(
self.model.works[worker.id, week, day, shift.name]
for day in shift.shift_days))
#
@@ -528,7 +687,7 @@ class RotaBuilder(object):
p2week, p2day = weeks_days[n - 2]
except IndexError:
p2week, p2day = weeks_days[n]
n2 = 0
p2 = 0
n1 = 1
n2 = 1
@@ -544,50 +703,58 @@ class RotaBuilder(object):
n2week, n2day = weeks_days[n]
n2 = 0
# Unable to work (hard constraint not preference)
self.model.constraints.add(
self.model.available[worker.id, week, day] >= sum(
if self.get_shift_names_by_day(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)))
# 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)))
# 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)))
# if working a night ensure preceeding (1) or subsequent (2) shifts can only be nights
if "night" in self.get_shift_names():
# Ensure night prior to unavalibity is not assigned
self.model.constraints.add(
self.model.available[worker.id, week, day] >=
self.model.works[worker.id, pweek, pday, "night"])
if self.constraint_options["constrain_time_off_after_nights"]:
for constraint_shift in self.get_shifts_with_constraint(
"night"):
# Ensure night prior to unavalibity is not assigned
self.model.constraints.add(
self.model.available[worker.id, week, day] >=
self.model.works[worker.id, pweek, pday,
constraint_shift.name])
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day, "night"] +
sum(n1 *
self.model.works[worker.id, nweek, nday, shift]
for shift in self.get_shift_names_by_day(nday)
if shift != "night"))
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_day(nday)
if shift != constraint_shift.name))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day, "night"] +
sum(n2 *
self.model.works[worker.id, n2week, n2day, shift]
for shift in self.get_shift_names_by_day(n2day)
if shift != "night"))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day, "night"] +
sum(p1 *
self.model.works[worker.id, pweek, pday, shift]
for shift in self.get_shift_names_by_day(pday)
if shift != "night"))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day, "night"] +
sum(p2 *
self.model.works[worker.id, p2week, p2day, shift]
for shift in self.get_shift_names_by_day(p2day)
if shift != "night"))
self.model.constraints.add(
1 >= self.model.works[worker.id, week, day,
constraint_shift.name] +
sum(n2 * self.model.works[worker.id, n2week, n2day,
shift]
for shift in self.get_shift_names_by_day(n2day)
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)
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)
if shift != constraint_shift.name))
self.define_objectives()
@@ -597,12 +764,25 @@ class RotaBuilder(object):
# c = len(workers)
balance_modifier_constant = 1000
balance_modifier_constant = 500
shift_balancing = sum(balance_modifier_constant *
(self.model.shift_count_t1[(worker.id)] +
self.model.shift_count_t2[(worker.id)])
for worker in self.workers)
if self.constraint_options["balance_shifts"]:
shift_balancing = sum(balance_modifier_constant *
(self.model.shift_count_t1[(worker.id)] +
self.model.shift_count_t2[(worker.id)])
for worker in self.workers)
else:
shift_balancing = 0
if self.constraint_options["balance_nights"]:
night_balance_modifier_constant = 2000
night_shift_balancing = sum(
night_balance_modifier_constant *
(self.model.night_shift_count_t1[(worker.id)] +
self.model.night_shift_count_t2[(worker.id)])
for worker in self.workers)
else:
night_shift_balancing = 0
preference_constant = 10
# Preferences
@@ -614,19 +794,29 @@ class RotaBuilder(object):
# # Spread nights
nights_balancing = sum(
self.model.night_per_site_t1[week, block, site] * 20
#self.model.night_per_site2[week, block, site]
for week in self.weeks for block in ("weekday", "weekend")
for site in self.sites)
#nights_balancing = 0
if self.constraint_options["balance_nights_across_sites"]:
nights_site_balancing = sum(
(self.model.night_per_site_t1[week, shift.name, site] +
self.model.night_per_site_t2[week, shift.name, site]) * 20
#self.model.night_per_site2[week, block, site]
for week in self.weeks
for shift in self.get_shifts_with_constraint("night")
for site in self.sites)
else:
nights_site_balancing = 0
blocks_balancing = sum(
100 * self.model.blocks_assigned[week, shift]
for week in self.weeks
for shift in self.shifts_to_assign_as_blocks())
if self.constraint_options["balance_blocks"]:
blocks_balancing = sum(
100 * self.model.blocks_assigned[week, shift]
for week in self.weeks
for shift in self.shifts_to_assign_as_blocks())
else:
blocks_balancing = 0
return shift_balancing + preferences + nights_balancing + blocks_balancing
return night_shift_balancing
#return shift_balancing + preferences + blocks_balancing
#return shift_balancing + preferences + nights_site_balancing + blocks_balancing
return shift_balancing + night_shift_balancing + preferences + nights_site_balancing + blocks_balancing
# add objective function to the model. rule (pass function) or expr (pass expression directly)
self.model.obj = Objective(rule=obj_rule, sense=minimize)
@@ -874,6 +1064,9 @@ class RotaBuilder(object):
s.extend(self.shifts_to_force_as_blocks())
return s
def get_workers_total_fte(self):
return self.full_time_equivalent
class RotaResults(object):
def __init__(self, rota, results):
@@ -946,7 +1139,10 @@ class RotaResults(object):
timetable[worker.name][week][day] = shift
return timetable
def get_worker_timetable_brief(self, show_prefs=True, marker_every=19):
def get_worker_timetable_brief(self,
show_prefs=True,
marker_every=19,
show_unavailable=True):
model = self.rota.model
week_string = "{:20}".format("-Week-") + "".join(
[7 * str("{}".format(w))[-1:] for w in self.rota.weeks])
@@ -966,8 +1162,11 @@ class RotaResults(object):
shift_count = ""
for s in set(shifts):
shift_count = shift_count + "{}: {},".format(
shift_count = shift_count + "{}: {}, ".format(
s, shifts.count(s))
shift_count = shift_count + "#weekends_worked: {}\\#".format(
model.worker_weekend_count[worker.id].value)
timetable.append("{} {}".format("".join(w), shift_count))
if show_prefs:
@@ -980,6 +1179,16 @@ class RotaResults(object):
w.append("N")
timetable.append("".join(w))
if show_unavailable:
# prefs
w = ["{:20}".format("Unavailable")]
for week, day in self.rota.get_week_day_combinations():
if model.available[worker.id, week, day] > 0:
w.append("A")
else:
w.append("U")
timetable.append("".join(w))
i = marker_every
while i < len(timetable):
timetable.insert(i, week_string)
@@ -1024,26 +1233,27 @@ class RotaResults(object):
# """Extract to a list the workers not satisfied with their weekend preference."""
# return [worker.id for worker in workers if no_pref[worker.id].value == 1]
def get_night_blocks(self):
nights = self.rota.model.nights
timetable = {
worker.name: {
week: {block: ""
for block in self.rota.night_blocks}
for week in self.rota.weeks
}
for worker in self.rota.workers
}
for worker in self.rota.workers:
for week in self.rota.weeks:
for block in self.rota.night_blocks:
if nights[worker.id, week, block].value == 1:
timetable[worker.name][week][block] = "true"
return timetable
# def get_night_blocks(self):
# nights = self.rota.model.nights
# timetable = {
# worker.name: {
# week: {block: ""
# for block in self.rota.night_blocks}
# for week in self.rota.weeks
# }
# for worker in self.rota.workers
# }
# for worker in self.rota.workers:
# for week in self.rota.weeks:
# for block in self.rota.night_blocks:
# if nights[worker.id, week, block].value == 1:
# timetable[worker.name][week][block] = "true"
# return timetable
def get_multinight(self):
for site in self.rota.sites:
for block in ("weekend", "weekday"):
for shift in self.get_shifts_with_constraint("night"):
block = shift.name
for week in self.rota.weeks:
print(
site, week, block,
@@ -1053,3 +1263,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(
worker.name,
self.rota.model.night_shift_count[(worker.id)].value,
self.rota.model.night_shift_count_t1[(worker.id)].value,
self.rota.model.night_shift_count_t2[(worker.id)].value,
worker.shift_target_number["night_weekday"] +
worker.shift_target_number["night_weekend"])