Coverage for pybeepop/beepop/epadata.py: 79%

110 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-30 13:34 +0000

1"""BeePop+ EPA Pesticide Data Management Module. 

2 

3This module contains classes for managing pesticide active ingredient (AI) 

4data including toxicity parameters, consumption rates, and environmental fate 

5characteristics. This data drives pesticide exposure and mortality calculations 

6throughout the simulation.Different life stages have varying sensitivity 

7to pesticide exposure based on physiological differences and exposure pathways. 

8 

9Classes: 

10 AIItem: Individual active ingredient with toxicity and fate parameters 

11 EPAData: EPA pesticide database manager with consumption and exposure data 

12""" 

13 

14from datetime import datetime 

15from typing import List, Optional 

16 

17 

18class AIItem: 

19 """Not currently being used? Only supported one AI at a time, 

20 directly setting class variables in EPAData""" 

21 

22 def __init__(self, name: str = ""): 

23 self.name = name 

24 self.adult_slope = 0.0 

25 self.adult_ld50 = 0.0 

26 self.adult_slope_contact = 0.0 

27 self.adult_ld50_contact = 0.0 

28 self.larva_slope = 0.0 

29 self.larva_ld50 = 0.0 

30 self.kow = 0.0 

31 self.koc = 0.0 

32 self.half_life = 0.0 

33 self.contact_factor = 0.0 

34 

35 

36class EPAData: 

37 """EPA pesticide database manager with active ingredient and consumption data. 

38 

39 Manages pesticide data including active ingredient toxicity 

40 parameters, bee consumption rates by life stage, and foraging behavior data. 

41 Provides centralized access to pesticide exposure and effects parameters 

42 used throughout the simulation. 

43 

44 Integrates toxicity data with consumption patterns to calculate realistic 

45 pesticide exposure doses for different bee castes and life stages. Supports 

46 both oral and contact exposure routes (contact for foliar application mode only). 

47 

48 """ 

49 

50 def __init__(self): 

51 self.ai_items: List[AIItem] = [] 

52 self.current_ai: Optional[AIItem] = None 

53 

54 # Currently selected AI attributes (matching C++ m_AI_* naming) 

55 self.m_AI_Name = "" 

56 self.m_AI_AdultSlope = 0.0 

57 self.m_AI_AdultLD50 = 0.0 

58 self.m_AI_AdultSlope_Contact = 0.0 # foliar application only 

59 self.m_AI_AdultLD50_Contact = 0.0 # foliar application only 

60 self.m_AI_LarvaSlope = 0.0 

61 self.m_AI_LarvaLD50 = 0.0 

62 self.m_AI_KOW = 0.0 # soil application only 

63 self.m_AI_KOC = 0.0 # soil application only 

64 self.m_AI_HalfLife = 0.0 

65 self.m_AI_ContactFactor = 0.0 # foliar application only 

66 

67 # Consumption (match C++ m_C_* naming exactly) 

68 self.m_C_L4_Pollen = 0.0 

69 self.m_C_L4_Nectar = 0.0 

70 self.m_C_L5_Pollen = 0.0 

71 self.m_C_L5_Nectar = 0.0 

72 self.m_C_LD_Pollen = 0.0 

73 self.m_C_LD_Nectar = 0.0 

74 self.m_C_A13_Pollen = 0.0 

75 self.m_C_A13_Nectar = 0.0 

76 self.m_C_A410_Pollen = 0.0 

77 self.m_C_A410_Nectar = 0.0 

78 self.m_C_A1120_Pollen = 0.0 

79 self.m_C_A1120_Nectar = 0.0 

80 self.m_C_AD_Pollen = 0.0 

81 self.m_C_AD_Nectar = 0.0 

82 self.m_C_Forager_Pollen = 0.0 

83 self.m_C_Forager_Nectar = 0.0 

84 

85 # Incoming (match C++ m_I_* naming exactly) 

86 self.m_I_PollenTrips = 0 

87 self.m_I_NectarTrips = 0 

88 self.m_I_PercentNectarForagers = 0.0 

