Enhance Rota Management System
- Updated .gitignore to exclude 'cons/' directory. - Refactored gen_cons.py to extract leave and rota assignments from calendar data, and added functionality to handle shift assignments from rota data. - Modified timetable.css to include styles for force-assigned shifts. - Enhanced shifts.py to manage forced assignments and handle leave conflicts, including new methods for date-based assignments. - Updated workers.py to introduce structured hard day dependencies and exclusions, allowing for more flexible scheduling. - Adjusted test_workers.py to reflect changes in hard day dependency and exclusion methods, ensuring compatibility with new data structures.
This commit is contained in:
+49
-9
@@ -1,7 +1,7 @@
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
from typing import Iterable, List, Literal, Optional
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
from typing import Iterable, List, Literal, Optional, Set
|
||||
from pydantic import BaseModel, ConfigDict, field_validator, Field
|
||||
|
||||
from rich.pretty import pprint
|
||||
from rota.console import console
|
||||
@@ -31,6 +31,31 @@ class NotAvailableToWork(BaseModel):
|
||||
date: datetime.date
|
||||
reason: str = "unknown"
|
||||
|
||||
class HardDayDependency(BaseModel):
|
||||
days: Set[str] = Field(..., description="List of days for the dependency group")
|
||||
weeks: Optional[List[int]] = None # If None, applies to all weeks
|
||||
start_date: Optional[datetime.date] = None # Optional start date for the dependency
|
||||
end_date: Optional[datetime.date] = None # Optional end date for the dependency
|
||||
|
||||
@field_validator("weeks")
|
||||
def weeks_none_if_dates_set(cls, weeks, values):
|
||||
data = values.data
|
||||
if (data.get("start_date") is not None or data.get("end_date") is not None) and weeks is not None:
|
||||
raise ValueError("If start_date or end_date is set, weeks must not be set")
|
||||
return weeks
|
||||
|
||||
class HardDayExclusion(BaseModel):
|
||||
days: set[str] = Field(..., description="List of days to exclude together")
|
||||
weeks: Optional[List[int]] = None # If None, applies to all weeks
|
||||
start_date: Optional[datetime.date] = None # Optional start date for the dependency
|
||||
end_date: Optional[datetime.date] = None # Optional end date for the dependency
|
||||
|
||||
@field_validator("weeks")
|
||||
def weeks_none_if_dates_set(cls, weeks, values):
|
||||
data = values.data
|
||||
if (data.get("start_date") is not None or data.get("end_date") is not None) and weeks is not None:
|
||||
raise ValueError("If start_date or end_date is set, weeks must not be set")
|
||||
return weeks
|
||||
|
||||
def generate_not_available_to_works(
|
||||
start_date: datetime.date,
|
||||
@@ -114,11 +139,12 @@ class Worker(BaseModel):
|
||||
allowed_multi_shift_sets: list[set] = [] # or list/set of frozensets
|
||||
prefer_multi_shift_together: int = 0 # Default: no preference
|
||||
# Set to a positive integer to prefer, negative to discourage, 0 for neutral
|
||||
hard_day_dependencies: set[frozenset[str]] = set() # e.g. {frozenset({"Mon", "Tue"}), frozenset({"Fri", "Sun"})}
|
||||
hard_day_exclusions: list[tuple[str, str]] = [] # e.g. [("Fri", "Sun")]
|
||||
hard_day_dependencies: list[HardDayDependency] = []
|
||||
hard_day_exclusions: list[HardDayExclusion] = []
|
||||
force_assign_with: dict[str, list[str]] = {} # e.g. {"oncall": ["weekend"]}
|
||||
max_shifts_per_week_by_shift_name: dict[str, int] = {} # e.g. {"night": 2, "a": 3}
|
||||
forced_assignments: List[tuple[int, str, str]] = [] # (week, day, shift_name)
|
||||
forced_assignments_by_date: List[tuple[datetime.date, str]] = [] # (date, shift_name)
|
||||
|
||||
|
||||
assign_as_block_preferences: dict[str, float] = {} # shift_name -> weight (positive = prefer block)
|
||||
@@ -410,15 +436,25 @@ class Worker(BaseModel):
|
||||
self.allowed_multi_shift_sets = []
|
||||
self.allowed_multi_shift_sets.append(frozenset(shifts))
|
||||
|
||||
def add_hard_day_dependency(self, *days: str):
|
||||
def add_hard_day_dependency(self, days: set[str], weeks: Optional[List[int]] = None, start_date: Optional[datetime.date] = None, end_date: Optional[datetime.date] = None):
|
||||
"""
|
||||
Add a hard day dependency for this worker.
|
||||
days: List of days that must be grouped together.
|
||||
weeks: Optional list of weeks this dependency applies to. If None, applies to all weeks.
|
||||
"""
|
||||
if not hasattr(self, "hard_day_dependencies"):
|
||||
self.hard_day_dependencies = set()
|
||||
self.hard_day_dependencies.add(frozenset(days))
|
||||
self.hard_day_dependencies = []
|
||||
self.hard_day_dependencies.append(HardDayDependency(days=days, weeks=weeks, start_date=start_date, end_date=end_date))
|
||||
|
||||
def add_hard_day_exclusion(self, day1: str, day2: str):
|
||||
def add_hard_day_exclusion(self, days: set[str], weeks: Optional[List[int]] = None, start_date: Optional[datetime.date] = None, end_date: Optional[datetime.date] = None):
|
||||
"""
|
||||
Add a hard day exclusion for this worker.
|
||||
days: List of days that must be excluded together.
|
||||
weeks: Optional list of weeks this exclusion applies to. If None, applies to all weeks.
|
||||
"""
|
||||
if not hasattr(self, "hard_day_exclusions"):
|
||||
self.hard_day_exclusions = []
|
||||
self.hard_day_exclusions.append((day1, day2))
|
||||
self.hard_day_exclusions.append(HardDayExclusion(days=days, weeks=weeks, start_date=start_date, end_date=end_date))
|
||||
|
||||
def add_force_assign_with(self, shift: str, with_shifts: list[str] | str):
|
||||
if not hasattr(self, "force_assign_with"):
|
||||
@@ -436,3 +472,7 @@ class Worker(BaseModel):
|
||||
def force_assign_shift(self, week: int, day: str, shift_name: str):
|
||||
"""Force this worker to be assigned to a shift on a specific week/day."""
|
||||
self.forced_assignments.append((week, day, shift_name))
|
||||
|
||||
def force_assign_shift_by_date(self, date: datetime.date, shift_name: str):
|
||||
"""Force this worker to be assigned to a shift on a specific date."""
|
||||
self.forced_assignments_by_date.append((date, shift_name))
|
||||
|
||||
Reference in New Issue
Block a user