Coverage for pybeepop/beepop/session.py: 47%

1276 statements  

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

1"""BeePop+ Session Management Module. 

2 

3This module contains the VarroaPopSession class, which serves as the central 

4coordinator for BeePop+ simulations. It manages the simulation state, coordinates 

5major objects (colony, weather, treatments), handles results output, and provides 

6error/information reporting capabilities. 

7 

8The VarroaPopSession class is a Python port of the C++ CVarroaPopSession class 

9and maintains compatibility with the original API while adding Python-specific 

10enhancements. 

11 

12Architecture: 

13 VarroaPopSession acts as the main controller that coordinates: 

14 

15 VarroaPopSession (Central Controller) 

16 ├── Colony (Bee population dynamics) 

17 ├── WeatherEvents (Environmental conditions) 

18 ├── Results Management 

19 │ ├── Output formatting 

20 │ ├── Header generation 

21 │ └── Data collection 

22 ├── Treatment Protocols 

23 │ ├── Mite treatments 

24 │ ├── Spore treatments 

25 │ └── Comb removal 

26 ├── Immigration Events 

27 │ ├── Mite immigration 

28 │ └── Schedule management 

29 ├── Requeening Events 

30 │ ├── Queen replacement 

31 │ └── Colony recovery 

32 └── Error/Information Reporting 

33 ├── Error tracking 

34 ├── Warning management 

35 └── Status reporting 

36 

37Key Features: 

38 - Centralized simulation state management 

39 - Results formatting and output coordination 

40 - Weather event integration 

41 - Treatment protocol scheduling 

42 - Error and warning tracking 

43 - Immigration and requeening event management 

44 

45""" 

46 

47import math 

48from datetime import timedelta 

49import datetime 

50from pybeepop.beepop.colony import Colony 

51from pybeepop.beepop.weatherevents import WeatherEvents 

52from pybeepop.beepop.mite import Mite 

53from pybeepop.beepop.mitetreatments import MiteTreatmentItem 

54 

55 

56class VarroaPopSession: 

57 """Central session manager for BeePop+ simulations. 

58 

59 This class serves as the main coordinator for BeePop+ simulations, managing 

60 the simulation state, coordinating major simulation objects, handling results 

61 output, and providing comprehensive error and information reporting. 

62 

63 The VarroaPopSession maintains the simulation timeline, manages colony and 

64 weather interactions, coordinates treatment protocols, and formats output 

65 data for analysis. It serves as a Python port of the C++ CVarroaPopSession. 

66 

67 """ 

68 

69 def set_default_headers(self): 

70 """ 

71 Sets the default header strings for output/plotting, matching the C++ results header logic. 

72 """ 

73 self.results_header = [ 

74 "Date", 

75 "ColSze", 

76 "AdDrns", 

77 "AdWkrs", 

78 "Forgr", 

79 "DrnBrd", 

80 "WkrBrd", 

81 "DrnLrv", 

82 "WkrLrv", 

83 "DrnEggs", 

84 "WkrEggs", 

85 "TotalEggs", 

86 "DD", 

87 "L", 

88 "N", 

89 "P", 

90 "dd", 

91 "l", 

92 "n", 

93 "FreeMts", 

94 "DBrdMts", 

95 "WBrdMts", 

96 "Mts/DBrd", 

97 "Mts/WBrd", 

98 "Mts Dying", 

99 "PropMts Dying", 

100 "ColPollen(g)", 

101 "PPestConc(ug/g)", 

102 "ColNectar(g)", 

103 "NPestConc(ug/g)", 

104 "Dead DLarv", 

105 "Dead WLarv", 

106 "Dead DAdults", 

107 "Dead WAdults", 

108 "Dead Foragers", 

109 "Queen Strength", 

110 "Temp (DegC)", 

111 "Precip", 

112 "Min Temp (C)", 

113 "Max Temp (C)", 

114 "Daylight hours", 

115 "Forage Inc", 

116 "Forage Day", 

117 ] 

118 self.results_file_header = list(self.results_header) 

119 # Add additional headers if needed for file output 

120 

121 def __init__(self): 

122 """Initialize a new VarroaPopSession. 

123 

124 Creates a new simulation session with default settings, initializes 

125 the colony and weather objects, sets up results tracking, and 

126 configures all treatment and event management systems. 

127 

128 The session is initialized with conservative default values for all 

129 parameters and empty data structures for results collection. 

130 

131 Raises: 

132 RuntimeError: If colony or weather initialization fails. 

133 

134 Note: 

135 After initialization, the session requires additional configuration 

136 (dates, latitude, initial conditions) before running simulations. 

137 """ 

138 # Output/Plotting Attributes 

139 self.results_header = [] 

140 self.results_file_header = [] 

141 self.results_text = [] 

142 self.results_file_text = [] 

143 self.disp_weekly_data = False 

144 # Options Selection 

145 self.col_titles = True 

146 self.init_conds = False 

147 self.version = True 

148 self.weather_colony = False 

149 self.field_delimiter = 0 

150 self.disp_frequency = 1 

151 # Error/Status 

152 self.error_list = [] 

153 self.information_list = [] 

154 self._enable_error_reporting = True 

155 self._enable_info_reporting = True 

156 # Simulation Data 

157 self.sim_start_time = None 

158 self.sim_end_time = None 

159 self.simulation_complete = False 

160 self.results_ready = False 

161 # Immigration Data 

162 self.immigration_type = "None" 

163 self.tot_immigrating_mites = 0 

164 self.inc_immigrating_mites = 0 

165 self.cum_immigrating_mites = 0 

166 self.imm_mite_pct_resistant = 0.0 

167 # Use sentinel date (1999-01-01) to match C++ behavior when dates are not set 

168 self.immigration_start_date = datetime.datetime(1999, 1, 1) 

169 self.immigration_end_date = datetime.datetime(1999, 1, 1) 

170 self.immigration_enabled = False 

171 self.imm_enabled = False # ImmEnabled parameter 

172 # Re-Queening Data 

173 self.rq_egg_laying_delay = 10 # RQEggLayDelay parameter 

174 self.rq_wkr_drn_ratio = 0.0 

175 self.rq_enable_requeen = False 

176 self.rq_scheduled = 1 

177 self.rq_queen_strength = 5.0 

178 self.rq_once = 0 

179 self.rq_requeen_date = None 

180 # Varroa Miticide Treatment Data 

181 self.vt_treatment_start = None 

182 self.vt_treatment_duration = 0 

183 self.vt_mortality = 0 

184 self.init_mite_pct_resistant = 0.0 

185 self.vt_enable = False 

186 # Varroa Spore Treatment Data 

187 self.sp_enable = False 

188 self.sp_treatment_start = None 

189 self.sp_initial = 0 

190 self.mort10 = 0.0 

191 self.mort25 = 0.0 

192 self.mort50 = 0.0 

193 self.mort75 = 0.0 

194 self.mort90 = 0.0 

195 # Comb Removal 

196 self.comb_remove_date = None 

197 self.comb_remove_enable = False 

198 self.comb_remove_pct = 0.0 

199 # EPA Mortality 

200 self.ied_enable = False 

201 self.results_file_format_stg = "" 

202 

203 # Initialize main objects (matching C++ session constructor) 

204 # Create the colony object (equivalent to CColony theColony) 

205 self.colony = Colony(session=self) 

206 

207 # Set session reference in colony (matching theColony.SetSession(this)) 

208 if hasattr(self.colony, "set_session"): 

209 self.colony.set_session(self) 

210 

211 # Create the weather events object (equivalent to new CWeatherEvents) 

212 self.weather = WeatherEvents() 

213 self.first_result_entry = True 

214 self.weather_loaded = False 

215 self.show_warnings = ( 

216 False # Not appropriate to show warnings for library version 

217 ) 

218 

219 # Main object accessors (matching C++ GetColony() and GetWeather()) 

220 def get_colony(self): 

221 """Get the colony object (equivalent to C++ GetColony()).""" 

222 return self.colony 

223 

224 def get_weather(self): 

225 """Get the weather events object (equivalent to C++ GetWeather()).""" 

226 return self.weather 

227 

228 def set_latitude(self, lat): 

229 """Set the latitude for weather calculations.""" 

230 if self.weather is not None: 

231 if hasattr(self.weather, "set_latitude"): 

232 self.weather.set_latitude(lat) 

233 

234 def get_latitude(self): 

235 """Get the current latitude setting.""" 

236 if self.weather is not None and hasattr(self.weather, "get_latitude"): 

237 return self.weather.get_latitude() 

238 return 30.0 # Default latitude 

239 

240 def is_weather_loaded(self): 

241 """Check if weather data has been loaded.""" 

242 return self.weather_loaded 

243 

244 def set_weather_loaded(self, load_complete): 

245 """Set the weather loaded status.""" 

246 self.weather_loaded = load_complete 

247 

248 def is_show_warnings(self): 

249 """Check if warnings should be shown.""" 

250 return self.show_warnings 

251 

252 def set_show_warnings(self, warn): 

253 """Set whether warnings should be shown.""" 

254 self.show_warnings = warn 

255 

256 # Error/Status list access 

257 def clear_error_list(self): 

258 self.error_list.clear() 

259 

260 def clear_info_list(self): 

261 self.information_list.clear() 

262 

263 def add_to_error_list(self, err_stg): 

264 self.error_list.append(err_stg) 

265 

266 def add_to_info_list(self, info_stg): 

267 self.information_list.append(info_stg) 

268 

269 def get_error_list(self): 

270 return self.error_list 

271 

272 def get_info_list(self): 

273 return self.information_list 

274 

275 def is_error_reporting_enabled(self): 

276 return self._enable_error_reporting 

277 

278 def is_info_reporting_enabled(self): 

279 return self._enable_info_reporting 

280 

281 def enable_error_reporting(self, enable): 

282 self._enable_error_reporting = bool(enable) 

283 

284 def enable_info_reporting(self, enable): 

285 self._enable_info_reporting = bool(enable) 

286 

287 # Simulation Operations 

288 def get_sim_start(self): 

289 return self.sim_start_time 

290 

291 def get_sim_end(self): 

292 return self.sim_end_time 

293 

294 def set_sim_start(self, start): 

295 self.sim_start_time = start 

296 # Mark that simulation dates were explicitly set 

297 self._sim_dates_explicitly_set = True 

298 

299 def set_sim_end(self, end): 

300 self.sim_end_time = end 

301 # Mark that simulation dates were explicitly set 

302 self._sim_dates_explicitly_set = True 

303 

304 def get_sim_days(self): 

305 if self.sim_start_time and self.sim_end_time: 

306 return (self.sim_end_time - self.sim_start_time).days + 1 

