start adding locum availability

This commit is contained in:
Ross
2025-01-20 10:56:47 +00:00
parent 7ce4677921
commit 8459a4b3a9
5 changed files with 429 additions and 124 deletions
+318 -14
View File
@@ -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)