Files
proc-rota/gen.py
T
Ross e0d87b71a8 .
2023-06-22 20:58:40 +01:00

369 lines
11 KiB
Python

import datetime
import os
import sys
import time
from rota.shifts import NoWorkers, RotaBuilder, SingleShift, days
sites = (
"truro",
"exeter",
"torbay",
"barnstaple",
"plymouth",
"taunton",
"proc",
)
from rota.workers import (
Worker,
NotAvailableToWork,
NonWorkingDays,
WorkRequests,
PreferenceNotToWork,
OutOfProgramme,
)
suspend_on_finish = False
solve = True
# time_to_run = 508500
# time_to_run = 2660000
time_to_run = 100
# allow = 5000
ratio = 0.41
# ratio = 0.9
start_date = datetime.date(2023, 9, 4)
# start_date = datetime.date(2023, 5, 1)
# start_date = start_date + datetime.timedelta(weeks=13)
# start_date = datetime.date(2022, 10, 10)
Rota = RotaBuilder(
start_date, weeks_to_rota=10, balance_offset_modifier=1, name="proc rota"
)
# Rota = RotaBuilder(start_date, weeks_to_rota=20, balance_offset_modifier=1)
Rota.constraint_options["balance_weekends"] = True
Rota.constraint_options["max_night_frequency"] = 3 # 3
Rota.constraint_options["max_night_frequency_week_exclusions"] = []
Rota.constraint_options["max_weekend_frequency"] = 2
Rota.constraint_options["hard_constrain_pair_separation"] = True
# Rota.constraint_options["avoid_st2_first_month"] = True
Rota.add_shifts(
SingleShift(
sites=("exeter", "exeter twilights and weekends"),
name="exeter_twilight",
length=12.5,
days=days[:5],
balance_offset=4,
constraint=[
{
"name": "max_shifts_per_week",
"options": 2,
}
],
),
SingleShift(
sites=("truro", "truro twilights"),
name="truro_twilight",
length=12.5,
days=days[:4],
balance_offset=4,
),
SingleShift(
sites=("torbay", "torbay twilights"),
name="torbay_twilight",
length=12.5,
days=days[:4],
balance_offset=4,
assign_as_block=True,
),
SingleShift(
sites=("plymouth", "plymouth twilights", "plymouth twilights and weekends"),
name="plymouth_twilight",
length=12.5,
days=days[:5],
balance_offset=4,
workers_required=1,
constraint=[{"name": "max_shifts_per_week", "options": 2}],
),
SingleShift(
sites=("exeter", "exeter twilights and weekends"),
name="weekend_exeter",
length=12.5,
days=days[5:],
balance_offset=3,
rota_on_nwds=True,
force_as_block=True,
constraint=[{"name": "post", "options": 2}, {"name": "pre", "options": 2}],
# constraint=[{"name": "pre", "options": 2}, {"name": "post", "options": 2},],
),
SingleShift(
sites=("truro",),
name="weekend_truro",
length=12.5,
days=days[4:],
balance_offset=3,
rota_on_nwds=True,
force_as_block=True,
constraint=[{"name": "pre", "options": 2}, {"name": "post", "options": 2}],
# force_as_block_unless_nwd=True
),
SingleShift(
sites=("torbay",),
name="weekend_torbay",
length=12.5,
days=days[4:],
balance_offset=3,
rota_on_nwds=True,
force_as_block=True,
constraint=[{"name": "pre", "options": 2}, {"name": "post", "options": 2}],
# force_as_block_unless_nwd=True
),
SingleShift(
sites=("plymouth", "plymouth twilights and weekends"),
name="weekend_plymouth1",
length=8,
days=days[5:],
balance_offset=2.9,
workers_required=1,
rota_on_nwds=True,
force_as_block=True,
constraint=[{"name": "pre", "options": 2}, {"name": "post", "options": 2}],
),
SingleShift(
sites=("plymouth", "plymouth twilights and weekends"),
name="weekend_plymouth2",
length=8,
days=days[5:],
balance_offset=2.9,
workers_required=1,
rota_on_nwds=True,
force_as_block=True,
constraint=[{"name": "pre", "options": 2}, {"name": "post", "options": 2}],
),
SingleShift(
sites=("plymouth", "plymouth twilights", "plymouth twilights and weekends"),
name="plymouth_bank_holidays",
length=8,
days=days[5:],
balance_offset=1,
workers_required=1,
rota_on_nwds=True,
force_as_block=False,
bank_holidays_only=True,
),
SingleShift(
sites=sites,
name="night_weekday",
length=12.25,
days=days[:4],
balance_offset=3.9,
balance_weighting=1,
# hard_constrain_shift=False,
workers_required=3,
force_as_block=True,
rota_on_nwds=True,
constraint=[
{"name": "night"},
{"name": "pre", "options": 2},
{"name": "post", "options": 2},
{"name": "require_remote_site_presence_week", "options": ("plymouth", 1)},
{"name": "limit_grade_number", "options": {2: 1}},
{"name": "minimum_grade_number", "options": (4, 1)},
],
),
SingleShift(
sites=(sites),
name="night_weekend",
length=12.25,
days=days[4:],
balance_offset=2.9,
balance_weighting=1,
# hard_constrain_shift=False,
workers_required=3,
force_as_block=True,
rota_on_nwds=True,
constraint=[
{"name": "night"},
{"name": "pre", "options": 2},
{"name": "post", "options": 3},
{"name": "require_remote_site_presence_week", "options": ("plymouth", 1)},
{"name": "limit_grade_number", "options": {2: 1}},
{"name": "minimum_grade_number", "options": (4, 1)},
],
),
)
# Rota.add_grade_constraint_by_week([2], [1, 2], ["night_weekday", "night_weekend"])
load_leave = True
Rota.build_shifts()
# Rota.add_grade_constraint_by_week([2], [1], Rota.get_shift_names())
if load_leave:
import leave
workers = leave.load_leave(Rota)
n = 0
for worker in workers:
n = n + 1
worker_name = worker
w = workers[worker]
site = w["site"]
grade = w["grade"]
try:
fte = float(w["fte"]) * 100
except ValueError:
print(f"{worker} has invalid fte: {fte}")
raise ValueError
nwd = w["nwd"]
end_date = w["end_date"]
start_date = w["start_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 or fte == "0" or fte == "":
continue
nwds = []
if nwd:
for i in nwd.split(","):
nwd_start_date = Rota.start_date
nwd_end_date = Rota.rota_end_date
if "[" in i:
print("Split nwd", worker_name, i)
a, b = i.split("[")[1][:-1].split("-")
print(f"{a=} {b=}")
nwd_start_date = datetime.datetime.strptime(a, "%d/%m/%y").date()
nwd_end_date = datetime.datetime.strptime(b, "%d/%m/%y").date()
nwds.append(
NonWorkingDays(
day=i.split("[")[0].strip()[:3].capitalize(),
start_date=nwd_start_date,
end_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
if oop:
formatted_oops = []
for dates in oop.split(","):
s, e = dates.split("-")
formatted_oops.append(
{
"start_date": datetime.datetime.strptime(
s.strip(), "%d/%m/%y"
).date(),
"end_date": datetime.datetime.strptime(
e.strip(), "%d/%m/%y"
).date(),
}
)
oop = formatted_oops
else:
oop = []
print(f"{worker} OOP {oop}")
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"]
w = Worker(
name=worker_name,
site=site.lower(),
grade=int(grade[2]),
id=n,
fte=int(fte),
nwds=nwds,
start_date=start_date,
end_date=end_date,
oop=oop,
not_available_to_work=leave_requests,
work_requests=work_requests,
remote_site=site_pref,
previous_shifts=previous_shifts,
pair=pair,
shift_balance_extra=w["shift_balance_extra"],
bank_holiday_extra=w["bank_holiday_extra"],
)
print(w)
Rota.add_worker(w)
# Rota.build_workers()
# Rota.build_model()
solver_options = {"ratio": ratio, "seconds": time_to_run, "threads": 10}
#start_time = time.time()
Rota.build_and_solve(solver_options, export=True)
# Rota.solve_shifts_by_block(solver_options, block_length=13)
# Rota.solve_shifts_individually(solver_options)
# Rota.solve_model(options=solver_options)
#end_time = time.time()
#print(f"Time taken {end_time-start_time}")
# print(Rota.get_worker_details())
# optimizer = SolverFactory('cbc')
# result = optimizer.solve(prob,tee=True)
# result.Solver.Status = SolverStatus.warning
# prob.solutions.load_from(result)
# ResultsHolder = RotaResults(Rota)
# worker_timetable_brief = ResultsHolder.get_worker_timetable_brief(
# show_prefs=False, show_unavailable=False
# )
#
# print(worker_timetable_brief)
#Rota.export_rota_to_html("proc_rota")
# Rota.export_rota_to_csv("rota")
if suspend_on_finish:
os.system("systemctl suspend")