307 return 0 

308 

309 def get_sim_day_number(self, the_date): 

310 if self.sim_start_time: 

311 return (the_date - self.sim_start_time).days + 1 

312 return 0 

313 

314 def get_sim_date(self, day_num): 

315 if self.sim_start_time: 

316 return self.sim_start_time + timedelta(days=day_num) 

317 return None 

318 

319 def ready_to_simulate(self): 

320 return ( 

321 self.colony 

322 and self.colony.is_initialized() 

323 and self.weather 

324 and self.weather.is_initialized() 

325 ) 

326 

327 def is_simulation_complete(self): 

328 return self.simulation_complete 

329 

330 def are_results_ready(self): 

331 return self.results_ready 

332 

333 def get_results_length(self): 

334 return len(self.results_text) 

335 

336 def date_in_range(self, start_range, stop_range, the_time): 

337 """ 

338 Helper function to check if a date falls within a range. 

339 Matches C++ DateInRange function. 

340 """ 

341 return (the_time >= start_range) and (the_time <= stop_range) 

342 

343 def check_date_consistency(self, show_warning=True): 

344 """ 

345 Checks Dates for Immigration, Varroa Treatment and Re-Queening to verify 

346 they fall inside the Simulation range. If not, a warning message is displayed 

347 and the user is given the opportunity to continue or quit the simulation. 

348 

349 Return value: True if simulation should continue, False otherwise. User can 

350 override consistency check and continue from warning message box. Otherwise, 

351 inconsistent times will return false. 

352 

353 Ported from C++ CheckDateConsistency function. 

354 """ 

355 consistent = True 

356 if show_warning: # we only check if show_warnings is on. Is this intended? 

357 warn_strings = [] 

358 

359 # Check Re-Queening dates if enabled 

360 if ( 

361 hasattr(self, "rq_enable_requeen") 

362 and self.rq_enable_requeen 

363 and hasattr(self, "rq_requeen_date") 

364 and self.rq_requeen_date 

365 ): 

366 if not self.date_in_range( 

367 self.sim_start_time, self.sim_end_time, self.rq_requeen_date 

368 ): 

369 warn_strings.append(" ReQueening") 

370 consistent = False 

371 

372 # Check Immigration dates if enabled 

373 if self.immigration_enabled: 

374 # Check immigration start date 

375 if self.immigration_start_date and not self.date_in_range( 

376 self.sim_start_time, self.sim_end_time, self.immigration_start_date 

377 ): 

378 warn_strings.append(" Immigration Start") 

379 consistent = False 

380 

381 # Check immigration end date 

382 if self.immigration_end_date and not self.date_in_range( 

383 self.sim_start_time, self.sim_end_time, self.immigration_end_date 

384 ): 

385 warn_strings.append(" Immigration End") 

386 consistent = False 

387 

388 # Check Varroa Treatment dates if enabled 

389 if ( 

390 hasattr(self, "vt_enable") 

391 and self.vt_enable 

392 and hasattr(self, "vt_treatment_start") 

393 and self.vt_treatment_start 

394 ): 

395 if not self.date_in_range( 

396 self.sim_start_time, self.sim_end_time, self.vt_treatment_start 

397 ): 

398 warn_strings.append(" Varroa Treatment Start") 

399 consistent = False 

400 

401 # Display warnings if enabled and inconsistencies found 

402 if show_warning and not consistent: 

403 warn_message = ( 

404 "Date consistency check failed. The following dates are outside simulation range:\n" 

405 + "\n".join(warn_strings) 

406 ) 

407 self.add_to_error_list(warn_message) 

408 # In C++, this would show a dialog for user override 

409 # For Python, we'll just log the error and return False 

410 

411 return consistent 

412 

413 def simulate(self): 

414 """ 

415 Main simulation loop. Coordinates colony, weather, immigration, treatments, and results. 

416 Ported from C++ Simulate, preserving logic and comments. 

417 """ 

418 if not self.ready_to_simulate(): 

419 self.add_to_error_list( 

420 "Simulation not ready: colony or weather not initialized." 

421 ) 

422 return 

423 

424 # Check date consistency before starting simulation (matches C++ behavior) 

425 if not self.check_date_consistency(self.show_warnings): 

426 return 

427 

428 # Set results frequency 

429 res_freq = 7 if self.disp_weekly_data else 1 

430 

431 # Format string setup (simplified for Python) 

432 delimiter = " " 

433 if self.field_delimiter == 1: 

434 delimiter = "," 

435 elif self.field_delimiter == 2: 

436 delimiter = "\t" 

437 

438 # Initialize results headers 

439 self.set_default_headers() 

440 self.results_text.clear() 

441 self.results_file_text.clear() 

442 

443 # Add header rows (simplified) 

444 self.results_text.append(" ".join(self.results_header)) 

445 

446 # Generate Initial row showing exact initial conditions (like C++ version) 

447 initial_row = self._generate_initial_conditions_row() 

448 self.results_text.append(initial_row) 

449 

450 # Get simulation start and end 

451 sim_start = self.get_sim_start() 

452 sim_days = self.get_sim_days() 

453 day_count = 1 

454 tot_foraging_days = 0 

455 

456 # Get first event from weather 

457 event = self.weather.get_day_event(sim_start) 

458 

459 # Main simulation loop 

460 while event is not None and day_count <= sim_days: 

461 # Requeening logic 

462 self.colony.requeen_if_needed( 

463 day_count, 

464 event, 

465 self.rq_egg_laying_delay, 

466 self.rq_wkr_drn_ratio, 

467 self.rq_enable_requeen, 

468 self.rq_scheduled, 

469 self.rq_queen_strength, 

470 self.rq_once, 

471 self.rq_requeen_date, 

472 ) 

473 

474 # Update bees 

475 self.colony.update_bees(event, day_count) 

476 

477 # Immigration 

478 if self.is_immigration_enabled() and self.is_immigration_window(event): 

479 imm_mites_dict = self.get_immigration_mites(event) 

480 # Convert dict to Mite object for colony.add_mites() 

481 imm_mites = Mite( 

482 imm_mites_dict["resistant"], imm_mites_dict["non_resistant"] 

483 ) 

484 self.inc_immigrating_mites = imm_mites 

485 self.colony.add_mites(imm_mites) 

486 else: 

487 self.inc_immigrating_mites = 0 

488 

489 # Update mites 

490 self.colony.update_mites(event, day_count) 

491 

492 # Comb removal 

493 if ( 

494 self.comb_remove_enable 

495 and event.time.year == self.comb_remove_date.year 

496 and event.time.month == self.comb_remove_date.month 

497 and event.time.day == self.comb_remove_date.day 

498 ): 

499 self.colony.remove_drone_comb(self.comb_remove_pct) 

500 

501 # Pending events 

502 self.colony.do_pending_events(event, day_count) 

503 

504 # Results output 

505 if day_count % res_freq == 0: 

506 # Collect results for the day using C++ compatible formatting 

507 result_row = [ 

508 event.time.strftime("%m/%d/%Y"), # "%s" 

509 "%6d" % self.colony.get_colony_size(), # "%6d" 

510 "%8d" % self.colony.get_adult_drones(), # "%8d" 

511 "%8d" % self.colony.get_adult_workers(), # "%8d" 

512 "%8d" % self.colony.get_foragers(), # "%8d" 

513 "%8d" % self.colony.get_active_foragers(), # "%8d" 

514 "%7d" % self.colony.get_drone_brood(), # "%7d" 

515 "%6d" % self.colony.get_worker_brood(), # "%6d" 

516 "%6d" % self.colony.get_drone_larvae(), # "%6d" 

517 "%6d" % self.colony.get_worker_larvae(), # "%6d" 

518 "%6d" % self.colony.get_drone_eggs(), # "%6d" 

519 "%6d" % self.colony.get_worker_eggs(), # "%6d" 

520 "%6d" % self.colony.get_total_eggs_laid_today(), # "%6d" 

521 "%7.2f" % self.colony.get_dd_today(), # "%7.2f" 

522 "%6.2f" % self.colony.get_l_today(), # "%6.2f" 

523 "%6.2f" % self.colony.get_n_today(), # "%6.2f" 

524 "%8.2f" % self.colony.get_p_today(), # "%8.2f" 

525 "%7.2f" % self.colony.get_dd_lower(), # "%7.2f" 

526 "%6.2f" % self.colony.get_l_lower(), # "%6.2f" 

527 "%8.2f" % self.colony.get_n_lower(), # "%8.2f" 

528 "%6.2f" % self.colony.get_free_mites(), # "%6.2f" 

529 "%6.2f" % self.colony.get_drone_brood_mites(), # "%6.2f" 

530 "%6.2f" % self.colony.get_worker_brood_mites(), # "%6.2f" 

531 "%6.2f" % self.colony.get_mites_per_drone_brood(), # "%6.2f" 

532 "%6.2f" % self.colony.get_mites_per_worker_brood(), # "%6.2f" 

533 "%6.0f" % self.colony.get_mites_dying_this_period(), # "%6.0f" 

534 "%6.2f" 

535 % self.colony.get_prop_mites_dying(), # "%6.2f" - changed from c++ code which uses 0 decimal places 

536 "%8.1f" % self.colony.get_col_pollen(), # "%8.1f" 

537 "%7.4f" % self.colony.get_pollen_pest_conc(), # "%7.4f" 

538 "%8.1f" % self.colony.get_col_nectar(), # "%8.1f" 

539 "%7.4f" % self.colony.get_nectar_pest_conc(), # "%7.4f" 

540 "%6d" 

541 % self.colony.get_dead_drone_larvae_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths 

542 "%6d" 

543 % self.colony.get_dead_worker_larvae_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths 

544 "%6d" 

545 % self.colony.get_dead_drone_adults_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths 

546 "%6d" 

547 % self.colony.get_dead_worker_adults_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths 

548 "%6d" 

549 % self.colony.get_dead_foragers_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths 

550 "%8.3f" % self.colony.get_queen_strength(), # "%8.3f" 

551 "%8.3f" % event.temp, # "%8.3f" 

552 "%6.3f" % event.rainfall, # "%6.3f" 

553 "%8.3f" % event.min_temp, # "%8.3f" 

554 "%8.3f" % event.max_temp, # "%8.3f" 

555 "%8.2f" 

556 % event.daylight_hours, # "%8.2f" - KEY FORMATTING FOR DAYLIGHT HOURS 

557 "%8.2f" % event.forage_inc, # "%8.2f" 

558 "Yes" if event.is_forage_day() else "No", # "%s" 

559 ] 

560 self.results_text.append(delimiter.join(result_row)) 

