add worker requirements
This commit is contained in:
+160
-51
@@ -1,14 +1,13 @@
|
||||
import datetime
|
||||
import pytest
|
||||
from rota.shifts import InvalidShift, NoWorkers, RotaBuilder, SingleShift, WarningTermination, days
|
||||
from rota.shifts import InvalidShift, NoWorkers, RotaBuilder, SingleShift, WarningTermination, days, WorkerRequirement
|
||||
|
||||
from rota.workers import NotAvailableToWork, WorkRequests, Worker
|
||||
from rota.workers import NotAvailableToWork, WorkRequests, Worker, generate_not_available_to_works
|
||||
|
||||
import itertools
|
||||
|
||||
|
||||
def generate_basic_rota(weeks_to_rota=10, workers=2):
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
def generate_basic_rota(weeks_to_rota=10, workers=2, start_date=datetime.date(2022, 3, 7)):
|
||||
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
@@ -19,7 +18,6 @@ def generate_basic_rota(weeks_to_rota=10, workers=2):
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(name=f"worker{i}", site="group1", grade=1) for i in range(1, workers+1)
|
||||
#Worker(name="worker2", site="group1", grade=1),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -547,56 +545,167 @@ class TestShiftDates:
|
||||
for worker in Rota.get_workers_by_group()["group2"]:
|
||||
assert Rota.get_worker_shifts_by_date(worker)[request_date] == "c"
|
||||
|
||||
def test_shift_start_date_end_date_block_paired(self):
|
||||
Rota = generate_basic_rota(workers=7)
|
||||
class TestShiftWorkerRequirements:
|
||||
def test_start_and_end_date(self):
|
||||
WorkerRequirement(start_date=datetime.date(2022, 3, 7), end_date=datetime.date(2022, 3, 14))
|
||||
|
||||
def test_end_date_before_start_date(self):
|
||||
with pytest.raises(ValueError):
|
||||
WorkerRequirement(start_date=datetime.date(2022, 3, 14), end_date=datetime.date(2022, 3, 7))
|
||||
|
||||
request_date = Rota.start_date + datetime.timedelta(days=(7*6)+4)
|
||||
|
||||
no_work = [NotAvailableToWork(date=d, shift="*") for d in date_generator(request_date, 18)]
|
||||
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(name=f"worker-wr-{i}", site="group2", grade=1, not_available_to_work=no_work) for i in range(1, 4)
|
||||
#Worker(name="worker2", site="group1", grade=1),
|
||||
]
|
||||
)
|
||||
a, b, c = (SingleShift(
|
||||
sites=("group1", "group2"), name="a", length= 12.5, days=days[:4],
|
||||
workers_required=8,
|
||||
constraint=[{"name": "max_shifts_per_week", "options": 4}],
|
||||
end_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
#end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="b", length= 12.5, days=days[4:],
|
||||
assign_as_block=True,
|
||||
force_as_block=True,
|
||||
workers_required=4,
|
||||
constraint=[{"name": "max_shifts_per_week", "options": 4},
|
||||
{"name": "pre", "options": 3}, {"name": "post", "options": 3}],
|
||||
start_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="c", length= 12.5, days=days[4:],
|
||||
assign_as_block=True,
|
||||
force_as_block=True,
|
||||
workers_required=4,
|
||||
constraint=[{"name": "max_shifts_per_week", "options": 4},
|
||||
{"name": "pre", "options": 3}, {"name": "post", "options": 3}],
|
||||
start_date=(Rota.start_date + datetime.timedelta(days=(7*6))),
|
||||
))
|
||||
|
||||
def test_worker_requirement(self):
|
||||
Rota = generate_basic_rota(workers=2)
|
||||
|
||||
wr = [WorkerRequirement(start_date=datetime.date(2022, 3, 7), end_date=datetime.date(2022, 3, 21))]
|
||||
print(wr)
|
||||
Rota.add_shifts(
|
||||
a,
|
||||
b, c
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="a", length= 12.5, days=days[:4],
|
||||
assign_as_block=True,
|
||||
force_as_block=True,
|
||||
workers_required=wr,
|
||||
#start_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
#end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
)
|
||||
Rota.pair_shifts(b, c)
|
||||
|
||||
Rota.build_and_solve(options={"ratio": 0.01})
|
||||
Rota.export_rota_to_html("test_shift_start_date")
|
||||
Rota.build_and_solve(options={"ratio": 0.1})
|
||||
Rota.export_rota_to_html("test_worker_requirement")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
assert Rota.results.solver.termination_condition == "optimal"
|
||||
|
||||
total_shifts = Rota.get_workers_total_shifts()
|
||||
|
||||
for worker in Rota.get_workers():
|
||||
assert total_shifts[worker.name] == 4
|
||||
assert "aaaa" in Rota.get_worker_shift_list_string(worker)
|
||||
|
||||
|
||||
def test_worker_requirement2(self):
|
||||
Rota = generate_basic_rota(workers=2)
|
||||
|
||||
wr = [WorkerRequirement(start_date=datetime.date(2022, 3, 7), end_date=datetime.date(2022, 3, 21)),
|
||||
WorkerRequirement(start_date=datetime.date(2022, 3, 28))
|
||||
]
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="a", length= 12.5, days=days[:4],
|
||||
assign_as_block=True,
|
||||
force_as_block=True,
|
||||
workers_required=wr,
|
||||
#start_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
#end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
)
|
||||
Rota.build_and_solve(options={"ratio": 0.1})
|
||||
Rota.export_rota_to_html("test_worker_requirement")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
|
||||
total_shifts = Rota.get_workers_total_shifts()
|
||||
|
||||
for worker in Rota.get_workers():
|
||||
assert total_shifts[worker.name] == 18
|
||||
assert Rota.get_worker_shift_list_string(worker)[14:21] == "-------"
|
||||
|
||||
def test_worker_requirement_increase_workers(self):
|
||||
Rota = generate_basic_rota(workers=2)
|
||||
|
||||
wr = [WorkerRequirement(start_date=datetime.date(2022, 3, 7), end_date=datetime.date(2022, 3, 14)),
|
||||
WorkerRequirement(start_date=datetime.date(2022, 3, 28), number=2)
|
||||
]
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="a", length= 12.5, days=days[:4],
|
||||
assign_as_block=True,
|
||||
force_as_block=True,
|
||||
workers_required=wr,
|
||||
#start_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
#end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
)
|
||||
Rota.build_and_solve(options={"ratio": 0.1})
|
||||
Rota.export_rota_to_html("test_worker_requirement")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
|
||||
total_shifts = Rota.get_workers_total_shifts()
|
||||
|
||||
for worker in Rota.get_workers():
|
||||
assert total_shifts[worker.name] == 30
|
||||
assert Rota.get_worker_shift_list_string(worker)[7:21] == "-" * 14
|
||||
assert Rota.get_worker_shift_list_string(worker).endswith("aaaa---"*7)
|
||||
|
||||
def test_worker_requirement_balancing(self):
|
||||
Rota = generate_basic_rota(workers=2)
|
||||
|
||||
natws = generate_not_available_to_works(datetime.date(2022,4,18), days=21, reason="holiday")
|
||||
|
||||
Rota.add_worker(
|
||||
Worker(name=f"worker3", site="group1", grade=1, not_available_to_work=natws)
|
||||
)
|
||||
|
||||
wr = [WorkerRequirement(start_date=datetime.date(2022, 3, 7), end_date=datetime.date(2022, 3, 14)),
|
||||
WorkerRequirement(start_date=datetime.date(2022, 3, 28), number=2)
|
||||
]
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="a", length= 12.5, days=days[:4],
|
||||
assign_as_block=True,
|
||||
workers_required=wr,
|
||||
#start_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
#end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
)
|
||||
Rota.build_and_solve(options={"ratio": 0.0})
|
||||
Rota.export_rota_to_html("test_worker_requirement")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
|
||||
total_shifts = Rota.get_workers_total_shifts()
|
||||
|
||||
for worker in Rota.get_workers():
|
||||
assert total_shifts[worker.name] == 20
|
||||
assert Rota.get_worker_shift_list_string(worker)[7:21] == "-" * 14
|
||||
|
||||
if worker.name == "worker3":
|
||||
assert Rota.get_worker_shift_list_string(worker).endswith("aaaa---")
|
||||
assert Rota.get_worker_shift_list_string(worker).startswith("aaaa---")
|
||||
|
||||
|
||||
def test_worker_requirement_multiple_shifts(self):
|
||||
Rota = generate_basic_rota(workers=4)
|
||||
|
||||
wr = [WorkerRequirement(start_date=datetime.date(2022, 3, 7), end_date=datetime.date(2022, 3, 14)),
|
||||
WorkerRequirement(start_date=datetime.date(2022, 3, 28), number=2)
|
||||
]
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="b", length= 12.5, days=days[4:],
|
||||
assign_as_block=True,
|
||||
force_as_block=True,
|
||||
workers_required=2,
|
||||
#start_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
#end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"), name="a", length= 12.5, days=days[:4],
|
||||
assign_as_block=True,
|
||||
force_as_block=True,
|
||||
workers_required=wr,
|
||||
#start_date=(Rota.start_date + datetime.timedelta(days=7)),
|
||||
#end_date=(Rota.start_date + datetime.timedelta(days=((7*6)-1))),
|
||||
),
|
||||
)
|
||||
Rota.build_and_solve(options={"ratio": 0.1})
|
||||
Rota.export_rota_to_html("test_worker_requirement")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
|
||||
total_shifts = Rota.get_workers_total_shifts()
|
||||
|
||||
for worker in Rota.get_workers():
|
||||
assert total_shifts[worker.name] == 30
|
||||
+54
-10
@@ -2,23 +2,29 @@ import pytest
|
||||
from rota.shifts import NoWorkers, RotaBuilder, SingleShift, WarningTermination, days
|
||||
|
||||
import datetime
|
||||
from rota.workers import Worker
|
||||
from rota.workers import NotAvailableToWork, Worker, generate_not_available_to_works
|
||||
|
||||
def generate_basic_rota(weeks_to_rota=10, workers=0, start_date=datetime.date(2022, 3, 7)):
|
||||
|
||||
def setup_basic_rota():
|
||||
weeks_to_rota = 10
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
return RotaBuilder(
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
)
|
||||
|
||||
# Add a few workers
|
||||
if workers:
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(name=f"worker{i}", site="group1", grade=1) for i in range(1, workers+1)
|
||||
]
|
||||
)
|
||||
|
||||
return Rota
|
||||
|
||||
class TestWorkers:
|
||||
def test_no_workers(self):
|
||||
"""Check error raised if attempt to solve with no workers"""
|
||||
Rota = setup_basic_rota()
|
||||
Rota = generate_basic_rota()
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
@@ -43,7 +49,7 @@ class TestWorkers:
|
||||
|
||||
def test_workers_duplicate_name(self):
|
||||
|
||||
Rota = setup_basic_rota()
|
||||
Rota = generate_basic_rota()
|
||||
|
||||
workers = []
|
||||
workers.append(Worker(name="worker1", site="group1", grade=1, fte=100))
|
||||
@@ -325,7 +331,7 @@ class TestWorkers:
|
||||
Rota.export_rota_to_html("worker_oops")
|
||||
|
||||
def test_worker_pairs(self):
|
||||
Rota = setup_basic_rota()
|
||||
Rota = generate_basic_rota()
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
@@ -395,4 +401,42 @@ class TestWorkers:
|
||||
Rota.export_rota_to_html("worker_pairs")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
assert Rota.get_workers_total_shifts()["worker3"] == 50
|
||||
assert Rota.get_workers_total_shifts()["worker3"] == 50
|
||||
|
||||
def test_not_available_to_work(self):
|
||||
|
||||
Rota = generate_basic_rota()
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=days,
|
||||
workers_required=1,
|
||||
),
|
||||
)
|
||||
|
||||
natws = generate_not_available_to_works(Rota.start_date, days=21)
|
||||
|
||||
natws.append(NotAvailableToWork(date=Rota.rota_end_date-datetime.timedelta(days=1)))
|
||||
|
||||
|
||||
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(name="worker1", site="group1", grade=1),
|
||||
Worker(name="worker2", site="group1", grade=1, not_available_to_work=natws),
|
||||
]
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
Rota.export_rota_to_html("test_not_available_to_work")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
|
||||
assert Rota.get_workers_total_shifts()["worker1"] == 35
|
||||
assert Rota.get_workers_total_shifts()["worker2"] == 35
|
||||
|
||||
assert Rota.get_worker_shift_list_string(Rota.get_worker_by_name("worker2")).startswith("-" * 21)
|
||||
assert Rota.get_worker_shift_list_string(Rota.get_worker_by_name("worker2")).endswith("-")
|
||||
Reference in New Issue
Block a user