flight-crew-scheduling-java
/
src
/test
/java
/org
/acme
/flighcrewscheduling
/solver
/FlightCrewSchedulingConstraintProviderTest.java
| package org.acme.flighcrewscheduling.solver; | |
| import java.time.LocalDate; | |
| import java.time.LocalDateTime; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import jakarta.inject.Inject; | |
| import ai.timefold.solver.test.api.score.stream.ConstraintVerifier; | |
| import org.acme.flighcrewscheduling.domain.Airport; | |
| import org.acme.flighcrewscheduling.domain.Employee; | |
| import org.acme.flighcrewscheduling.domain.Flight; | |
| import org.acme.flighcrewscheduling.domain.FlightAssignment; | |
| import org.acme.flighcrewscheduling.domain.FlightCrewSchedule; | |
| import org.junit.jupiter.api.Test; | |
| import io.quarkus.test.junit.QuarkusTest; | |
| class FlightCrewSchedulingConstraintProviderTest { | |
| private final ConstraintVerifier<FlightCrewSchedulingConstraintProvider, FlightCrewSchedule> constraintVerifier; | |
| public FlightCrewSchedulingConstraintProviderTest( | |
| ConstraintVerifier<FlightCrewSchedulingConstraintProvider, FlightCrewSchedule> constraintVerifier) { | |
| this.constraintVerifier = constraintVerifier; | |
| } | |
| void requiredSkill() { | |
| FlightAssignment assignment = new FlightAssignment("1", null, 0, "1"); | |
| Employee employee = new Employee("1"); | |
| employee.setSkills(List.of("2")); | |
| assignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::requiredSkill) | |
| .given(assignment) | |
| .penalizesBy(1); // missing requiredSkill | |
| } | |
| void flightConflict() { | |
| Employee employee = new Employee("1"); | |
| Flight flight = new Flight("1", null, LocalDateTime.now(), null, LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment assignment = new FlightAssignment("1", flight); | |
| assignment.setEmployee(employee); | |
| Flight overlappingFlight = | |
| new Flight("1", null, LocalDateTime.now().plusMinutes(1), null, LocalDateTime.now().plusMinutes(11)); | |
| FlightAssignment overlappingAssignment = new FlightAssignment("2", overlappingFlight); | |
| overlappingAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::flightConflict) | |
| .given(assignment, overlappingAssignment) | |
| .penalizesBy(1); // one overlapping thirdFlight | |
| } | |
| void transferBetweenTwoFlights() { | |
| Employee employee = new Employee("1"); | |
| Airport firstAirport = new Airport("1"); | |
| Airport secondAirport = new Airport("2"); | |
| Flight firstFlight = | |
| new Flight("1", firstAirport, LocalDateTime.now(), secondAirport, LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| Flight firstInvalidFlight = | |
| new Flight("2", firstAirport, LocalDateTime.now().plusMinutes(11), secondAirport, | |
| LocalDateTime.now().plusMinutes(12)); | |
| FlightAssignment firstInvalidAssignment = new FlightAssignment("2", firstInvalidFlight); | |
| firstInvalidAssignment.setEmployee(employee); | |
| Flight secondInvalidFlight = | |
| new Flight("3", firstAirport, LocalDateTime.now().plusMinutes(13), secondAirport, | |
| LocalDateTime.now().plusMinutes(14)); | |
| FlightAssignment secondInvalidAssignment = new FlightAssignment("3", secondInvalidFlight); | |
| secondInvalidAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::transferBetweenTwoFlights) | |
| .given(firstAssignment, firstInvalidAssignment, secondInvalidAssignment) | |
| .penalizesBy(2); // two invalid connections | |
| } | |
| void employeeUnavailability() { | |
| var date = LocalDate.now(); | |
| var employee = new Employee("1"); | |
| employee.setUnavailableDays(List.of(date)); | |
| var flight = | |
| new Flight("1", null, date.atStartOfDay(), null, date.atStartOfDay().plusMinutes(10)); | |
| var assignment = new FlightAssignment("1", flight); | |
| assignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::employeeUnavailability) | |
| .given(assignment) | |
| .penalizesBy(1); // unavailable at departure | |
| flight.setDepartureUTCDateTime(date.minusDays(1).atStartOfDay()); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::employeeUnavailability) | |
| .given(assignment) | |
| .penalizesBy(1); // unavailable during flight | |
| flight.setDepartureUTCDateTime(date.plusDays(1).atStartOfDay()); | |
| flight.setArrivalUTCDateTime(date.plusDays(1).atStartOfDay().plusMinutes(10)); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::employeeUnavailability) | |
| .given(assignment) | |
| .penalizesBy(0); // employee available | |
| } | |
| void firstAssignmentNotDepartingFromHome() { | |
| Employee employee = new Employee("1"); | |
| employee.setHomeAirport(new Airport("1")); | |
| employee.setUnavailableDays(List.of(LocalDate.now())); | |
| Flight flight = | |
| new Flight("1", new Airport("2"), LocalDateTime.now(), new Airport("3"), | |
| LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment assignment = new FlightAssignment("1", flight); | |
| assignment.setEmployee(employee); | |
| Flight secondFlight = | |
| new Flight("2", new Airport("2"), LocalDateTime.now().plusMinutes(1), new Airport("3"), | |
| LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| Flight thirdFlight = | |
| new Flight("3", new Airport("2"), LocalDateTime.now().plusMinutes(1), new Airport("3"), | |
| LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment thirdAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdAssignment.setEmployee(employee); | |
| Employee secondEmployee = new Employee("2"); | |
| secondEmployee.setHomeAirport(new Airport("3")); | |
| secondEmployee.setUnavailableDays(List.of(LocalDate.now())); | |
| Flight fourthFlight = | |
| new Flight("4", new Airport("3"), LocalDateTime.now(), new Airport("4"), | |
| LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment fourthAssignment = new FlightAssignment("4", fourthFlight); | |
| fourthAssignment.setEmployee(secondEmployee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::firstAssignmentNotDepartingFromHome) | |
| .given(employee, secondEmployee, assignment, secondAssignment, thirdAssignment, fourthAssignment) | |
| .penalizesBy(1); // invalid first airport | |
| } | |
| void lastAssignmentNotArrivingAtHome() { | |
| Employee employee = new Employee("1"); | |
| employee.setHomeAirport(new Airport("1")); | |
| employee.setUnavailableDays(List.of(LocalDate.now())); | |
| Flight firstFlight = | |
| new Flight("1", new Airport("2"), LocalDateTime.now(), new Airport("3"), | |
| LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| Flight secondFlight = | |
| new Flight("2", new Airport("3"), LocalDateTime.now().plusMinutes(11), new Airport("4"), | |
| LocalDateTime.now().plusMinutes(12)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| Employee secondEmployee = new Employee("2"); | |
| secondEmployee.setHomeAirport(new Airport("2")); | |
| secondEmployee.setUnavailableDays(List.of(LocalDate.now())); | |
| Flight thirdFlight = | |
| new Flight("3", new Airport("2"), LocalDateTime.now(), new Airport("3"), | |
| LocalDateTime.now().plusMinutes(10)); | |
| FlightAssignment thirdFlightAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdFlightAssignment.setEmployee(secondEmployee); | |
| Flight fourthFlight = | |
| new Flight("4", new Airport("3"), LocalDateTime.now().plusMinutes(11), new Airport("2"), | |
| LocalDateTime.now().plusMinutes(12)); | |
| FlightAssignment fourthFlightAssignment = new FlightAssignment("4", fourthFlight); | |
| fourthFlightAssignment.setEmployee(secondEmployee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::lastAssignmentNotArrivingAtHome) | |
| .given(employee, secondEmployee, firstAssignment, secondAssignment, thirdFlightAssignment, | |
| fourthFlightAssignment) | |
| .penalizesBy(1); // invalid last airport | |
| } | |
| void minimumRestAtHomeBase_satisfied() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: 8 hours duration, FDP = 8h + 65min = ~8.08h | |
| // Required rest at home base = max(8.08, 12) = 12 hours | |
| Flight firstFlight = new Flight("1", new Airport("LHR"), now, | |
| new Airport("JFK"), now.plusHours(8)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight departs from home base 13 hours after first flight duty end | |
| // Duty end of first = arrival + 20 min = now + 8h + 20min | |
| // Second flight duty start = departure - 45 min | |
| // Gap = 13 hours (satisfies 12 hour minimum) | |
| LocalDateTime secondDeparture = now.plusHours(8).plusMinutes(20).plusHours(13).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", homeAirport, secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(1)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAtHomeBase) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(0); // 13 hours rest satisfies 12 hour minimum | |
| } | |
| void minimumRestAtHomeBase_violated() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: 8 hours duration | |
| Flight firstFlight = new Flight("1", new Airport("LHR"), now, | |
| new Airport("JFK"), now.plusHours(8)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight departs from home base only 10 hours after first flight duty end | |
| // Violates 12 hour minimum by 2 hours = 120 minutes | |
| LocalDateTime secondDeparture = now.plusHours(8).plusMinutes(20).plusHours(10).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", homeAirport, secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(1)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAtHomeBase) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(120); // 2 hours = 120 minutes violation | |
| } | |
| void minimumRestAtHomeBase_notApplicableAwayFromHome() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| Flight firstFlight = new Flight("1", new Airport("LHR"), now, | |
| new Airport("JFK"), now.plusHours(8)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight does NOT depart from home base - constraint should not apply | |
| LocalDateTime secondDeparture = now.plusHours(8).plusMinutes(20).plusHours(5).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", new Airport("JFK"), secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(1)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAtHomeBase) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(0); // constraint doesn't apply when not at home base | |
| } | |
| void minimumRestAwayFromHomeBase_satisfied() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| Airport jfk = new Airport("JFK"); | |
| Airport atl = new Airport("ATL"); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight ends at JFK | |
| Flight firstFlight = new Flight("1", new Airport("LHR"), now, | |
| jfk, now.plusHours(8)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight departs from ATL (away from home) | |
| // Required rest = max(8.08, 10) = 10 hours (no taxi time adjustment for test simplicity) | |
| // Provide 11 hours rest | |
| LocalDateTime secondDeparture = now.plusHours(8).plusMinutes(20).plusHours(11).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", atl, secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(2)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAwayFromHomeBase) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(0); // 11 hours satisfies 10 hour minimum | |
| } | |
| void minimumRestAwayFromHomeBase_violated() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| Airport jfk = new Airport("JFK"); | |
| Airport atl = new Airport("ATL"); | |
| LocalDateTime now = LocalDateTime.now(); | |
| Flight firstFlight = new Flight("1", new Airport("LHR"), now, | |
| jfk, now.plusHours(8)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight away from home with only 8 hours rest | |
| // Violates 10 hour minimum by 2 hours = 120 minutes | |
| LocalDateTime secondDeparture = now.plusHours(8).plusMinutes(20).plusHours(8).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", atl, secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(2)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAwayFromHomeBase) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(120); // 2 hours = 120 minutes violation | |
| } | |
| void minimumRestAwayFromHomeBase_withTravelTimeAdjustment() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| Airport jfk = new Airport("JFK"); | |
| jfk.setTaxiTimeInMinutes(java.util.Map.of("ATL", 180L)); // 3 hours taxi time | |
| Airport atl = new Airport("ATL"); | |
| LocalDateTime now = LocalDateTime.now(); | |
| Flight firstFlight = new Flight("1", new Airport("LHR"), now, | |
| jfk, now.plusHours(8)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Taxi time = 180 minutes, excess = 180 - 30 = 150 minutes | |
| // Travel adjustment = 150 * 2 / 60 = 5 hours | |
| // Required rest = max(8.08, 10) + 5 = 15 hours | |
| // Provide only 12 hours rest - violates by 3 hours = 180 minutes | |
| LocalDateTime secondDeparture = now.plusHours(8).plusMinutes(20).plusHours(12).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", atl, secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(2)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAwayFromHomeBase) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(180); // 3 hours violation due to travel time adjustment | |
| } | |
| void extendedRecoveryRestPeriod_satisfied_frequentLongRests() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime start = LocalDateTime.of(2025, 1, 1, 8, 0); | |
| // Pattern: Work 3 days, 40h rest, repeat (well within compliance) | |
| List<Object> given = new ArrayList<>(); | |
| given.add(employee); | |
| LocalDateTime currentTime = start; | |
| for (int cycle = 0; cycle < 3; cycle++) { | |
| // 3 assignments with short rests | |
| for (int i = 0; i < 3; i++) { | |
| Flight flight = new Flight("C" + cycle + "F" + i, homeAirport, currentTime, | |
| new Airport("JFK"), currentTime.plusHours(8)); | |
| FlightAssignment assignment = new FlightAssignment("C" + cycle + "A" + i, flight); | |
| assignment.setEmployee(employee); | |
| given.add(assignment); | |
| // 15 hour rest between assignments | |
| currentTime = currentTime.plusHours(8).plusMinutes(20).plusHours(15).plusMinutes(45); | |
| } | |
| // Long rest (40 hours) after every 3 assignments | |
| currentTime = currentTime.minusMinutes(45).plusHours(40).plusMinutes(45); | |
| } | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::extendedRecoveryRestPeriod) | |
| .given(given.toArray()) | |
| .penalizesBy(0); | |
| } | |
| void extendedRecoveryRestPeriod_violated_continuousShortRests() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime start = LocalDateTime.of(2025, 1, 1, 8, 0); | |
| // Create 10 assignments over 10 days with only 15-hour rests (< 36h) | |
| // This spans ~230 hours without a qualifying rest - should violate | |
| List<Object> given = new ArrayList<>(); | |
| given.add(employee); | |
| LocalDateTime currentTime = start; | |
| for (int i = 0; i < 10; i++) { | |
| Flight flight = new Flight("F" + i, homeAirport, currentTime, | |
| new Airport("JFK"), currentTime.plusHours(8)); | |
| FlightAssignment assignment = new FlightAssignment("A" + i, flight); | |
| assignment.setEmployee(employee); | |
| given.add(assignment); | |
| // 15 hour rest (duty end to next duty start) | |
| // Duty end = arrival + 20min = currentTime + 8h 20min | |
| // Next duty start = duty end + 15h | |
| // Next departure = duty start + 45min | |
| currentTime = currentTime.plusHours(23).plusMinutes(20); | |
| } | |
| // Expected violations: multiple windows will exceed 168h without 36h rest | |
| // With 10 assignments over ~230 hours with no 36h rest, there are 2 violations | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::extendedRecoveryRestPeriod) | |
| .given(given.toArray()) | |
| .penalizesBy(2); | |
| } | |
| void extendedRecoveryRestPeriod_satisfied_sparseSchedule() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime start = LocalDateTime.of(2025, 1, 1, 8, 0); | |
| // Two assignments 10 days apart with long rest | |
| Flight flight1 = new Flight("F1", homeAirport, start, | |
| new Airport("JFK"), start.plusHours(8)); | |
| FlightAssignment a1 = new FlightAssignment("A1", flight1); | |
| a1.setEmployee(employee); | |
| // 230 hour (9.5 day) rest period - well over 36h | |
| LocalDateTime secondDeparture = start.plusHours(8).plusMinutes(20) | |
| .plusHours(230).plusMinutes(45); | |
| Flight flight2 = new Flight("F2", homeAirport, secondDeparture, | |
| new Airport("JFK"), secondDeparture.plusHours(8)); | |
| FlightAssignment a2 = new FlightAssignment("A2", flight2); | |
| a2.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::extendedRecoveryRestPeriod) | |
| .given(employee, a1, a2) | |
| .penalizesBy(0); | |
| } | |
| void extendedRecoveryRestPeriod_edgeCase_singleAssignment() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime start = LocalDateTime.of(2025, 1, 1, 8, 0); | |
| Flight flight = new Flight("F1", homeAirport, start, | |
| new Airport("JFK"), start.plusHours(8)); | |
| FlightAssignment assignment = new FlightAssignment("A1", flight); | |
| assignment.setEmployee(employee); | |
| // Single assignment - no violation possible | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::extendedRecoveryRestPeriod) | |
| .given(employee, assignment) | |
| .penalizesBy(0); | |
| } | |
| void extendedRecoveryRestPeriod_satisfied_weeklyPattern() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime start = LocalDateTime.of(2025, 1, 1, 8, 0); | |
| // Realistic pattern: Work Mon-Fri with 15h rests, then 48h weekend rest | |
| List<Object> given = new ArrayList<>(); | |
| given.add(employee); | |
| LocalDateTime currentTime = start; | |
| // Week 1: Mon-Fri | |
| for (int i = 0; i < 5; i++) { | |
| Flight flight = new Flight("W1F" + i, homeAirport, currentTime, | |
| new Airport("JFK"), currentTime.plusHours(8)); | |
| FlightAssignment assignment = new FlightAssignment("W1A" + i, flight); | |
| assignment.setEmployee(employee); | |
| given.add(assignment); | |
| currentTime = currentTime.plusHours(23).plusMinutes(20); // 15h rest | |
| } | |
| // Weekend rest: 48 hours | |
| currentTime = currentTime.minusMinutes(45).plusHours(48).plusMinutes(45); | |
| // Week 2: Mon-Fri | |
| for (int i = 0; i < 5; i++) { | |
| Flight flight = new Flight("W2F" + i, homeAirport, currentTime, | |
| new Airport("JFK"), currentTime.plusHours(8)); | |
| FlightAssignment assignment = new FlightAssignment("W2A" + i, flight); | |
| assignment.setEmployee(employee); | |
| given.add(assignment); | |
| currentTime = currentTime.plusHours(23).plusMinutes(20); | |
| } | |
| // Should satisfy - 48h rest every week | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::extendedRecoveryRestPeriod) | |
| .given(given.toArray()) | |
| .penalizesBy(0); | |
| } | |
| void extendedRecoveryRestPeriod_multipleEmployees_isolated() { | |
| // Employee 1: Violates ERRP | |
| Employee employee1 = new Employee("1"); | |
| employee1.setHomeAirport(new Airport("LHR")); | |
| // Employee 2: Satisfies ERRP | |
| Employee employee2 = new Employee("2"); | |
| employee2.setHomeAirport(new Airport("LHR")); | |
| LocalDateTime start = LocalDateTime.of(2025, 1, 1, 8, 0); | |
| List<Object> given = new ArrayList<>(); | |
| given.add(employee1); | |
| given.add(employee2); | |
| // Employee 1: Continuous short rests (violation) | |
| LocalDateTime time1 = start; | |
| for (int i = 0; i < 10; i++) { | |
| Flight flight = new Flight("E1F" + i, new Airport("LHR"), time1, | |
| new Airport("JFK"), time1.plusHours(8)); | |
| FlightAssignment assignment = new FlightAssignment("E1A" + i, flight); | |
| assignment.setEmployee(employee1); | |
| given.add(assignment); | |
| time1 = time1.plusHours(23).plusMinutes(20); | |
| } | |
| // Employee 2: Long rests (no violation) | |
| LocalDateTime time2 = start; | |
| for (int i = 0; i < 3; i++) { | |
| Flight flight = new Flight("E2F" + i, new Airport("LHR"), time2, | |
| new Airport("JFK"), time2.plusHours(8)); | |
| FlightAssignment assignment = new FlightAssignment("E2A" + i, flight); | |
| assignment.setEmployee(employee2); | |
| given.add(assignment); | |
| time2 = time2.plusHours(8).plusMinutes(20).plusHours(50).plusMinutes(45); | |
| } | |
| // Should only penalize employee1's violations (2 violations for employee1) | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::extendedRecoveryRestPeriod) | |
| .given(given.toArray()) | |
| .penalizesBy(2); | |
| } | |
| void transferBetweenTwoFlights_withExcessiveTaxiTime() { | |
| Employee employee = new Employee("1"); | |
| Airport lhr = new Airport("LHR"); | |
| // Set taxi time to same airport as 400 minutes (> 5 hours limit) | |
| lhr.setTaxiTimeInMinutes(java.util.Map.of("LHR", 400L)); | |
| LocalDateTime now = LocalDateTime.now(); | |
| Flight firstFlight = new Flight("1", lhr, now, lhr, now.plusHours(2)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight also at LHR but taxi time is excessive | |
| Flight secondFlight = new Flight("2", lhr, now.plusHours(3), lhr, now.plusHours(5)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::transferBetweenTwoFlights) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(1); // same airport but excessive taxi time | |
| } | |
| void transferBetweenTwoFlights_withAcceptableTaxiTime() { | |
| Employee employee = new Employee("1"); | |
| Airport lhr = new Airport("LHR"); | |
| // Set acceptable taxi time (< 5 hours) | |
| lhr.setTaxiTimeInMinutes(java.util.Map.of("LHR", 200L)); | |
| LocalDateTime now = LocalDateTime.now(); | |
| Flight firstFlight = new Flight("1", lhr, now, lhr, now.plusHours(2)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| Flight secondFlight = new Flight("2", lhr, now.plusHours(3), lhr, now.plusHours(5)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::transferBetweenTwoFlights) | |
| .given(firstAssignment, secondAssignment) | |
| .penalizesBy(0); // acceptable taxi time at same airport | |
| } | |
| void minimumRestAfterLongHaul_satisfied() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: Long haul (9 hours) | |
| Flight longHaulFlight = new Flight("1", homeAirport, now, | |
| new Airport("JFK"), now.plusHours(9)); | |
| FlightAssignment longHaulAssignment = new FlightAssignment("1", longHaulFlight); | |
| longHaulAssignment.setEmployee(employee); | |
| // Second flight: 50 hours rest after long haul (exceeds 48h requirement) | |
| // Duty end of long haul = arrival + 20 min = now + 9h + 20min | |
| // Second duty start = duty end + 50h | |
| LocalDateTime secondDeparture = now.plusHours(9).plusMinutes(20) | |
| .plusHours(50).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", homeAirport, secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(2)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterLongHaul) | |
| .given(longHaulAssignment, secondAssignment) | |
| .penalizesBy(0); // 50 hours rest satisfies 48 hour minimum | |
| } | |
| void minimumRestAfterLongHaul_violated() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: Long haul (10 hours) | |
| Flight longHaulFlight = new Flight("1", homeAirport, now, | |
| new Airport("JFK"), now.plusHours(10)); | |
| FlightAssignment longHaulAssignment = new FlightAssignment("1", longHaulFlight); | |
| longHaulAssignment.setEmployee(employee); | |
| // Second flight: Only 30 hours rest after long haul | |
| // Violates 48 hour minimum by 18 hours = 1080 minutes | |
| LocalDateTime secondDeparture = now.plusHours(10).plusMinutes(20) | |
| .plusHours(30).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", homeAirport, secondDeparture, | |
| new Airport("ATL"), secondDeparture.plusHours(3)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterLongHaul) | |
| .given(longHaulAssignment, secondAssignment) | |
| .penalizesBy(1080); // 18 hours = 1080 minutes violation | |
| } | |
| void minimumRestAfterLongHaul_notApplicableToShortHaul() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: Short haul (3 hours, below 8h threshold) | |
| Flight shortHaulFlight = new Flight("1", homeAirport, now, | |
| new Airport("BRU"), now.plusHours(3)); | |
| FlightAssignment shortHaulAssignment = new FlightAssignment("1", shortHaulFlight); | |
| shortHaulAssignment.setEmployee(employee); | |
| // Second flight: Only 15 hours rest | |
| // Constraint should not apply because first flight is not long haul | |
| LocalDateTime secondDeparture = now.plusHours(3).plusMinutes(20) | |
| .plusHours(15).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", homeAirport, secondDeparture, | |
| new Airport("ATL"), secondDeparture.plusHours(8)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterLongHaul) | |
| .given(shortHaulAssignment, secondAssignment) | |
| .penalizesBy(0); // constraint doesn't apply to short haul flights | |
| } | |
| void minimumRestAfterLongHaul_exactlyTenHours() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: Exactly 10 hours (boundary case - should be long haul) | |
| Flight boundaryFlight = new Flight("1", homeAirport, now, | |
| new Airport("JFK"), now.plusHours(10)); | |
| FlightAssignment boundaryAssignment = new FlightAssignment("1", boundaryFlight); | |
| boundaryAssignment.setEmployee(employee); | |
| // Second flight: Only 24 hours rest | |
| // Should violate because 10h exactly is considered long haul | |
| LocalDateTime secondDeparture = now.plusHours(10).plusMinutes(20) | |
| .plusHours(24).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", homeAirport, secondDeparture, | |
| new Airport("ATL"), secondDeparture.plusHours(3)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterLongHaul) | |
| .given(boundaryAssignment, secondAssignment) | |
| .penalizesBy(1440); // 24 hours = 1440 minutes violation | |
| } | |
| void minimumRestAfterLongHaul_awayFromHomeBase() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| Airport jfk = new Airport("JFK"); | |
| Airport atl = new Airport("ATL"); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: Long haul ending at JFK (away from home) - 10 hours | |
| Flight longHaulFlight = new Flight("1", homeAirport, now, | |
| jfk, now.plusHours(10)); | |
| FlightAssignment longHaulAssignment = new FlightAssignment("1", longHaulFlight); | |
| longHaulAssignment.setEmployee(employee); | |
| // Second flight: Departing from ATL with only 36 hours rest | |
| // Should still violate 48h requirement even away from home | |
| LocalDateTime secondDeparture = now.plusHours(10).plusMinutes(20) | |
| .plusHours(36).plusMinutes(45); | |
| Flight secondFlight = new Flight("2", atl, secondDeparture, | |
| new Airport("BRU"), secondDeparture.plusHours(7)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterLongHaul) | |
| .given(longHaulAssignment, secondAssignment) | |
| .penalizesBy(720); // 12 hours = 720 minutes violation | |
| } | |
| void minimumRestAfterLongHaul_multipleLongHauls() { | |
| Employee employee = new Employee("1"); | |
| Airport homeAirport = new Airport("LHR"); | |
| employee.setHomeAirport(homeAirport); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First long haul | |
| Flight firstLongHaul = new Flight("1", homeAirport, now, | |
| new Airport("JFK"), now.plusHours(9)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstLongHaul); | |
| firstAssignment.setEmployee(employee); | |
| // Second long haul after 50h rest (satisfies first constraint) | |
| LocalDateTime secondDeparture = now.plusHours(9).plusMinutes(20) | |
| .plusHours(50).plusMinutes(45); | |
| Flight secondLongHaul = new Flight("2", homeAirport, secondDeparture, | |
| new Airport("ATL"), secondDeparture.plusHours(10)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondLongHaul); | |
| secondAssignment.setEmployee(employee); | |
| // Third flight after only 20h rest (violates second constraint) | |
| LocalDateTime thirdDeparture = secondDeparture.plusHours(10).plusMinutes(20) | |
| .plusHours(20).plusMinutes(45); | |
| Flight thirdFlight = new Flight("3", homeAirport, thirdDeparture, | |
| new Airport("BRU"), thirdDeparture.plusHours(2)); | |
| FlightAssignment thirdAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterLongHaul) | |
| .given(firstAssignment, secondAssignment, thirdAssignment) | |
| .penalizesBy(1680); // 28 hours violation for second-to-third | |
| } | |
| void minimumRestAfterLongHaul_multipleEmployeesIsolated() { | |
| // Employee 1: Violates constraint | |
| Employee employee1 = new Employee("1"); | |
| employee1.setHomeAirport(new Airport("LHR")); | |
| // Employee 2: Satisfies constraint | |
| Employee employee2 = new Employee("2"); | |
| employee2.setHomeAirport(new Airport("LHR")); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // Employee 1: Long haul with insufficient rest - changed to 10 hours | |
| Flight e1Flight1 = new Flight("F1", new Airport("LHR"), now, | |
| new Airport("JFK"), now.plusHours(10)); | |
| FlightAssignment e1Assignment1 = new FlightAssignment("A1", e1Flight1); | |
| e1Assignment1.setEmployee(employee1); | |
| Flight e1Flight2 = new Flight("F2", new Airport("LHR"), | |
| now.plusHours(10).plusMinutes(20).plusHours(30).plusMinutes(45), | |
| new Airport("ATL"), now.plusHours(43)); | |
| FlightAssignment e1Assignment2 = new FlightAssignment("A2", e1Flight2); | |
| e1Assignment2.setEmployee(employee1); | |
| // Employee 2: Long haul with sufficient rest | |
| Flight e2Flight1 = new Flight("F3", new Airport("LHR"), now, | |
| new Airport("BNE"), now.plusHours(20)); | |
| FlightAssignment e2Assignment1 = new FlightAssignment("A3", e2Flight1); | |
| e2Assignment1.setEmployee(employee2); | |
| Flight e2Flight2 = new Flight("F4", new Airport("LHR"), | |
| now.plusHours(20).plusMinutes(20).plusHours(60).plusMinutes(45), | |
| new Airport("JFK"), now.plusHours(88)); | |
| FlightAssignment e2Assignment2 = new FlightAssignment("A4", e2Flight2); | |
| e2Assignment2.setEmployee(employee2); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterLongHaul) | |
| .given(employee1, employee2, e1Assignment1, e1Assignment2, e2Assignment1, e2Assignment2) | |
| .penalizesBy(1080); // Only employee1's violation (18 hours) | |
| } | |
| void minimumRestAfterConsecutiveLongHaul_satisfied() { | |
| Employee employee = new Employee("1"); | |
| Airport lhr = new Airport("LHR"); | |
| Airport jfk = new Airport("JFK"); | |
| Airport lax = new Airport("LAX"); | |
| employee.setHomeAirport(lhr); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: 5 hours (short haul alone) | |
| Flight firstFlight = new Flight("1", lhr, now, | |
| jfk, now.plusHours(5)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight: 6 hours, consecutive with first (total 11 hours = long haul) | |
| // Departs 1 hour after first arrives (within 2 hour window) | |
| LocalDateTime secondDeparture = now.plusHours(5).plusHours(1); | |
| Flight secondFlight = new Flight("2", jfk, secondDeparture, | |
| lax, secondDeparture.plusHours(6)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| // Third flight: 50 hours rest after the consecutive long haul | |
| // Should satisfy 48 hour requirement | |
| LocalDateTime thirdDeparture = secondDeparture.plusHours(6).plusMinutes(20) | |
| .plusHours(50).plusMinutes(45); | |
| Flight thirdFlight = new Flight("3", lhr, thirdDeparture, | |
| lax, thirdDeparture.plusHours(3)); | |
| FlightAssignment thirdAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterConsecutiveLongHaul) | |
| .given(firstAssignment, secondAssignment, thirdAssignment) | |
| .penalizesBy(0); // 50 hours rest satisfies 48 hour minimum | |
| } | |
| void minimumRestAfterConsecutiveLongHaul_violated() { | |
| Employee employee = new Employee("1"); | |
| Airport lhr = new Airport("LHR"); | |
| Airport jfk = new Airport("JFK"); | |
| Airport lax = new Airport("LAX"); | |
| employee.setHomeAirport(lhr); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: 5 hours | |
| Flight firstFlight = new Flight("1", lhr, now, | |
| jfk, now.plusHours(5)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight: 6 hours, consecutive (total 11 hours = long haul) | |
| LocalDateTime secondDeparture = now.plusHours(5).plusHours(1); | |
| Flight secondFlight = new Flight("2", jfk, secondDeparture, | |
| lax, secondDeparture.plusHours(6)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| // Third flight: Only 30 hours rest after consecutive long haul | |
| // Violates 48 hour minimum by 18 hours = 1080 minutes | |
| LocalDateTime thirdDeparture = secondDeparture.plusHours(6).plusMinutes(20) | |
| .plusHours(30).plusMinutes(45); | |
| Flight thirdFlight = new Flight("3", lhr, thirdDeparture, | |
| lax, thirdDeparture.plusHours(3)); | |
| FlightAssignment thirdAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterConsecutiveLongHaul) | |
| .given(firstAssignment, secondAssignment, thirdAssignment) | |
| .penalizesBy(1080); // 18 hours = 1080 minutes violation | |
| } | |
| void minimumRestAfterConsecutiveLongHaul_notApplicableToNonConsecutive() { | |
| Employee employee = new Employee("1"); | |
| Airport lhr = new Airport("LHR"); | |
| Airport jfk = new Airport("JFK"); | |
| Airport atl = new Airport("ATL"); | |
| employee.setHomeAirport(lhr); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: 5 hours | |
| Flight firstFlight = new Flight("1", lhr, now, | |
| jfk, now.plusHours(5)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight: 6 hours but NOT consecutive (different airport) | |
| LocalDateTime secondDeparture = now.plusHours(5).plusHours(1); | |
| Flight secondFlight = new Flight("2", atl, secondDeparture, // ATL, not JFK | |
| lhr, secondDeparture.plusHours(6)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| // Third flight: Only 20 hours rest | |
| // Should NOT violate because first + second don't form consecutive long haul | |
| LocalDateTime thirdDeparture = secondDeparture.plusHours(6).plusMinutes(20) | |
| .plusHours(20).plusMinutes(45); | |
| Flight thirdFlight = new Flight("3", lhr, thirdDeparture, | |
| jfk, thirdDeparture.plusHours(5)); | |
| FlightAssignment thirdAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterConsecutiveLongHaul) | |
| .given(firstAssignment, secondAssignment, thirdAssignment) | |
| .penalizesBy(0); // constraint doesn't apply to non-consecutive flights | |
| } | |
| void minimumRestAfterConsecutiveLongHaul_notApplicableToShortTotal() { | |
| Employee employee = new Employee("1"); | |
| Airport lhr = new Airport("LHR"); | |
| Airport jfk = new Airport("JFK"); | |
| Airport lax = new Airport("LAX"); | |
| employee.setHomeAirport(lhr); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: 3 hours | |
| Flight firstFlight = new Flight("1", lhr, now, | |
| jfk, now.plusHours(3)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight: 4 hours, consecutive (total only 7 hours = NOT long haul) | |
| LocalDateTime secondDeparture = now.plusHours(3).plusHours(1); | |
| Flight secondFlight = new Flight("2", jfk, secondDeparture, | |
| lax, secondDeparture.plusHours(4)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| // Third flight: Only 20 hours rest | |
| // Should NOT violate because combined duration < 10 hours | |
| LocalDateTime thirdDeparture = secondDeparture.plusHours(4).plusMinutes(20) | |
| .plusHours(20).plusMinutes(45); | |
| Flight thirdFlight = new Flight("3", lhr, thirdDeparture, | |
| lax, thirdDeparture.plusHours(3)); | |
| FlightAssignment thirdAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterConsecutiveLongHaul) | |
| .given(firstAssignment, secondAssignment, thirdAssignment) | |
| .penalizesBy(0); // constraint doesn't apply when total < 10 hours | |
| } | |
| void minimumRestAfterConsecutiveLongHaul_tooLongGapBetweenFlights() { | |
| Employee employee = new Employee("1"); | |
| Airport lhr = new Airport("LHR"); | |
| Airport jfk = new Airport("JFK"); | |
| Airport lax = new Airport("LAX"); | |
| employee.setHomeAirport(lhr); | |
| LocalDateTime now = LocalDateTime.now(); | |
| // First flight: 5 hours | |
| Flight firstFlight = new Flight("1", lhr, now, | |
| jfk, now.plusHours(5)); | |
| FlightAssignment firstAssignment = new FlightAssignment("1", firstFlight); | |
| firstAssignment.setEmployee(employee); | |
| // Second flight: 6 hours but 3 hours after first (> 2 hour limit) | |
| // Not considered consecutive despite total being 11 hours | |
| LocalDateTime secondDeparture = now.plusHours(5).plusHours(3); | |
| Flight secondFlight = new Flight("2", jfk, secondDeparture, | |
| lax, secondDeparture.plusHours(6)); | |
| FlightAssignment secondAssignment = new FlightAssignment("2", secondFlight); | |
| secondAssignment.setEmployee(employee); | |
| // Third flight: Only 20 hours rest | |
| // Should NOT violate because gap between flights is too long | |
| LocalDateTime thirdDeparture = secondDeparture.plusHours(6).plusMinutes(20) | |
| .plusHours(20).plusMinutes(45); | |
| Flight thirdFlight = new Flight("3", lhr, thirdDeparture, | |
| lax, thirdDeparture.plusHours(3)); | |
| FlightAssignment thirdAssignment = new FlightAssignment("3", thirdFlight); | |
| thirdAssignment.setEmployee(employee); | |
| constraintVerifier.verifyThat(FlightCrewSchedulingConstraintProvider::minimumRestAfterConsecutiveLongHaul) | |
| .given(firstAssignment, secondAssignment, thirdAssignment) | |
| .penalizesBy(0); // constraint doesn't apply when gap > 2 hours | |
| } | |
| } | |