561 

562 if day_count % res_freq == 0: 

563 self.colony.set_start_sample_period() 

564 

565 day_count += 1 

566 if getattr(event, "is_forage_day", False): 

567 tot_foraging_days += 1 

568 event = self.weather.get_next_event() 

569 

570 self.results_ready = True 

571 self.simulation_complete = True 

572 self.colony.clear() 

573 self.simulation_complete = False 

574 

575 # Immigration Operations 

576 def set_immigration_type(self, im_type): 

577 self.immigration_type = im_type 

578 

579 def get_immigration_type(self): 

580 return self.immigration_type 

581 

582 def set_num_immigration_mites(self, mites): 

583 # Store total mites (matching C++ logic where m_TotImmigratingMites.GetTotal() returns total) 

584 self.tot_immigrating_mites = mites 

585 

586 def get_num_immigration_mites(self): 

587 return self.tot_immigrating_mites 

588 

589 def set_immigration_start(self, start): 

590 self.immigration_start_date = start 

591 

592 def get_immigration_start(self): 

593 return self.immigration_start_date 

594 

595 def set_immigration_end(self, end): 

596 self.immigration_end_date = end 

597 

598 def get_immigration_end(self): 

599 return self.immigration_end_date 

600 

601 def set_immigration_enabled(self, enabled): 

602 self.immigration_enabled = enabled 

603 

604 def is_immigration_enabled(self): 

605 return self.immigration_enabled 

606 

607 def is_immigration_window(self, event): 

608 today = event.time 

609 # Match C++ logic: (today >= m_ImmigrationStartDate) && (today <= m_ImmigrationEndDate) 

610 return ( 

611 today >= self.immigration_start_date and today <= self.immigration_end_date 

612 ) 

613 

614 def get_imm_pct_resistant(self): 

615 return self.imm_mite_pct_resistant 

616 

617 def set_imm_pct_resistant(self, pctres): 

618 self.imm_mite_pct_resistant = pctres 

619 

620 def get_immigration_mites(self, event): 

621 """ 

622 Returns the number of immigration mites for a given event (date and colony count), supporting all immigration models. 

623 Ported from C++ GetImmigrationMites. 

624 

625 This routine calculates the number of mites to immigrate on the 

626 specified date. It also keeps track of the cumulative number of 

627 mites that have migrated so far. First calculate the total quantity 

628 of immigrating mites then return a CMite based on percent resistance to miticide 

629 

630 The equations of immigration were derived by identifying the desired function, 

631 e.g. f(x) = A*Cos(x), then calculating the constants by setting the integral of 

632 the function (over the range 0..1) to 1. This means that the area under the 

633 curve is = 1. This ensures that 100% of m_TotImmigratingMites were added to the 

634 colony. With the constants were established, a very simple numerical integration 

635 is performed using sum(f(x)*DeltaX) for each day of immigration. 

636 

637 The immigration functions are: 

638 

639 Cosine -> f(x) = 1.188395*cos(x) 

640 

641 Sine -> f(x) = 1.57078*sin(PI*x) 

642 

643 Tangent -> f(x) = 2.648784*tan(1.5*x) 

644 

645 Exponential -> f(x) = (1.0/(e-2))*(exp(1 - (x)) - 1.0) 

646 

647 Logarithmic -> f(x) = -1.0*log(x) day #2 and on 

648 

649 Polynomial -> f(x) = 3.0*(x) - 1.5*(x*x) 

650 

651 In the case of Logarithmic, since there is an infinity at x=0, the 

652 actual value of the integral over the range (0..DeltaX) is used on the first 

653 day. 

654 

655 Mites only immigrate on foraging days. 

656 

657 Args: 

658 event: An object with 'time' (datetime), 'num_colonies' (int), and 'is_forage_day' (bool) attributes. 

659 Returns: 

660 dict: {'resistant': float, 'non_resistant': float} number of immigration mites for the event's day. 

661 """ 

662 

663 # Immigration only occurs if enabled, on foraging days, and within the window 

664 if not getattr(self, "immigration_enabled", False): 

665 return {"resistant": 0, "non_resistant": 0} 

666 the_date = event.time 

667 # Check immigration window (matches C++ logic: today >= start AND today <= end) 

668 if not ( 

669 the_date >= self.immigration_start_date 

670 and the_date <= self.immigration_end_date 

671 ): 

672 return {"resistant": 0, "non_resistant": 0} 

673 if not getattr(event, "is_forage_day", lambda: True)(): 

674 return {"resistant": 0, "non_resistant": 0} 

675 

676 sim_day_today = (the_date - self.sim_start_time).days + 1 

677 sim_day_im_start = (self.immigration_start_date - self.sim_start_time).days + 1 

678 sim_day_im_stop = (self.immigration_end_date - self.sim_start_time).days + 1 

679 

680 # Set cumulative immigration to 0 on first day 

681 if sim_day_today == sim_day_im_start: 

682 self.cum_immigrating_mites = 0 

683 

684 # Calculate proportion of days into immigration 

685 im_prop = float(sim_day_today - sim_day_im_start) / float( 

686 1 + sim_day_im_stop - sim_day_im_start 

687 ) 

688 delta_x = 1.0 / (sim_day_im_stop - sim_day_im_start + 1) 

689 x = im_prop + delta_x / 2 

690 

691 immigration_type = str(self.immigration_type).upper() 

692 total_mites = 0.0 

693 A = self.get_num_immigration_mites() # Total mites to distribute 

694 

695 if immigration_type == "NONE": 

696 total_mites = 0.0 

697 elif immigration_type == "COSINE": 

698 total_mites = A * 1.188395 * math.cos(x) * delta_x 

699 elif immigration_type == "EXPONENTIAL": 

700 total_mites = ( 

701 A * (1.0 / (math.exp(1.0) - 2)) * (math.exp(1.0 - x) - 1.0) * delta_x 

702 ) 

703 elif immigration_type == "LOGARITHMIC": 

704 if im_prop == 0: 

705 total_mites = A * (-1.0 * delta_x * math.log(delta_x) - delta_x) 

706 else: 

707 total_mites = A * (-1.0 * math.log(x) * delta_x) 

708 elif immigration_type == "POLYNOMIAL": 

709 total_mites = A * (3.0 * x - 1.5 * (x * x)) * delta_x 

710 elif immigration_type == "SINE": 

711 total_mites = A * 1.57078 * math.sin(math.pi * x) * delta_x 

712 elif immigration_type == "TANGENT": 

713 total_mites = A * 2.648784 * math.tan(1.5 * x) * delta_x 

714 else: 

715 total_mites = 0.0 

716 

717 if total_mites < 0.0: 

718 total_mites = 0.0 

719 

720 resistant = total_mites * self.imm_mite_pct_resistant / 100.0 

721 non_resistant = total_mites - resistant 

722 self.cum_immigrating_mites += resistant + non_resistant 

723 return {"resistant": resistant, "non_resistant": non_resistant} 

724 

725 # Implementation 

726 def update_colony_parameters(self, param_name, param_value): 

727 """ 

728 Updates colony parameters based on the provided name and value. 

729 Ported from C++ UpdateColonyParameters, with pythonic improvements. 

730 Args: 

731 param_name (str): The name of the parameter to update. 

732 param_value (str): The value to set for the parameter. 

733 Returns: 

734 bool: True if the parameter was updated, False otherwise. 

735 """ 

736 

737 name = param_name.strip().lower() 

738 value = param_value.strip() 

739 

740 def parse_bool(val): 

741 return str(val).lower() in ("1", "true", "yes") 

742 

743 def parse_date(val): 

744 try: 

745 return datetime.datetime.strptime(val, "%m/%d/%Y") 

746 except Exception: 

747 return None 

748 

749 # Session parameters 

750 if name == "simstart": 

751 dt = parse_date(value) 

752 if dt: 

753 self.set_sim_start(dt) 

754 return True 

755 self.add_to_error_list(f"Invalid simstart date: {value}") 

756 return False 

757 if name == "simend": 

758 dt = parse_date(value) 

759 if dt: 

760 self.set_sim_end(dt) 

761 return True 

762 self.add_to_error_list(f"Invalid simend date: {value}") 

763 return False 

764 if name == "latitude": 

765 try: 

766 self.latitude = float(value) 

767 if self.weather and hasattr(self.weather, "set_latitude"): 

768 self.weather.set_latitude(self.latitude) 

769 return True 

770 except Exception: 

771 self.add_to_error_list(f"Invalid latitude: {value}") 

772 return False 

773 

774 # Initial Conditions parameters (IC) - Store directly in colony.m_init_cond 

775 if name == "icdroneadults": 

776 if self.colony and hasattr(self.colony, "m_init_cond"): 

777 try: 

778 self.colony.m_init_cond.m_droneAdultsField = int(value) 

779 return True 

780 except Exception: 

781 self.add_to_error_list(f"Invalid icdroneadults: {value}") 

782 return False 

783 if name == "icworkeradults": 

784 if self.colony and hasattr(self.colony, "m_init_cond"): 

785 try: 

786 self.colony.m_init_cond.m_workerAdultsField = int(value) 

787 return True 

788 except Exception: 

789 self.add_to_error_list(f"Invalid icworkeradults: {value}") 

790 return False 

791 if name == "icdronebrood": 

792 if self.colony and hasattr(self.colony, "m_init_cond"): 

793 try: 

794 self.colony.m_init_cond.m_droneBroodField = int(value) 

795 return True 

796 except Exception: 

797 self.add_to_error_list(f"Invalid icdronebrood: {value}") 

798 return False 

799 if name == "icworkerbrood": 

800 if self.colony and hasattr(self.colony, "m_init_cond"): 

801 try: 

802 self.colony.m_init_cond.m_workerBroodField = int(value) 

803 return True 

804 except Exception: 

805 self.add_to_error_list(f"Invalid icworkerbrood: {value}") 

806 return False 

807 if name == "icdronelarvae": 

808 if self.colony and hasattr(self.colony, "m_init_cond"): 

809 try: 

810 self.colony.m_init_cond.m_droneLarvaeField = int(value) 

811 return True 

812 except Exception: 

813 self.add_to_error_list(f"Invalid icdronelarvae: {value}") 

814 return False 

815 if name == "icworkerlarvae": 

816 if self.colony and hasattr(self.colony, "m_init_cond"): 

817 try: 

818 self.colony.m_init_cond.m_workerLarvaeField = int(value) 

819 return True 

820 except Exception: 

821 self.add_to_error_list(f"Invalid icworkerlarvae: {value}") 

822 return False 

823 if name == "icdroneeggs": 

824 if self.colony and hasattr(self.colony, "m_init_cond"): 

