|
|
from solverforge_legacy.solver.test import ConstraintVerifier |
|
|
|
|
|
from meeting_scheduling.domain import ( |
|
|
Meeting, |
|
|
MeetingAssignment, |
|
|
MeetingSchedule, |
|
|
Person, |
|
|
PreferredAttendance, |
|
|
RequiredAttendance, |
|
|
Room, |
|
|
TimeGrain, |
|
|
) |
|
|
from meeting_scheduling.constraints import ( |
|
|
define_constraints, |
|
|
room_conflict, |
|
|
avoid_overtime, |
|
|
required_attendance_conflict, |
|
|
required_room_capacity, |
|
|
start_and_end_on_same_day, |
|
|
required_and_preferred_attendance_conflict, |
|
|
preferred_attendance_conflict, |
|
|
room_stability, |
|
|
) |
|
|
|
|
|
|
|
|
DEFAULT_TIME_GRAINS = [ |
|
|
TimeGrain( |
|
|
id=str(i + 1), grain_index=i, day_of_year=1, starting_minute_of_day=480 + i * 15 |
|
|
) |
|
|
for i in range(8) |
|
|
] |
|
|
|
|
|
DEFAULT_ROOM = Room(id="1", name="Room 1", capacity=10) |
|
|
SMALL_ROOM = Room(id="2", name="Small Room", capacity=1) |
|
|
LARGE_ROOM = Room(id="3", name="Large Room", capacity=2) |
|
|
ROOM_A = Room(id="4", name="Room A", capacity=10) |
|
|
ROOM_B = Room(id="5", name="Room B", capacity=10) |
|
|
|
|
|
|
|
|
constraint_verifier = ConstraintVerifier.build( |
|
|
define_constraints, MeetingSchedule, MeetingAssignment |
|
|
) |
|
|
|
|
|
|
|
|
def test_room_conflict_unpenalized(): |
|
|
"""Test that no penalty is applied when meetings in the same room do not overlap.""" |
|
|
meeting1 = create_meeting(1) |
|
|
left_assignment = create_meeting_assignment( |
|
|
0, meeting1, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
meeting2 = create_meeting(2) |
|
|
right_assignment = create_meeting_assignment( |
|
|
1, meeting2, DEFAULT_TIME_GRAINS[4], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(room_conflict).given( |
|
|
left_assignment, right_assignment |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_room_conflict_penalized(): |
|
|
"""Test that a penalty is applied when meetings in the same room overlap.""" |
|
|
meeting1 = create_meeting(1) |
|
|
left_assignment = create_meeting_assignment( |
|
|
0, meeting1, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
meeting2 = create_meeting(2) |
|
|
right_assignment = create_meeting_assignment( |
|
|
1, meeting2, DEFAULT_TIME_GRAINS[2], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(room_conflict).given( |
|
|
left_assignment, right_assignment |
|
|
).penalizes_by(2) |
|
|
|
|
|
|
|
|
def test_avoid_overtime_unpenalized(): |
|
|
"""Test that no penalty is applied when a meeting fits within available time grains (no overtime).""" |
|
|
meeting = create_meeting(1) |
|
|
meeting_assignment = create_meeting_assignment( |
|
|
0, meeting, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(avoid_overtime).given( |
|
|
meeting_assignment, *DEFAULT_TIME_GRAINS |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_avoid_overtime_penalized(): |
|
|
"""Test that a penalty is applied when a meeting exceeds available time grains (overtime).""" |
|
|
meeting = create_meeting(1) |
|
|
meeting_assignment = create_meeting_assignment( |
|
|
0, meeting, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(avoid_overtime).given( |
|
|
meeting_assignment |
|
|
).penalizes_by(3) |
|
|
|
|
|
|
|
|
def test_required_attendance_conflict_unpenalized(): |
|
|
"""Test that no penalty is applied when a person does not have overlapping required meetings.""" |
|
|
person = create_person(1) |
|
|
|
|
|
left_meeting = create_meeting(1, duration=2) |
|
|
required_attendance1 = create_required_attendance(0, person, left_meeting) |
|
|
|
|
|
right_meeting = create_meeting(2, duration=2) |
|
|
required_attendance2 = create_required_attendance(1, person, right_meeting) |
|
|
|
|
|
left_assignment = create_meeting_assignment( |
|
|
0, left_meeting, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM |
|
|
) |
|
|
right_assignment = create_meeting_assignment( |
|
|
1, right_meeting, DEFAULT_TIME_GRAINS[2], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(required_attendance_conflict).given( |
|
|
required_attendance1, required_attendance2, left_assignment, right_assignment |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_required_attendance_conflict_penalized(): |
|
|
"""Test that a penalty is applied when a person has overlapping required meetings.""" |
|
|
person = create_person(1) |
|
|
|
|
|
left_meeting = create_meeting(1, duration=2) |
|
|
required_attendance1 = create_required_attendance(0, person, left_meeting) |
|
|
|
|
|
right_meeting = create_meeting(2, duration=2) |
|
|
required_attendance2 = create_required_attendance(1, person, right_meeting) |
|
|
|
|
|
left_assignment = create_meeting_assignment( |
|
|
0, left_meeting, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM |
|
|
) |
|
|
right_assignment = create_meeting_assignment( |
|
|
1, right_meeting, DEFAULT_TIME_GRAINS[1], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(required_attendance_conflict).given( |
|
|
required_attendance1, required_attendance2, left_assignment, right_assignment |
|
|
).penalizes_by(1) |
|
|
|
|
|
|
|
|
def test_required_room_capacity_unpenalized(): |
|
|
"""Test that no penalty is applied when the room has enough capacity for all required and preferred attendees.""" |
|
|
person1 = create_person(1) |
|
|
person2 = create_person(2) |
|
|
|
|
|
meeting = create_meeting(1, duration=2) |
|
|
create_required_attendance(0, person1, meeting) |
|
|
create_preferred_attendance(1, person2, meeting) |
|
|
|
|
|
meeting_assignment = create_meeting_assignment( |
|
|
0, meeting, DEFAULT_TIME_GRAINS[0], LARGE_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(required_room_capacity).given( |
|
|
meeting_assignment |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_required_room_capacity_penalized(): |
|
|
"""Test that a penalty is applied when the room does not have enough capacity for all required and preferred attendees.""" |
|
|
person1 = create_person(1) |
|
|
person2 = create_person(2) |
|
|
|
|
|
meeting = create_meeting(1, duration=2) |
|
|
create_required_attendance(0, person1, meeting) |
|
|
create_preferred_attendance(1, person2, meeting) |
|
|
|
|
|
meeting_assignment = create_meeting_assignment( |
|
|
0, meeting, DEFAULT_TIME_GRAINS[0], SMALL_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(required_room_capacity).given( |
|
|
meeting_assignment |
|
|
).penalizes_by(1) |
|
|
|
|
|
|
|
|
def test_start_and_end_on_same_day_unpenalized(): |
|
|
"""Test that no penalty is applied when a meeting starts and ends on the same day.""" |
|
|
|
|
|
start_time_grain = TimeGrain( |
|
|
id="1", grain_index=0, day_of_year=0, starting_minute_of_day=480 |
|
|
) |
|
|
end_time_grain = TimeGrain( |
|
|
id="2", grain_index=3, day_of_year=0, starting_minute_of_day=525 |
|
|
) |
|
|
|
|
|
meeting = create_meeting(1) |
|
|
meeting_assignment = create_meeting_assignment( |
|
|
0, meeting, start_time_grain, DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(start_and_end_on_same_day).given( |
|
|
meeting_assignment, end_time_grain |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_start_and_end_on_same_day_penalized(): |
|
|
"""Test that a penalty is applied when a meeting starts and ends on different days.""" |
|
|
|
|
|
start_time_grain = TimeGrain( |
|
|
id="1", grain_index=0, day_of_year=0, starting_minute_of_day=480 |
|
|
) |
|
|
end_time_grain = TimeGrain( |
|
|
id="2", grain_index=3, day_of_year=1, starting_minute_of_day=525 |
|
|
) |
|
|
|
|
|
meeting = create_meeting(1) |
|
|
meeting_assignment = create_meeting_assignment( |
|
|
0, meeting, start_time_grain, DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(start_and_end_on_same_day).given( |
|
|
meeting_assignment, end_time_grain |
|
|
).penalizes_by(1) |
|
|
|
|
|
|
|
|
def test_multiple_constraint_violations(): |
|
|
"""Test that multiple constraints can be violated simultaneously.""" |
|
|
person = create_person(1) |
|
|
|
|
|
left_meeting = create_meeting(1) |
|
|
required_attendance1 = create_required_attendance(0, person, left_meeting) |
|
|
left_assignment = create_meeting_assignment( |
|
|
0, left_meeting, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
right_meeting = create_meeting(2) |
|
|
required_attendance2 = create_required_attendance(1, person, right_meeting) |
|
|
right_assignment = create_meeting_assignment( |
|
|
1, right_meeting, DEFAULT_TIME_GRAINS[2], DEFAULT_ROOM |
|
|
) |
|
|
|
|
|
constraint_verifier.verify_that(room_conflict).given( |
|
|
left_assignment, right_assignment |
|
|
).penalizes_by(2) |
|
|
constraint_verifier.verify_that(required_attendance_conflict).given( |
|
|
required_attendance1, required_attendance2, left_assignment, right_assignment |
|
|
).penalizes_by(2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_meeting(id, topic="Meeting", duration=4): |
|
|
"""Helper to create a meeting with standard parameters.""" |
|
|
return Meeting(id=str(id), topic=f"{topic} {id}", duration_in_grains=duration) |
|
|
|
|
|
|
|
|
def create_meeting_assignment(id, meeting, time_grain, room): |
|
|
"""Helper to create a meeting assignment.""" |
|
|
return MeetingAssignment( |
|
|
id=str(id), meeting=meeting, starting_time_grain=time_grain, room=room |
|
|
) |
|
|
|
|
|
|
|
|
def create_person(id): |
|
|
"""Helper to create a person.""" |
|
|
return Person(id=str(id), full_name=f"Person {id}") |
|
|
|
|
|
|
|
|
def create_required_attendance(id, person, meeting): |
|
|
"""Helper to create and link required attendance.""" |
|
|
attendance = RequiredAttendance(id=str(id), person=person, meeting_id=meeting.id) |
|
|
meeting.required_attendances = [attendance] |
|
|
return attendance |
|
|
|
|
|
|
|
|
def create_preferred_attendance(id, person, meeting): |
|
|
"""Helper to create and link preferred attendance.""" |
|
|
attendance = PreferredAttendance(id=str(id), person=person, meeting_id=meeting.id) |
|
|
meeting.preferred_attendances = [attendance] |
|
|
return attendance |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_required_and_preferred_attendance_conflict_unpenalized(): |
|
|
"""Test no penalty when required and preferred meetings don't overlap.""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
meeting1 = create_meeting(1, duration=4) |
|
|
attendance1 = create_required_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM) |
|
|
|
|
|
|
|
|
meeting2 = create_meeting(2, duration=4) |
|
|
attendance2 = create_preferred_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[4], ROOM_A) |
|
|
|
|
|
constraint_verifier.verify_that(required_and_preferred_attendance_conflict).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes_by(0) |
|
|
|
|
|
|
|
|
def test_required_and_preferred_attendance_conflict_penalized(): |
|
|
"""Test penalty when person required at one meeting and preferred at overlapping meeting.""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
meeting1 = create_meeting(1, duration=4) |
|
|
attendance1 = create_required_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM) |
|
|
|
|
|
|
|
|
meeting2 = create_meeting(2, duration=4) |
|
|
attendance2 = create_preferred_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[2], ROOM_A) |
|
|
|
|
|
|
|
|
constraint_verifier.verify_that(required_and_preferred_attendance_conflict).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes_by(2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_preferred_attendance_conflict_unpenalized(): |
|
|
"""Test no penalty when preferred attendee has non-overlapping meetings.""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
meeting1 = create_meeting(1, duration=4) |
|
|
attendance1 = create_preferred_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM) |
|
|
|
|
|
|
|
|
meeting2 = create_meeting(2, duration=4) |
|
|
attendance2 = create_preferred_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[4], ROOM_A) |
|
|
|
|
|
constraint_verifier.verify_that(preferred_attendance_conflict).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes_by(0) |
|
|
|
|
|
|
|
|
def test_preferred_attendance_conflict_penalized(): |
|
|
"""Test penalty when person preferred at multiple overlapping meetings.""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
meeting1 = create_meeting(1, duration=4) |
|
|
attendance1 = create_preferred_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[0], DEFAULT_ROOM) |
|
|
|
|
|
|
|
|
meeting2 = create_meeting(2, duration=4) |
|
|
attendance2 = create_preferred_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[1], ROOM_A) |
|
|
|
|
|
|
|
|
constraint_verifier.verify_that(preferred_attendance_conflict).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes_by(3) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_room_stability_same_room_no_penalty(): |
|
|
""" |
|
|
Test that no penalty is applied when a person attends consecutive |
|
|
meetings in the same room (stability is maintained). |
|
|
""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
meeting1 = create_meeting(1, duration=2) |
|
|
attendance1 = create_required_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[0], ROOM_A) |
|
|
|
|
|
|
|
|
meeting2 = create_meeting(2, duration=2) |
|
|
attendance2 = create_required_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[3], ROOM_A) |
|
|
|
|
|
|
|
|
constraint_verifier.verify_that(room_stability).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_room_stability_different_room_with_required_attendance(): |
|
|
""" |
|
|
Test that a penalty is applied when a person with required attendance |
|
|
has to change rooms between closely scheduled meetings. |
|
|
Weighted penalty: back-to-back switches cost more than switches with gaps. |
|
|
""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
left_grain_index = 0 |
|
|
left_duration = 2 |
|
|
meeting1 = create_meeting(1, duration=left_duration) |
|
|
attendance1 = create_required_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[left_grain_index], ROOM_A) |
|
|
|
|
|
|
|
|
right_grain_index = 3 |
|
|
meeting2 = create_meeting(2, duration=2) |
|
|
attendance2 = create_required_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[right_grain_index], ROOM_B) |
|
|
|
|
|
|
|
|
gap = right_grain_index - left_duration - left_grain_index |
|
|
expected_penalty = 3 - gap |
|
|
|
|
|
constraint_verifier.verify_that(room_stability).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes_by(expected_penalty) |
|
|
|
|
|
|
|
|
def test_room_stability_different_room_with_preferred_attendance(): |
|
|
""" |
|
|
Test that a penalty is applied when a person with preferred attendance |
|
|
has to change rooms between closely scheduled meetings. |
|
|
Weighted penalty applies to preferred attendance too. |
|
|
""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
left_grain_index = 0 |
|
|
left_duration = 2 |
|
|
meeting1 = create_meeting(1, duration=left_duration) |
|
|
attendance1 = create_preferred_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[left_grain_index], ROOM_A) |
|
|
|
|
|
|
|
|
right_grain_index = 3 |
|
|
meeting2 = create_meeting(2, duration=2) |
|
|
attendance2 = create_preferred_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[right_grain_index], ROOM_B) |
|
|
|
|
|
|
|
|
gap = right_grain_index - left_duration - left_grain_index |
|
|
expected_penalty = 3 - gap |
|
|
|
|
|
constraint_verifier.verify_that(room_stability).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes_by(expected_penalty) |
|
|
|
|
|
|
|
|
def test_room_stability_mixed_attendance_types(): |
|
|
""" |
|
|
Test that room stability penalty applies when mixing required and preferred |
|
|
attendance types for the same person. |
|
|
""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
left_grain_index = 0 |
|
|
left_duration = 2 |
|
|
meeting1 = create_meeting(1, duration=left_duration) |
|
|
required_attendance = create_required_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[left_grain_index], ROOM_A) |
|
|
|
|
|
|
|
|
right_grain_index = 3 |
|
|
meeting2 = create_meeting(2, duration=2) |
|
|
preferred_attendance = create_preferred_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[right_grain_index], ROOM_B) |
|
|
|
|
|
|
|
|
gap = right_grain_index - left_duration - left_grain_index |
|
|
expected_penalty = 3 - gap |
|
|
|
|
|
constraint_verifier.verify_that(room_stability).given( |
|
|
required_attendance, preferred_attendance, assignment1, assignment2 |
|
|
).penalizes_by(expected_penalty) |
|
|
|
|
|
|
|
|
def test_room_stability_far_apart_meetings_no_penalty(): |
|
|
""" |
|
|
Test that no penalty is applied when meetings are far apart in time, |
|
|
even if they're in different rooms. |
|
|
""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
meeting1 = create_meeting(1, duration=2) |
|
|
attendance1 = create_required_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[0], ROOM_A) |
|
|
|
|
|
|
|
|
|
|
|
meeting2 = create_meeting(2, duration=2) |
|
|
attendance2 = create_required_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[6], ROOM_B) |
|
|
|
|
|
|
|
|
constraint_verifier.verify_that(room_stability).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_room_stability_different_people_no_penalty(): |
|
|
""" |
|
|
Test that no penalty is applied when different people have meetings |
|
|
in different rooms (room stability is per-person). |
|
|
""" |
|
|
person1 = create_person(1) |
|
|
person2 = create_person(2) |
|
|
|
|
|
|
|
|
meeting1 = create_meeting(1, duration=2) |
|
|
attendance1 = create_required_attendance(0, person1, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[0], ROOM_A) |
|
|
|
|
|
|
|
|
meeting2 = create_meeting(2, duration=2) |
|
|
attendance2 = create_required_attendance(1, person2, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[3], ROOM_B) |
|
|
|
|
|
|
|
|
constraint_verifier.verify_that(room_stability).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes(0) |
|
|
|
|
|
|
|
|
def test_room_stability_back_to_back_highest_penalty(): |
|
|
""" |
|
|
Test that back-to-back room switches (gap=0) incur the highest penalty (3). |
|
|
This verifies the weighted penalty gradient: closer switches cost more. |
|
|
""" |
|
|
person = create_person(1) |
|
|
|
|
|
|
|
|
left_grain_index = 0 |
|
|
left_duration = 2 |
|
|
meeting1 = create_meeting(1, duration=left_duration) |
|
|
attendance1 = create_required_attendance(0, person, meeting1) |
|
|
assignment1 = create_meeting_assignment(0, meeting1, DEFAULT_TIME_GRAINS[left_grain_index], ROOM_A) |
|
|
|
|
|
|
|
|
right_grain_index = 2 |
|
|
meeting2 = create_meeting(2, duration=2) |
|
|
attendance2 = create_required_attendance(1, person, meeting2) |
|
|
assignment2 = create_meeting_assignment(1, meeting2, DEFAULT_TIME_GRAINS[right_grain_index], ROOM_B) |
|
|
|
|
|
|
|
|
gap = right_grain_index - left_duration - left_grain_index |
|
|
expected_penalty = 3 - gap |
|
|
assert gap == 0, f"Test setup error: expected gap=0, got {gap}" |
|
|
assert expected_penalty == 3, f"Test setup error: expected penalty=3, got {expected_penalty}" |
|
|
|
|
|
constraint_verifier.verify_that(room_stability).given( |
|
|
attendance1, attendance2, assignment1, assignment2 |
|
|
).penalizes_by(expected_penalty) |
|
|
|