many changes

This commit is contained in:
Ross
2022-01-24 18:25:36 +00:00
parent 85c1344737
commit a34631bc22
15 changed files with 1821 additions and 738 deletions
+320 -400
View File
@@ -1,452 +1,372 @@
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
import os
use_neos = False
use_cplex = False
solve = True
weeks_to_rota = 52
weeks_to_rota = 26
sites = ("truro", "exeter", "torquay", "barnstaple", "plymouth")
time_to_run = 258500
# allow = 5000
ratio = 0.001
Rota = RotaBuilder(datetime.date(2020, 9, 7),
weeks_to_rota=weeks_to_rota,
balance_offset_modifier=1,
max_weekend_frequency=2,
max_night_frequency=2)
use_live_rota = False
suspend_on_finish = True
rota_collections = {
"current": deepcopy(Rota),
# "extended proc nights only": deepcopy(Rota),
# "no weekends": deepcopy(Rota),
# "no twighlights": deepcopy(Rota),
# "no nights": deepcopy(Rota),
# "nights + proc twighlights3 + normal weekends": deepcopy(Rota),
# "nights + proc twighlights4 + normal weekends": deepcopy(Rota),
# "nights + proc twighlights + proc weekends": deepcopy(Rota),
}
rota_collections['current'].add_shifts(
SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:5]),
SingleShift(("truro", ), "truro_twilight", 12.5, days[:5]),
SingleShift(("torquay", ), "torquay_twilight", 12.5, days[:5]),
SingleShift(("plymouth", ), "plymouth_twilight", 12.5, days[:5]),
SingleShift(("exeter", ),
"weekend_exeter",
12.5,
days[5:],
assign_as_block=True),
SingleShift(("truro", ),
"weekend_truro",
12.5,
days[5:],
assign_as_block=True),
SingleShift(("torquay", ),
"weekend_torquay",
12.5,
days[5:],
assign_as_block=True),
SingleShift(("plymouth", ),
"weekend_plymouth",
8,
days[5:],
workers_required=2,
assign_as_block=True),
SingleShift((sites),
"night_weekday",
12.25,
days[:4],
balance_offset=5,
balance_weighting=1,
workers_required=3,
force_as_block=False,
rota_on_nwds=True,
constraints=["night"]),
SingleShift((sites),
"night_weekend",
12.25,
days[4:],
balance_offset=4,
balance_weighting=1,
workers_required=3,
force_as_block=False,
rota_on_nwds=True,
constraints=["night"]),
sites = ("truro", "exeter", "torbay", "barnstaple", "plymouth", "proc", "truro no proc")
Rota = RotaBuilder(
# datetime.date(2022, 6, 6),
datetime.date(2022, 3, 7),
# Rota = RotaBuilder(datetime.date(2021, 12, 6),
# Rota = RotaBuilder(datetime.date(2021, 11, 8),
weeks_to_rota=weeks_to_rota,
balance_offset_modifier=1,
max_weekend_frequency=2,
max_night_frequency=2,
use_shift_balance_extra=True,
use_bank_holiday_extra=True,
)
# rota_collections['extended proc nights only'].add_shifts(
# #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True),
# SingleShift((sites),
# "night_weekday",
# 13,
# days[:4],
# balance_offset=5,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# SingleShift((sites),
# "night_weekend",
# 13,
# days[4:],
# balance_offset=4,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# )
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_shifts"] = True
Rota.constraint_options["balance_bank_holidays"] = True
Rota.constraint_options["avoid_st2_first_month"] = False
Rota.constraint_options["hard_constrain_pair_separation"] = True
Rota.constraint_options["max_weekends"] = 7
Rota.constraint_options["prevent_monday_and_tuesday_after_full_weekends"] = [
"truro",
"torbay",
"exeter",
"plymouth",
"plymouth no proc",
"truro no proc",
]
Rota.constraint_options["prevent_monday_after_full_weekends"] = [
"plymouth",
"plymouth no proc",
]
Rota.constraint_options["prevent_fridays_before_full_weekends"] = [
"plymouth",
"plymouth no proc",
]
Rota.constraint_options["prevent_thursdays_before_full_weekends"] = [
"truro",
"torbay",
"exeter",
"truro no proc",
]
# rota_collections['no weekends'].add_shifts(
# SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:5]),
# SingleShift(("truro", ), "truro_twilight", 12.5, days[:5]),
# SingleShift(("torquay", ), "torquay_twilight", 12.5, days[:5]),
# SingleShift(("plymouth", ), "plymouth_twilight", 12.5, days[:5]),
# #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True),
# SingleShift((sites),
# "night_weekday",
# 13,
# days[:4],
# balance_offset=5,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# SingleShift((sites),
# "night_weekend",
# 13,
# days[4:],
# balance_offset=4,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# )
# rota_collections['no twighlights'].add_shifts(
# SingleShift(("exeter", ),
# "weekend_exeter",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("truro", ),
# "weekend_truro",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("torquay", ),
# "weekend_torquay",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("plymouth", ),
# "weekend_plymouth",
# 8,
# days[5:],
# workers_required=2,
# assign_as_block=True),
# #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True),
# SingleShift((sites),
# "night_weekday",
# 13,
# days[:4],
# balance_offset=5,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# SingleShift((sites),
# "night_weekend",
# 13,
# days[4:],
# balance_offset=4,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# )
# rota_collections['no nights'].add_shifts(
# SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:5]),
# SingleShift(("truro", ), "truro_twilight", 12.5, days[:5]),
# SingleShift(("torquay", ), "torquay_twilight", 12.5, days[:5]),
# SingleShift(("plymouth", ), "plymouth_twilight", 12.5, days[:5]),
# SingleShift(("exeter", ),
# "weekend_exeter",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("truro", ),
# "weekend_truro",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("torquay", ),
# "weekend_torquay",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("plymouth", ),
# "weekend_plymouth",
# 8,
# days[5:],
# workers_required=2,
# assign_as_block=True),
# #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True),
# )
# rota_collections['nights + proc twighlights4 + normal weekends'].add_shifts(
# SingleShift((sites), "proc_twilight", 12.5, days[:5], workers_required=4),
# SingleShift(("exeter", ),
# "weekend_exeter",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("truro", ),
# "weekend_truro",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("torquay", ),
# "weekend_torquay",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("plymouth", ),
# "weekend_plymouth",
# 8,
# days[5:],
# workers_required=2,
# assign_as_block=True),
# #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True),
# SingleShift((sites),
# "night_weekday",
# 12.25,
# days[:4],
# balance_offset=5,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# SingleShift((sites),
# "night_weekend",
# 12.25,
# days[4:],
# balance_offset=4,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# )
# rota_collections['nights + proc twighlights3 + normal weekends'].add_shifts(
# SingleShift((sites), "proc_twilight", 12.5, days[:5], workers_required=3),
# SingleShift(("exeter", ),
# "weekend_exeter",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("truro", ),
# "weekend_truro",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("torquay", ),
# "weekend_torquay",
# 12.5,
# days[5:],
# assign_as_block=True),
# SingleShift(("plymouth", ),
# "weekend_plymouth",
# 8,
# days[5:],
# workers_required=2,
# assign_as_block=True),
# #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True),
# SingleShift((sites),
# "night_weekday",
# 12.25,
# days[:4],
# balance_offset=5,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# SingleShift((sites),
# "night_weekend",
# 12.25,
# days[4:],
# balance_offset=4,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# )
# rota_collections['nights + proc twighlights + proc weekends'].add_shifts(
# SingleShift((sites), "proc_twilight", 12.5, days[:5], workers_required=3),
# SingleShift((sites), "proc_weekends", 12.5, days[5:], workers_required=3),
# #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True),
# SingleShift((sites),
# "night_weekday",
# 12.25,
# days[:4],
# balance_offset=5,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# SingleShift((sites),
# "night_weekend",
# 12.25,
# days[4:],
# balance_offset=4,
# balance_weighting=1,
# workers_required=3,
# force_as_block=False,
# rota_on_nwds=True,
# constraints=["night"]),
# )
Rota.add_shifts(
SingleShift(
("exeter",),
"exeter_twilight",
12.5,
days[:4],
balance_offset=3,
constraints=["max_2_shifts_per_week"],
),
SingleShift(
("truro", "truro no proc"), "truro_twilight", 12.5, days[:4], balance_offset=3
),
SingleShift(
("torbay", "torbay twilights"),
"torbay_twilight",
12.5,
days[:4],
balance_offset=5,
assign_as_block=True,
),
SingleShift(
("plymouth", "plymouth_twilights"),
"plymouth_twilight",
12.5,
days[:5],
balance_offset=3,
workers_required=1,
constraints=["max_2_shifts_per_week"],
),
SingleShift(
("exeter",),
"weekend_exeter",
12.5,
days[4:],
balance_offset=4,
rota_on_nwds=True,
force_as_block=True,
),
SingleShift(
("truro", "truro no proc"),
"weekend_truro",
12.5,
days[4:],
balance_offset=4,
rota_on_nwds=True,
force_as_block=True,
# force_as_block_unless_nwd=True
),
SingleShift(
("torbay",),
"weekend_torbay",
12.5,
days[4:],
balance_offset=4,
rota_on_nwds=True,
force_as_block=True,
# force_as_block_unless_nwd=True
),
SingleShift(
("plymouth",),
"weekend_plymouth1",
8,
days[5:],
balance_offset=4,
workers_required=1,
rota_on_nwds=True,
force_as_block=True,
),
SingleShift(
("plymouth",),
"weekend_plymouth2",
8,
days[5:],
balance_offset=4,
workers_required=1,
rota_on_nwds=True,
force_as_block=True,
),
SingleShift(
("plymouth", "plymouth_twilights"),
"plymouth_bank_holidays",
8,
days[5:],
balance_offset=2,
workers_required=1,
rota_on_nwds=True,
force_as_block=False,
bank_holidays_only=True,
),
SingleShift(
(sites[:-1]),
"night_weekday",
12.25,
days[:4],
balance_offset=4.9,
balance_weighting=1,
# hard_constrain_shift=False,
workers_required=3,
force_as_block=False,
rota_on_nwds=True,
constraints=["night"],
),
SingleShift(
(sites[:-1]),
"night_weekend",
12.25,
days[4:],
balance_offset=3.9,
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()
if load_leave:
import leave
leave = leave.load_leave()
workers = leave.load_leave(Rota, use_live_rota)
# Import trainee data
import csv
with open('trainees.csv', newline='') as f:
reader = csv.reader(f, delimiter=',', quotechar='|')
next(reader) # skip header
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,
"Truro {}".format(i),
"truro",
4,
id=i,
)
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)
]
)
else:
n = 0
for row in reader:
for worker in workers:
n = n + 1
#print(row)
name, site, grade, fte, nwd, end_date, oop, extra = row
worker_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"]
pair = w["pair"]
# 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":
if fte == 0 or fte == "0" or fte == "":
continue
nwds = nwd.split("/") if nwd else None
nwds = None
if nwd:
nwds = []
for i in nwd.split(","):
nwd_start_date = Rota.start_date
nwd_end_date = Rota.rota_end_date
if "[" in i:
a, b = i.split("[")[1][:-1].split("-")
nwd_start_date = datetime.datetime.strptime(a, "%d/%m/%y").date()
nwd_end_date = datetime.datetime.strptime(b, "%d/%m/%y").date()
nwds.append(
(
i.split("[")[0].strip()[:3].capitalize(),
nwd_start_date,
nwd_end_date,
)
)
# 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)
if load_leave:
if name not in leave:
raise ValueError("{} not found it leave requests".format(name))
leave_requests = w["leave"]
work_requests = w["requests"]
site_pref = w["site_pref"]
leave_requests = [i[0] for i in leave[name] if i[0] != ""]
Rota.add_worker(
Worker(
Rota,
worker_name,
site.lower(),
int(grade[2]),
n,
int(fte),
nwds,
end_date,
oop,
not_available_to_work=leave_requests,
work_requests=work_requests,
proc_site=site_pref,
previous_shifts=previous_shifts,
pair=pair,
shift_balance_extra=w["shift_balance_extra"],
bank_holiday_extra=w["bank_holiday_extra"],
)
)
#print(nwds, end_date, oop)
for rot in rota_collections:
print("Building model")
Rota.build_workers()
Rota = rota_collections[rot]
Rota.add_worker(
Worker(Rota, n, name, site.lower(), int(grade[2]), int(fte),
nwds, end_date, oop, not_available_to_work=leave_requests))
with open("rotas.html", "w") as f:
f.write("""<html>
<head>
<link rel="stylesheet" type="text/css" href="timetable.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="timetable.js" defer></script>
</head>
<body>""")
Rota.sites.add("plymouth_twilights")
for rot in rota_collections:
print("Running rota: ", rot)
Rota = rota_collections[rot]
Rota.build_model()
Rota.build_shifts_and_workers()
print(Rota.get_worker_details())
print("-Building model")
Rota.build_model()
print("-Building model: complete")
if not solve:
ResultsHolder = RotaResults(Rota)
ResultsHolder.export_rota_to_html()
sys.exit(0)
opt = SolverFactory('cbc') # choose a solver
#opt = SolverFactory('ipopt') # choose a solver
solver_options = {"ratio": ratio, "seconds": time_to_run, "threads": 10}
if use_neos:
print("-Solving with neos")
solver_manager = SolverManagerFactory('neos') # Solve in neos server
results = solver_manager.solve(Rota.model,
opt=opt,
logfile="{}.log".format(rot))
else:
print("-Solving with local cbc")
results = opt.solve(
Rota.model,
tee=True,
options={
"seconds": 10200,
"allow": 2000,
},
logfile="{}.log".format(rot)
) # solve the model with the, options="seconds=60" selected solver
Rota.solve_model(options=solver_options)
results.solver.status
# print(Rota.get_worker_details())
ResultsHolder = RotaResults(Rota, results)
# optimizer = SolverFactory('cbc')
# result = optimizer.solve(prob,tee=True)
# result.Solver.Status = SolverStatus.warning
# prob.solutions.load_from(result)
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)
ResultsHolder = RotaResults(Rota)
worker_shift_summary = ResultsHolder.get_shift_summary()
worker_timetable_brief = ResultsHolder.get_worker_timetable_brief(
show_prefs=False, show_unavailable=False
)
ResultsHolder.export_rota_to_csv("rotas/" + rot)
with open("rotas.html", "a") as f:
f.write("<hr>")
f.write("<h2>{}</h2>".format(rot))
#f.write("----------------------------")
f.write("\n\n")
print(worker_timetable_brief)
for s in Rota.shifts:
f.write("<div id='{}-{}-shift-summary' class='shift-summary'>{}</div>".format(rot, s.name, s.get_shift_summary()))
f.write("\n")
f.write("\n\n")
f.write(ResultsHolder.get_worker_timetable_html(table_name=rot))
f.write("\n\n")
f.write("<pre class='worker-shift-summary'>{}</pre>".format(worker_shift_summary))
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()
#multi = ResultsHolder.get_multinight()
ResultsHolder.export_rota_to_html()
ResultsHolder.export_rota_to_csv("rota")
#break
with open("rotas.html", "a") as f:
f.write("</body></html>")
if suspend_on_finish:
os.system("systemctl suspend")