add worker requirements

This commit is contained in:
Ross
2025-01-01 21:58:48 +00:00
parent 9af01a3415
commit 7ce4677921
5 changed files with 535 additions and 218 deletions
+160 -51
View File
@@ -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
View File
@@ -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("-")