825 try: 

826 self.colony.m_init_cond.m_droneEggsField = int(value) 

827 return True 

828 except Exception: 

829 self.add_to_error_list(f"Invalid icdroneeggs: {value}") 

830 return False 

831 if name == "icworkereggs": 

832 if self.colony and hasattr(self.colony, "m_init_cond"): 

833 try: 

834 self.colony.m_init_cond.m_workerEggsField = int(value) 

835 return True 

836 except Exception: 

837 self.add_to_error_list(f"Invalid icworkereggs: {value}") 

838 return False 

839 if name == "icqueenstrength": 

840 if self.colony and hasattr(self.colony, "m_init_cond"): 

841 try: 

842 self.colony.m_init_cond.m_QueenStrength = float(value) 

843 return True 

844 except Exception: 

845 self.add_to_error_list(f"Invalid icqueenstrength: {value}") 

846 return False 

847 if name == "icforagerlifespan": 

848 if self.colony and hasattr(self.colony, "m_init_cond"): 

849 try: 

850 self.colony.m_init_cond.m_ForagerLifespan = int(value) 

851 return True 

852 except Exception: 

853 self.add_to_error_list(f"Invalid icforagerlifespan: {value}") 

854 return False 

855 

856 # Handle common parameter name variations (for user convenience) 

857 if name == "queenstrength": 

858 return self.update_colony_parameters("icqueenstrength", value) 

859 if name == "foragerlifespan": 

860 return self.update_colony_parameters("icforagerlifespan", value) 

861 if name == "workeradults": 

862 return self.update_colony_parameters("icworkeradults", value) 

863 if name == "workerbrood": 

864 return self.update_colony_parameters("icworkerbrood", value) 

865 if name == "workereggs": 

866 return self.update_colony_parameters("icworkereggs", value) 

867 if name == "workerlarvae": 

868 return self.update_colony_parameters("icworkerlarvae", value) 

869 if name == "droneadults": 

870 return self.update_colony_parameters("icdroneadults", value) 

871 if name == "dronebrood": 

872 return self.update_colony_parameters("icdronebrood", value) 

873 if name == "droneeggs": 

874 return self.update_colony_parameters("icdroneeggs", value) 

875 if name == "dronelarvae": 

876 return self.update_colony_parameters("icdronelarvae", value) 

877 

878 # Mite Parameters (ICDroneAdultInfest, etc.) - Following C++ session.cpp pattern 

879 if name == "icdroneadultinfest": 

880 if self.colony and hasattr(self.colony, "m_init_cond"): 

881 try: 

882 self.colony.m_init_cond.m_droneAdultInfestField = float(value) 

883 return True 

884 except Exception: 

885 self.add_to_error_list(f"Invalid icdroneadultinfest: {value}") 

886 return False 

887 if name == "icdronebroodinfest": 

888 if self.colony and hasattr(self.colony, "m_init_cond"): 

889 try: 

890 self.colony.m_init_cond.m_droneBroodInfestField = float(value) 

891 return True 

892 except Exception: 

893 self.add_to_error_list(f"Invalid icdronebroodinfest: {value}") 

894 return False 

895 if name == "icdronemiteoffspring": 

896 if self.colony and hasattr(self.colony, "m_init_cond"): 

897 try: 

898 self.colony.m_init_cond.m_droneMiteOffspringField = float(value) 

899 return True 

900 except Exception: 

901 self.add_to_error_list(f"Invalid icdronemiteoffspring: {value}") 

902 return False 

903 if name == "icdronemitesurvivorship": 

904 if self.colony and hasattr(self.colony, "m_init_cond"): 

905 try: 

906 self.colony.m_init_cond.m_droneMiteSurvivorshipField = float(value) 

907 return True 

908 except Exception: 

909 self.add_to_error_list(f"Invalid icdronemitesurvivorship: {value}") 

910 return False 

911 if name == "icworkeradultinfest": 

912 if self.colony and hasattr(self.colony, "m_init_cond"): 

913 try: 

914 self.colony.m_init_cond.m_workerAdultInfestField = float(value) 

915 return True 

916 except Exception: 

917 self.add_to_error_list(f"Invalid icworkeradultinfest: {value}") 

918 return False 

919 if name == "icworkerbroodinfest": 

920 if self.colony and hasattr(self.colony, "m_init_cond"): 

921 try: 

922 self.colony.m_init_cond.m_workerBroodInfestField = float(value) 

923 return True 

924 except Exception: 

925 self.add_to_error_list(f"Invalid icworkerbroodinfest: {value}") 

926 return False 

927 if name == "icworkermiteoffspring": 

928 if self.colony and hasattr(self.colony, "m_init_cond"): 

929 try: 

930 self.colony.m_init_cond.m_workerMiteOffspring = float(value) 

931 return True 

932 except Exception: 

933 self.add_to_error_list(f"Invalid icworkermiteoffspring: {value}") 

934 return False 

935 if name == "icworkermitesurvivorship": 

936 if self.colony and hasattr(self.colony, "m_init_cond"): 

937 try: 

938 self.colony.m_init_cond.m_workerMiteSurvivorship = float(value) 

939 return True 

940 except Exception: 

941 self.add_to_error_list(f"Invalid icworkermitesurvivorship: {value}") 

942 return False 

943 if name == "initmitepctresistant": 

944 try: 

945 self.init_mite_pct_resistant = float(value) 

946 return True 

947 except Exception: 

948 self.add_to_error_list(f"Invalid initmitepctresistant: {value}") 

949 return False 

950 

951 # AI/Pesticide Parameters (following C++ session.cpp pattern) 

952 if name == "ainame": 

953 if self.colony and hasattr(self.colony, "m_epadata"): 

954 self.colony.m_epadata.m_AI_Name = value 

955 return True 

956 if name == "aiadultslope": 

957 if self.colony and hasattr(self.colony, "m_epadata"): 

958 try: 

959 self.colony.m_epadata.m_AI_AdultSlope = float(value) 

960 return True 

961 except Exception: 

962 self.add_to_error_list(f"Invalid aiadultslope: {value}") 

963 return False 

964 if name == "aiadultld50": 

965 if self.colony and hasattr(self.colony, "m_epadata"): 

966 try: 

967 self.colony.m_epadata.m_AI_AdultLD50 = float(value) 

968 return True 

969 except Exception: 

970 self.add_to_error_list(f"Invalid aiadultld50: {value}") 

971 return False 

972 if name == "aiadultslopecontact": 

973 if self.colony and hasattr(self.colony, "m_epadata"): 

974 try: 

975 self.colony.m_epadata.m_AI_AdultSlope_Contact = float(value) 

976 return True 

977 except Exception: 

978 self.add_to_error_list(f"Invalid aiadultslopecontact: {value}") 

979 return False 

980 if name == "aiadultld50contact": 

981 if self.colony and hasattr(self.colony, "m_epadata"): 

982 try: 

983 self.colony.m_epadata.m_AI_AdultLD50_Contact = float(value) 

984 return True 

985 except Exception: 

986 self.add_to_error_list(f"Invalid aiadultld50contact: {value}") 

987 return False 

988 if name == "ailarvaslope": 

989 if self.colony and hasattr(self.colony, "m_epadata"): 

990 try: 

991 self.colony.m_epadata.m_AI_LarvaSlope = float(value) 

992 return True 

993 except Exception: 

994 self.add_to_error_list(f"Invalid ailarvaslope: {value}") 

995 return False 

996 if name == "ailarvald50": 

997 if self.colony and hasattr(self.colony, "m_epadata"): 

998 try: 

999 self.colony.m_epadata.m_AI_LarvaLD50 = float(value) 

1000 return True 

1001 except Exception: 

1002 self.add_to_error_list(f"Invalid ailarvald50: {value}") 

1003 return False 

1004 if name == "aikow": 

1005 if self.colony and hasattr(self.colony, "m_epadata"): 

1006 try: 

1007 self.colony.m_epadata.m_AI_KOW = float(value) 

1008 return True 

1009 except Exception: 

1010 self.add_to_error_list(f"Invalid aikow: {value}") 

1011 return False 

1012 if name == "aikoc": 

1013 if self.colony and hasattr(self.colony, "m_epadata"): 

1014 try: 

1015 self.colony.m_epadata.m_AI_KOC = float(value) 

1016 return True 

1017 except Exception: 

1018 self.add_to_error_list(f"Invalid aikoc: {value}") 

1019 return False 

1020 if name == "aihalflife": 

1021 if self.colony and hasattr(self.colony, "m_epadata"): 

1022 try: 

1023 self.colony.m_epadata.m_AI_HalfLife = float(value) 

1024 return True 

1025 except Exception: 

1026 self.add_to_error_list(f"Invalid aihalflife: {value}") 

1027 return False 

1028 if name == "aicontactfactor": 

1029 if self.colony and hasattr(self.colony, "m_epadata"): 

1030 try: 

1031 self.colony.m_epadata.m_AI_ContactFactor = float(value) 

1032 return True 

1033 except Exception: 

1034 self.add_to_error_list(f"Invalid aicontactfactor: {value}") 

1035 return False 

1036 

1037 # Consumption Parameters (CL4Pollen, etc.) - following C++ session.cpp pattern 

1038 if name == "cl4pollen": 

1039 if self.colony and hasattr(self.colony, "m_epadata"): 

1040 try: 

1041 self.colony.m_epadata.m_C_L4_Pollen = float(value) 

1042 return True 

1043 except Exception: 

1044 self.add_to_error_list(f"Invalid cl4pollen: {value}") 

1045 return False 

1046 if name == "cl4nectar": 

1047 if self.colony and hasattr(self.colony, "m_epadata"): 

1048 try: 

1049 self.colony.m_epadata.m_C_L4_Nectar = float(value) 

1050 return True 

1051 except Exception: 

1052 self.add_to_error_list(f"Invalid cl4nectar: {value}") 

1053 return False 

1054 if name == "cl5pollen": 

1055 if self.colony and hasattr(self.colony, "m_epadata"): 

1056 try: 

1057 self.colony.m_epadata.m_C_L5_Pollen = float(value) 

1058 return True 

1059 except Exception: 

1060 self.add_to_error_list(f"Invalid cl5pollen: {value}") 

1061 return False 

1062 if name == "cl5nectar": 

1063 if self.colony and hasattr(self.colony, "m_epadata"): 

1064 try: 

1065 self.colony.m_epadata.m_C_L5_Nectar = float(value) 

1066 return True 

1067 except Exception: 

1068 self.add_to_error_list(f"Invalid cl5nectar: {value}") 

1069 return False 

1070 if name == "cldpollen": 

