Refactor weekend day balancing logic by commenting out unused option and storing adjusted targets on worker objects
This commit is contained in:
+1
-1
@@ -488,7 +488,7 @@ def main(
|
|||||||
balance_weekends=True,
|
balance_weekends=True,
|
||||||
max_shifts_per_week=6,
|
max_shifts_per_week=6,
|
||||||
max_days_per_week_block=[(4, 2), (4, 4)],
|
max_days_per_week_block=[(4, 2), (4, 4)],
|
||||||
balance_weekend_days=True,
|
#balance_weekend_days=True,
|
||||||
balance_weekend_days_quadratic=True,
|
balance_weekend_days_quadratic=True,
|
||||||
weekend_day_hard_max_deviation=2,
|
weekend_day_hard_max_deviation=2,
|
||||||
balance_weekend_days_use_previous_shifts=True,
|
balance_weekend_days_use_previous_shifts=True,
|
||||||
|
|||||||
Regular → Executable
+3
@@ -19,3 +19,6 @@ django>=6.0,<7
|
|||||||
django-crispy-forms
|
django-crispy-forms
|
||||||
crispy-bulma
|
crispy-bulma
|
||||||
django-debug-toolbar
|
django-debug-toolbar
|
||||||
|
pandas
|
||||||
|
odfpy
|
||||||
|
openpyxl
|
||||||
+15
-58
@@ -2596,7 +2596,10 @@ class RotaBuilder(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
weekend_day_shift_target_number = weekend_day_shift_target_number + prev_adj
|
weekend_day_shift_target_number = weekend_day_shift_target_number + prev_adj
|
||||||
|
# store adjusted per-day weekend target on the worker object for later use in objectives
|
||||||
|
if not hasattr(worker, 'weekend_day_target'):
|
||||||
|
worker.weekend_day_target = {}
|
||||||
|
worker.weekend_day_target[day_name] = weekend_day_shift_target_number
|
||||||
self.model.constraints.add(
|
self.model.constraints.add(
|
||||||
self.model.weekend_day_shift_count_t1[worker.id, day_name]
|
self.model.weekend_day_shift_count_t1[worker.id, day_name]
|
||||||
- self.model.weekend_day_shift_count_t2[worker.id, day_name]
|
- self.model.weekend_day_shift_count_t2[worker.id, day_name]
|
||||||
@@ -3587,64 +3590,18 @@ class RotaBuilder(object):
|
|||||||
weekend_day_term = 0
|
weekend_day_term = 0
|
||||||
if self.constraint_options_model.balance_weekend_days_quadratic:
|
if self.constraint_options_model.balance_weekend_days_quadratic:
|
||||||
wd_weight = self.constraint_options_model.balance_weekend_days_weight
|
wd_weight = self.constraint_options_model.balance_weekend_days_weight
|
||||||
# Quadratic penalty: strongly discourage deviations from per-day targets
|
# HiGHS/appsi does not accept arbitrary nonlinear objective expressions here.
|
||||||
weekend_day_term = 0
|
# Use the existing t1/t2 linear representation (which approximates absolute
|
||||||
for worker in self.workers:
|
# deviation) in the objective instead of a true squared term.
|
||||||
for day_name in ("Sat", "Sun"):
|
weekend_day_term = sum(
|
||||||
# base target derived from shift targets distributed over shift days
|
wd_weight
|
||||||
base_target = sum(
|
* (
|
||||||
worker.shift_target_number[shift.name] / len(shift.days)
|
self.model.weekend_day_shift_count_t1[(worker.id, day_name)]
|
||||||
for shift in self.get_shifts()
|
+ self.model.weekend_day_shift_count_t2[(worker.id, day_name)]
|
||||||
if day_name in shift.days
|
)
|
||||||
|
for worker in self.workers
|
||||||
|
for day_name in ("Sat", "Sun")
|
||||||
)
|
)
|
||||||
|
|
||||||
# optionally include previous_shifts adjustments into the per-day target
|
|
||||||
prev_adj = 0.0
|
|
||||||
if self.use_previous_shifts and self.constraint_options_model.balance_weekend_days_use_previous_shifts:
|
|
||||||
for shift in self.get_shifts():
|
|
||||||
if day_name in shift.days and shift.name in getattr(worker, "previous_shifts", {}):
|
|
||||||
entry = worker.previous_shifts.get(shift.name)
|
|
||||||
if isinstance(entry, dict):
|
|
||||||
# prefer explicit per-day entry
|
|
||||||
if day_name in entry:
|
|
||||||
try:
|
|
||||||
worked = float(entry[day_name][0])
|
|
||||||
allocated = float(entry[day_name][1])
|
|
||||||
prev_adj += (allocated - worked)
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
elif "__all__" in entry:
|
|
||||||
try:
|
|
||||||
worked = float(entry["__all__"][0])
|
|
||||||
allocated = float(entry["__all__"][1])
|
|
||||||
prev_adj += (allocated - worked) / max(1, len(shift.days))
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
# sum any tuple/list values found in the dict (spread over days)
|
|
||||||
try:
|
|
||||||
total_worked = 0.0
|
|
||||||
total_alloc = 0.0
|
|
||||||
for v in entry.values():
|
|
||||||
if isinstance(v, (list, tuple)):
|
|
||||||
total_worked += float(v[0])
|
|
||||||
total_alloc += float(v[1])
|
|
||||||
prev_adj += (total_alloc - total_worked) / max(1, len(shift.days))
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
worked = float(entry[0])
|
|
||||||
allocated = float(entry[1])
|
|
||||||
prev_adj += (allocated - worked) / max(1, len(shift.days))
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
adjusted_target = base_target + prev_adj
|
|
||||||
|
|
||||||
weekend_day_term += wd_weight * (
|
|
||||||
self.model.weekend_day_shift_count[(worker.id, day_name)] - adjusted_target
|
|
||||||
) ** 2
|
|
||||||
elif self.constraint_options_model.balance_weekend_days:
|
elif self.constraint_options_model.balance_weekend_days:
|
||||||
weekend_day_modifier = self.constraint_options_model.balance_weekend_days_weight
|
weekend_day_modifier = self.constraint_options_model.balance_weekend_days_weight
|
||||||
weekend_day_term = sum(
|
weekend_day_term = sum(
|
||||||
|
|||||||
Reference in New Issue
Block a user