add worker requirements
This commit is contained in:
+66
-25
@@ -32,6 +32,27 @@ class NotAvailableToWork(BaseModel):
|
||||
reason: str = "unknown"
|
||||
|
||||
|
||||
def generate_not_available_to_works(
|
||||
start_date: datetime.date,
|
||||
end_date: datetime.date | None = None,
|
||||
days: int | None = None,
|
||||
reason: str = "unknown",
|
||||
) -> List[NotAvailableToWork]:
|
||||
if end_date is None and days is None:
|
||||
raise ValueError("Either end_date or days must be provided")
|
||||
if end_date is not None and days is not None:
|
||||
raise ValueError("Only one of end_date or days must be provided")
|
||||
|
||||
if end_date is not None:
|
||||
days = (end_date - start_date).days
|
||||
|
||||
not_available_to_works = []
|
||||
for day in range(days):
|
||||
not_available_to_works.append(NotAvailableToWork(date=start_date + datetime.timedelta(days=day), reason=reason))
|
||||
|
||||
return not_available_to_works
|
||||
|
||||
|
||||
class NonWorkingDays(BaseModel):
|
||||
day: days
|
||||
start_date: datetime.date | None = None
|
||||
@@ -68,7 +89,7 @@ class Worker(BaseModel):
|
||||
# rules is easier.
|
||||
grade: int
|
||||
# We can either have a user generated ID
|
||||
id: Optional[int| uuid.UUID| str] = None
|
||||
id: Optional[int | uuid.UUID | str] = None
|
||||
fte: int = 100
|
||||
nwds: list[NonWorkingDays] = []
|
||||
start_date: datetime.date | None = None
|
||||
@@ -84,7 +105,7 @@ class Worker(BaseModel):
|
||||
pair: int | str | None = None
|
||||
|
||||
model_config = ConfigDict(
|
||||
extra = "allow",
|
||||
extra="allow",
|
||||
)
|
||||
|
||||
# def __init__(
|
||||
@@ -111,14 +132,17 @@ class Worker(BaseModel):
|
||||
|
||||
def load_rota(self, Rota: "RotaBuilder"):
|
||||
self.proportion_rota_to_work: float = 1
|
||||
self.shift_target_number: dict[ str, int ] = defaultdict(int)
|
||||
self.shift_target_number: dict[str, int] = defaultdict(int)
|
||||
|
||||
if self.id is None:
|
||||
self.id = uuid.uuid4()
|
||||
|
||||
if self.start_date is not None and self.end_date is not None:
|
||||
if self.start_date >= self.end_date:
|
||||
Rota.add_warning("Worker/Early end date", f"{self.name} [{self.id}] end date is before rota start date: {self.end_date}")
|
||||
Rota.add_warning(
|
||||
"Worker/Early end date",
|
||||
f"{self.name} [{self.id}] end date is before rota start date: {self.end_date}",
|
||||
)
|
||||
raise ValueError(f"End date must be after start date: {self.name}")
|
||||
|
||||
# if no start date default to the start of the rota
|
||||
@@ -132,7 +156,10 @@ class Worker(BaseModel):
|
||||
self.calculated_start_date = Rota.start_date
|
||||
elif self.start_date >= Rota.rota_end_date:
|
||||
# If it is after add a warning
|
||||
Rota.add_warning("Worker/Late start date", f"{self.name} [{self.id}] start date is after rota end date: {self.start_date}")
|
||||
Rota.add_warning(
|
||||
"Worker/Late start date",
|
||||
f"{self.name} [{self.id}] start date is after rota end date: {self.start_date}",
|
||||
)
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -152,18 +179,21 @@ class Worker(BaseModel):
|
||||
|
||||
if date < self.calculated_start_date:
|
||||
Rota.unavailable_to_work.add((self.id, week, day))
|
||||
Rota.unavailable_to_work_reason[
|
||||
(self.id, week, day)
|
||||
] = f"START DATE: {self.calculated_start_date}"
|
||||
Rota.unavailable_to_work_reason[(self.id, week, day)] = (
|
||||
f"START DATE: {self.calculated_start_date}"
|
||||
)
|
||||
|
||||
if date >= self.calculated_end_date:
|
||||
Rota.unavailable_to_work.add((self.id, week, day))
|
||||
Rota.unavailable_to_work_reason[
|
||||
(self.id, week, day)
|
||||
] = f"END DATE: {self.calculated_end_date}"
|
||||
Rota.unavailable_to_work_reason[(self.id, week, day)] = (
|
||||
f"END DATE: {self.calculated_end_date}"
|
||||
)
|
||||
|
||||
if not self.not_available_to_work:
|
||||
Rota.add_warning("Worker/No unavailability", f"{self.name} [{self.id}] has no unavailabilities (leave)")
|
||||
Rota.add_warning(
|
||||
"Worker/No unavailability",
|
||||
f"{self.name} [{self.id}] has no unavailabilities (leave)",
|
||||
)
|
||||
|
||||
for item in self.oop:
|
||||
start_oop = item.start_date
|
||||
@@ -207,9 +237,9 @@ class Worker(BaseModel):
|
||||
]:
|
||||
week, day = weeks_days
|
||||
Rota.unavailable_to_work.add((self.id, week, day))
|
||||
Rota.unavailable_to_work_reason[
|
||||
(self.id, week, day)
|
||||
] = f"OOP ({oop_name})".format(self.oop)
|
||||
Rota.unavailable_to_work_reason[(self.id, week, day)] = (
|
||||
f"OOP ({oop_name})".format(self.oop)
|
||||
)
|
||||
|
||||
days_to_end = (self.calculated_end_date - Rota.start_date).days
|
||||
|
||||
@@ -239,10 +269,11 @@ class Worker(BaseModel):
|
||||
week = days_from_start // 7 + 1
|
||||
day = Rota.days[(days_from_start % 7)]
|
||||
Rota.unavailable_to_work.add((self.id, week, day))
|
||||
Rota.unavailable_to_work_reason[(self.id, week, day)] = unavalability.reason
|
||||
Rota.unavailable_to_work_reason[(self.id, week, day)] = (
|
||||
unavalability.reason
|
||||
)
|
||||
unavailable_set.add((week, day))
|
||||
|
||||
|
||||
for request in self.work_requests:
|
||||
days_from_start = (request.date - Rota.start_date).days
|
||||
week = days_from_start // 7 + 1
|
||||
@@ -262,7 +293,9 @@ class Worker(BaseModel):
|
||||
|
||||
if self.fte_adj > 100:
|
||||
# Shouldn't happen ? bug if it does
|
||||
Rota.add_warning("FTE > 100%", f"{self.name} [{self.id}] FTE = {self.fte_adj}")
|
||||
Rota.add_warning(
|
||||
"FTE > 100%", f"{self.name} [{self.id}] FTE = {self.fte_adj}"
|
||||
)
|
||||
raise ValueError("{} : fte_ajd = {}".format(self.name, self.fte_adj))
|
||||
|
||||
# TODO: this has already been validated, consider moving
|
||||
@@ -275,14 +308,22 @@ class Worker(BaseModel):
|
||||
if non_working_day.end_date is not None:
|
||||
end_date = non_working_day.end_date
|
||||
|
||||
self.non_working_day_list.append((non_working_day.day, start_date, end_date))
|
||||
self.non_working_day_list.append(
|
||||
(non_working_day.day, start_date, end_date)
|
||||
)
|
||||
|
||||
if days_to_work < 1:
|
||||
self.fte_adj = 0
|
||||
|
||||
if days_to_work > 0 and (days_to_work - len(unavailable_set)) / days_to_work < 0.3:
|
||||
if (
|
||||
days_to_work > 0
|
||||
and (days_to_work - len(unavailable_set)) / days_to_work < 0.3
|
||||
):
|
||||
console.print(f"{ self.name }: {days_to_work}, {Rota.rota_days_length}")
|
||||
console.print(f"Warning {self.name} has excessive unavailability [{len(unavailable_set)}/{days_to_work}, adjusted fte={self.fte_adj}] (this may be infeasible)", style="red")
|
||||
console.print(
|
||||
f"Warning {self.name} has excessive unavailability [{len(unavailable_set)}/{days_to_work}, adjusted fte={self.fte_adj}] (this may be infeasible)",
|
||||
style="red",
|
||||
)
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
return (self.site, self.grade, self.fte_adj, self.name) < (
|
||||
@@ -295,17 +336,17 @@ class Worker(BaseModel):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name}, {self.site}, {self.oop}"
|
||||
|
||||
#nwds = (
|
||||
# nwds = (
|
||||
# ", ".join([str(i) for i, start, end in self.non_working_day_list])
|
||||
# if self.non_working_day_list is not None
|
||||
# else ""
|
||||
#)
|
||||
#return "{} {} [{}] REMOTE SITE:{}".format(
|
||||
# )
|
||||
# return "{} {} [{}] REMOTE SITE:{}".format(
|
||||
# self.name,
|
||||
# (self.site, self.grade, self.fte_adj),
|
||||
# nwds,
|
||||
# self.remote_site,
|
||||
#)
|
||||
# )
|
||||
|
||||
def get_details(self) -> str:
|
||||
return "{} {} {}".format(self.id, self.site[0], self.grade)
|
||||
|
||||
Reference in New Issue
Block a user