Exemplo n.º 1
0
    def _picking_up_state(self, orders: Dict[int, Order]):
        """State that simulates a courier picking up stuff at the pick up location"""

        self.condition = 'picking_up'

        self._log(f'Courier {self.courier_id} begins pick up state')

        state_start = sec_to_time(self.env.now)

        try:
            self.dispatcher.courier_picking_up_event(courier=self)
            self.dispatcher.orders_in_store_event(orders)

        except Interrupt:
            pass

        try:
            service_time = max(order.pick_up_service_time
                               for order in orders.values())
            latest_ready_time = max(order.ready_time
                                    for order in orders.values())
            waiting_time = time_diff(latest_ready_time,
                                     sec_to_time(self.env.now))
            yield self.env.timeout(delay=service_time + max(0, waiting_time))

        except Interrupt:
            pass

        self.utilization_time += time_diff(sec_to_time(self.env.now),
                                           state_start)

        self._log(f'Courier {self.courier_id} finishes pick up state')

        self.dispatcher.orders_picked_up_event(orders)
Exemplo n.º 2
0
    def calculate_metrics(self) -> Dict[str, Any]:
        """Method to calculate the metrics of a courier"""

        courier_delivery_earnings = len(
            self.fulfilled_orders) * settings.COURIER_EARNINGS_PER_ORDER
        shift_duration = time_diff(self.off_time, self.on_time)

        if shift_duration > 0:
            courier_utilization = self.utilization_time / shift_duration
            courier_orders_delivered_per_hour = len(
                self.fulfilled_orders) / sec_to_hour(shift_duration)
            courier_bundles_picked_per_hour = len(
                self.accepted_notifications) / sec_to_hour(shift_duration)

        else:
            courier_utilization, courier_orders_delivered_per_hour, courier_bundles_picked_per_hour = 0, 0, 0

        return {
            'courier_id': self.courier_id,
            'on_time': self.on_time,
            'off_time': self.off_time,
            'fulfilled_orders': len(self.fulfilled_orders),
            'earnings': self.earnings,
            'utilization_time': self.utilization_time,
            'accepted_notifications': len(self.accepted_notifications),
            'guaranteed_compensation': self.guaranteed_compensation,
            'courier_utilization': courier_utilization,
            'courier_delivery_earnings': courier_delivery_earnings,
            'courier_compensation': self.earnings,
            'courier_orders_delivered_per_hour':
            courier_orders_delivered_per_hour,
            'courier_bundles_picked_per_hour': courier_bundles_picked_per_hour
        }
Exemplo n.º 3
0
    def _is_prospect(route: Route, courier: Courier, env_time: int) -> bool:
        """Method to establish if a courier and route are matching prospects"""

        _, time_to_first_stop = OSRMService.estimate_travelling_properties(
            origin=courier.location,
            destination=route.stops[0].location,
            vehicle=courier.vehicle)
        stops_time_offset = sum(
            abs(
                time_diff(time_1=sec_to_time(
                    int(env_time + time_to_first_stop +
                        stop.arrive_at[courier.vehicle])),
                          time_2=stop.calculate_latest_expected_time()))
            for stop in route.stops)
        distance_condition = (haversine(courier.location.coordinates,
                                        route.stops[0].location.coordinates) <=
                              settings.DISPATCHER_PROSPECTS_MAX_DISTANCE)
        stop_offset_condition = (
            stops_time_offset <=
            settings.DISPATCHER_PROSPECTS_MAX_STOP_OFFSET * route.num_stops
            if route.time_since_ready(env_time) <=
            settings.DISPATCHER_PROSPECTS_MAX_READY_TIME else True)
        courier_state_condition = (
            courier.condition == 'idle'
            or (courier.condition == 'picking_up'
                and route.initial_prospect == courier.courier_id))

        return distance_condition and stop_offset_condition and courier_state_condition
Exemplo n.º 4
0
    def test_calculate_earnings(self):
        """Test to verify the mechanics of calculating the shift's earnings"""

        # Constants
        random.seed(523)
        on_time = time(0, 0, 0)
        off_time = time(2, 0, 0)

        # Services
        env = Environment()

        # Creates a two hour - shift courier
        courier = Courier(env=env, on_time=on_time, off_time=off_time)

        # Verifies for two scenarios how the earnings are calculated.
        # In the first test, raw earnings from orders are chosen.
        # In the second test, the hourly earnings rate is chosen.

        # Test 1. Creates courier earnings to select the raw earnings from orders.
        # Asserts that these earnings are selected over the hourly earnings rate
        courier.fulfilled_orders = [Order()] * 7
        courier.earnings = courier._calculate_earnings()
        self.assertEqual(
            courier.earnings,
            len(courier.fulfilled_orders) *
            settings.COURIER_EARNINGS_PER_ORDER)

        # Test 2. Creates courier earnings to select the hourly earnings rate.
        # Asserts that these earnings are selected over the order earnings
        courier.fulfilled_orders = [Order()] * 2
        courier.earnings = courier._calculate_earnings()
        self.assertEqual(
            courier.earnings,
            sec_to_hour(time_diff(courier.off_time, courier.on_time)) *
            settings.COURIER_EARNINGS_PER_HOUR)
Exemplo n.º 5
0
    def _schedule_log_off_event(self):
        """Method that allows the courier to schedule the log off time"""

        log_off_event = Event(env=self.env)
        log_off_event.callbacks.append(self._log_off_callback)
        log_off_delay = time_diff(self.off_time, self.on_time)
        self.env.schedule(event=log_off_event,
                          priority=NORMAL,
                          delay=log_off_delay)