1071 if self.colony and hasattr(self.colony, "m_epadata"): 

1072 try: 

1073 self.colony.m_epadata.m_C_LD_Pollen = float(value) 

1074 return True 

1075 except Exception: 

1076 self.add_to_error_list(f"Invalid cldpollen: {value}") 

1077 return False 

1078 if name == "cldnectar": 

1079 if self.colony and hasattr(self.colony, "m_epadata"): 

1080 try: 

1081 self.colony.m_epadata.m_C_LD_Nectar = float(value) 

1082 return True 

1083 except Exception: 

1084 self.add_to_error_list(f"Invalid cldnectar: {value}") 

1085 return False 

1086 if name == "ca13pollen": 

1087 if self.colony and hasattr(self.colony, "m_epadata"): 

1088 try: 

1089 self.colony.m_epadata.m_C_A13_Pollen = float(value) 

1090 return True 

1091 except Exception: 

1092 self.add_to_error_list(f"Invalid ca13pollen: {value}") 

1093 return False 

1094 if name == "ca13nectar": 

1095 if self.colony and hasattr(self.colony, "m_epadata"): 

1096 try: 

1097 self.colony.m_epadata.m_C_A13_Nectar = float(value) 

1098 return True 

1099 except Exception: 

1100 self.add_to_error_list(f"Invalid ca13nectar: {value}") 

1101 return False 

1102 if name == "ca410pollen": 

1103 if self.colony and hasattr(self.colony, "m_epadata"): 

1104 try: 

1105 self.colony.m_epadata.m_C_A410_Pollen = float(value) 

1106 return True 

1107 except Exception: 

1108 self.add_to_error_list(f"Invalid ca410pollen: {value}") 

1109 return False 

1110 if name == "ca410nectar": 

1111 if self.colony and hasattr(self.colony, "m_epadata"): 

1112 try: 

1113 self.colony.m_epadata.m_C_A410_Nectar = float(value) 

1114 return True 

1115 except Exception: 

1116 self.add_to_error_list(f"Invalid ca410nectar: {value}") 

1117 return False 

1118 if name == "ca1120pollen": 

1119 if self.colony and hasattr(self.colony, "m_epadata"): 

1120 try: 

1121 self.colony.m_epadata.m_C_A1120_Pollen = float(value) 

1122 return True 

1123 except Exception: 

1124 self.add_to_error_list(f"Invalid ca1120pollen: {value}") 

1125 return False 

1126 if name == "ca1120nectar": 

1127 if self.colony and hasattr(self.colony, "m_epadata"): 

1128 try: 

1129 self.colony.m_epadata.m_C_A1120_Nectar = float(value) 

1130 return True 

1131 except Exception: 

1132 self.add_to_error_list(f"Invalid ca1120nectar: {value}") 

1133 return False 

1134 if name == "cadpollen": 

1135 if self.colony and hasattr(self.colony, "m_epadata"): 

1136 try: 

1137 self.colony.m_epadata.m_C_AD_Pollen = float(value) 

1138 return True 

1139 except Exception: 

1140 self.add_to_error_list(f"Invalid cadpollen: {value}") 

1141 return False 

1142 if name == "cadnectar": 

1143 if self.colony and hasattr(self.colony, "m_epadata"): 

1144 try: 

1145 self.colony.m_epadata.m_C_AD_Nectar = float(value) 

1146 return True 

1147 except Exception: 

1148 self.add_to_error_list(f"Invalid cadnectar: {value}") 

1149 return False 

1150 if name == "cforagerpollen": 

1151 if self.colony and hasattr(self.colony, "m_epadata"): 

1152 try: 

1153 self.colony.m_epadata.m_C_Forager_Pollen = float(value) 

1154 return True 

1155 except Exception: 

1156 self.add_to_error_list(f"Invalid cforagerpollen: {value}") 

1157 return False 

1158 if name == "cforagernectar": 

1159 if self.colony and hasattr(self.colony, "m_epadata"): 

1160 try: 

1161 self.colony.m_epadata.m_C_Forager_Nectar = float(value) 

1162 return True 

1163 except Exception: 

1164 self.add_to_error_list(f"Invalid cforagernectar: {value}") 

1165 return False 

1166 

1167 # Foraging Parameters (IPollenTrips, etc.) - following C++ session.cpp pattern 

1168 if name == "ipollentrips": 

1169 if self.colony and hasattr(self.colony, "m_epadata"): 

1170 try: 

1171 self.colony.m_epadata.m_I_PollenTrips = int( 

1172 float(value) 

1173 ) # Convert float to int like C++ 

1174 return True 

1175 except Exception: 

1176 self.add_to_error_list(f"Invalid ipollentrips: {value}") 

1177 return False 

1178 if name == "inectartrips": 

1179 if self.colony and hasattr(self.colony, "m_epadata"): 

1180 try: 

1181 self.colony.m_epadata.m_I_NectarTrips = int( 

1182 float(value) 

1183 ) # Convert float to int like C++ 

1184 return True 

1185 except Exception: 

1186 self.add_to_error_list(f"Invalid inectartrips: {value}") 

1187 return False 

1188 if name == "ipercentnectarforagers": 

1189 if self.colony and hasattr(self.colony, "m_epadata"): 

1190 try: 

1191 self.colony.m_epadata.m_I_PercentNectarForagers = float(value) 

1192 return True 

1193 except Exception: 

1194 self.add_to_error_list(f"Invalid ipercentnectarforagers: {value}") 

1195 return False 

1196 if name == "ipollenload": 

1197 if self.colony and hasattr(self.colony, "m_epadata"): 

1198 try: 

1199 self.colony.m_epadata.m_I_PollenLoad = float(value) 

1200 return True 

1201 except Exception: 

1202 self.add_to_error_list(f"Invalid ipollenload: {value}") 

1203 return False 

1204 if name == "inectarload": 

1205 if self.colony and hasattr(self.colony, "m_epadata"): 

1206 try: 

1207 self.colony.m_epadata.m_I_NectarLoad = float(value) 

1208 return True 

1209 except Exception: 

1210 self.add_to_error_list(f"Invalid inectarload: {value}") 

1211 return False 

1212 

1213 # Feature Flags (FoliarEnabled, etc.) - following C++ session.cpp pattern 

1214 if name == "foliarenabled": 

1215 if self.colony and hasattr(self.colony, "m_epadata"): 

1216 self.colony.m_epadata.m_FoliarEnabled = parse_bool(value) 

1217 return True 

1218 if name == "soilenabled": 

1219 if self.colony and hasattr(self.colony, "m_epadata"): 

1220 self.colony.m_epadata.m_SoilEnabled = parse_bool(value) 

1221 return True 

1222 if name == "seedenabled": 

1223 if self.colony and hasattr(self.colony, "m_epadata"): 

1224 self.colony.m_epadata.m_SeedEnabled = parse_bool(value) 

1225 return True 

1226 if name == "necpolfileenable": 

1227 if self.colony and hasattr(self.colony, "m_epadata"): 

1228 self.colony.m_epadata.m_NecPolFileEnabled = parse_bool(value) 

1229 return True 

1230 

1231 # Exposure Parameters (EAppRate, etc.) - following C++ session.cpp pattern 

1232 if name == "eapprate": 

1233 if self.colony and hasattr(self.colony, "m_epadata"): 

1234 try: 

1235 self.colony.m_epadata.m_E_AppRate = float(value) 

1236 return True 

1237 except Exception: 

1238 self.add_to_error_list(f"Invalid eapprate: {value}") 

1239 return False 

1240 if name == "esoiltheta": 

1241 if self.colony and hasattr(self.colony, "m_epadata"): 

1242 try: 

1243 self.colony.m_epadata.m_E_SoilTheta = float(value) 

1244 return True 

1245 except Exception: 

1246 self.add_to_error_list(f"Invalid esoiltheta: {value}") 

1247 return False 

1248 if name == "esoilp": 

1249 if self.colony and hasattr(self.colony, "m_epadata"): 

1250 try: 

1251 self.colony.m_epadata.m_E_SoilP = float(value) 

1252 return True 

1253 except Exception: 

1254 self.add_to_error_list(f"Invalid esoilp: {value}") 

1255 return False 

1256 if name == "esoilfoc": 

1257 if self.colony and hasattr(self.colony, "m_epadata"): 

1258 try: 

1259 self.colony.m_epadata.m_E_SoilFoc = float(value) 

1260 return True 

1261 except Exception: 

1262 self.add_to_error_list(f"Invalid esoilfoc: {value}") 

1263 return False 

1264 if name == "esoilconcentration": 

1265 if self.colony and hasattr(self.colony, "m_epadata"): 

1266 try: 

1267 self.colony.m_epadata.m_E_SoilConcentration = float(value) 

1268 return True 

1269 except Exception: 

1270 self.add_to_error_list(f"Invalid esoilconcentration: {value}") 

1271 return False 

1272 if name == "eseedconcentration": 

1273 if self.colony and hasattr(self.colony, "m_epadata"): 

1274 try: 

1275 self.colony.m_epadata.m_E_SeedConcentration = float(value) 

1276 return True 

1277 except Exception: 

1278 self.add_to_error_list(f"Invalid eseedconcentration: {value}") 

1279 return False 

1280 

1281 # Foliar Date Parameters - following C++ session.cpp pattern 

1282 if name == "foliarappdate": 

1283 if self.colony and hasattr(self.colony, "m_epadata"): 

1284 dt = parse_date(value) 

1285 if dt: 

1286 self.colony.m_epadata.m_FoliarAppDate = dt 

1287 return True 

1288 self.add_to_error_list( 

1289 f"Invalid foliarappdate format (expected MM/DD/YYYY): {value}" 

1290 ) 

1291 return False 

1292 if name == "foliarforagebegin": 

1293 if self.colony and hasattr(self.colony, "m_epadata"): 

1294 dt = parse_date(value) 

1295 if dt: 

1296 self.colony.m_epadata.m_FoliarForageBegin = dt 

1297 return True 

1298 self.add_to_error_list( 

1299 f"Invalid foliarforagebegin format (expected MM/DD/YYYY): {value}" 

1300 ) 

1301 return False 

1302 if name == "foliarforageend": 

1303 if self.colony and hasattr(self.colony, "m_epadata"): 

1304 dt = parse_date(value) 

1305 if dt: 

1306 self.colony.m_epadata.m_FoliarForageEnd = dt 

1307 return True 

1308 self.add_to_error_list( 

1309 f"Invalid foliarforageend format (expected MM/DD/YYYY): {value}" 

1310 ) 

1311 return False 

1312 if name == "soilforagebegin": 

1313 if self.colony and hasattr(self.colony, "m_epadata"): 

