Files
proc-rota/test.py
T
Ross ffb63a3978 .
2021-06-13 21:15:09 +01:00

331 lines
9.8 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
import datetime
import sys
use_neos = False
use_cplex = False
solve = True
weeks_to_rota = 13
time_to_run = 28500
#allow = 5000
ratio = 0.5
use_live_rota = False
sites = ("truro", "exeter", "torbay", "barnstaple", "plymouth")
Rota = RotaBuilder(datetime.date(2021, 9, 6),
#Rota = RotaBuilder(datetime.date(2021, 11, 8),
weeks_to_rota=weeks_to_rota,
balance_offset_modifier=2,
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["balance_bank_holidays"] = True
Rota.constraint_options["avoid_st2_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, use_live_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"]
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['ratio'] = ratio
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,
"ratio": ratio,
"threads": 10,
},
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")