start adding locum availability
This commit is contained in:
+65
-13
@@ -209,6 +209,7 @@ class SingleShift(BaseModel):
|
||||
return self.workers_required
|
||||
|
||||
for wr in self.workers_required:
|
||||
#print(date, wr)
|
||||
if wr.start_date <= date < wr.end_date:
|
||||
return wr.number
|
||||
|
||||
@@ -315,6 +316,12 @@ class RotaBuilder(object):
|
||||
|
||||
self.exported_rota_file = None
|
||||
|
||||
def set_rota_constraint(self, constraint: str, value: Any):
|
||||
if constraint not in self.constraint_options:
|
||||
raise ValueError(f"Constraint {constraint} not valid")
|
||||
|
||||
self.constraint_options[constraint] = value
|
||||
|
||||
def set_rota_dates(self, start_date: datetime.date, weeks_to_rota: int):
|
||||
self.weeks_to_rota = weeks_to_rota
|
||||
|
||||
@@ -354,6 +361,10 @@ class RotaBuilder(object):
|
||||
Tuple[str | uuid.UUID | int, WeekInt, DayStr, ShiftName]
|
||||
] = set()
|
||||
self.work_requests_map = {}
|
||||
self.locum_availability: set[
|
||||
Tuple[str | uuid.UUID | int, WeekInt, DayStr, ShiftName]
|
||||
] = set()
|
||||
self.locum_availability_map = {}
|
||||
|
||||
def solve_model(
|
||||
self,
|
||||
@@ -836,6 +847,37 @@ class RotaBuilder(object):
|
||||
initialize=availability_init,
|
||||
)
|
||||
|
||||
locum_request_sets = set()
|
||||
for locum_request in self.locum_availability:
|
||||
if locum_request[3] == "*":
|
||||
pass
|
||||
|
||||
else:
|
||||
locum_request_sets.add(locum_request[3])
|
||||
|
||||
# Validate locum shifts are valid
|
||||
invalid_shifts = locum_request_sets - set(self.get_shift_names())
|
||||
if invalid_shifts and not self.ignore_valid_shifts:
|
||||
raise InvalidShift(
|
||||
f"Invalid locum shift request [shift(s): ${invalid_shifts}]"
|
||||
)
|
||||
|
||||
|
||||
def locum_request_init(model, wid, week, day, shift):
|
||||
if (wid, week, day, shift) in self.locum_availability:
|
||||
self.locum_availability_map[(wid, week, day)] = shift
|
||||
return 1
|
||||
return 0
|
||||
|
||||
self.model.locum_availability = Param(
|
||||
(
|
||||
(worker.id, week, day, shift)
|
||||
for worker in self.workers
|
||||
for week, day, shift in self.get_all_shiftname_combinations()
|
||||
),
|
||||
initialize=locum_request_init,
|
||||
)
|
||||
|
||||
work_request_sets = set()
|
||||
for work_request in self.work_requests:
|
||||
if work_request[3] == "*":
|
||||
@@ -845,7 +887,6 @@ class RotaBuilder(object):
|
||||
work_request_sets.add(work_request[3])
|
||||
|
||||
# Validate shifts are valid
|
||||
# work_request_sets = set([i[3] for i in self.work_requests])
|
||||
invalid_shifts = work_request_sets - set(self.get_shift_names())
|
||||
if invalid_shifts and not self.ignore_valid_shifts:
|
||||
raise InvalidShift(
|
||||
@@ -1190,6 +1231,7 @@ class RotaBuilder(object):
|
||||
workers_required = shift.get_worker_requirement_by_date(
|
||||
self.get_week_start_date(week)
|
||||
)
|
||||
print(worker.name, week, workers_required, self.get_week_start_date(week))
|
||||
|
||||
if shift.force_as_block_unless_nwd:
|
||||
workers = self.get_workers_for_shift(shift)
|
||||
@@ -1376,11 +1418,11 @@ class RotaBuilder(object):
|
||||
)
|
||||
|
||||
full_time_equivalent_joined = sum(
|
||||
self.full_time_equivalent_sites[i] for i in shift.sites
|
||||
self.full_time_equivalent_sites[i][shift.name] for i in shift.sites
|
||||
)
|
||||
|
||||
target_shifts = (
|
||||
total_shifts / full_time_equivalent_joined * worker.fte_adj
|
||||
total_shifts / full_time_equivalent_joined * worker.get_fte(shift=shift.name)
|
||||
)
|
||||
|
||||
if self.use_previous_shifts:
|
||||
@@ -1408,7 +1450,7 @@ class RotaBuilder(object):
|
||||
|
||||
if shift.hard_constrain_shift:
|
||||
adjusted_balance_offset = shift.balance_offset
|
||||
if worker.fte_adj < 100:
|
||||
if worker.get_fte(shift=shift.name) < 100:
|
||||
adjusted_balance_offset = (
|
||||
adjusted_balance_offset * self.ltft_balance_offset
|
||||
)
|
||||
@@ -2412,6 +2454,8 @@ class RotaBuilder(object):
|
||||
# c = len(workers)
|
||||
|
||||
balance_modifier_constant = 1
|
||||
balance_quadratic_shift_modifier_constant = 3
|
||||
block_shift_balancing_constant = 1
|
||||
|
||||
if self.constraint_options["balance_shifts_over_workers"]:
|
||||
worker_shift_balancing = sum(
|
||||
@@ -2440,7 +2484,7 @@ class RotaBuilder(object):
|
||||
|
||||
if self.constraint_options["balance_shifts_quadratic"]:
|
||||
quadratic_shift_balancing = sum(
|
||||
balance_modifier_constant
|
||||
balance_quadratic_shift_modifier_constant
|
||||
* (
|
||||
self.model.shift_count_t1[worker.id, shift.name]
|
||||
+ self.model.shift_count_t2[worker.id, shift.name]
|
||||
@@ -2550,7 +2594,7 @@ class RotaBuilder(object):
|
||||
|
||||
if self.constraint_options["balance_blocks"]:
|
||||
blocks_balancing = sum(
|
||||
1 * self.model.blocks_assigned[week, shift]
|
||||
block_shift_balancing_constant * self.model.blocks_assigned[week, shift]
|
||||
for week in self.weeks
|
||||
for shift in self.shifts_to_assign_as_blocks()
|
||||
)
|
||||
@@ -2646,15 +2690,19 @@ class RotaBuilder(object):
|
||||
|
||||
self.workers = sorted(self.workers)
|
||||
|
||||
self.full_time_equivalent = sum(w.fte_adj for w in self.workers)
|
||||
self.full_time_equivalent = sum(w.fte_adj for w in self.workers) # TODO: rewrite as per shift
|
||||
self.full_time_equivalent_sites = {}
|
||||
self.workers_at_sites = {}
|
||||
|
||||
for site in track(self.sites, description="Generating site workers"):
|
||||
self.workers_at_sites[site] = [w for w in self.workers if w.site == site]
|
||||
self.full_time_equivalent_sites[site] = sum(
|
||||
w.fte_adj for w in self.workers if w.site == site
|
||||
)
|
||||
|
||||
self.full_time_equivalent_sites[site] = {}
|
||||
|
||||
for shift in self.get_shifts():
|
||||
self.full_time_equivalent_sites[site][shift.name] = sum(
|
||||
w.get_fte(shift=shift.name) for w in self.workers if w.site == site
|
||||
)
|
||||
|
||||
self.worker_pairs = []
|
||||
pairs = defaultdict(list)
|
||||
@@ -3131,16 +3179,18 @@ class RotaBuilder(object):
|
||||
return [worker for worker in self.workers if worker.site in shift.sites]
|
||||
|
||||
def get_workers_total_fte(self) -> float:
|
||||
"""Does not take into account shift adjusted ftes"""
|
||||
return self.full_time_equivalent
|
||||
|
||||
def get_worker_details(self):
|
||||
w = defaultdict(list)
|
||||
w: dict[str, Worker] = defaultdict(list)
|
||||
worker: Worker
|
||||
for worker in self.workers:
|
||||
w[worker.site].append(worker)
|
||||
|
||||
l = []
|
||||
for site in w:
|
||||
l.append(f"{site}: {sum(worker.fte_adj for worker in w[site]) / 100}")
|
||||
l.append(f"{site}: {sum(worker.get_fte() for worker in w[site]) / 100}")
|
||||
t = "\n".join(l)
|
||||
return f"Full time equivalent trainees by site:\n{t}"
|
||||
|
||||
@@ -3158,6 +3208,8 @@ class RotaBuilder(object):
|
||||
]
|
||||
|
||||
def get_week_start_date(self, week: int):
|
||||
"""Week start date is 1 indexed"""
|
||||
week = week - 1
|
||||
return self.start_date + datetime.timedelta(weeks=week)
|
||||
|
||||
# RESULTS
|
||||
@@ -3454,7 +3506,7 @@ class RotaBuilder(object):
|
||||
worker_id=worker.id,
|
||||
name=worker.name,
|
||||
fte=worker.fte,
|
||||
fte_adj=worker.fte_adj,
|
||||
fte_adj=worker.get_fte(),
|
||||
start_date=worker.calculated_start_date,
|
||||
end_date=worker.calculated_end_date,
|
||||
oops=json.dumps(json.dumps(worker.oop, default=str)),
|
||||
|
||||
Reference in New Issue
Block a user