from pyomo.environ import * from pyomo.opt import SolverFactory from pyomo.opt.results import SolverStatus from rota.shifts import SingleShift, RotaBuilder, days from rota.workers import Worker import datetime import sys import os use_neos = False use_cplex = False solve = True weeks_to_rota = 13 time_to_run = 258500 # allow = 5000 ratio = 0.001 use_live_rota = False suspend_on_finish = False 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.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_over_workers"] = 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.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, constraints=["preclear2", "postclear2"], ), SingleShift( ("truro", "truro no proc"), "weekend_truro", 12.5, days[4:], balance_offset=4, rota_on_nwds=True, force_as_block=True, constraints=["preclear2", "postclear2"], # 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, constraints=["preclear2", "postclear2"], # 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, constraints=["preclear2", "postclear2"], ), SingleShift( ("plymouth",), "weekend_plymouth2", 8, days[5:], balance_offset=4, workers_required=1, rota_on_nwds=True, force_as_block=True, constraints=["preclear2", "postclear2"], ), 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=True, rota_on_nwds=True, constraints=["night", "preclear2", "postclear2"], ), 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=True, rota_on_nwds=True, constraints=["night", "preclear2", "postclear2"], ), ) use_test_workers = False load_leave = True Rota.build_shifts() 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, "Truro {}".format(i), "truro", 4, id=i, ) for i in range(1, 9) ] ) Rota.add_workers( [Worker(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 worker in workers: n = n + 1 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 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: 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) leave_requests = w["leave"] work_requests = w["requests"] site_pref = w["site_pref"] 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, 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("Building model") Rota.build_workers() Rota.sites.add("plymouth_twilights") Rota.build_model() print(Rota.get_worker_details()) if not solve: ResultsHolder = RotaResults(Rota) ResultsHolder.export_rota_to_html() sys.exit(0) solver_options = {"ratio": ratio, "seconds": time_to_run, "threads": 10} Rota.solve_model(options=solver_options) # 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() Rota.export_rota_to_csv("rota") if suspend_on_finish: os.system("systemctl suspend")