start adding locum availability
This commit is contained in:
+318
-14
@@ -1,11 +1,13 @@
|
||||
import pytest
|
||||
from rota.shifts import NoWorkers, RotaBuilder, SingleShift, WarningTermination, days
|
||||
from rota.shifts import NoWorkers, RotaBuilder, SingleShift, WarningTermination, WorkerRequirement, days
|
||||
|
||||
import datetime
|
||||
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 generate_basic_rota(
|
||||
weeks_to_rota=10, workers=0, start_date=datetime.date(2022, 3, 7)
|
||||
):
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
@@ -15,12 +17,14 @@ def generate_basic_rota(weeks_to_rota=10, workers=0, start_date=datetime.date(20
|
||||
if workers:
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(name=f"worker{i}", site="group1", grade=1) for i in range(1, workers+1)
|
||||
Worker(name=f"worker{i:02}", 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"""
|
||||
@@ -48,7 +52,6 @@ class TestWorkers:
|
||||
Rota.build_and_solve(solve=False)
|
||||
|
||||
def test_workers_duplicate_name(self):
|
||||
|
||||
Rota = generate_basic_rota()
|
||||
|
||||
workers = []
|
||||
@@ -74,7 +77,6 @@ class TestWorkers:
|
||||
Rota.build_and_solve(solve=False)
|
||||
|
||||
def test_worker_ftes(self):
|
||||
|
||||
weeks_to_rota = 10
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
@@ -151,13 +153,12 @@ class TestWorkers:
|
||||
|
||||
with pytest.raises(WarningTermination):
|
||||
Rota.build_and_solve()
|
||||
|
||||
|
||||
Rota.terminate_on_warning.remove("Worker/no valid shifts")
|
||||
Rota.build_and_solve()
|
||||
|
||||
# All workers should have an adjusted fte of 50 %
|
||||
for worker in Rota.get_workers():
|
||||
|
||||
# Except those in group3
|
||||
if worker.site == "group3":
|
||||
assert worker.fte_adj == 0
|
||||
@@ -404,7 +405,6 @@ class TestWorkers:
|
||||
assert Rota.get_workers_total_shifts()["worker3"] == 50
|
||||
|
||||
def test_not_available_to_work(self):
|
||||
|
||||
Rota = generate_basic_rota()
|
||||
|
||||
Rota.add_shifts(
|
||||
@@ -419,14 +419,16 @@ class TestWorkers:
|
||||
|
||||
natws = generate_not_available_to_works(Rota.start_date, days=21)
|
||||
|
||||
natws.append(NotAvailableToWork(date=Rota.rota_end_date-datetime.timedelta(days=1)))
|
||||
|
||||
|
||||
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),
|
||||
Worker(
|
||||
name="worker2", site="group1", grade=1, not_available_to_work=natws
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -438,5 +440,307 @@ class TestWorkers:
|
||||
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("-")
|
||||
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("-")
|
||||
|
||||
|
||||
class TestWorkerFtes:
|
||||
def test_worker_ftes(self):
|
||||
Rota = generate_basic_rota(workers=2)
|
||||
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(
|
||||
name="worker3",
|
||||
site="group1",
|
||||
grade=1,
|
||||
fte=60,
|
||||
),
|
||||
Worker(
|
||||
name="worker4",
|
||||
site="group1",
|
||||
grade=1,
|
||||
fte=40,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=days[:6],
|
||||
workers_required=1,
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
|
||||
Rota.export_rota_to_html("test_worker_ftes")
|
||||
|
||||
total_shifts = Rota.get_workers_total_shifts()
|
||||
for worker in Rota.get_workers():
|
||||
match worker.name:
|
||||
case "worker1":
|
||||
assert worker.get_fte() == 100
|
||||
assert total_shifts[worker.name] == 20
|
||||
case "worker2":
|
||||
assert worker.get_fte() == 100
|
||||
assert total_shifts[worker.name] == 20
|
||||
case "worker3":
|
||||
assert worker.get_fte() == 60
|
||||
assert total_shifts[worker.name] == 12
|
||||
case "worker4":
|
||||
assert worker.get_fte() == 40
|
||||
assert total_shifts[worker.name] == 8
|
||||
|
||||
def test_worker_fte_shift_override(self):
|
||||
Rota = generate_basic_rota(workers=1)
|
||||
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(
|
||||
name="worker2",
|
||||
site="group1",
|
||||
grade=1,
|
||||
fte=100,
|
||||
shift_fte_overrides={"b": 50}
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=days[:2],
|
||||
workers_required=1,
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=days[2:5],
|
||||
workers_required=1,
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
|
||||
Rota.export_rota_to_html("test_worker_fte_shift_override")
|
||||
|
||||
total_shifts = Rota.get_workers_total_shifts()
|
||||
for worker in Rota.get_workers():
|
||||
match worker.name:
|
||||
case "worker1":
|
||||
assert worker.get_fte() == 100
|
||||
assert total_shifts[worker.name] == 30
|
||||
assert worker.get_fte("b") == 100
|
||||
case "worker2":
|
||||
assert worker.get_fte() == 100
|
||||
assert worker.get_fte("b") == 50
|
||||
assert total_shifts[worker.name] == 20
|
||||
|
||||
class TestComplexWorkers:
|
||||
def test_worker_requirement_and_force_block(self):
|
||||
Rota = generate_basic_rota(workers=10, weeks_to_rota=10)
|
||||
|
||||
wr = [
|
||||
WorkerRequirement(
|
||||
end_date=Rota.start_date + datetime.timedelta(weeks=5),
|
||||
number=2,
|
||||
),
|
||||
WorkerRequirement(
|
||||
start_date=Rota.start_date + datetime.timedelta(weeks=5),
|
||||
number=3,
|
||||
),
|
||||
]
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=days[:2],
|
||||
workers_required=1,
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=days[2:5],
|
||||
workers_required=1,
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="c",
|
||||
length=12.5,
|
||||
days=days[:4],
|
||||
workers_required=wr,
|
||||
force_as_block=True,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
]
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="d",
|
||||
length=12.5,
|
||||
days=days[4:],
|
||||
workers_required=wr,
|
||||
force_as_block=True,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
|
||||
Rota.export_rota_to_html("test_worker_complex")
|
||||
|
||||
#total_shifts = Rota.get_workers_total_shifts()
|
||||
#for worker in Rota.get_workers():
|
||||
# match worker.name:
|
||||
# case "worker1":
|
||||
# assert worker.get_fte() == 100
|
||||
# assert total_shifts[worker.name] == 30
|
||||
# assert worker.get_fte("b") == 100
|
||||
# case "worker2":
|
||||
# assert worker.get_fte() == 100
|
||||
# assert worker.get_fte("b") == 50
|
||||
# assert total_shifts[worker.name] == 20
|
||||
#total_shifts = Rota.get_workers_total_shifts()
|
||||
for worker in Rota.get_workers():
|
||||
shift_string = Rota.get_worker_shift_list_string(worker)
|
||||
assert shift_string.count("cccc") == shift_string.count("c") / 4
|
||||
assert shift_string.count("ddd") == shift_string.count("d") / 3
|
||||
|
||||
def test_split_shift(self):
|
||||
Rota = generate_basic_rota(workers=11, weeks_to_rota=22)
|
||||
|
||||
Rota.set_rota_constraint("max_shifts_per_week", 2)
|
||||
Rota.set_rota_constraint("max_weekend_frequency", 3)
|
||||
Rota.set_rota_constraint("max_shifts_per_month", 4)
|
||||
|
||||
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=days[:4],
|
||||
workers_required=1,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
{"name": "max_shifts_per_week", "options": 1},
|
||||
]
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=(days[4], days[6]),
|
||||
workers_required=1,
|
||||
assign_as_block=True,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
]
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="c",
|
||||
length=12.5,
|
||||
days=days[5],
|
||||
workers_required=1,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
|
||||
Rota.export_rota_to_html("test_split")
|
||||
|
||||
|
||||
class TestLocums:
|
||||
def test_locums(self):
|
||||
Rota = generate_basic_rota(workers=9, weeks_to_rota=22)
|
||||
|
||||
Rota.set_rota_constraint("max_shifts_per_week", 2)
|
||||
Rota.set_rota_constraint("max_weekend_frequency", 3)
|
||||
Rota.set_rota_constraint("max_shifts_per_month", 4)
|
||||
|
||||
Rota.terminate_on_warning.remove("Worker/no valid shifts")
|
||||
|
||||
Rota.add_workers(
|
||||
[
|
||||
Worker(name="locum1", site="group1", locum=True),
|
||||
Worker(
|
||||
name="locum2", site="group1", locum=True
|
||||
),
|
||||
Worker(
|
||||
name="extra1", site="locum"
|
||||
),
|
||||
Worker(
|
||||
name="extra2", site="locum"
|
||||
),
|
||||
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=days[:4],
|
||||
workers_required=1,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
{"name": "max_shifts_per_week", "options": 1},
|
||||
]
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=(days[4], days[6]),
|
||||
workers_required=1,
|
||||
assign_as_block=True,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
]
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1", "group2"),
|
||||
name="c",
|
||||
length=12.5,
|
||||
days=days[5],
|
||||
workers_required=1,
|
||||
constraint=[
|
||||
{"name": "pre", "options": 2},
|
||||
{"name": "post", "options": 3},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
|
||||
Rota.export_rota_to_html("test_locums", timestamp_filename=False)
|
||||
|
||||
Reference in New Issue
Block a user