.
This commit is contained in:
+21
-29
@@ -3,7 +3,7 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from rota_generator.shifts import MaxShiftsPerWeekConstraint, NoWorkers, PostShiftConstraint, PreShiftConstraint, RotaBuilder, SingleShift, WorkerRequirement, days
|
from rota_generator.shifts import MaxShiftsPerWeekConstraint, NoWorkers, PostShiftConstraint, PreShiftConstraint, RotaBuilder, SingleShift, WorkerRequirement, days, RotaConstraintOptions
|
||||||
import typer
|
import typer
|
||||||
import subprocess
|
import subprocess
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -38,11 +38,11 @@ def extract_leave_and_rota_from_calender(calender_df):
|
|||||||
|
|
||||||
# Get rota data from the second row (index 1), starting from column 5 (index 4)
|
# Get rota data from the second row (index 1), starting from column 5 (index 4)
|
||||||
# Extract the rota data for each worker from the first data row (index 0), columns 5 onwards
|
# Extract the rota data for each worker from the first data row (index 0), columns 5 onwards
|
||||||
rota_row = calender_df.iloc[0]
|
#rota_row = calender_df.iloc[0]
|
||||||
#rota_data = {}
|
#rota_data = {}
|
||||||
for i, worker in enumerate(worker_names):
|
#for i, worker in enumerate(worker_names):
|
||||||
assignment = str(rota_row.iloc[5 + i]).strip() if len(rota_row) > 5 + i else ""
|
# assignment = str(rota_row.iloc[5 + i]).strip() if len(rota_row) > 5 + i else ""
|
||||||
#rota_data[worker] = f"rota {assignment.lower()}"
|
# #rota_data[worker] = f"rota {assignment.lower()}"
|
||||||
|
|
||||||
leave_requests = []
|
leave_requests = []
|
||||||
work_requests = []
|
work_requests = []
|
||||||
@@ -55,6 +55,9 @@ def extract_leave_and_rota_from_calender(calender_df):
|
|||||||
date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date()
|
date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||||
except Exception:
|
except Exception:
|
||||||
continue # skip rows with invalid date
|
continue # skip rows with invalid date
|
||||||
|
|
||||||
|
if date < datetime.date(2026, 2, 16):
|
||||||
|
continue # skip rows before rota start date
|
||||||
for i, worker in enumerate(worker_names):
|
for i, worker in enumerate(worker_names):
|
||||||
if not worker or worker.lower() == "nan":
|
if not worker or worker.lower() == "nan":
|
||||||
continue
|
continue
|
||||||
@@ -91,6 +94,7 @@ def extract_shift_assignments_from_rota(rota_df):
|
|||||||
shifts = ["twilight", "oncall"]
|
shifts = ["twilight", "oncall"]
|
||||||
assignments = []
|
assignments = []
|
||||||
|
|
||||||
|
skipped_rows = 0
|
||||||
for idx, row in rota_df.iloc[2:].iterrows():
|
for idx, row in rota_df.iloc[2:].iterrows():
|
||||||
week_start_str = str(row.iloc[0]).strip().split(" ")[0] # Get the week start date from the first column (index 0)
|
week_start_str = str(row.iloc[0]).strip().split(" ")[0] # Get the week start date from the first column (index 0)
|
||||||
try:
|
try:
|
||||||
@@ -99,6 +103,10 @@ def extract_shift_assignments_from_rota(rota_df):
|
|||||||
print(f"Invalid date format in row {idx}: {week_start_str}")
|
print(f"Invalid date format in row {idx}: {week_start_str}")
|
||||||
continue # skip rows with invalid date
|
continue # skip rows with invalid date
|
||||||
|
|
||||||
|
if week_start < datetime.date(2026, 2, 16):
|
||||||
|
skipped_rows += 1
|
||||||
|
continue # skip rows before rota start date
|
||||||
|
|
||||||
for i, day in enumerate(days):
|
for i, day in enumerate(days):
|
||||||
for j, shift in enumerate(shifts):
|
for j, shift in enumerate(shifts):
|
||||||
if i < 5:
|
if i < 5:
|
||||||
@@ -122,7 +130,7 @@ def extract_shift_assignments_from_rota(rota_df):
|
|||||||
shift = "weekend"
|
shift = "weekend"
|
||||||
assignments.append({
|
assignments.append({
|
||||||
"week_start": week_start,
|
"week_start": week_start,
|
||||||
"week": idx-1, # 1-indexed week number
|
"week": idx-1-skipped_rows, # 1-indexed week number
|
||||||
"day": day,
|
"day": day,
|
||||||
"date": week_start + datetime.timedelta(days=i),
|
"date": week_start + datetime.timedelta(days=i),
|
||||||
"shift": shift,
|
"shift": shift,
|
||||||
@@ -147,7 +155,7 @@ def load_workers():
|
|||||||
|
|
||||||
# Extract sheets
|
# Extract sheets
|
||||||
days_df = xls["Days"]
|
days_df = xls["Days"]
|
||||||
calender_df = xls["Calender"]
|
calender_df = xls["Calendar"]
|
||||||
rota_df = xls["Rota"]
|
rota_df = xls["Rota"]
|
||||||
|
|
||||||
|
|
||||||
@@ -193,7 +201,7 @@ def load_workers():
|
|||||||
for i, day in enumerate(weekday_cols):
|
for i, day in enumerate(weekday_cols):
|
||||||
val = row.iloc[3 + i]
|
val = row.iloc[3 + i]
|
||||||
# You can adjust the logic below depending on your marking scheme (e.g. "Y", "Yes", "1", etc.)
|
# You can adjust the logic below depending on your marking scheme (e.g. "Y", "Yes", "1", etc.)
|
||||||
available = not ("no" in str(val).strip().lower())
|
available = not ("no" in str(val).strip().lower() or str(val).strip().lower() == "yes*")
|
||||||
availability[day] = available
|
availability[day] = available
|
||||||
workers_days.append({"initial": initial, "name": name, "availability": availability, "requests": requests})
|
workers_days.append({"initial": initial, "name": name, "availability": availability, "requests": requests})
|
||||||
|
|
||||||
@@ -329,14 +337,12 @@ def main(
|
|||||||
balance_offset_modifier=bom,
|
balance_offset_modifier=bom,
|
||||||
name="cons rota feb 2026",
|
name="cons rota feb 2026",
|
||||||
allow_force_assignment_with_leave_conflict=True,
|
allow_force_assignment_with_leave_conflict=True,
|
||||||
|
constraint_options=RotaConstraintOptions(
|
||||||
|
balance_weekends=True,
|
||||||
|
max_shifts_per_week=6,
|
||||||
|
max_days_per_week_block=[(4, 2), (6, 3)],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
# Rota = RotaBuilder(start_date, weeks_to_rota=20, balance_offset_modifier=1)
|
|
||||||
Rota.constraint_options["balance_weekends"] = True
|
|
||||||
#Rota.constraint_options["max_weekend_frequency"] = 2
|
|
||||||
Rota.constraint_options["max_shifts_per_week"] = 6
|
|
||||||
Rota.constraint_options["max_days_worked_per_week"] = 3
|
|
||||||
# Rota.constraint_options["avoid_st2_first_month"] = True
|
|
||||||
Rota.constraint_options["max_days_per_week_block"] = [(4, 2), (6, 3)] # (max_days, week_block)
|
|
||||||
|
|
||||||
#Rota.enable_unavailabilities = False
|
#Rota.enable_unavailabilities = False
|
||||||
|
|
||||||
@@ -351,10 +357,6 @@ def main(
|
|||||||
constraints=[
|
constraints=[
|
||||||
PreShiftConstraint(days=2, start_date="2025-11-17", exclude_days=("Fri", "Sat", "Sun")),
|
PreShiftConstraint(days=2, start_date="2025-11-17", exclude_days=("Fri", "Sat", "Sun")),
|
||||||
PostShiftConstraint(days=1, start_date="2025-11-17", exclude_days=("Fri", "Sat", "Sun"), allow_self=True),
|
PostShiftConstraint(days=1, start_date="2025-11-17", exclude_days=("Fri", "Sat", "Sun"), allow_self=True),
|
||||||
#{
|
|
||||||
# "name": "max_shifts_per_week",
|
|
||||||
# "options": 3,
|
|
||||||
#}
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SingleShift(
|
SingleShift(
|
||||||
@@ -365,9 +367,6 @@ def main(
|
|||||||
days=days[5:],
|
days=days[5:],
|
||||||
balance_offset=1,
|
balance_offset=1,
|
||||||
constraints=[
|
constraints=[
|
||||||
#{"name": "pre", "options": 2},
|
|
||||||
|
|
||||||
#{"name": "post", "options": 1},
|
|
||||||
PostShiftConstraint(days=1, start_date="2025-11-17", ignore_shifts=["oncall"]),
|
PostShiftConstraint(days=1, start_date="2025-11-17", ignore_shifts=["oncall"]),
|
||||||
],
|
],
|
||||||
display_char="a",
|
display_char="a",
|
||||||
@@ -380,10 +379,6 @@ def main(
|
|||||||
length=12.5,
|
length=12.5,
|
||||||
days=days[5:],
|
days=days[5:],
|
||||||
balance_offset=2,
|
balance_offset=2,
|
||||||
#constraint=[
|
|
||||||
# {"name": "pre", "options": 1},
|
|
||||||
# {"name": "post", "options": 1},
|
|
||||||
#],
|
|
||||||
display_char="b",
|
display_char="b",
|
||||||
),
|
),
|
||||||
SingleShift(
|
SingleShift(
|
||||||
@@ -398,11 +393,8 @@ def main(
|
|||||||
days=days[:5],
|
days=days[:5],
|
||||||
balance_offset=1,
|
balance_offset=1,
|
||||||
constraints=[
|
constraints=[
|
||||||
#PreShiftConstraint(days=1, start_date="2025-11-17", ignore_shifts=["oncall"]),
|
|
||||||
PreShiftConstraint(days=1, start_date="2025-11-17", ignore_shifts=["oncall"], exclude_days=("Sat", "Sun")),
|
PreShiftConstraint(days=1, start_date="2025-11-17", ignore_shifts=["oncall"], exclude_days=("Sat", "Sun")),
|
||||||
#PostShiftConstraint(days=1, start_date="2025-11-17", ignore_shifts=["oncall", "weekend a", "weekend b"], include_weekends=False),
|
|
||||||
MaxShiftsPerWeekConstraint(max_shifts=1),
|
MaxShiftsPerWeekConstraint(max_shifts=1),
|
||||||
#{"name": "post", "options": 1},
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ class RotaBuilder(object):
|
|||||||
SHIFT_BOUNDS=SHIFT_BOUNDS,
|
SHIFT_BOUNDS=SHIFT_BOUNDS,
|
||||||
name: str = "",
|
name: str = "",
|
||||||
bank_holidays: dict[datetime.date, str] = bank_holiday_map,
|
bank_holidays: dict[datetime.date, str] = bank_holiday_map,
|
||||||
|
constraint_options: RotaConstraintOptions | dict | None = None,
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user