blackopsrepl/vehicle-routing-python
2
1"""2Tests for timeline visualization fields in API serialization.3 4These tests verify that all fields required by the frontend timeline5visualizations (By vehicle, By visit tabs) are correctly serialized.6"""7from datetime import datetime, timedelta8from vehicle_routing.domain import (9 Location,10 Visit,11 Vehicle,12 VehicleRoutePlan,13)14from vehicle_routing.converters import (15 visit_to_model,16 vehicle_to_model,17 plan_to_model,18)19 20 21def create_test_location(lat: float = 43.77, lng: float = 11.25) -> Location:22 """Create a test location."""23 return Location(latitude=lat, longitude=lng)24 25 26def create_test_vehicle(27 departure_time: datetime = None,28 visits: list = None,29) -> Vehicle:30 """Create a test vehicle with optional visits."""31 if departure_time is None:32 departure_time = datetime(2024, 1, 1, 6, 0, 0)33 return Vehicle(34 id="1",35 name="Alpha",36 capacity=25,37 home_location=create_test_location(),38 departure_time=departure_time,39 visits=visits or [],40 )41 42 43def create_test_visit(44 vehicle: Vehicle = None,45 previous_visit: "Visit" = None,46 arrival_time: datetime = None,47) -> Visit:48 """Create a test visit."""49 visit = Visit(50 id="101",51 name="Test Customer",52 location=create_test_location(43.78, 11.26),53 demand=5,54 min_start_time=datetime(2024, 1, 1, 9, 0, 0),55 max_end_time=datetime(2024, 1, 1, 17, 0, 0),56 service_duration=timedelta(minutes=15),57 vehicle=vehicle,58 previous_visit=previous_visit,59 arrival_time=arrival_time,60 )61 return visit62 63 64def create_test_plan(vehicles: list = None, visits: list = None) -> VehicleRoutePlan:65 """Create a test route plan."""66 if vehicles is None:67 vehicles = [create_test_vehicle()]68 if visits is None:69 visits = []70 return VehicleRoutePlan(71 name="Test Plan",72 south_west_corner=create_test_location(43.75, 11.20),73 north_east_corner=create_test_location(43.80, 11.30),74 vehicles=vehicles,75 visits=visits,76 )77 78 79class TestVisitTimelineFields:80 """Tests for visit timeline serialization fields."""81 82 def test_unassigned_visit_has_null_timeline_fields(self):83 """Unassigned visits should have null timeline fields."""84 visit = create_test_visit(vehicle=None, arrival_time=None)85 model = visit_to_model(visit)86 87 assert model.arrival_time is None88 assert model.start_service_time is None89 assert model.departure_time is None90 assert model.driving_time_seconds_from_previous_standstill is None91 92 def test_assigned_visit_has_timeline_fields(self):93 """Assigned visits with arrival_time should have all timeline fields."""94 vehicle = create_test_vehicle()95 arrival = datetime(2024, 1, 1, 9, 30, 0)96 visit = create_test_visit(vehicle=vehicle, arrival_time=arrival)97 vehicle.visits = [visit]98 99 model = visit_to_model(visit)100 101 # arrival_time should be serialized102 assert model.arrival_time is not None103 assert model.arrival_time == "2024-01-01T09:30:00"104 105 # start_service_time = max(arrival_time, min_start_time)106 # Since arrival (09:30) > min_start (09:00), start_service = 09:30107 assert model.start_service_time is not None108 assert model.start_service_time == "2024-01-01T09:30:00"109 110 # departure_time = start_service_time + service_duration111 # = 09:30 + 15min = 09:45112 assert model.departure_time is not None113 assert model.departure_time == "2024-01-01T09:45:00"114 115 # driving_time_seconds should be calculated from vehicle home116 assert model.driving_time_seconds_from_previous_standstill is not None117 118 def test_early_arrival_uses_min_start_time(self):119 """When arrival is before min_start_time, start_service uses min_start_time."""120 vehicle = create_test_vehicle()121 # Arrive at 08:30, but min_start is 09:00122 early_arrival = datetime(2024, 1, 1, 8, 30, 0)123 visit = create_test_visit(vehicle=vehicle, arrival_time=early_arrival)124 vehicle.visits = [visit]125 126 model = visit_to_model(visit)127 128 # start_service_time should be min_start_time (09:00), not arrival (08:30)129 assert model.start_service_time == "2024-01-01T09:00:00"130 131 # departure should be min_start_time + service_duration = 09:15132 assert model.departure_time == "2024-01-01T09:15:00"133 134 135class TestVehicleTimelineFields:136 """Tests for vehicle timeline serialization fields."""137 138 def test_empty_vehicle_arrival_equals_departure(self):139 """Vehicle with no visits should have arrival_time = departure_time."""140 departure = datetime(2024, 1, 1, 6, 0, 0)141 vehicle = create_test_vehicle(departure_time=departure, visits=[])142 143 model = vehicle_to_model(vehicle)144 145 assert model.departure_time == "2024-01-01T06:00:00"146 assert model.arrival_time == "2024-01-01T06:00:00"147 148 def test_vehicle_with_visits_has_later_arrival(self):149 """Vehicle with visits should have arrival_time after last visit departure."""150 departure = datetime(2024, 1, 1, 6, 0, 0)151 vehicle = create_test_vehicle(departure_time=departure)152 153 # Create a visit assigned to this vehicle154 arrival = datetime(2024, 1, 1, 9, 30, 0)155 visit = create_test_visit(vehicle=vehicle, arrival_time=arrival)156 vehicle.visits = [visit]157 158 model = vehicle_to_model(vehicle)159 160 assert model.departure_time == "2024-01-01T06:00:00"161 # arrival_time should be > departure_time162 assert model.arrival_time is not None163 # arrival_time should be after visit departure + travel back to depot164 165 166class TestPlanTimelineFields:167 """Tests for route plan timeline window fields."""168 169 def test_plan_has_start_and_end_datetime(self):170 """Route plan should have startDateTime and endDateTime for timeline window."""171 departure = datetime(2024, 1, 1, 6, 0, 0)172 vehicle = create_test_vehicle(departure_time=departure)173 plan = create_test_plan(vehicles=[vehicle])174 175 model = plan_to_model(plan)176 177 # startDateTime should be earliest vehicle departure178 assert model.start_date_time is not None179 assert model.start_date_time == "2024-01-01T06:00:00"180 181 # endDateTime should be latest vehicle arrival182 # For empty vehicle, arrival = departure183 assert model.end_date_time is not None184 assert model.end_date_time == "2024-01-01T06:00:00"185 186 def test_plan_with_multiple_vehicles(self):187 """Plan timeline window should span all vehicles."""188 early_vehicle = create_test_vehicle(189 departure_time=datetime(2024, 1, 1, 5, 0, 0)190 )191 early_vehicle.id = "1"192 late_vehicle = create_test_vehicle(193 departure_time=datetime(2024, 1, 1, 8, 0, 0)194 )195 late_vehicle.id = "2"196 197 plan = create_test_plan(vehicles=[early_vehicle, late_vehicle])198 model = plan_to_model(plan)199 200 # startDateTime should be earliest departure (05:00)201 assert model.start_date_time == "2024-01-01T05:00:00"202 203 # endDateTime should be latest arrival204 # Both vehicles empty, so arrival = departure for each205 # Latest is late_vehicle at 08:00206 assert model.end_date_time == "2024-01-01T08:00:00"207 208 def test_empty_plan_has_null_datetimes(self):209 """Plan with no vehicles should have null datetime fields."""210 plan = create_test_plan(vehicles=[])211 212 model = plan_to_model(plan)213 214 assert model.start_date_time is None215 assert model.end_date_time is None216 