89 self.m_I_PollenLoad = 0.0 

90 self.m_I_NectarLoad = 0.0 

91 

92 # Current dosages (matching C++ m_D_* naming) 

93 self.m_D_L4 = 0.0 # Current dosage for larvae 4 days old 

94 self.m_D_L5 = 0.0 # Current dosage for larvae 5 days old 

95 self.m_D_LD = 0.0 # Current dosage for larvae drone 

96 self.m_D_A13 = 0.0 # Current dosage for adult workers 1-3 days old 

97 self.m_D_A410 = 0.0 # Current dosage for adult workers 4-10 days old 

98 self.m_D_A1120 = 0.0 # Current dosage for adult workers 11-20 days old 

99 self.m_D_AD = 0.0 # Current dosage for adult drones 

100 self.m_D_C_Foragers = 0.0 # Current contact dosage for foragers 

101 self.m_D_D_Foragers = 0.0 # Current diet dosage for foragers 

102 

103 # Maximum doses seen so far for each life stage 

104 self.m_D_L4_Max = 0.0 

105 self.m_D_L5_Max = 0.0 

106 self.m_D_LD_Max = 0.0 

107 self.m_D_A13_Max = 0.0 

108 self.m_D_A410_Max = 0.0 

109 self.m_D_A1120_Max = 0.0 

110 self.m_D_AD_Max = 0.0 

111 self.m_D_C_Foragers_Max = 0.0 

112 self.m_D_D_Foragers_Max = 0.0 

113 

114 # Exposure (match C++ naming exactly) 

115 self.m_FoliarEnabled = False 

116 self.m_SoilEnabled = False 

117 self.m_SeedEnabled = False 

118 self.m_E_AppRate = 0.0 

119 self.m_E_SoilTheta = 0.0 

120 self.m_E_SoilP = 0.0 

121 self.m_E_SoilFoc = 0.0 

122 self.m_E_SoilConcentration = 0.0 

123 self.m_E_SeedConcentration = 0.0 

124 self.m_FoliarAppDate = datetime.now() 

125 self.m_FoliarForageBegin = datetime.now() 

126 self.m_FoliarForageEnd = datetime.now() 

127 self.m_SoilForageBegin = datetime.now() 

128 self.m_SoilForageEnd = datetime.now() 

129 self.m_SeedForageBegin = datetime.now() 

130 self.m_SeedForageEnd = datetime.now() 

131 

132 # Nectar/Pollen Bypass File Processing (match C++ naming exactly) 

133 self.m_NecPolFileEnabled = False 

134 self.m_NecPolFileName = "" 

135 

136 def add_ai_item(self, item: AIItem): 

137 self.ai_items.append(item) 

138 

139 def remove_ai_item(self, name: str) -> bool: 

140 for i, item in enumerate(self.ai_items): 

141 if item.name == name: 

142 del self.ai_items[i] 

143 return True 

144 return False 

145 

146 def get_ai_item(self, name: str) -> Optional[AIItem]: 

147 for item in self.ai_items: 

148 if item.name == name: 

149 return item 

150 return None 

151 

152 def set_current_ai_item(self, item: AIItem): 

153 self.current_ai = item 

154 

155 def get_ai_item_count(self) -> int: 

156 return len(self.ai_items) 

157 

158 def dose_response(self, dose: float, ld50: float, slope: float) -> float: 

159 """ 

160 Replicates the C++ DoseResponse logic: 

161 - Converts dose from grams to micrograms 

162 - Assumes LD50 is already in micrograms/bee (as per C++ comment) 

163 - Validates input 

164 - Returns proportion killed 

165 """ 

166 dose *= 1_000_000.0 # Convert grams to micrograms 

167 # LD50 is assumed to already be in micrograms/bee (per C++ comment) 

168 valid = (dose > ld50 * 0.05) and (ld50 > 0) and (slope >= 0) and (slope < 20) 

169 if not valid: 

170 return 0.0 

171 return 1.0 / (1.0 + (dose / ld50) ** (-slope))