1314 dt = parse_date(value) 

1315 if dt: 

1316 self.colony.m_epadata.m_SoilForageBegin = dt 

1317 return True 

1318 self.add_to_error_list( 

1319 f"Invalid soilforagebegin format (expected MM/DD/YYYY): {value}" 

1320 ) 

1321 return False 

1322 if name == "soilforageend": 

1323 if self.colony and hasattr(self.colony, "m_epadata"): 

1324 dt = parse_date(value) 

1325 if dt: 

1326 self.colony.m_epadata.m_SoilForageEnd = dt 

1327 return True 

1328 self.add_to_error_list( 

1329 f"Invalid soilforageend format (expected MM/DD/YYYY): {value}" 

1330 ) 

1331 return False 

1332 if name == "seedforagebegin": 

1333 if self.colony and hasattr(self.colony, "m_epadata"): 

1334 dt = parse_date(value) 

1335 if dt: 

1336 self.colony.m_epadata.m_SeedForageBegin = dt 

1337 return True 

1338 self.add_to_error_list( 

1339 f"Invalid seedforagebegin format (expected MM/DD/YYYY): {value}" 

1340 ) 

1341 return False 

1342 if name == "seedforageend": 

1343 if self.colony and hasattr(self.colony, "m_epadata"): 

1344 dt = parse_date(value) 

1345 if dt: 

1346 self.colony.m_epadata.m_SeedForageEnd = dt 

1347 return True 

1348 self.add_to_error_list( 

1349 f"Invalid seedforageend format (expected MM/DD/YYYY): {value}" 

1350 ) 

1351 return False 

1352 

1353 # Resource Management Parameters (following C++ session.cpp pattern) 

1354 if name == "initcolnectar": 

1355 if self.colony: 

1356 try: 

1357 self.colony.m_ColonyNecInitAmount = float(value) 

1358 return True 

1359 except Exception: 

1360 self.add_to_error_list(f"Invalid initcolnectar: {value}") 

1361 return False 

1362 if name == "initcolpollen": 

1363 if self.colony: 

1364 try: 

1365 self.colony.m_ColonyPolInitAmount = float(value) 

1366 return True 

1367 except Exception: 

1368 self.add_to_error_list(f"Invalid initcolpollen: {value}") 

1369 return False 

1370 if name == "maxcolnectar": 

1371 if self.colony: 

1372 try: 

1373 self.colony.m_ColonyNecMaxAmount = float(value) 

1374 return True 

1375 except Exception: 

1376 self.add_to_error_list(f"Invalid maxcolnectar: {value}") 

1377 return False 

1378 if name == "maxcolpollen": 

1379 if self.colony: 

1380 try: 

1381 self.colony.m_ColonyPolMaxAmount = float(value) 

1382 return True 

1383 except Exception: 

1384 self.add_to_error_list(f"Invalid maxcolpollen: {value}") 

1385 return False 

1386 if name == "suppollenenable": 

1387 if self.colony: 

1388 self.colony.m_SuppPollenEnabled = parse_bool(value) 

1389 return True 

1390 if name == "supnectarenable": 

1391 if self.colony: 

1392 self.colony.m_SuppNectarEnabled = parse_bool(value) 

1393 return True 

1394 if name == "suppollenamount": 

1395 if self.colony and hasattr(self.colony, "m_SuppPollen"): 

1396 try: 

1397 self.colony.m_SuppPollen.m_StartingAmount = float(value) 

1398 return True 

1399 except Exception: 

1400 self.add_to_error_list(f"Invalid suppollenamount: {value}") 

1401 return False 

1402 if name == "suppollenbegin": 

1403 if self.colony and hasattr(self.colony, "m_SuppPollen"): 

1404 try: 

1405 # Parse date in format MM/DD/YYYY (like C++) 

1406 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y") 

1407 self.colony.m_SuppPollen.m_BeginDate = date_obj 

1408 return True 

1409 except Exception: 

1410 self.add_to_error_list( 

1411 f"Invalid suppollenbegin date format (expected MM/DD/YYYY): {value}" 

1412 ) 

1413 return False 

1414 if name == "suppollenend": 

1415 if self.colony and hasattr(self.colony, "m_SuppPollen"): 

1416 try: 

1417 # Parse date in format MM/DD/YYYY (like C++) 

1418 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y") 

1419 self.colony.m_SuppPollen.m_EndDate = date_obj 

1420 return True 

1421 except Exception: 

1422 self.add_to_error_list( 

1423 f"Invalid suppollenend date format (expected MM/DD/YYYY): {value}" 

1424 ) 

1425 return False 

1426 if name == "supnectaramount": 

1427 if self.colony and hasattr(self.colony, "m_SuppNectar"): 

1428 try: 

1429 self.colony.m_SuppNectar.m_StartingAmount = float(value) 

1430 return True 

1431 except Exception: 

1432 self.add_to_error_list(f"Invalid supnectaramount: {value}") 

1433 return False 

1434 if name == "supnectarbegin": 

1435 if self.colony and hasattr(self.colony, "m_SuppNectar"): 

1436 try: 

1437 # Parse date in format MM/DD/YYYY (like C++) 

1438 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y") 

1439 self.colony.m_SuppNectar.m_BeginDate = date_obj 

1440 return True 

1441 except Exception: 

1442 self.add_to_error_list( 

1443 f"Invalid supnectarbegin date format (expected MM/DD/YYYY): {value}" 

1444 ) 

1445 return False 

1446 if name == "supnectarend": 

1447 if self.colony and hasattr(self.colony, "m_SuppNectar"): 

1448 try: 

1449 # Parse date in format MM/DD/YYYY (like C++) 

1450 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y") 

1451 self.colony.m_SuppNectar.m_EndDate = date_obj 

1452 return True 

1453 except Exception: 

1454 self.add_to_error_list( 

1455 f"Invalid supnectarend date format (expected MM/DD/YYYY): {value}" 

1456 ) 

1457 return False 

1458 if name == "foragermaxprop": 

1459 if self.colony and hasattr(self.colony, "foragers"): 

1460 try: 

1461 self.colony.foragers.set_prop_actual_foragers(float(value)) 

1462 return True 

1463 except Exception: 

1464 self.add_to_error_list(f"Invalid foragermaxprop: {value}") 

1465 return False 

1466 if name == "needresourcestolive": 

1467 if self.colony: 

1468 self.colony.m_NoResourceKillsColony = parse_bool(value) 

1469 return True 

1470 

1471 # Life Stage Transition Parameters (following C++ session.cpp pattern) 

1472 if name == "etolxitionen": 

1473 # EtoL Transition Enable - theColony.m_InitCond.m_EggTransitionDRV.SetEnabled(Value == "true") 

1474 if self.colony and hasattr(self.colony, "m_init_cond"): 

1475 self.colony.m_init_cond.m_EggTransitionDRV.set_enabled( 

1476 parse_bool(value) 

1477 ) 

1478 return True 

1479 return False 

1480 if name == "ltobxitionen": 

1481 # LtoB Transition Enable - theColony.m_InitCond.m_LarvaeTransitionDRV.SetEnabled(Value == "true") 

1482 if self.colony and hasattr(self.colony, "m_init_cond"): 

1483 self.colony.m_init_cond.m_LarvaeTransitionDRV.set_enabled( 

1484 parse_bool(value) 

1485 ) 

1486 return True 

1487 return False 

1488 if name == "btoaxitionen": 

1489 # BtoA Transition Enable - theColony.m_InitCond.m_BroodTransitionDRV.SetEnabled(Value == "true") 

1490 if self.colony and hasattr(self.colony, "m_init_cond"): 

1491 self.colony.m_init_cond.m_BroodTransitionDRV.set_enabled( 

1492 parse_bool(value) 

1493 ) 

1494 return True 

1495 return False 

1496 if name == "atofxitionen": 

1497 # AtoF Transition Enable - theColony.m_InitCond.m_AdultTransitionDRV.SetEnabled(Value == "true") 

1498 if self.colony and hasattr(self.colony, "m_init_cond"): 

1499 self.colony.m_init_cond.m_AdultTransitionDRV.set_enabled( 

1500 parse_bool(value) 

1501 ) 

1502 return True 

1503 return False 

1504 

1505 # Lifespan Control Parameters (following C++ session.cpp pattern) 

1506 if name == "alifespanen": 

1507 # Adult Lifespan Enable - theColony.m_InitCond.m_AdultLifespanDRV.SetEnabled(Value == "true") 

1508 if self.colony and hasattr(self.colony, "m_init_cond"): 

1509 self.colony.m_init_cond.m_AdultLifespanDRV.set_enabled( 

1510 parse_bool(value) 

1511 ) 

1512 return True 

1513 return False 

1514 if name == "flifespanen": 

1515 # Forager Lifespan Enable - theColony.m_InitCond.m_ForagerLifespanDRV.SetEnabled(Value == "true") 

1516 if self.colony and hasattr(self.colony, "m_init_cond"): 

1517 self.colony.m_init_cond.m_ForagerLifespanDRV.set_enabled( 

1518 parse_bool(value) 

1519 ) 

1520 return True 

1521 return False 

1522 

1523 # Adult Aging Delay Parameters (following C++ session.cpp pattern) 

1524 if name == "adultagedelay": 

1525 # Adult Age Delay - theColony.SetAdultAgingDelay(atoi(Value)) 

1526 if self.colony: 

1527 try: 

1528 self.colony.set_adult_aging_delay(int(value)) 

1529 return True 

1530 except Exception: 

1531 self.add_to_error_list(f"Invalid adultagedelay: {value}") 

1532 return False 

1533 return False 

1534 if name == "adultagedelayeggthreshold": 

1535 # Adult Age Delay Egg Threshold - theColony.SetAdultAgingDelayEggThreshold(atoi(Value)) 

1536 if self.colony: 

1537 try: 

1538 self.colony.set_adult_aging_delay_egg_threshold(int(value)) 

1539 return True 

1540 except Exception: 

1541 self.add_to_error_list( 

1542 f"Invalid adultagedelayeggthreshold: {value}" 

1543 ) 

1544 return False 

1545 return False 

1546 

1547 # Life Stage Transition Percentage Parameters (following C++ session.cpp pattern) 

1548 if name == "etolxition": 

1549 # Egg to Larva Transition - theColony.m_InitCond.m_EggTransitionDRV.AddItem() or ClearAll() 

1550 if self.colony and hasattr(self.colony, "m_init_cond"): 

1551 if value.lower() == "clear": 

1552 self.colony.m_init_cond.m_EggTransitionDRV.clear_all() 

1553 return True 

1554 else: 

1555 try: 

