import pytest from rota.shifts import NoWorkers, RotaBuilder, SingleShift, days import datetime from rota.workers import Worker def setup_basic_rota(): weeks_to_rota = 10 start_date = datetime.date(2022, 3, 7) return RotaBuilder( start_date, weeks_to_rota=weeks_to_rota, ) class TestWorkers: def test_no_workers(self): """Check error raised if attempt to solve with no workers""" Rota = setup_basic_rota() Rota.add_shifts( SingleShift( ("group1",), "d", 12.5, days[:5], balance_offset=10, workers_required=1, ), SingleShift(("group1",), "w", 12.5, days[5:], workers_required=1), ) with pytest.raises(NoWorkers): Rota.build_and_solve(solve=False) def test_workers_duplicate_name(self): Rota = setup_basic_rota() workers = [] workers.append(Worker(Rota, "worker1", "group1", 1, fte=100)) workers.append(Worker(Rota, "worker1", "group1", 1, fte=100)) workers.append(Worker(Rota, "worker3", "group2", 1, fte=50)) workers.append(Worker(Rota, "worker4", "group2", 1, fte=50)) Rota.add_workers(workers) Rota.add_shifts( SingleShift( ("group1",), "d", 12.5, days[:5], balance_offset=10, workers_required=1, ), ) with pytest.raises(ValueError): Rota.build_and_solve(solve=False) def test_worker_ftes(self): weeks_to_rota = 10 start_date = datetime.date(2022, 3, 7) Rota = RotaBuilder( start_date, weeks_to_rota=weeks_to_rota, ) worker1_end_date = start_date + datetime.timedelta(weeks=weeks_to_rota / 2) worker2_start_date = start_date + datetime.timedelta(weeks=weeks_to_rota / 2) worker3_start_date = start_date + datetime.timedelta(weeks=weeks_to_rota / 4) worker3_end_date = start_date + datetime.timedelta(weeks=3 * weeks_to_rota / 4) workers = [] workers.append( Worker(Rota, "worker1", "group1", 1, fte=100, end_date=worker1_end_date) ) workers.append( Worker(Rota, "worker2", "group1", 1, fte=100, start_date=worker2_start_date) ) workers.append( Worker( Rota, "worker3", "group2", 1, fte=100, start_date=worker3_start_date, end_date=worker3_end_date, ) ) workers.append(Worker(Rota, "worker4", "group2", 1, fte=50)) workers.append(Worker(Rota, "worker5", "group2", 1, fte=50, nwd=["Mon", "Tue"])) workers.append( Worker( Rota, "worker6", "group3", 1, fte=100, start_date=start_date + datetime.timedelta(weeks=weeks_to_rota), ) ) workers.append( Worker(Rota, "worker7", "group3", 1, fte=50, end_date=start_date) ) Rota.add_workers(workers) Rota.add_shifts( SingleShift( ("group1", "group2"), "d", 12.5, days[:5], balance_offset=10, workers_required=1, ), ) Rota.build_and_solve() # All workers should have an adjusted fte of 50 % for worker in Rota.get_workers(): if worker.site == "group3": assert worker.fte_adj == 0 assert Rota.get_worker_shift_list(worker).count("d") == 0 continue assert worker.fte_adj == 50 if worker.fte == 100: assert worker.proportion_rota_to_work == 0.5 assert worker.days_to_work == 35 assert Rota.get_worker_shift_list(worker).count("d") == 10 if worker.name == "worker1": assert Rota.get_worker_shift_list_string(worker).endswith("-"*(Rota.rota_days_length//2)) if worker.name == "worker2": assert Rota.get_worker_shift_list_string(worker).startswith("-"*(Rota.rota_days_length//2)) if worker.name == "worker3": assert Rota.get_worker_shift_list_string(worker).startswith("-"*(Rota.rota_days_length//4)) assert Rota.get_worker_shift_list_string(worker).endswith("-"*(Rota.rota_days_length//4)) assert Rota.results.solver.status == "ok" assert Rota.results.solver.termination_condition == "optimal" Rota.export_rota_to_html("worker_ftes") def test_worker_oops(self): weeks_to_rota = 10 start_date = datetime.date(2022, 3, 7) Rota = RotaBuilder( start_date, weeks_to_rota=weeks_to_rota, ) worker1_oop = [ ( start_date, start_date + datetime.timedelta(weeks=weeks_to_rota / 2), "First half OOP", ) ] worker2_oop = [ ( start_date + datetime.timedelta(weeks=weeks_to_rota / 2), Rota.rota_end_date, "First half OOP", ) ] worker3_start_date = start_date + datetime.timedelta(weeks=weeks_to_rota / 4) worker3_oop = [ ( start_date + datetime.timedelta(weeks=weeks_to_rota / 2), start_date + datetime.timedelta(weeks=3 * weeks_to_rota / 4), "Quarter oop", ) ] worker4_oop = [ ( start_date, start_date + datetime.timedelta(weeks=weeks_to_rota / 4), "Quarter oop", ), ( start_date + datetime.timedelta(weeks=weeks_to_rota / 2), start_date + datetime.timedelta(weeks=weeks_to_rota / 2) + datetime.timedelta(weeks=weeks_to_rota / 4), "Quarter oop 2", ), ] workers = [] workers.append(Worker(Rota, "worker1", "group1", 1, fte=100, oop=worker1_oop)) workers.append(Worker(Rota, "worker2", "group1", 1, fte=100, oop=worker2_oop)) workers.append( Worker( Rota, "worker3", "group2", 1, fte=100, start_date=worker3_start_date, oop=worker3_oop, ) ) workers.append(Worker(Rota, "worker4", "group2", 1, oop=worker4_oop)) workers.append(Worker(Rota, "worker5", "group2", 1, fte=50, nwd=["Mon", "Tue"])) worker6_oop = [ ( start_date, start_date + datetime.timedelta(weeks=weeks_to_rota / 6), "oop", ), ( start_date + datetime.timedelta(weeks=weeks_to_rota / 2), start_date + datetime.timedelta(weeks=weeks_to_rota / 2) + datetime.timedelta(weeks=weeks_to_rota / 3), " oop 2", ), ] worker7_oop = [ ( start_date-datetime.timedelta(weeks=1), Rota.rota_end_date, "oop", ), ] workers.append(Worker(Rota, "worker6", "group2", 1, fte=100, oop=worker6_oop)) workers.append(Worker(Rota, "worker7", "group3", 1, fte=100, oop=worker7_oop)) Rota.add_workers(workers) Rota.add_shifts( SingleShift( ("group1", "group2"), "d", 12.5, days[:5], balance_offset=10, workers_required=1, ), ) Rota.build_and_solve() # All workers should have an adjusted fte of 50 % for worker in Rota.get_workers(): if worker.site == "group3": assert worker.fte_adj == 0 continue assert worker.fte_adj == pytest.approx(50, 2) if worker.fte == 100: assert worker.proportion_rota_to_work == pytest.approx(0.5, 0.2) assert worker.days_to_work == pytest.approx(35, 1) assert Rota.get_worker_shift_list(worker).count("d") in (8, 9) assert Rota.results.solver.status == "ok" assert Rota.results.solver.termination_condition == "optimal" Rota.export_rota_to_html("worker_oops")