start working on shift paring balancing

This commit is contained in:
Ross
2024-12-26 16:39:04 +00:00
parent 1ed5f4fbcd
commit f0b9ee0ebc
6 changed files with 741 additions and 378 deletions
+61 -25
View File
@@ -125,6 +125,7 @@ class SingleShift(BaseModel):
constraint: List[ShiftConstraint] = []
start_date: datetime.date | None = None
end_date: datetime.date | None = None
pair_proxy: bool = False
model_config = ConfigDict(
extra = "allow",
@@ -280,6 +281,8 @@ class RotaBuilder(object):
self.paired_shifts = []
self.exported_rota_file = None
def set_rota_dates(self, start_date: datetime.date, weeks_to_rota: int):
self.weeks_to_rota = weeks_to_rota
@@ -1118,23 +1121,26 @@ class RotaBuilder(object):
print(shift_name)
print(week, self.get_week_start_date(week), shift.start_date, shift.end_date)
if self.get_week_start_date(week) <= shift.start_date:
continue
#if self.get_week_start_date(week) <= shift.start_date:
# continue
if self.get_week_start_date(week) > shift.end_date:
continue
#if self.get_week_start_date(week) > shift.end_date:
# continue
for worker in self.get_workers_for_shift(shift):
self.model.constraints.add(
8
* self.model.blocks_worker_shift_assigned[
worker.id, week, shift_name
]
>= sum(
self.model.works[worker.id, week, day, shift_name]
for day in shift.days
try:
self.model.constraints.add(
8
* self.model.blocks_worker_shift_assigned[
worker.id, week, shift_name
]
>= sum(
self.model.works[worker.id, week, day, shift_name]
for day in shift.days
)
)
)
except KeyError:
pass
self.model.constraints.add(
self.model.blocks_assigned[week, shift_name]
== sum(
@@ -1203,7 +1209,7 @@ class RotaBuilder(object):
worker.id, week, shift.name
]
for worker in self.workers
)
)
<= shift.workers_required
)
@@ -2161,15 +2167,23 @@ class RotaBuilder(object):
# shift spans 7 days you may get >7 allocations in a row
# as it only checks for a different shift allocation
for constraint_shift in self.get_shifts_with_constraints(
"pre", week=week
"pre"#, week=week
):
for n in range(0, constraint_shift.constraint_options["pre"]):
if day in constraint_shift.days:
self.model.constraints.add(
1
>= self.model.works[
#week_start_date = self.get_week_start_date(week)
#try:
#if constraint_shift.start_date <= week_start_date < constraint_shift.end_date:
# works = 1
try:
works = self.model.works[
worker.id, week, day, constraint_shift.name
]
except KeyError:
continue
self.model.constraints.add(
1
>= works
+ sum(
pre_map[n][0]
* self.model.works[
@@ -2209,15 +2223,25 @@ class RotaBuilder(object):
# )
for constraint_shift in self.get_shifts_with_constraints(
"post", week=week
"post"#, week=week
):
for n in range(0, constraint_shift.constraint_options["post"]):
if day in constraint_shift.days:
self.model.constraints.add(
1
>= self.model.works[
#week_start_date = self.get_week_start_date(week)
#if constraint_shift.start_date <= week_start_date < constraint_shift.end_date:
#works = 1
#else:
try:
works = self.model.works[
worker.id, week, day, constraint_shift.name
]
except KeyError:
# If we can't find the key the shift is not assigned
# so we don't need to worry about it
continue
self.model.constraints.add(
1
>= works
+ sum(
post_map[n][0]
* self.model.works[
@@ -2230,6 +2254,8 @@ class RotaBuilder(object):
for w in workers
)
)
#except KeyError:
# pass
# for constraint_shift in self.get_shifts_with_constraints(
# "preclear", "preclear2"
@@ -2664,7 +2690,7 @@ class RotaBuilder(object):
def pair_shifts(self, shift1: SingleShift, shift2: SingleShift):
# NOTE: currently designed to pair two shifts
# this could be extended...
self.pair_shifts.append(set(shift1, shift2))
self.paired_shifts.append([shift1, shift2])
def build_shifts(self):
"""
@@ -2684,6 +2710,11 @@ class RotaBuilder(object):
for s in self.shifts:
pass
## For paired shifts we create proxy shifts
#self.paired_shift_map = {}
#for shifts in self.paired_shifts:
for s in self.shifts:
if s.name in self.shift_names:
raise InvalidShift(f"Duplicate shift: {s.name}")
@@ -2746,6 +2777,7 @@ class RotaBuilder(object):
for shift in self.get_shifts_with_constraint("post"):
self.max_post = max(shift.constraint_options["post"], self.max_post)
# # Todo replace with week_day.....
# self.day_shift_product = []
# self.day_shiftclass_product = []
@@ -3038,6 +3070,7 @@ class RotaBuilder(object):
f.write(self.get_worker_timetable_html(True))
url = urllib.parse.quote(str(output_file.resolve()))
print(f"Rota exported [bold blue]('[link url={url}]{url}')[/bold blue]")
self.exported_rota_file = output_file
def export_rota_to_csv(self, filename: str = "rota"):
output_file = Path("output", f"{filename}.csv")
@@ -3272,6 +3305,8 @@ class RotaBuilder(object):
shift = self.get_shift_by_name(shift_name)
if "require_remote_site_presence_week" in shift.constraints:
remote_site = f" data-shift-remote-site='{shift.constraint_options['require_remote_site_presence_week'][0]}'"
if "night" in shift.constraints:
css_class = " ".join((css_class, "night-shift"))
shift_tds.append(
f"<td title='{title}' class='rota-day {css_class}' data-shift='{shift_name}' data-available='{available}' data-unavailable_reason='{unavailable_reason}' data-date='{d}' data-week='{week}' data-day='{day}'{remote_site}{requests}{bank_holiday}>{a}</td>"
@@ -3453,7 +3488,7 @@ class RotaBuilder(object):
return timetable
def get_worker_shifts_by_date(self, worker: Worker) -> Dict:
def get_worker_shifts_by_date(self, worker: Worker) -> Dict[datetime.date, str]:
shifts = {}
n = 0
@@ -3472,6 +3507,7 @@ class RotaBuilder(object):
shifts = {}
for worker in self.workers:
print("*", worker.name)
shifts[worker.name] = len(
[i for i in self.get_worker_shift_list(worker) if i != ""]
)
@@ -3487,7 +3523,7 @@ class RotaBuilder(object):
temp = ""
for shift in self.get_shift_names_by_week_day(week, day):
if self.model.works[worker.id, week, day, shift].value > 0:
if self.model.works[worker.id, week, day, shift].value > 0.90:
temp = shift
shifts.append(temp)