1556 # Parse comma-separated format: StartDate,EndDate,Percentage 

1557 parts = [part.strip() for part in value.split(",")] 

1558 if len(parts) >= 3: 

1559 start_date_str, end_date_str, percentage_str = ( 

1560 parts[0], 

1561 parts[1], 

1562 parts[2], 

1563 ) 

1564 start_date = parse_date(start_date_str) 

1565 end_date = parse_date(end_date_str) 

1566 percentage = float(percentage_str) 

1567 if start_date and end_date: 

1568 self.colony.m_init_cond.m_EggTransitionDRV.add_item( 

1569 start_date, end_date, percentage 

1570 ) 

1571 return True 

1572 except Exception: 

1573 self.add_to_error_list(f"Invalid etolxition format: {value}") 

1574 return False 

1575 return False 

1576 if name == "ltobxition": 

1577 # Larva to Brood Transition - theColony.m_InitCond.m_LarvaeTransitionDRV.AddItem() or ClearAll() 

1578 if self.colony and hasattr(self.colony, "m_init_cond"): 

1579 if value.lower() == "clear": 

1580 self.colony.m_init_cond.m_LarvaeTransitionDRV.clear_all() 

1581 return True 

1582 else: 

1583 try: 

1584 # Parse comma-separated format: StartDate,EndDate,Percentage 

1585 parts = [part.strip() for part in value.split(",")] 

1586 if len(parts) >= 3: 

1587 start_date_str, end_date_str, percentage_str = ( 

1588 parts[0], 

1589 parts[1], 

1590 parts[2], 

1591 ) 

1592 start_date = parse_date(start_date_str) 

1593 end_date = parse_date(end_date_str) 

1594 percentage = float(percentage_str) 

1595 if start_date and end_date: 

1596 self.colony.m_init_cond.m_LarvaeTransitionDRV.add_item( 

1597 start_date, end_date, percentage 

1598 ) 

1599 return True 

1600 except Exception: 

1601 self.add_to_error_list(f"Invalid ltobxition format: {value}") 

1602 return False 

1603 return False 

1604 if name == "btoaxition": 

1605 # Brood to Adult Transition - theColony.m_InitCond.m_BroodTransitionDRV.AddItem() or ClearAll() 

1606 if self.colony and hasattr(self.colony, "m_init_cond"): 

1607 if value.lower() == "clear": 

1608 self.colony.m_init_cond.m_BroodTransitionDRV.clear_all() 

1609 return True 

1610 else: 

1611 try: 

1612 # Parse comma-separated format: StartDate,EndDate,Percentage 

1613 parts = [part.strip() for part in value.split(",")] 

1614 if len(parts) >= 3: 

1615 start_date_str, end_date_str, percentage_str = ( 

1616 parts[0], 

1617 parts[1], 

1618 parts[2], 

1619 ) 

1620 start_date = parse_date(start_date_str) 

1621 end_date = parse_date(end_date_str) 

1622 percentage = float(percentage_str) 

1623 if start_date and end_date: 

1624 self.colony.m_init_cond.m_BroodTransitionDRV.add_item( 

1625 start_date, end_date, percentage 

1626 ) 

1627 return True 

1628 except Exception: 

1629 self.add_to_error_list(f"Invalid btoaxition format: {value}") 

1630 return False 

1631 return False 

1632 if name == "atofxition": 

1633 # Adult to Forager Transition - theColony.m_InitCond.m_AdultTransitionDRV.AddItem() or ClearAll() 

1634 if self.colony and hasattr(self.colony, "m_init_cond"): 

1635 if value.lower() == "clear": 

1636 self.colony.m_init_cond.m_AdultTransitionDRV.clear_all() 

1637 return True 

1638 else: 

1639 try: 

1640 # Parse comma-separated format: StartDate,EndDate,Percentage 

1641 parts = [part.strip() for part in value.split(",")] 

1642 if len(parts) >= 3: 

1643 start_date_str, end_date_str, percentage_str = ( 

1644 parts[0], 

1645 parts[1], 

1646 parts[2], 

1647 ) 

1648 start_date = parse_date(start_date_str) 

1649 end_date = parse_date(end_date_str) 

1650 percentage = float(percentage_str) 

1651 if start_date and end_date: 

1652 self.colony.m_init_cond.m_AdultTransitionDRV.add_item( 

1653 start_date, end_date, percentage 

1654 ) 

1655 return True 

1656 except Exception: 

1657 self.add_to_error_list(f"Invalid atofxition format: {value}") 

1658 return False 

1659 return False 

1660 

1661 # Lifespan Data Parameters (following C++ session.cpp pattern) 

1662 if name == "alifespan": 

1663 # Adult Lifespan - theColony.m_InitCond.m_AdultLifespanDRV.AddItem() or ClearAll() 

1664 if self.colony and hasattr(self.colony, "m_init_cond"): 

1665 if value.lower() == "clear": 

1666 self.colony.m_init_cond.m_AdultLifespanDRV.clear_all() 

1667 return True 

1668 else: 

1669 try: 

1670 # Parse comma-separated format: StartDate,EndDate,LifespanDays 

1671 parts = [part.strip() for part in value.split(",")] 

1672 if len(parts) >= 3: 

1673 start_date_str, end_date_str, lifespan_str = ( 

1674 parts[0], 

1675 parts[1], 

1676 parts[2], 

1677 ) 

1678 start_date = parse_date(start_date_str) 

1679 end_date = parse_date(end_date_str) 

1680 lifespan_days = float(lifespan_str) 

1681 # Apply C++ constraint: Adult bee age constraint (7-21 days) 

1682 if start_date and end_date and 7 <= lifespan_days <= 21: 

1683 self.colony.m_init_cond.m_AdultLifespanDRV.add_item( 

1684 start_date, end_date, lifespan_days 

1685 ) 

1686 return True 

1687 elif not (7 <= lifespan_days <= 21): 

1688 self.add_to_error_list( 

1689 f"Adult lifespan must be 7-21 days, got: {lifespan_days}" 

1690 ) 

1691 return False 

1692 except Exception: 

1693 self.add_to_error_list(f"Invalid alifespan format: {value}") 

1694 return False 

1695 return False 

1696 if name == "flifespan": 

1697 # Forager Lifespan - theColony.m_InitCond.m_ForagerLifespanDRV.AddItem() or ClearAll() 

1698 if self.colony and hasattr(self.colony, "m_init_cond"): 

1699 if value.lower() == "clear": 

1700 self.colony.m_init_cond.m_ForagerLifespanDRV.clear_all() 

1701 return True 

1702 else: 

1703 try: 

1704 # Parse comma-separated format: StartDate,EndDate,LifespanDays 

1705 parts = [part.strip() for part in value.split(",")] 

1706 if len(parts) >= 3: 

1707 start_date_str, end_date_str, lifespan_str = ( 

1708 parts[0], 

1709 parts[1], 

1710 parts[2], 

1711 ) 

1712 start_date = parse_date(start_date_str) 

1713 end_date = parse_date(end_date_str) 

1714 lifespan_days = float(lifespan_str) 

1715 # Apply C++ constraint: Forager lifespan constraint (0-20 days) 

1716 if start_date and end_date and 0 <= lifespan_days <= 20: 

1717 self.colony.m_init_cond.m_ForagerLifespanDRV.add_item( 

1718 start_date, end_date, lifespan_days 

1719 ) 

1720 return True 

1721 elif not (0 <= lifespan_days <= 20): 

1722 self.add_to_error_list( 

1723 f"Forager lifespan must be 0-20 days, got: {lifespan_days}" 

1724 ) 

1725 return False 

1726 except Exception: 

1727 self.add_to_error_list(f"Invalid flifespan format: {value}") 

1728 return False 

1729 return False 

1730 

1731 # Varroa Treatment Parameters (following C++ session.cpp pattern) 

1732 if name == "vtenable": 

1733 self.vt_enable = parse_bool(value) 

1734 return True 

1735 

1736 # Immigration parameters 

1737 if name == "immenabled": 

1738 self.immigration_enabled = parse_bool(value) 

1739 return True 

1740 if name == "immtype": 

1741 self.immigration_type = value 

1742 return True 

1743 if name == "totalimmmites": 

1744 try: 

1745 self.tot_immigrating_mites = int(value) 

1746 return True 

1747 except Exception: 

1748 self.add_to_error_list(f"Invalid totalimmmites: {value}") 

1749 return False 

1750 if name == "pctimmmitesresistant": 

1751 try: 

1752 self.imm_mite_pct_resistant = float(value) 

1753 return True 

1754 except Exception: 

1755 self.add_to_error_list(f"Invalid pctimmmitesresistant: {value}") 

1756 return False 

1757 if name == "immstart": 

1758 dt = parse_date(value) 

1759 if dt: 

1760 self.immigration_start_date = dt 

1761 return True 

1762 self.add_to_error_list(f"Invalid immstart date: {value}") 

1763 return False 

1764 if name == "immend": 

1765 dt = parse_date(value) 

1766 if dt: 

1767 self.immigration_end_date = dt 

1768 return True 

1769 self.add_to_error_list(f"Invalid immend date: {value}") 

1770 return False 

1771 if name == "immenabled": 

1772 self.immigration_enabled = parse_bool(value) 

1773 return True 

1774 

1775 # Requeening parameters 

1776 if name == "rqegglaydelay": 

1777 try: 

1778 self.rq_egg_laying_delay = int(value) 

1779 return True 

1780 except Exception: 

1781 self.add_to_error_list(f"Invalid rqegglaydelay: {value}") 

1782 return False 

1783 if name == "rqwkrdrnratio": 

1784 try: 

1785 self.rq_wkr_drn_ratio = float(value) 

1786 return True 

1787 except Exception: 

1788 self.add_to_error_list(f"Invalid rqwkrdrnratio: {value}") 

1789 return False 

1790 if name == "rqrequeendate": 

1791 dt = parse_date(value) 

1792 if dt: 

1793 self.rq_requeen_date = dt 

1794 return True 

1795 self.add_to_error_list(f"Invalid rqrequeendate: {value}") 

1796 return False 

1797 if name == "rqenablerequeen": 

1798 self.rq_enable_requeen = parse_bool(value) 

1799 return True 

1800 if name == "rqscheduled": 

1801 self.rq_scheduled = 0 if parse_bool(value) else 1 

1802 return True 

1803 if name == "rqqueenstrength": 

1804 try: 

1805 self.rq_queen_strength = float(value) 

1806 if self.colony and hasattr(self.colony, "add_requeen_strength"): 

1807 self.colony.add_requeen_strength(self.rq_queen_strength) 

1808 return True 

1809 except Exception: 

