diff --git a/gen.py b/gen.py index 6719e07..2ecd894 100644 --- a/gen.py +++ b/gen.py @@ -3,6 +3,10 @@ import os import sys import time from rota.shifts import NoWorkers, RotaBuilder, SingleShift, days +import typer +import subprocess + +app = typer.Typer() sites = ( @@ -15,6 +19,8 @@ sites = ( "proc", ) +NIGHT_REGISTRAR_NUMBER = 4 + from rota.workers import ( Worker, NotAvailableToWork, @@ -24,355 +30,484 @@ from rota.workers import ( OutOfProgramme, ) +@app.command() +def main( + suspend_on_finish: bool = False, + solve: bool = True, + time_to_run: int = 60 * 60, + ratio: float = 0.001, + start_date: datetime.datetime = "2025-03-03", + weeks: int = 26, + bom: int = 1, +): -suspend_on_finish = False -solve = True -# time_to_run = 508500 -# time_to_run = 2660000 -time_to_run = 60 * 60 * 100 -#time_to_run = 60 * 300 -# allow = 5000 -ratio = 0.5 -#ratio = 0.9 + rota_start_date = start_date.date() + Rota = RotaBuilder( + rota_start_date, weeks_to_rota=weeks, balance_offset_modifier=bom, 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 -start_date = datetime.date(2024, 3, 4) -# start_date = datetime.date(2023, 5, 1) -# start_date = start_date + datetime.timedelta(weeks=13) -# start_date = datetime.date(2022, 10, 10) + Rota.add_shifts( + SingleShift( + sites=( + "exeter", + "exeter twilights and weekends", + "exeter no nights", + "exeter twilights", + ), + name="exeter_twilight", + length=12.5, + days=days[:5], + balance_offset=4, + constraint=[ + { + "name": "max_shifts_per_week", + "options": 2, + } + ], + ), + SingleShift( + sites=( + "truro", + "truro no nights", + "truro twilights", + "truro twilights and weekends", + "truro twilights and weekend nights", + "truro twilights, weekends and weekend nights", + ), + name="truro_twilight", + length=12.5, + days=days[:4], + balance_offset=4, + ), + SingleShift( + sites=("torbay", "torbay twilights", "torbay twilights and weekends"), + 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", + "plymouth twilights, weekends and weekend nights", + ), + 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", + "exeter no nights", + "exeter 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", + "truro no nights", + "truro twilights and weekends", + "truro weekends", + "truro twilights, weekends and weekend nights", + ), + name="weekend_truro", + length=12.5, + days=days[4:], + balance_offset=3, + #rota_on_nwds=True, + #force_as_block=True, + assign_as_block=True, + constraint=[{"name": "pre", "options": 2}, {"name": "post", "options": 2}], + # force_as_block_unless_nwd=True + ), + SingleShift( + sites=("torbay", "torbay twilights and weekends"), + 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", + "plymouth first on weekends only", + "plymouth twilights, weekends and weekend nights", + ), + 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", + "plymouth twilights, weekends and weekend nights", + ), + 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", + "plymouth twilights, weekends and weekend nights", + ), + 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)}, + ], + end_date=datetime.datetime.strptime("2025-06-01", "%Y-%m-%d").date(), + ), + SingleShift( + sites=[ + *sites, + "plymouth twilights, weekends and weekend nights", + "truro twilights and weekend nights", + "weekend nights", + "truro twilights, weekends and weekend nights", + ], + 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)}, + ], + end_date=datetime.datetime.strptime("2025-06-01", "%Y-%m-%d").date(), + ), + SingleShift( + sites=sites, + name="night_weekday4", + length=12.25, + days=days[:4], + balance_offset=3.9, + balance_weighting=1, + # hard_constrain_shift=False, + workers_required=NIGHT_REGISTRAR_NUMBER, + 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)}, + ], + start_date=datetime.datetime.strptime("2025-06-02", "%Y-%m-%d").date(), + ), + SingleShift( + sites=[ + *sites, + "plymouth twilights, weekends and weekend nights", + "truro twilights and weekend nights", + "weekend nights", + "truro twilights, weekends and weekend nights", + ], + name="night_weekend4", + length=12.25, + days=days[4:], + balance_offset=2.9, + balance_weighting=1, + # hard_constrain_shift=False, + workers_required=NIGHT_REGISTRAR_NUMBER, + 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)}, + ], + start_date=datetime.datetime.strptime("2025-06-02", "%Y-%m-%d").date(), + ), + ) -Rota = RotaBuilder( - start_date, weeks_to_rota=8, 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_grade_constraint_by_week([2], [1, 2], ["night_weekday", "night_weekend"]) -Rota.add_shifts( - SingleShift( - sites=("exeter", "exeter twilights and weekends", "exeter no nights"), - 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", "truro twilights and weekends"), - name="truro_twilight", - length=12.5, - days=days[:4], - balance_offset=4, - ), - SingleShift( - sites=("torbay", "torbay twilights", "torbay twilights and weekends"), - 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", "plymouth twilights, weekends and weekend nights"), - 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", "exeter no nights"), - 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", "truro twilights and weekends"), - 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", "torbay twilights and weekends"), - 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", "plymouth first on weekends only", "plymouth twilights, weekends and weekend nights"), - 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", "plymouth twilights, weekends and weekend nights"), - 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", "plymouth twilights, weekends and weekend nights"), - 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, "plymouth twilights, weekends and weekend nights"], - 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)}, - ], - ), -) + load_leave = True + Rota.build_shifts() + # Rota.add_grade_constraint_by_week([2], [1], Rota.get_shift_names()) -# Rota.add_grade_constraint_by_week([2], [1, 2], ["night_weekday", "night_weekend"]) + if load_leave: + import leave -load_leave = True -Rota.build_shifts() -# Rota.add_grade_constraint_by_week([2], [1], Rota.get_shift_names()) + workers = leave.load_leave(Rota) -if load_leave: - import leave + 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 - workers = leave.load_leave(Rota) + 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 - 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 + # Ignore trainees if fte == 0 (what are they doing here anyway) + if fte == 0 or fte == "0" or fte == "": + continue - 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 + nwds = [] + if nwd: + for i in nwd.split(","): + nwd_start_date = Rota.start_date + nwd_end_date = Rota.rota_end_date - # Ignore trainees if fte == 0 (what are they doing here anyway) - if fte == 0 or fte == "0" or fte == "": - continue + 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 = [] - 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.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 + # 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(","): - print(dates) - s, e = dates.split("-") - try: - 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(), - } - ) - except ValueError as e: - print(e) - print("WORKER", worker) - print("DATES", s, e) - raise + if oop: + formatted_oops = [] + for dates in oop.split(","): + print(dates) + if "-" in dates: + s, e = dates.split("-") + elif "to" in dates: + s, e = dates.split(" to ") + else: + raise ValueError(f"Cannot parse OOP dates: {dates}") + try: + 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(), + } + ) + except ValueError: + try: + 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(), + } + ) + except ValueError as e: + print(e) + print("WORKER", worker) + print("DATES", s, e) + raise + + 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} + #solver_options = {"seconds": time_to_run, "threads": 10} + + # start_time = time.time() + Rota.build_and_solve(solver_options, export=True, solve=solve, solver="appsi_highs") + + #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") + subprocess.run(["scp", Rota.exported_rota_file, "ross@46.101.13.46:proc/proc-rota/output/"]) + + if suspend_on_finish: + os.system("systemctl suspend") - 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, solve=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") +if __name__ == "__main__": + app() diff --git a/leave.py b/leave.py index 0199480..6f0eddd 100644 --- a/leave.py +++ b/leave.py @@ -2,6 +2,9 @@ import csv from datetime import datetime import re from requests import Session +from loguru import logger + +from rich.pretty import pprint from rota.workers import Worker, NotAvailableToWork, NonWorkingDays, WorkRequests, PreferenceNotToWork, OutOfProgramme @@ -9,6 +12,8 @@ from rota.workers import Worker, NotAvailableToWork, NonWorkingDays, WorkRequest date_re = r"[\d]{1,2}\/[\d]{1,2}\/[\d]{2}" +live_rota = True + def load_leave(Rota): with Session() as s: @@ -21,14 +26,17 @@ def load_leave(Rota): "Pragma": "no-cache" } - download = s.get("https://docs.google.com/spreadsheets/d/e/2PACX-1vRYqSKq-OYW1tNnGJtx9LIMo_FXVfcHBRPF4HEMcBxWPfmXimWtr5oEDR_rIUSXrOndb9YjP3iC_4bS/pub?gid=0&single=true&output=csv", headers=headers) - decoded_content = download.content.decode('utf-8') + if live_rota: + download = s.get("https://docs.google.com/spreadsheets/d/e/2PACX-1vSRx9VWXSlRubPyA0RhiI-Oqf5eHNYYEc6rFzlraDbR5_8qqr5g13-4uV-gn4u-TjZxiSMv1fBUaESq/pub?gid=814517272&single=true&output=csv", headers=headers) + decoded_content = download.content.decode('utf-8') - reader = csv.reader(decoded_content.splitlines(), delimiter=',') + reader = csv.reader(decoded_content.splitlines(), delimiter=',') - #with open("/home/ross/Downloads/Draft PROC Rota March 24 - Sept 24 - Sheet1.csv") as f: - # reader = csv.reader(f.read().splitlines(), delimiter=',') + else: + with open("/home/ross/Downloads/PROC Draft RK version.csv") as f: + reader = csv.reader(f.read().splitlines(), delimiter=',') + n = 0 leave = {} requests = {} site_prefs = {} @@ -37,7 +45,11 @@ def load_leave(Rota): n = 1 for row in reader: - row_title = row[0] + if n < 10: + print(row) + row_title = row[1] + print(row_title) + row_date = row[0] r = row[2:] # header, extract names if n == 1: @@ -70,16 +82,16 @@ def load_leave(Rota): if lower_item == "derriford twilights": lower_item = "plymouth_twilights" - if "PROC site" in row_title: + if "PROC site" in row_title or row_title == "PROC nights from": worker["site_pref"] = lower_item - if row_title in ("Rotation", "Site"): + elif row_title in ("Rotation", "Site", "Placement", "Placement location"): worker["site"] = lower_item - elif row_title == "Grade": + elif row_title in ("Grade", "Grade (ST)"): worker["grade"] = lower_item - elif row_title == "%FTE": + elif row_title in ("%FTE", "%FTE on-call", "FTE", "FTE on-call"): worker["fte"] = lower_item elif "NWD" in row_title: @@ -98,7 +110,13 @@ def load_leave(Rota): elif row_title in ("Start date",): if lower_item: - date = datetime.strptime(lower_item, "%d/%m/%y").date() + try: + date = datetime.strptime(lower_item, "%d/%m/%y").date() + except ValueError: + date = datetime.strptime(lower_item, "%d/%m/%Y").date() + except ValueError: + logger.warning(f"Cannot parse date: {lower_item} (n: {n}, row: {row})") + raise ValueError(f"Cannot parse date: {lower_item}") worker["start_date"] = date else: worker["start_date"] = None @@ -128,19 +146,25 @@ def load_leave(Rota): if lower_item: worker["bank_holiday_extra"] = int(lower_item) - elif re.match(date_re, row_title) is not None: + elif re.match(date_re, row_date) is not None: if lower_item != "": try: try: - date = datetime.strptime(row[0], "%d/%m/%y").date() + date = datetime.strptime(row_date, "%d/%m/%y").date() except ValueError: - date = datetime.strptime(row[0], "%d/%m/%Y").date() + date = datetime.strptime(row_date, "%d/%m/%Y").date() + # This may be easier to do as a dict if lower_item in shifts: worker["requests"].append(WorkRequests(date=date, shift=lower_item)) + elif lower_item in ("nights only"): + if date.weekday() < 5: + worker["requests"].append(WorkRequests(date=date, shift="night_weekday")) + else: + worker["requests"].append(WorkRequests(date=date, shift="night_weekend")) - elif lower_item in ("work_request", "happy to work", "offered"): + elif lower_item in ("work_request", "happy to work", "offered", "may be", "volunteered"): worker["requests"].append(WorkRequests(date=date, shift="*")) diff --git a/output/timetable.js b/output/timetable.js index 4a5c27f..670eaf7 100644 --- a/output/timetable.js +++ b/output/timetable.js @@ -149,6 +149,8 @@ function generateExtra() { bank_holidays_worked = bank_holidays.length; + nights_worked= $(tr).find(".night-shift").length; + shift_diff = JSON.parse(worker_td.attr("data-shift-diff")); summed_shift_diff = Object.values(shift_diff).reduce((a, b) => a + b); @@ -161,6 +163,7 @@ function generateExtra() { worker_td.after(`