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
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 13:34 +0000
1"""BeePop+ EPA Pesticide Data Management Module.
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.
9Classes:
10 AIItem: Individual active ingredient with toxicity and fate parameters
11 EPAData: EPA pesticide database manager with consumption and exposure data
12"""
14from datetime import datetime
15from typing import List, Optional
18class AIItem:
19 """Not currently being used? Only supported one AI at a time,
20 directly setting class variables in EPAData"""
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
36class EPAData:
37 """EPA pesticide database manager with active ingredient and consumption data.
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.
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).
48 """
50 def __init__(self):
51 self.ai_items: List[AIItem] = []
52 self.current_ai: Optional[AIItem] = None
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
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
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
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
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
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()
132 # Nectar/Pollen Bypass File Processing (match C++ naming exactly)
133 self.m_NecPolFileEnabled = False
134 self.m_NecPolFileName = ""
136 def add_ai_item(self, item: AIItem):
137 self.ai_items.append(item)
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
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
152 def set_current_ai_item(self, item: AIItem):
153 self.current_ai = item
155 def get_ai_item_count(self) -> int:
156 return len(self.ai_items)
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))