Coverage for app/delivery_fee/fee_transformers.py: 95%
56 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-11-27 09:26 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-11-27 09:26 +0000
1from abc import ABC, abstractmethod
2from app.delivery_fee.models import OrderInfo, DeliveryFee
3from copy import deepcopy
4from pandas import Timestamp
5from datetime import time
6from pydantic import BaseModel
7from typing import Self
10class DeliveryFeeTransformer(ABC):
11 """Abstract class for all the fee transformers.
12 This class is used to transform the delivery fee after
13 the delivery fee has been calculated. So any subclass of this class
14 is essentially one of the rules to transform the delivery fee."""
16 class ConfigOptions(BaseModel):
17 """Configuration options for DeliveryFeeTransformer."""
19 @abstractmethod
20 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee:
21 """Transform the delivery fee."""
24class RushHourFeeTransformer(DeliveryFeeTransformer):
25 """Transforms the delivery fee base don the following:
26 During the Friday rush, 3 - 7 PM, the delivery fee (the
27 total fee including possible surcharges) will be multiplied
28 by 1.2x. Friday rush is 3 - 7 PM UTC."""
30 class ConfigOptions(BaseModel):
31 """Configuration options for RushHourFeeTransformer.
33 Default for Friday rush hour from 3:00:00 PM to 7:59:59.999999 PM UTC.
34 >>> from datetime import time
35 >>> rush_day: str = "Friday"
36 >>> rush_hour_start: time = time(hour=12+3, minute=0) # 3:00:00 PM (inclusive)
37 >>> # 7:59:59.999999 PM (inclusive)
38 >>> rush_hour_end: time = time(hour=12+7, minute=59, second=59, microsecond=999999)
39 >>> rush_hour_fee_factor: float = 1.2 # 20% increase
40 """
41 rush_day: str = "Friday"
42 rush_hour_start: time = time(hour=12+3, minute=0) # 3:00:00 PM
43 # 7:59:59.999999 PM
44 rush_hour_end: time = time(hour=12+7, minute=59, second=59, microsecond=999999)
45 rush_hour_fee_factor: float = 1.2 # 20% increase
47 def __init__(self, config_options: ConfigOptions | None = None) -> None:
48 super().__init__()
49 self.config_options = config_options
50 if config_options is None:
51 self.config_options = self.ConfigOptions()
53 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee:
54 timestamp = Timestamp(delivery_info.time)
55 transformed_delivery_fee = deepcopy(delivery_fee)
57 if (timestamp.day_name() == self.config_options.rush_day and
58 (self.config_options.rush_hour_start <= timestamp.time() <= self.config_options.rush_hour_end)):
59 transformed_delivery_fee *= self.config_options.rush_hour_fee_factor
61 return transformed_delivery_fee
64class LimitFeeTransformer(DeliveryFeeTransformer):
65 """Transforms the delivery fee base don the following:
66 The delivery fee can never be more than 15€, including possible surcharges."""
68 class ConfigOptions(BaseModel):
69 """Configuration options for LimitFeeTransformer.
71 Default options are:
72 >>> highest_limit_of_delivery_fee = 15e2 # 15€ (inclusive)
73 """
74 highest_limit_of_delivery_fee: int = 15e2
76 def __init__(self, config_options: ConfigOptions | None = None) -> None:
77 super().__init__()
78 self.config_options = config_options
79 if config_options is None:
80 self.config_options = self.ConfigOptions()
82 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee:
83 transformed_delivery_fee = deepcopy(delivery_fee)
85 if transformed_delivery_fee >= self.config_options.highest_limit_of_delivery_fee:
86 transformed_delivery_fee = DeliveryFee(
87 delivery_fee=self.config_options.highest_limit_of_delivery_fee)
89 return transformed_delivery_fee
92class ReduceFeeTransformer(DeliveryFeeTransformer):
93 """Transforms the delivery fee base don the following:
94 The delivery is free (0€) when the cart value is equal or more than 200€."""
96 class ConfigOptions(BaseModel):
97 """Configuration options for ExcludeFeeTransformer.
99 Default options are:
100 >>> exclusion_cart_value_threshold = 200e2 # 200€ (inclusive)
101 >>> exclusion_delivery_fee_factor = 1 # 100% decrease
102 """
103 exclusion_cart_value_threshold: int = 200e2
104 exclusion_delivery_fee_factor: float = 1
106 def __init__(self, config_options: ConfigOptions | None = None) -> None:
107 super().__init__()
108 self.config_options = config_options
109 if config_options is None:
110 self.config_options = self.ConfigOptions()
112 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee:
113 transformed_delivery_fee = deepcopy(delivery_fee)
115 if delivery_info.cart_value >= self.config_options.exclusion_cart_value_threshold:
116 transformed_delivery_fee -= (transformed_delivery_fee *
117 self.config_options.exclusion_delivery_fee_factor)
119 return transformed_delivery_fee
122"""
123In my opinion there should be another transformer that should check if the
124number of items are 0 and if so, set the delivery fee to 0.
125This would make sense since we are not delivering anything. so we should not
126charge anything.
128Or perhaps, one transformer that checks if cart_value, delivery_distance and
129number_of_items are 0 and if so, set the delivery fee to 0. Or throw error.
130"""