diff --git a/gen_proc.py b/gen_proc.py index 81d6b58..00b098d 100644 --- a/gen_proc.py +++ b/gen_proc.py @@ -47,9 +47,9 @@ def main( suspend: bool = False, solve: bool = True, time_to_run: int = 60 * 60 * 10, - ratio: float = 0.0001, - start_date: datetime.datetime = "2026-03-02", - weeks: int = 27, + ratio: float = 0.1, + start_date: datetime.datetime = "2026-09-07", + weeks: int = 26, bom: float = 1, ): rota_start_date = start_date.date() @@ -59,18 +59,17 @@ def main( rota_start_date, weeks_to_rota=weeks, balance_offset_modifier=bom, - name="proc_rota3", + name="proc_rota_sep_2026", ) # Rota = RotaBuilder(start_date, weeks_to_rota=20, balance_offset_modifier=1) Rota.constraint_options["balance_shifts"] = False Rota.constraint_options["balance_shifts_quadratic"] = True Rota.constraint_options["balance_weekends"] = True - Rota.constraint_options["max_night_frequency"] = 4 # 3 + #Rota.constraint_options["max_night_frequency"] = 4 # 3 Rota.constraint_options["max_night_frequency_week_exclusions"] = [] - Rota.constraint_options["max_weekend_frequency"] = 3 - Rota.constraint_options["hard_constrain_pair_separation"] = True + #Rota.constraint_options["max_weekend_frequency"] = 3 - Rota.constraint_options["max_days_per_week_block"] = [(8,4)] + #Rota.constraint_options["max_days_per_week_block"] = [(8,4)] #wr = [ # WorkerRequirement( @@ -376,9 +375,9 @@ def main( nwd_end_date = Rota.rota_end_date if "[" in i: - print("Split nwd", worker_name, i) + #print("Split nwd", worker_name, i) a, b = i.split("[")[1][:-1].split("-") - print(f"{a=} {b=}") + #print(f"{a=} {b=}") nwd_start_date = datetime.datetime.strptime( a, "%d/%m/%Y" ).date() @@ -432,7 +431,7 @@ def main( else: oop = [] - print(f"{worker} OOP {oop}") + #print(f"{worker} OOP {oop}") previous_shifts = {} # if twi: @@ -466,10 +465,10 @@ def main( shift_fte_overrides = {} - if worker_name == "Hadi Mohamed": - shift_fte_overrides = { - "weekend_exeter": 50, - } + #if worker_name == "Hadi Mohamed": + # shift_fte_overrides = { + # "weekend_exeter": 50, + # } #elif worker_name == "Joel Lim": # shift_fte_overrides = { # "plymouth_twilight": 100, @@ -500,7 +499,7 @@ def main( shift_fte_overrides=shift_fte_overrides, ) - print(w) + #print(w) Rota.add_worker(w) leave.load_academy(Rota) diff --git a/leave.py b/leave.py index 72ab3fd..bc98017 100644 --- a/leave.py +++ b/leave.py @@ -33,7 +33,7 @@ def load_leave(Rota): if live_rota: download = s.get( - "https://docs.google.com/spreadsheets/d/e/2PACX-1vQtNrp9F1fjtAh8vFX-W_R3RlD9H-2P_vCLMoHrVKR0e24yabbsdrD65VjwxoHlV1Qnatmw1NeghQHG/pub?gid=1199196967&single=true&output=csv", + "https://docs.google.com/spreadsheets/d/e/2PACX-1vSh64r9uUbPsCdhcV7zmZz10gVqIy6rhXFUvDgJxRD5W45IYEaZpKMxAqDsD1ph6dob4AiRzJXVVtOu/pub?gid=580824586&single=true&output=csv", headers=headers, ) # 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) @@ -55,10 +55,12 @@ def load_leave(Rota): n = 1 for row in reader: if n < 10: - print(row) + pass + #print(row) row_title = row[0] - print(row_title) + #print(row_title) row_date = row[0] + row_extra = row[1] r = row[2:] # header, extract names if n == 1: @@ -210,6 +212,14 @@ def load_leave(Rota): if lower_item: worker["bank_holiday_extra"] = int(lower_item) + elif row_title == "Weekends/Twilights": + if lower_item: + worker["site"] = lower_item + + + #elif row_title == "Academy": + # pass + elif re.match(date_re, row_date) is not None: if lower_item != "": try: @@ -258,70 +268,79 @@ def load_leave(Rota): return workers -# load_leave() - def load_academy(Rota): + """Load academy month blocks from the main published CSV and register avoids. + + Spreadsheet layout expected: + - Column A: row title (should be 'Academy' for relevant rows) + - Column B: start date for the month (e.g. '07/09/2026') + - Columns C...: worker columns matching the header row (first CSV row's columns C...) + Cells containing the word 'academy' (case-insensitive) mark that worker as being in academy + for that month. """ - Some trainees spend month blocks in the academy. This function loads that information from a google sheet that published it as a csv file. - """ - url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQtNrp9F1fjtAh8vFX-W_R3RlD9H-2P_vCLMoHrVKR0e24yabbsdrD65VjwxoHlV1Qnatmw1NeghQHG/pub?gid=1113837782&single=true&output=csv" + # Use the same published CSV as load_leave (main sheet) + url = ( + "https://docs.google.com/spreadsheets/d/e/2PACX-1vSh64r9uUbPsCdhcV7zmZz10gVqIy6rhXFUvDgJxRD5W45IYEaZpKMxAqDsD1ph6dob4AiRzJXVVtOu/pub?gid=580824586&single=true&output=csv" + ) with Session() as s: headers = {"Cache-Control": "no-cache", "Pragma": "no-cache"} download = s.get(url, headers=headers) decoded = download.content.decode("utf-8") - reader = csv.reader(decoded.splitlines(), delimiter=',') + reader = csv.reader(decoded.splitlines(), delimiter=",") + + names = [] + month_starts: list[datetime.date] = [] + month_cells: list[list[str]] = [] n = 0 - names = [] - # collect date rows (these are the month start boundaries) and their cells - date_rows: list[datetime.date] = [] - date_row_cells: list[list[str]] = [] - for row in reader: n += 1 - if n == 1: - # header row: first cell is column title, remaining are worker names - names = [c.strip() for c in row[1:]] - continue - if not row: continue - row_title = row[0].strip() - cells = row[1:] + # header row: collect worker names from column C onwards + if n == 1: + # some files may have two leading columns (like 'Email Address' and blank/extra), so take from index 2 + names = [c.strip() for c in (row[2:] if len(row) > 2 else row[1:])] + continue - # If this row is a date row like '02/03/2026' then treat it as a month-start boundary - if re.match(date_re, row_title): + row_title = row[0].strip() if len(row) > 0 else "" + start_cell = row[1].strip() if len(row) > 1 else "" + cells = row[2:] if len(row) > 2 else [] + + # Only treat rows where the first column mentions 'Academy' (some sheets may include other rows) + if row_title and "academy" in row_title.lower(): + # parse start date try: try: - d = datetime.strptime(row_title, "%d/%m/%Y").date() + start_date = datetime.strptime(start_cell, "%d/%m/%Y").date() except ValueError: - d = datetime.strptime(row_title, "%d/%m/%y").date() + start_date = datetime.strptime(start_cell, "%d/%m/%y").date() except Exception: + # skip rows without a valid date continue - date_rows.append(d) - # normalize cells length to match names - row_cells = [ (cells[i] if i < len(cells) else "").strip() for i in range(len(names)) ] - date_row_cells.append(row_cells) + month_starts.append(start_date) + # normalize cell list to names length + row_cells = [(cells[i] if i < len(cells) else "").strip() for i in range(len(names))] + month_cells.append(row_cells) - # If no date rows found, nothing to do - if not date_rows: + if not month_starts: return - # Identify twilight shift names available in the rota + # determine twilight shifts available twilight_shift_names = [s.name for s in Rota.get_shifts() if "twilight" in s.name] - # For each date boundary, compute its end (next boundary - 1 day) and register avoids where cell=='academy' - for idx, start_date in enumerate(date_rows): - if idx + 1 < len(date_rows): - end_date = date_rows[idx + 1] - timedelta(days=1) + # for each month start, compute end date and register avoids + for idx, start_date in enumerate(month_starts): + if idx + 1 < len(month_starts): + end_date = month_starts[idx + 1] - timedelta(days=1) else: end_date = Rota.rota_end_date - cells = date_row_cells[idx] + cells = month_cells[idx] for i, cell in enumerate(cells): if i >= len(names): break @@ -341,7 +360,7 @@ def load_academy(Rota): sh = Rota.get_shift_by_name(sh_name) except Exception: continue - if hasattr(worker, "site") and worker.site in sh.sites: + if hasattr(worker, "site") and worker.site in getattr(sh, "sites", []): candidate_shifts.append(sh_name) elif sh_name.startswith(f"{worker.site}_"): candidate_shifts.append(sh_name) @@ -349,7 +368,12 @@ def load_academy(Rota): if not candidate_shifts: candidate_shifts = twilight_shift_names.copy() - # register as a date range with a reason 'academy' + #print(f"Academy: {worker.name} on academy from {start_date} to {end_date}, avoiding shifts: {candidate_shifts}") + Rota.add_avoid_shifts_for_workers_on_dates( - names=[worker.name], shifts=candidate_shifts, start_date=start_date, end_date=end_date, reason="academy" + names=[worker.name], + shifts=candidate_shifts, + start_date=start_date, + end_date=end_date, + reason="academy", ) \ No newline at end of file