Files
proc-rota/test.py
T
2021-06-07 23:23:12 +01:00

402 lines
12 KiB
Python

from pyomo.environ import *
from pyomo.opt import SolverFactory
from pyomo.opt.results import SolverStatus
from shifts import SingleShift, RotaBuilder, RotaResults, days
from workers import Worker
from copy import deepcopy
import math
import scipy.stats as stats
import statistics
import datetime
import itertools
import operator
import sys
use_neos = True
solve = True
weeks_to_rota = 26
time_to_run = 28500
allow = 10000
use_cplex = True
sites = ("truro", "exeter", "torbay", "barnstaple", "plymouth")
Rota = RotaBuilder(datetime.date(2021, 9, 6),
weeks_to_rota=weeks_to_rota,
balance_offset_modifier=1,
max_weekend_frequency=2,
max_night_frequency=2)
Rota.constraint_options["limit_to_1_st2_on_nights"] = True
Rota.constraint_options["ensure_1_st4_plus_on_nights"] = True
Rota.constraint_options["balance_nights"] = True
Rota.constraint_options["constrain_time_off_after_nights"] = True
Rota.constraint_options["balance_nights_across_sites"] = True
Rota.constraint_options["balance_blocks"] = True
Rota.constraint_options["balance_weekends"] = True
Rota.constraint_options["avoid_st1_first_month"] = True
Rota.constraint_options["max_weekends"] = 20
Rota.constraint_options["prevent_monday_and_tuesday_after_full_weekends"] = [
"truro", "torbay", "exeter"
]
Rota.constraint_options["prevent_monday_after_full_weekends"] = ["plymouth"]
Rota.constraint_options["prevent_fridays_before_full_weekends"] = ["plymouth"]
Rota.constraint_options["prevent_thursdays_before_full_weekends"] = [
"truro", "torbay", "exeter"
]
Rota.add_shifts(
SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:4]),
SingleShift(("truro", ), "truro_twilight", 12.5, days[:4]),
SingleShift(("torbay", ),
"torbay_twilight",
12.5,
days[:4],
balance_offset=3,
assign_as_block=True),
SingleShift(("plymouth", ),
"plymouth_twilight",
12.5,
days[:5],
balance_offset=3,
workers_required=1,
constraints=["max_2_consecutive_shifts_per_week"]),
SingleShift(("exeter", ),
"weekend_exeter",
12.5,
days[4:],
balance_offset=3,
rota_on_nwds=True,
force_as_block=True),
SingleShift(("truro", ),
"weekend_truro",
12.5,
days[4:],
balance_offset=3,
rota_on_nwds=True,
force_as_block=True),
SingleShift(("torbay", ),
"weekend_torbay",
12.5,
days[4:],
balance_offset=3,
rota_on_nwds=True,
force_as_block=True),
SingleShift(("plymouth", ),
"weekend_plymouth1",
8,
days[5:],
balance_offset=3,
workers_required=1,
rota_on_nwds=True,
force_as_block=True),
SingleShift(("plymouth", ),
"weekend_plymouth2",
8,
days[5:],
balance_offset=3,
workers_required=1,
rota_on_nwds=True,
force_as_block=True),
SingleShift(
(sites),
"night_weekday",
12.25,
days[:4],
balance_offset=3,
balance_weighting=1,
#hard_constrain_shift=False,
workers_required=3,
force_as_block=False,
rota_on_nwds=True,
constraints=["night"]),
SingleShift(
(sites),
"night_weekend",
12.25,
days[4:],
balance_offset=3,
balance_weighting=1,
#hard_constrain_shift=False,
workers_required=3,
force_as_block=False,
rota_on_nwds=True,
constraints=["night"]),
)
use_test_workers = False
load_leave = True
Rota.build_shifts_and_workers()
if load_leave:
import leave
workers = leave.load_leave(Rota)
# Import trainee data
if use_test_workers:
# Rota.add_workers([
# Worker(Rota,
# i,
# "Truro {}".format(i),
# "truro",
# 3,
# nwd=['Fri', "Sat", "Sun"]) for i in range(1, 2)
# ])
Rota.add_workers([
Worker(
Rota,
i,
"Truro {}".format(i),
"truro",
4,
) for i in range(1, 9)
])
Rota.add_workers([
Worker(Rota, i, "Plym {}".format(i), "plymouth", 4, 50)
for i in range(9, 17)
])
Rota.add_workers([
Worker(Rota,
i,
"Plym {}".format(i),
"plymouth",
4,
80,
end_date="2020/10/05") for i in range(17, 19)
])
# Rota.add_workers([
# Worker(
# Rota,
# i,
# "Exe {}".format(i),
# "exeter",
# 4,
# ) for i in range(20, 25)
# ])
# Rota.add_workers([
# Worker(
# Rota,
# i,
# "Torq {}".format(i),
# "torbay",
# 2,
# 50,
# ) for i in range(25, 29)
# ])
else:
n = 0
for worker in workers:
n = n + 1
name = worker
w = workers[worker]
site = w["site"]
grade = w["grade"]
fte = w["fte"]
nwd = w["nwd"]
end_date = w["end_date"]
oop = w["oop"]
grade = w["grade"]
#name, site, grade, fte, nwd, end_date, oop, twi, twi_we, night, night_we, pw1, pw2, extra = row
# Ignore trainees if fte == 0 (what are they doing here anyway)
if fte == 0 or fte == "0" or fte == "":
continue
nwds = [i.strip()[:3].capitalize() for i in nwd.split("/")] if nwd else None
end_date = end_date if end_date else None
oop = oop.split("-") if oop else None
previous_shifts = {}
#if twi:
# worked = twi.split(" ")[0]
# allocated = twi.split(" ")[1][1:-1]
# previous_shifts["{}_twilight".format(site.lower())] = (worked, allocated)
#if twi_we:
# worked = twi_we.split(" ")[0]
# allocated = twi_we.split(" ")[1][1:-1]
# previous_shifts["weekend_{}".format(site.lower())] = (worked, allocated)
#if night:
# worked = night.split(" ")[0]
# allocated = night.split(" ")[1][1:-1]
# previous_shifts["night_weekday".format(site.lower())] = (worked, allocated)
#if night_we:
# worked = night_we.split(" ")[0]
# allocated = night_we.split(" ")[1][1:-1]
# previous_shifts["night_weekend".format(site.lower())] = (worked, allocated)
#if pw1:
# worked = pw1.split(" ")[0]
# allocated = pw1.split(" ")[1][1:-1]
# previous_shifts["weekend_plymouth1"] = (worked, allocated)
#if pw2:
# worked = pw2.split(" ")[0]
# allocated = pw2.split(" ")[1][1:-1]
# previous_shifts["weekend_plymouth2"] = (worked, allocated)
leave_requests = w["leave"]
work_requests = w["requests"]
site_pref = w["site_pref"]
#print(nwds, end_date, oop)
# Rota.add_worker(
# Worker(Rota, n, name, site.lower(), int(grade[2]), 100,
# ))
# if name.startswith(("Alex", "Michal", "Ross")):
# Rota.add_worker(
# Worker(Rota,
# n,
# name,
# site.lower(),
# int(grade[2]),
# int(fte),
# nwds,
# end_date,
# oop,
# work_requests=(("2020/09/07", "night_weekday"), )))
# continue
# if "Salma" in name:
# Rota.add_worker(
# Worker(Rota,
# n,
# name,
# site.lower(),
# int(grade[2]),
# int(fte),
# nwds,
# end_date,
# oop,
# work_requests=(("2020/12/25",
# "exeter_twilight"), )))
# continue
Rota.add_worker(
Worker(Rota,
n,
name,
site.lower(),
int(grade[2]),
float(fte),
nwds,
end_date,
oop,
not_available_to_work=leave_requests,
work_requests=work_requests,
proc_site=site_pref,
previous_shifts=previous_shifts))
print("Building model")
Rota.build_shifts_and_workers()
Rota.build_model()
print(Rota.get_worker_details())
if not solve:
sys.exit(0)
#print(Rota.get_worker_details())
print("Setting up solver")
if use_cplex:
opt = SolverFactory('cplex')
else:
opt = SolverFactory('cbc') # choose a solver
#opt = SolverFactory('ipopt') # choose a solver
opt.options['allow'] = allow
opt.options['seconds'] = time_to_run
print("Solving")
if use_neos:
solver_manager = SolverManagerFactory('neos') # Solve in neos server
#results = solver_manager.solve(Rota.model, opt=opt, logfile="test.log")
results = solver_manager.solve(Rota.model, keepfiles=True, tee=True, opt=opt, logfile="test.log")
else:
results = opt.solve(
Rota.model,
tee=True,
options={
"seconds": time_to_run,
#"allow": allow,
"threads": 2,
},
logfile="test.log"
) # solve the model with the, options="seconds=60" selected solver
print(results)
results.solver.status
if not results.solver.status:
sys.exit(0)
# optimizer = SolverFactory('cbc')
# result = optimizer.solve(prob,tee=True)
# result.Solver.Status = SolverStatus.warning
# prob.solutions.load_from(result)
ResultsHolder = RotaResults(Rota, results)
week_table = ResultsHolder.get_work_table() # list with the required workers
worker_timetable = ResultsHolder.get_worker_timetable()
worker_timetable_brief = ResultsHolder.get_worker_timetable_brief(
show_prefs=False, show_unavailable=False)
#print(worker_timetable_brief)
#print(Rota.get_workers_total_fte())
ResultsHolder.get_weekend_details()
#ResultsHolder.get_night_details()
with open("test.html", "w") as f:
f.write(ResultsHolder.get_worker_timetable_html(True))
ResultsHolder.export_rota_to_csv("rota")
# for i in ResultsHolder.rota.model.blocks_worker_shift_count:
# n = ResultsHolder.rota.model.blocks_worker_shift_count[i].value
# if n > 0:
# print(i, n)
# #print(ResultsHolder.rota.model.blocks_assigned[i].value)
# else:
# pass
# #print("SHIT")
# for i in ResultsHolder.rota.model.blocks_assigned:
# n = ResultsHolder.rota.model.blocks_assigned[i].value
# if n:
# print(i, n)
# #print(ResultsHolder.rota.model.blocks_assigned[i].value)
# else:
# pass
# ResultsHolder.export_rota_to_csv("rotas/"+rot)
# with open("cest", "a") as f:
# f.write(rot)
# f.write("\n\n")
# for s in Rota.shifts:
# f.write(s.get_shift_summary())
# f.write("\n")
# f.write("\n\n")
# f.write(worker_timetable_brief)
# f.write("\n\n\n")
#workers_no_pref = ResultsHolder.get_no_preference(Rota.model.no_pref) # list with the non-satisfied workers (work on Sat but not on Sun)
# worker_night_block = ResultsHolder.get_night_blocks()
# worker_shift_summary = ResultsHolder.get_shift_summary()
# multi = ResultsHolder.get_multinight()
#break