diff --git a/gen.py b/gen.py index 89a5451..b937e7f 100644 --- a/gen.py +++ b/gen.py @@ -38,8 +38,8 @@ def main( time_to_run: int = 60 * 60, ratio: float = 0.001, start_date: datetime.datetime = "2025-09-01", - weeks: int = 26, - bom: int = 1, + weeks: int = 16, + bom: int = 2, ): rota_start_date = start_date.date() suspend_on_finish = suspend @@ -267,70 +267,20 @@ def main( "name": "require_remote_site_presence_week", "options": ("plymouth", 1), }, - {"name": "limit_grade_number", "options": {2: 1}}, + {"name": "limit_grade_number", "options": {2: 2}}, {"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.add_grade_constraint_by_week([2], [1, 2], ["night_weekday", "night_weekend"]) + # Prevent shifts for ST2s for 2 weeks + Rota.add_grade_constraint_by_week([2], [1, 2], [shift.name for shift in Rota.shifts]) Rota.add_min_summed_grade_by_shifts_per_day_constraint(["weekend_plymouth1", "weekend_plymouth2"], 6) + Rota.add_min_summed_grade_by_shifts_per_day_constraint(["plymouth_twilight", "plymouth_bank_holidays"], 6) + + Rota.terminate_on_warning.remove("Worker/no valid shifts") load_leave = True Rota.build_shifts() diff --git a/rota/shifts.py b/rota/shifts.py index ceae404..493024a 100644 --- a/rota/shifts.py +++ b/rota/shifts.py @@ -1029,13 +1029,15 @@ class RotaBuilder(object): for day in days_list: # Only consider if all shifts are present that day if all(shift in self.get_shift_names_by_week_day(week, day) for shift in constraint.shifts): - self.model.constraints.add( - sum( - worker.grade * self.model.works[worker.id, week, day, shift] - for shift in constraint.shifts - for worker in self.get_workers_for_shift(self.get_shift_by_name(shift)) - ) >= constraint.min_grade_sum - ) + expr_terms = [ + worker.grade * self.model.works[worker.id, week, day, shift] + for shift in constraint.shifts + for worker in self.get_workers_for_shift(self.get_shift_by_name(shift)) + ] + if expr_terms: # Only add constraint if there are eligible workers + self.model.constraints.add( + sum(expr_terms) >= constraint.min_grade_sum + ) @@ -1783,6 +1785,15 @@ class RotaBuilder(object): - adjusted_balance_offset * self.balance_offset_modifier ) + # --- Store for export --- + if not hasattr(worker, "hard_constrain_shift_limits"): + worker.hard_constrain_shift_limits = {} + worker.hard_constrain_shift_limits[shift.name] = { + "min_shifts": min_shifts, + "max_shifts": max_shifts, + "target_shifts": target_shifts, + } + self.model.constraints.add( inequality( min_shifts, @@ -1791,8 +1802,6 @@ class RotaBuilder(object): for week, day in self.get_week_day_combinations_for_shift( shift ) - # for week, day in self.get_week_day_combinations() - # if shift.name in self.get_shift_names_by_week_day(week, day) ), max_shifts, ) @@ -4190,6 +4199,15 @@ class RotaBuilder(object): joined_timetable = "\n".join(timetable) + hard_constrain_shift_info = [] + for worker in self.workers: + for shift_name, limits in getattr(worker, "hard_constrain_shift_limits", {}).items(): + hard_constrain_shift_info.append({ + "worker": worker.name, + "shift": shift_name, + **limits, + }) + html = f"""
+ {json.dumps(hard_constrain_shift_info, indent=4)}
+ "