Add support for avoiding shifts on specific dates for workers
- Introduced AvoidShiftOnDates model to define constraints for workers. - Enhanced Worker class to register avoid-shift rules with the rota builder. - Implemented logic in RotaBuilder to enforce these constraints during shift assignment. - Added tests to verify functionality for avoiding shifts on single dates and date ranges.
This commit is contained in:
@@ -111,6 +111,29 @@ class OutOfProgramme(BaseModel):
|
||||
reason: str = ""
|
||||
|
||||
|
||||
class AvoidShiftOnDates(BaseModel):
|
||||
"""Worker-level constraint: avoid listed shifts on given dates or date range."""
|
||||
shifts: list[str]
|
||||
start_date: Optional[datetime.date] = None
|
||||
end_date: Optional[datetime.date] = None
|
||||
dates: Optional[list[datetime.date]] = None
|
||||
|
||||
@field_validator("start_date", "end_date", mode="before")
|
||||
def coerce_dates(cls, v):
|
||||
# allow strings in common formats
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, datetime.date):
|
||||
return v
|
||||
if isinstance(v, str):
|
||||
for fmt in ("%d/%m/%Y", "%d/%m/%y", "%Y-%m-%d"):
|
||||
try:
|
||||
return datetime.datetime.strptime(v, fmt).date()
|
||||
except Exception:
|
||||
continue
|
||||
raise ValueError(f"Cannot parse date: {v}")
|
||||
|
||||
|
||||
class Worker(BaseModel):
|
||||
name: str
|
||||
site: str
|
||||
@@ -154,6 +177,7 @@ class Worker(BaseModel):
|
||||
shift_fte_overrides: dict[str, int] = {} # Need checks to ensure shifts exist
|
||||
|
||||
weekend_shift_target_number: int = 0
|
||||
avoid_shifts_on_dates: list[AvoidShiftOnDates] = []
|
||||
|
||||
|
||||
|
||||
@@ -395,6 +419,22 @@ class Worker(BaseModel):
|
||||
style="red",
|
||||
)
|
||||
|
||||
# Register any per-worker avoid-shift-on-dates rules with the rota builder
|
||||
try:
|
||||
for av in getattr(self, "avoid_shifts_on_dates", []):
|
||||
entry = {
|
||||
"names": [self.name],
|
||||
"shifts": av.shifts,
|
||||
"start_date": av.start_date,
|
||||
"end_date": av.end_date,
|
||||
"dates": av.dates,
|
||||
}
|
||||
# append to rota-level list; the model builder will enforce these
|
||||
if hasattr(Rota.constraint_options_model, "avoid_shifts_by_worker_dates"):
|
||||
Rota.constraint_options_model.avoid_shifts_by_worker_dates.append(entry)
|
||||
except Exception:
|
||||
Rota.add_warning("Worker/avoid_shifts_on_dates", f"Failed to register avoid_shifts_on_dates for {self.name}")
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
return (self.site, self.grade, self.fte_adj, self.name) < (
|
||||
other.site,
|
||||
|
||||
Reference in New Issue
Block a user