add worker requirements

This commit is contained in:
Ross
2025-01-01 21:58:48 +00:00
parent 9af01a3415
commit 7ce4677921
5 changed files with 535 additions and 218 deletions
+66 -25
View File
@@ -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)