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 import os use_neos = False use_cplex = False solve = True weeks_to_rota = 26 time_to_run = 158500 #allow = 5000 ratio = 0.05 use_live_rota = False suspend_on_finish = True sites = ("truro", "exeter", "torbay", "barnstaple", "plymouth", "proc", "truro no proc") Rota = RotaBuilder(datetime.date(2021, 9, 6), #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) 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["hard_constrain_pair_separation"] = True Rota.constraint_options["max_weekends"] = 20 Rota.constraint_options["prevent_monday_and_tuesday_after_full_weekends"] = [ "truro", "torbay", "exeter", "plymouth", "truro no proc" ] 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", "truro no proc" ] Rota.add_shifts( SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:4], constraints=["max_2_shifts_per_week"]), SingleShift(("truro", "truro no proc" ), "truro_twilight", 12.5, days[:4]), SingleShift(("torbay", ), "torbay_twilight", 12.5, days[:4], balance_offset=4, 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=3, rota_on_nwds=True, force_as_block=True), SingleShift(("truro", "truro no proc"), "weekend_truro", 12.5, days[4:], balance_offset=3, rota_on_nwds=True, force_as_block=True, #force_as_block_unless_nwd=True ), SingleShift(("torbay", ), "weekend_torbay", 12.5, days[4:], balance_offset=3, rota_on_nwds=True, force_as_block=True, #force_as_block_unless_nwd=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(("plymouth", "plymouth_twilights"), "plymouth_bank_holidays", 8, days[5:], balance_offset=3, 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, 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, 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"] 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 = 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) 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, pair=pair)) print("Building model") Rota.build_shifts_and_workers() Rota.sites.add("plymouth_twilights") 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") if suspend_on_finish: os.system("systemctl suspend")