1810 self.add_to_error_list(f"Invalid rqqueenstrength: {value}") 

1811 return False 

1812 if name == "rqonce": 

1813 self.rq_once = 0 if parse_bool(value) else 1 

1814 return True 

1815 

1816 # Treatment parameters 

1817 if name == "vttreatmentduration": 

1818 try: 

1819 self.vt_treatment_duration = int(value) 

1820 return True 

1821 except Exception: 

1822 self.add_to_error_list(f"Invalid vttreatmentduration: {value}") 

1823 return False 

1824 if name == "vtmortality": 

1825 try: 

1826 self.vt_mortality = int(value) 

1827 return True 

1828 except Exception: 

1829 self.add_to_error_list(f"Invalid vtmortality: {value}") 

1830 return False 

1831 if name == "vttreatmentstart": 

1832 dt = parse_date(value) 

1833 if dt: 

1834 self.vt_treatment_start = dt 

1835 return True 

1836 self.add_to_error_list(f"Invalid vttreatmentstart: {value}") 

1837 return False 

1838 if name == "vtenable": 

1839 if self.colony and hasattr(self.colony, "set_vt_enable"): 

1840 self.colony.set_vt_enable(parse_bool(value)) 

1841 return True 

1842 return False 

1843 

1844 # Add more explicit parameter handling for other classes (nutrient contamination, cold storage, etc.) as needed 

1845 # Example: cold storage 

1846 if name == "coldstoragestart": 

1847 dt = parse_date(value) 

1848 if dt and hasattr(self, "cold_storage_simulator"): 

1849 self.cold_storage_simulator.set_start_date(dt) 

1850 return True 

1851 if name == "coldstorageend": 

1852 dt = parse_date(value) 

1853 if dt and hasattr(self, "cold_storage_simulator"): 

1854 self.cold_storage_simulator.set_end_date(dt) 

1855 return True 

1856 if name == "coldstorageenable": 

1857 if hasattr(self, "cold_storage_simulator"): 

1858 self.cold_storage_simulator.set_enabled(parse_bool(value)) 

1859 return True 

1860 

1861 # Fallback: unknown parameter 

1862 self.add_to_error_list(f"Unknown parameter: {param_name}") 

1863 return False 

1864 

1865 def _generate_initial_conditions_row(self): 

1866 """ 

1867 Generate the Initial row that shows exact initial conditions. 

1868 Matches C++ logic from session.cpp lines 418-475. 

1869 This row has date="Initial" and shows the colony state before any simulation days. 

1870 """ 

1871 if not self.colony: 

1872 return "Initial 0 0 0 0 0 0 0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 0 0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 No" 

1873 

1874 # Generate Initial row using exact C++ field order and formatting 

1875 initial_data = [ 

1876 "Initial ", # "%s" - Date field = "Initial" (padded like C++) 

1877 "%6d" % self.colony.get_colony_size(), # "%6d" - Colony size 

1878 "%8d" 

1879 % self.colony.get_adult_drones(), # "%8d" - Adult Drones (Dadl.GetQuantity()) 

1880 "%8d" 

1881 % self.colony.get_adult_workers(), # "%8d" - Adult Workers (Wadl.GetQuantity()) 

1882 "%8d" 

1883 % self.colony.get_foragers(), # "%8d" - Foragers (foragers.GetQuantity()) 

1884 "%8d" 

1885 % self.colony.get_active_foragers(), # "%8d" - Active Foragers (foragers.GetActiveQuantity()) 

1886 "%7d" 

1887 % self.colony.get_drone_brood(), # "%7d" - Drone Brood (CapDrn.GetQuantity()) 

1888 "%6d" 

1889 % self.colony.get_worker_brood(), # "%6d" - Worker Brood (CapWkr.GetQuantity()) 

1890 "%6d" 

1891 % self.colony.get_drone_larvae(), # "%6d" - Drone Larvae (Dlarv.GetQuantity()) 

1892 "%6d" 

1893 % self.colony.get_worker_larvae(), # "%6d" - Worker Larvae (Wlarv.GetQuantity()) 

1894 "%6d" 

1895 % self.colony.get_drone_eggs(), # "%6d" - Drone Eggs (Deggs.GetQuantity()) 

1896 "%6d" 

1897 % self.colony.get_worker_eggs(), # "%6d" - Worker Eggs (Weggs.GetQuantity()) 

1898 "%6d" 

1899 % self.colony.get_total_eggs_laid_today(), # "%6d" - Total Eggs (GetEggsToday()) 

1900 "%7.2f" % 0.0, # "%7.2f" - DD (GetDDToday() - 0 for Initial) 

1901 "%6.2f" % 0.0, # "%6.2f" - L (GetLToday() - 0 for Initial) 

1902 "%6.2f" % 0.0, # "%6.2f" - N (GetNToday() - 0 for Initial) 

1903 "%8.2f" % 0.0, # "%8.2f" - P (GetPToday() - 0 for Initial) 

1904 "%7.2f" % 0.0, # "%7.2f" - dd (GetddToday() - 0 for Initial) 

1905 "%6.2f" % 0.0, # "%6.2f" - l (GetlToday() - 0 for Initial) 

1906 "%8.2f" % 0.0, # "%8.2f" - n (GetnToday() - 0 for Initial) 

1907 "%6.2f" 

1908 % self.colony.get_free_mites(), # "%6.2f" - Free Mites (RunMite.GetTotal()) 

1909 "%6.2f" 

1910 % self.colony.get_drone_brood_mites(), # "%6.2f" - DBrood Mites (CapDrn.GetMiteCount()) 

1911 "%6.2f" 

1912 % self.colony.get_worker_brood_mites(), # "%6.2f" - WBrood Mites (CapWkr.GetMiteCount()) 

1913 "%6.2f" 

1914 % self.colony.get_mites_per_drone_brood(), # "%6.2f" - DMite/Cell (CapDrn.GetMitesPerCell()) 

1915 "%6.2f" 

1916 % self.colony.get_mites_per_worker_brood(), # "%6.2f" - WMite/Cell (CapWkr.GetMitesPerCell()) 

1917 "%6.0f" % 0, # "%6.0f" - Mites Dying (0 for Initial) 

1918 "%6.0f" % 0.0, # "%6.0f" - Prop Mites Dying (0.0 for Initial) 

1919 "%8.1f" 

1920 % 0.0, # "%8.1f" - Colony Pollen (0.0 for Initial - matches C++ logic) 

1921 "%7.4f" % 0.0, # "%7.4f" - Conc Pollen Pest (0.0 for Initial) 

1922 "%8.1f" 

1923 % 0.0, # "%8.1f" - Colony Nectar (0.0 for Initial - matches C++ logic) 

1924 "%7.4f" % 0.0, # "%7.4f" - Conc Nectar Pest (0.0 for Initial) 

1925 "%6d" % 0, # "%6d" - Dead DLarv (0 for Initial) 

1926 "%6d" % 0, # "%6d" - Dead WLarv (0 for Initial) 

1927 "%6d" % 0, # "%6d" - Dead DAdlt (0 for Initial) 

1928 "%6d" % 0, # "%6d" - Dead WAdlt (0 for Initial) 

1929 "%6d" % 0, # "%6d" - Dead Foragers (0 for Initial) 

1930 "%8.3f" 

1931 % self.colony.get_queen_strength(), # "%8.3f" - Queen Strength (queen.GetQueenStrength()) 

1932 "%8.3f" % 0.0, # "%8.3f" - Ave Temp (0.0 for Initial - no weather yet) 

1933 "%6.3f" % 0.0, # "%6.3f" - Rain (0.0 for Initial) 

1934 "%8.3f" % 0.0, # "%8.3f" - Min Temp (0.0 for Initial) 

1935 "%8.3f" % 0.0, # "%8.3f" - Max Temp (0.0 for Initial) 

1936 "%8.2f" 

1937 % 0.0, # "%8.2f" - Daylight Hours (0.0 for Initial) - KEY FORMATTING 

1938 "%8.2f" % 0.0, # "%8.2f" - Activity Ratio (0.0 for Initial) 

1939 "No", # "%s" - Forage Day ("No" for Initial) 

1940 ] 

1941 

1942 return " ".join(initial_data) 

1943 

1944 def initialize_simulation(self): 

1945 self.results_text.clear() 

1946 self.results_header.clear() 

1947 self.results_file_header.clear() 

1948 self.inc_immigrating_mites = 0 

1949 if self.colony: 

1950 self.colony.initialize_colony() 

1951 self.colony.set_mite_pct_resistance(self.init_mite_pct_resistant) 

1952 

1953 # Transfer VT enable flag from session to colony 

1954 if hasattr(self, "vt_enable"): 

1955 self.colony.set_vt_enable(self.vt_enable) 

1956 

1957 # PYTHON-SPECIFIC EXTENSION: Create VT treatment item from individual parameters 

1958 # This functionality is MISSING from the C++ implementation! 

1959 # The C++ code stores VT parameters but never converts them into a treatment item. 

1960 # This is a bug fix/enhancement that the Python port provides. 

1961 if ( 

1962 hasattr(self, "vt_enable") 

1963 and self.vt_enable 

1964 and hasattr(self, "vt_treatment_start") 

1965 and self.vt_treatment_start 

1966 and hasattr(self, "vt_treatment_duration") 

1967 and self.vt_treatment_duration > 0 

1968 and hasattr(self, "vt_mortality") 

1969 and self.vt_mortality > 0 

1970 ): 

1971 

1972 # Create the VT treatment item from the individual VT parameters 

1973 # This bridges the gap between parameter storage and actual treatment application 

1974 # Use the init_mite_pct_resistant as the resistance percentage for VT treatment 

1975 # This matches the expected behavior where VT treatment affects mites based on resistance 

1976 vt_treatment = MiteTreatmentItem( 

1977 start_time=self.vt_treatment_start, 

1978 duration=self.vt_treatment_duration, 

1979 pct_mortality=float(self.vt_mortality), 

1980 pct_resistant=float(self.init_mite_pct_resistant), 

1981 ) 

1982 

1983 # Add the VT treatment to the colony's mite treatment info 

1984 self.colony.m_mite_treatment_info.add_item(vt_treatment) 

1985 

1986 if self.is_error_reporting_enabled(): 

1987 self.add_to_info_list( 

1988 f"VT Treatment created: Start={self.vt_treatment_start.strftime('%m/%d/%Y')}, Duration={self.vt_treatment_duration} days, Mortality={self.vt_mortality}%, Resistance={self.init_mite_pct_resistant}%" 

1989 ) 

1990 

1991 self.cum_immigrating_mites = 0 

1992 self.first_result_entry = True