Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,41 @@ def has_no_shift_same_day(self, shift):
)

def has_required_rest_time(self, shift):
new_date = datetime.date.fromisoformat(shift.date)

for assigned_shift in self.assigned_shifts:
existing_date = datetime.datetime.strptime(assigned_shift.date, "%Y-%m-%d")
new_date = datetime.datetime.strptime(shift.date, "%Y-%m-%d")
existing_date = datetime.date.fromisoformat(assigned_shift.date)

is_next_day = new_date == existing_date + datetime.timedelta(days=1)
is_evening_to_day = (
assigned_shift.shift_type == ShiftType.EVENING
and shift.shift_type == ShiftType.DAY
# A night blocks the following day; an evening blocks the
# following morning. Check both orders because shifts are
# assigned by priority, not necessarily by chronological date.
existing_is_previous_day = (
new_date == existing_date + datetime.timedelta(days=1)
)
new_is_previous_day = (
existing_date == new_date + datetime.timedelta(days=1)
)
is_night_before = assigned_shift.shift_type == ShiftType.NIGHT

if is_next_day and (is_evening_to_day or is_night_before):
if existing_is_previous_day and (
assigned_shift.shift_type == ShiftType.NIGHT
or (
assigned_shift.shift_type == ShiftType.EVENING
and shift.shift_type == ShiftType.DAY
)
):
return False

if new_is_previous_day and (
shift.shift_type == ShiftType.NIGHT
or (
shift.shift_type == ShiftType.EVENING
and assigned_shift.shift_type == ShiftType.DAY
)
):
return False

return True

def get_risk_flags(self, workload_ratio_threshold: float, average_score_threshold: float) -> list[str]:
flags = []

Expand Down
39 changes: 38 additions & 1 deletion tests/test_employee.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,41 @@ def test_employee_cannot_take_two_shifts_on_same_day():
result = employee.can_take_shift(shift2)

# Assert
assert result is False, "An employee may not have two shifts on the same date."
assert result is False, "An employee may not have two shifts on the same date."

def test_employee_cannot_take_evening_before_assigned_next_day():
employee = Employee(name="Reverse-Evening", max_shifts=5, skills=["CT"])
day_shift = Shift(date="2026-05-12", shift_type=ShiftType.DAY, required_skill="CT")
previous_evening = Shift(
date="2026-05-11", shift_type=ShiftType.EVENING, required_skill="CT"
)
employee.assigned_shifts.append(day_shift)

assert employee.can_take_shift(previous_evening) is False
assert "rest time violation" in employee.get_shift_blockers(previous_evening)


def test_employee_cannot_take_night_before_assigned_next_day():
employee = Employee(name="Reverse-Night", max_shifts=5, skills=["MR"])
next_evening = Shift(
date="2026-05-12", shift_type=ShiftType.EVENING, required_skill="MR"
)
previous_night = Shift(
date="2026-05-11", shift_type=ShiftType.NIGHT, required_skill="MR"
)
employee.assigned_shifts.append(next_evening)

assert employee.can_take_shift(previous_night) is False


def test_employee_can_take_day_before_assigned_next_day_evening():
employee = Employee(name="Safe-Sequence", max_shifts=5, skills=["CT"])
next_evening = Shift(
date="2026-05-12", shift_type=ShiftType.EVENING, required_skill="CT"
)
previous_day = Shift(
date="2026-05-11", shift_type=ShiftType.DAY, required_skill="CT"
)
employee.assigned_shifts.append(next_evening)

assert employee.can_take_shift(previous_day) is True
24 changes: 23 additions & 1 deletion tests/test_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,26 @@ def test_assign_shifts_still_works_without_employee_profiles():
employee_profiles=None,
)

assert employee in shift.assigned_employees
assert employee in shift.assigned_employees

def test_priority_order_does_not_bypass_rest_rules():
employee = Employee(name="Priority-Rest", max_shifts=3, skills=["CT"])
# Later calendar date has the higher scheduling priority.
next_day = Shift(
date="2026-05-12",
shift_type=ShiftType.DAY,
required_skill="CT",
priority=1,
)
previous_night = Shift(
date="2026-05-11",
shift_type=ShiftType.NIGHT,
required_skill="CT",
priority=2,
)

assign_shifts([employee], [previous_night, next_day])

assert employee in next_day.assigned_employees
assert previous_night.assigned_employees == []
assert employee.assigned_shifts == [next_day]
Loading