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

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 

8 

9 

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.""" 

15 

16 class ConfigOptions(BaseModel): 

17 """Configuration options for DeliveryFeeTransformer.""" 

18 

19 @abstractmethod 

20 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee: 

21 """Transform the delivery fee.""" 

22 

23 

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.""" 

29 

30 class ConfigOptions(BaseModel): 

31 """Configuration options for RushHourFeeTransformer. 

32 

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 

46 

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() 

52 

53 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee: 

54 timestamp = Timestamp(delivery_info.time) 

55 transformed_delivery_fee = deepcopy(delivery_fee) 

56 

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 

60 

61 return transformed_delivery_fee 

62 

63 

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.""" 

67 

68 class ConfigOptions(BaseModel): 

69 """Configuration options for LimitFeeTransformer. 

70 

71 Default options are: 

72 >>> highest_limit_of_delivery_fee = 15e2 # 15€ (inclusive) 

73 """ 

74 highest_limit_of_delivery_fee: int = 15e2 

75 

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() 

81 

82 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee: 

83 transformed_delivery_fee = deepcopy(delivery_fee) 

84 

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) 

88 

89 return transformed_delivery_fee 

90 

91 

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€.""" 

95 

96 class ConfigOptions(BaseModel): 

97 """Configuration options for ExcludeFeeTransformer. 

98 

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 

105 

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() 

111 

112 def transform(self, delivery_info: OrderInfo, delivery_fee: DeliveryFee) -> DeliveryFee: 

113 transformed_delivery_fee = deepcopy(delivery_fee) 

114 

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) 

118 

119 return transformed_delivery_fee 

120 

121 

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. 

127 

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"""