Exemplo n.º 6
0
    def _schedule_buffer_order_event(self, order: Order):
        """Method that allows the dispatcher to schedule the order buffering event"""

        buffering_event = Event(env=self.env)
        buffering_event.callbacks.append(self._buffer_order_callback)
        self.env.schedule(event=buffering_event,
                          priority=NORMAL,
                          delay=time_diff(order.preparation_time,
                                          order.placement_time))
Exemplo n.º 7
0
    def _moving_state(self, destination: Location):
        """State detailing how a courier moves to a destination"""

        self.condition = 'moving'
        state_start = sec_to_time(self.env.now)
        self.dispatcher.courier_moving_event(courier=self)
        yield self.env.process(
            self.movement_policy.execute(origin=self.location,
                                         destination=destination,
                                         env=self.env,
                                         courier=self))
        self.utilization_time += time_diff(sec_to_time(self.env.now),
                                           state_start)
Exemplo n.º 8
0
    def calculate_metrics(self) -> Dict[str, Any]:
        """Method to calculate the metrics of an order"""

        dropped_off = bool(self.drop_off_time)

        if dropped_off:
            click_to_door_time = time_diff(self.drop_off_time,
                                           self.placement_time)
            click_to_taken_time = time_diff(self.acceptance_time,
                                            self.placement_time)
            ready_to_door_time = time_diff(self.drop_off_time, self.ready_time)
            ready_to_pickup_time = time_diff(self.pick_up_time,
                                             self.ready_time)
            in_store_to_pickup_time = time_diff(self.pick_up_time,
                                                self.in_store_time)
            drop_off_lateness_time = time_diff(self.drop_off_time,
                                               self.expected_drop_off_time)
            click_to_cancel_time = None

        else:
            click_to_door_time = None
            click_to_taken_time = None
            ready_to_door_time = None
            ready_to_pickup_time = None
            in_store_to_pickup_time = None
            drop_off_lateness_time = None
            click_to_cancel_time = time_diff(self.cancellation_time,
                                             self.preparation_time)

        return {
            'order_id': self.order_id,
            'placement_time': self.placement_time,
            'preparation_time': self.preparation_time,
            'acceptance_time': self.acceptance_time,
            'in_store_time': self.in_store_time,
            'ready_time': self.ready_time,
            'pick_up_time': self.pick_up_time,
            'drop_off_time': self.drop_off_time,
            'expected_drop_off_time': self.expected_drop_off_time,
            'cancellation_time': self.cancellation_time,
            'dropped_off': dropped_off,
            'click_to_door_time': click_to_door_time,
            'click_to_taken_time': click_to_taken_time,
            'ready_to_door_time': ready_to_door_time,
            'ready_to_pick_up_time': ready_to_pickup_time,
            'in_store_to_pick_up_time': in_store_to_pickup_time,
            'drop_off_lateness_time': drop_off_lateness_time,
            'click_to_cancel_time': click_to_cancel_time
        }
Exemplo n.º 9
0
    def _dropping_off_state(self, orders: Dict[int, Order]):
        """State that simulates a courier dropping off stuff at the drop off location"""

        self.condition = 'dropping_off'

        self._log(
            f'Courier {self.courier_id} begins drop off state of orders {list(orders.keys())}'
        )

        state_start = sec_to_time(self.env.now)
        self.dispatcher.courier_dropping_off_event(courier=self)
        service_time = max(order.drop_off_service_time
                           for order in orders.values())
        yield self.env.timeout(delay=service_time)
        self.utilization_time += time_diff(sec_to_time(self.env.now),
                                           state_start)

        self._log(
            f'Courier {self.courier_id} finishes drop off state of orders {list(orders.keys())}'
        )

        self.dispatcher.orders_dropped_off_event(orders=orders, courier=self)
Exemplo n.º 10
0
    def _calculate_earnings(self) -> float:
        """Method to calculate earnings after the shift ends"""

        delivery_earnings = len(
            self.fulfilled_orders) * settings.COURIER_EARNINGS_PER_ORDER
        guaranteed_earnings = sec_to_hour(
            time_diff(self.off_time,
                      self.on_time)) * settings.COURIER_EARNINGS_PER_HOUR

        if guaranteed_earnings > delivery_earnings > 0:
            self.guaranteed_compensation = True
            earnings = guaranteed_earnings

        else:
            self.guaranteed_compensation = False
            earnings = delivery_earnings

        self._log(
            f'Courier {self.courier_id} received earnings of ${round(earnings, 2)} '
            f'for {len(self.fulfilled_orders)} orders during the complete shift'
        )

        return earnings
Exemplo n.º 11
0
    def _generate_matching_costs(routes: List[Route], couriers: List[Courier],
                                 prospects: np.ndarray,
                                 env_time: int) -> np.ndarray:
        """Method to estimate the cost of a possible match, based on the prospects"""

        costs = np.zeros(len(prospects))

        for ix, (courier_ix, route_ix) in enumerate(prospects):
            route, courier = routes[route_ix], couriers[courier_ix]
            distance_to_first_stop, time_to_first_stop = OSRMService.estimate_travelling_properties(
                origin=courier.location,
                destination=route.stops[0].location,
                vehicle=courier.vehicle)
            costs[ix] = (
                len(route.orders) /
                (time_to_first_stop + route.time[courier.vehicle]) - time_diff(
                    time_1=sec_to_time(
                        int(env_time + time_to_first_stop +
                            route.stops[0].arrive_at[courier.vehicle])),
                    time_2=max(order.ready_time
                               for order in route.stops[0].orders.values())) *
                settings.DISPATCHER_DELAY_PENALTY)

        return costs