1use crate::memory::importance_assessment::{
2 CircuitBreakerConfig, ImportanceAssessmentConfig, ImportancePattern, PerformanceConfig,
3 ReferenceEmbedding, Stage1Config, Stage2Config, Stage3Config,
4};
5use anyhow::{Context, Result};
6use serde::{Deserialize, Serialize};
7use std::fs;
8use std::path::Path;
9use tracing::{info, warn};
10
11pub struct ImportanceAssessmentConfigLoader;
13
14impl ImportanceAssessmentConfigLoader {
15 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<ImportanceAssessmentConfig> {
17 let content = fs::read_to_string(&path)
18 .with_context(|| format!("Failed to read config file: {}", path.as_ref().display()))?;
19
20 let config: ImportanceAssessmentConfigFile = toml::from_str(&content)
21 .with_context(|| format!("Failed to parse config file: {}", path.as_ref().display()))?;
22
23 info!(
24 "Loaded importance assessment config from: {}",
25 path.as_ref().display()
26 );
27 Self::validate_and_convert(config)
28 }
29
30 pub fn load_from_env() -> Result<ImportanceAssessmentConfig> {
32 let mut config = ImportanceAssessmentConfig::default();
33
34 if let Ok(threshold) = std::env::var("CODEX_STAGE1_CONFIDENCE_THRESHOLD") {
36 config.stage1.confidence_threshold = threshold
37 .parse()
38 .context("Invalid CODEX_STAGE1_CONFIDENCE_THRESHOLD")?;
39 }
40
41 if let Ok(max_time) = std::env::var("CODEX_STAGE1_MAX_TIME_MS") {
42 config.stage1.max_processing_time_ms = max_time
43 .parse()
44 .context("Invalid CODEX_STAGE1_MAX_TIME_MS")?;
45 }
46
47 if let Ok(threshold) = std::env::var("CODEX_STAGE2_CONFIDENCE_THRESHOLD") {
49 config.stage2.confidence_threshold = threshold
50 .parse()
51 .context("Invalid CODEX_STAGE2_CONFIDENCE_THRESHOLD")?;
52 }
53
54 if let Ok(max_time) = std::env::var("CODEX_STAGE2_MAX_TIME_MS") {
55 config.stage2.max_processing_time_ms = max_time
56 .parse()
57 .context("Invalid CODEX_STAGE2_MAX_TIME_MS")?;
58 }
59
60 if let Ok(cache_ttl) = std::env::var("CODEX_STAGE2_CACHE_TTL_SECONDS") {
61 config.stage2.embedding_cache_ttl_seconds = cache_ttl
62 .parse()
63 .context("Invalid CODEX_STAGE2_CACHE_TTL_SECONDS")?;
64 }
65
66 if let Ok(similarity_threshold) = std::env::var("CODEX_STAGE2_SIMILARITY_THRESHOLD") {
67 config.stage2.similarity_threshold = similarity_threshold
68 .parse()
69 .context("Invalid CODEX_STAGE2_SIMILARITY_THRESHOLD")?;
70 }
71
72 if let Ok(max_time) = std::env::var("CODEX_STAGE3_MAX_TIME_MS") {
74 config.stage3.max_processing_time_ms = max_time
75 .parse()
76 .context("Invalid CODEX_STAGE3_MAX_TIME_MS")?;
77 }
78
79 if let Ok(endpoint) = std::env::var("CODEX_STAGE3_LLM_ENDPOINT") {
80 config.stage3.llm_endpoint = endpoint;
81 }
82
83 if let Ok(max_concurrent) = std::env::var("CODEX_STAGE3_MAX_CONCURRENT") {
84 config.stage3.max_concurrent_requests = max_concurrent
85 .parse()
86 .context("Invalid CODEX_STAGE3_MAX_CONCURRENT")?;
87 }
88
89 if let Ok(usage_pct) = std::env::var("CODEX_STAGE3_TARGET_USAGE_PCT") {
90 config.stage3.target_usage_percentage = usage_pct
91 .parse()
92 .context("Invalid CODEX_STAGE3_TARGET_USAGE_PCT")?;
93 }
94
95 if let Ok(failure_threshold) = std::env::var("CODEX_CB_FAILURE_THRESHOLD") {
97 config.circuit_breaker.failure_threshold = failure_threshold
98 .parse()
99 .context("Invalid CODEX_CB_FAILURE_THRESHOLD")?;
100 }
101
102 if let Ok(failure_window) = std::env::var("CODEX_CB_FAILURE_WINDOW_SECONDS") {
103 config.circuit_breaker.failure_window_seconds = failure_window
104 .parse()
105 .context("Invalid CODEX_CB_FAILURE_WINDOW_SECONDS")?;
106 }
107
108 if let Ok(recovery_timeout) = std::env::var("CODEX_CB_RECOVERY_TIMEOUT_SECONDS") {
109 config.circuit_breaker.recovery_timeout_seconds = recovery_timeout
110 .parse()
111 .context("Invalid CODEX_CB_RECOVERY_TIMEOUT_SECONDS")?;
112 }
113
114 if let Ok(min_requests) = std::env::var("CODEX_CB_MINIMUM_REQUESTS") {
115 config.circuit_breaker.minimum_requests = min_requests
116 .parse()
117 .context("Invalid CODEX_CB_MINIMUM_REQUESTS")?;
118 }
119
120 if let Ok(target_ms) = std::env::var("CODEX_PERF_STAGE1_TARGET_MS") {
122 config.performance.stage1_target_ms = target_ms
123 .parse()
124 .context("Invalid CODEX_PERF_STAGE1_TARGET_MS")?;
125 }
126
127 if let Ok(target_ms) = std::env::var("CODEX_PERF_STAGE2_TARGET_MS") {
128 config.performance.stage2_target_ms = target_ms
129 .parse()
130 .context("Invalid CODEX_PERF_STAGE2_TARGET_MS")?;
131 }
132
133 if let Ok(target_ms) = std::env::var("CODEX_PERF_STAGE3_TARGET_MS") {
134 config.performance.stage3_target_ms = target_ms
135 .parse()
136 .context("Invalid CODEX_PERF_STAGE3_TARGET_MS")?;
137 }
138
139 info!("Loaded importance assessment config from environment variables");
140 Self::validate_config(&config)?;
141 Ok(config)
142 }
143
144 pub fn load_with_fallback<P: AsRef<Path>>(
146 config_path: Option<P>,
147 ) -> Result<ImportanceAssessmentConfig> {
148 if let Some(path) = config_path {
150 if path.as_ref().exists() {
151 match Self::load_from_file(path) {
152 Ok(config) => return Ok(config),
153 Err(e) => {
154 warn!(
155 "Failed to load config from file, falling back to environment: {}",
156 e
157 );
158 }
159 }
160 } else {
161 info!(
162 "Config file not found: {}, using environment/defaults",
163 path.as_ref().display()
164 );
165 }
166 }
167
168 Self::load_from_env()
170 }
171
172 pub fn production_config() -> ImportanceAssessmentConfig {
174 let mut config = ImportanceAssessmentConfig::default();
175
176 config.stage1.confidence_threshold = 0.7; config.stage1.max_processing_time_ms = 5; config.stage2.confidence_threshold = 0.8; config.stage2.max_processing_time_ms = 50; config.stage2.embedding_cache_ttl_seconds = 7200; config.stage2.similarity_threshold = 0.75; config.stage3.max_processing_time_ms = 500; config.stage3.max_concurrent_requests = 3; config.stage3.target_usage_percentage = 15.0; config.circuit_breaker.failure_threshold = 3; config.circuit_breaker.failure_window_seconds = 30; config.circuit_breaker.recovery_timeout_seconds = 60; config.circuit_breaker.minimum_requests = 2; config.performance.stage1_target_ms = 5; config.performance.stage2_target_ms = 50; config.performance.stage3_target_ms = 500; info!("Created production-optimized importance assessment config");
199 config
200 }
201
202 pub fn development_config() -> ImportanceAssessmentConfig {
204 let mut config = ImportanceAssessmentConfig::default();
205
206 config.stage1.confidence_threshold = 0.5; config.stage1.max_processing_time_ms = 20; config.stage2.confidence_threshold = 0.6; config.stage2.max_processing_time_ms = 200; config.stage2.embedding_cache_ttl_seconds = 1800; config.stage2.similarity_threshold = 0.6; config.stage3.max_processing_time_ms = 2000; config.stage3.max_concurrent_requests = 10; config.stage3.target_usage_percentage = 30.0; config.circuit_breaker.failure_threshold = 10; config.circuit_breaker.failure_window_seconds = 120; config.circuit_breaker.recovery_timeout_seconds = 30; config.circuit_breaker.minimum_requests = 5; config.performance.stage1_target_ms = 20; config.performance.stage2_target_ms = 200; config.performance.stage3_target_ms = 2000; info!("Created development-optimized importance assessment config");
229 config
230 }
231
232 pub fn export_to_file<P: AsRef<Path>>(
234 config: &ImportanceAssessmentConfig,
235 path: P,
236 ) -> Result<()> {
237 let config_file = Self::convert_to_file_format(config);
238 let toml_content =
239 toml::to_string_pretty(&config_file).context("Failed to serialize config to TOML")?;
240
241 fs::write(&path, toml_content)
242 .with_context(|| format!("Failed to write config file: {}", path.as_ref().display()))?;
243
244 info!(
245 "Exported importance assessment config to: {}",
246 path.as_ref().display()
247 );
248 Ok(())
249 }
250
251 fn validate_and_convert(
252 config_file: ImportanceAssessmentConfigFile,
253 ) -> Result<ImportanceAssessmentConfig> {
254 let config = ImportanceAssessmentConfig {
255 stage1: Stage1Config {
256 confidence_threshold: config_file.stage1.confidence_threshold,
257 pattern_library: config_file
258 .stage1
259 .patterns
260 .into_iter()
261 .map(|p| ImportancePattern {
262 name: p.name,
263 pattern: p.pattern,
264 weight: p.weight,
265 context_boosters: p.context_boosters.unwrap_or_default(),
266 category: p.category,
267 })
268 .collect(),
269 max_processing_time_ms: config_file.stage1.max_processing_time_ms,
270 },
271 stage2: Stage2Config {
272 confidence_threshold: config_file.stage2.confidence_threshold,
273 max_processing_time_ms: config_file.stage2.max_processing_time_ms,
274 embedding_cache_ttl_seconds: config_file.stage2.embedding_cache_ttl_seconds,
275 embedding_cache_max_size: config_file
276 .stage2
277 .embedding_cache_max_size
278 .unwrap_or(10000),
279 cache_eviction_threshold: config_file
280 .stage2
281 .cache_eviction_threshold
282 .unwrap_or(0.8),
283 similarity_threshold: config_file.stage2.similarity_threshold,
284 reference_embeddings: config_file
285 .stage2
286 .reference_embeddings
287 .unwrap_or_default()
288 .into_iter()
289 .map(|r| ReferenceEmbedding {
290 name: r.name,
291 embedding: r.embedding,
292 weight: r.weight,
293 category: r.category,
294 })
295 .collect(),
296 },
297 stage3: Stage3Config {
298 max_processing_time_ms: config_file.stage3.max_processing_time_ms,
299 llm_endpoint: config_file.stage3.llm_endpoint,
300 max_concurrent_requests: config_file.stage3.max_concurrent_requests,
301 prompt_template: config_file.stage3.prompt_template,
302 target_usage_percentage: config_file.stage3.target_usage_percentage,
303 },
304 circuit_breaker: CircuitBreakerConfig {
305 failure_threshold: config_file.circuit_breaker.failure_threshold,
306 failure_window_seconds: config_file.circuit_breaker.failure_window_seconds,
307 recovery_timeout_seconds: config_file.circuit_breaker.recovery_timeout_seconds,
308 minimum_requests: config_file.circuit_breaker.minimum_requests,
309 },
310 performance: PerformanceConfig {
311 stage1_target_ms: config_file.performance.stage1_target_ms,
312 stage2_target_ms: config_file.performance.stage2_target_ms,
313 stage3_target_ms: config_file.performance.stage3_target_ms,
314 },
315 };
316
317 Self::validate_config(&config)?;
318 Ok(config)
319 }
320
321 fn convert_to_file_format(
322 config: &ImportanceAssessmentConfig,
323 ) -> ImportanceAssessmentConfigFile {
324 ImportanceAssessmentConfigFile {
325 stage1: Stage1ConfigFile {
326 confidence_threshold: config.stage1.confidence_threshold,
327 max_processing_time_ms: config.stage1.max_processing_time_ms,
328 patterns: config
329 .stage1
330 .pattern_library
331 .iter()
332 .map(|p| ImportancePatternFile {
333 name: p.name.clone(),
334 pattern: p.pattern.clone(),
335 weight: p.weight,
336 context_boosters: if p.context_boosters.is_empty() {
337 None
338 } else {
339 Some(p.context_boosters.clone())
340 },
341 category: p.category.clone(),
342 })
343 .collect(),
344 },
345 stage2: Stage2ConfigFile {
346 confidence_threshold: config.stage2.confidence_threshold,
347 max_processing_time_ms: config.stage2.max_processing_time_ms,
348 embedding_cache_ttl_seconds: config.stage2.embedding_cache_ttl_seconds,
349 embedding_cache_max_size: Some(config.stage2.embedding_cache_max_size),
350 cache_eviction_threshold: Some(config.stage2.cache_eviction_threshold),
351 similarity_threshold: config.stage2.similarity_threshold,
352 reference_embeddings: if config.stage2.reference_embeddings.is_empty() {
353 None
354 } else {
355 Some(
356 config
357 .stage2
358 .reference_embeddings
359 .iter()
360 .map(|r| ReferenceEmbeddingFile {
361 name: r.name.clone(),
362 embedding: r.embedding.clone(),
363 weight: r.weight,
364 category: r.category.clone(),
365 })
366 .collect(),
367 )
368 },
369 },
370 stage3: Stage3ConfigFile {
371 max_processing_time_ms: config.stage3.max_processing_time_ms,
372 llm_endpoint: config.stage3.llm_endpoint.clone(),
373 max_concurrent_requests: config.stage3.max_concurrent_requests,
374 prompt_template: config.stage3.prompt_template.clone(),
375 target_usage_percentage: config.stage3.target_usage_percentage,
376 },
377 circuit_breaker: CircuitBreakerConfigFile {
378 failure_threshold: config.circuit_breaker.failure_threshold,
379 failure_window_seconds: config.circuit_breaker.failure_window_seconds,
380 recovery_timeout_seconds: config.circuit_breaker.recovery_timeout_seconds,
381 minimum_requests: config.circuit_breaker.minimum_requests,
382 },
383 performance: PerformanceConfigFile {
384 stage1_target_ms: config.performance.stage1_target_ms,
385 stage2_target_ms: config.performance.stage2_target_ms,
386 stage3_target_ms: config.performance.stage3_target_ms,
387 },
388 }
389 }
390
391 fn validate_config(config: &ImportanceAssessmentConfig) -> Result<()> {
392 if config.stage1.confidence_threshold < 0.0 || config.stage1.confidence_threshold > 1.0 {
394 return Err(anyhow::anyhow!(
395 "Stage 1 confidence threshold must be between 0.0 and 1.0"
396 ));
397 }
398
399 if config.stage1.max_processing_time_ms == 0 {
400 return Err(anyhow::anyhow!(
401 "Stage 1 max processing time must be greater than 0"
402 ));
403 }
404
405 if config.stage1.pattern_library.is_empty() {
406 return Err(anyhow::anyhow!("Stage 1 must have at least one pattern"));
407 }
408
409 for pattern in &config.stage1.pattern_library {
410 if pattern.weight < 0.0 || pattern.weight > 1.0 {
411 return Err(anyhow::anyhow!(
412 "Pattern '{}' weight must be between 0.0 and 1.0",
413 pattern.name
414 ));
415 }
416
417 if let Err(e) = regex::Regex::new(&pattern.pattern) {
419 return Err(anyhow::anyhow!(
420 "Pattern '{}' has invalid regex: {}",
421 pattern.name,
422 e
423 ));
424 }
425 }
426
427 if config.stage2.confidence_threshold < 0.0 || config.stage2.confidence_threshold > 1.0 {
429 return Err(anyhow::anyhow!(
430 "Stage 2 confidence threshold must be between 0.0 and 1.0"
431 ));
432 }
433
434 if config.stage2.max_processing_time_ms == 0 {
435 return Err(anyhow::anyhow!(
436 "Stage 2 max processing time must be greater than 0"
437 ));
438 }
439
440 if config.stage2.similarity_threshold < 0.0 || config.stage2.similarity_threshold > 1.0 {
441 return Err(anyhow::anyhow!(
442 "Stage 2 similarity threshold must be between 0.0 and 1.0"
443 ));
444 }
445
446 for reference in &config.stage2.reference_embeddings {
447 if reference.weight < 0.0 || reference.weight > 1.0 {
448 return Err(anyhow::anyhow!(
449 "Reference '{}' weight must be between 0.0 and 1.0",
450 reference.name
451 ));
452 }
453
454 if reference.embedding.is_empty() {
455 return Err(anyhow::anyhow!(
456 "Reference '{}' embedding cannot be empty",
457 reference.name
458 ));
459 }
460 }
461
462 if config.stage3.max_processing_time_ms == 0 {
464 return Err(anyhow::anyhow!(
465 "Stage 3 max processing time must be greater than 0"
466 ));
467 }
468
469 if config.stage3.max_concurrent_requests == 0 {
470 return Err(anyhow::anyhow!(
471 "Stage 3 max concurrent requests must be greater than 0"
472 ));
473 }
474
475 if config.stage3.target_usage_percentage < 0.0
476 || config.stage3.target_usage_percentage > 100.0
477 {
478 return Err(anyhow::anyhow!(
479 "Stage 3 target usage percentage must be between 0.0 and 100.0"
480 ));
481 }
482
483 if config.circuit_breaker.failure_threshold == 0 {
485 return Err(anyhow::anyhow!(
486 "Circuit breaker failure threshold must be greater than 0"
487 ));
488 }
489
490 if config.circuit_breaker.failure_window_seconds == 0 {
491 return Err(anyhow::anyhow!(
492 "Circuit breaker failure window must be greater than 0"
493 ));
494 }
495
496 if config.circuit_breaker.recovery_timeout_seconds == 0 {
497 return Err(anyhow::anyhow!(
498 "Circuit breaker recovery timeout must be greater than 0"
499 ));
500 }
501
502 if config.performance.stage1_target_ms == 0 {
504 return Err(anyhow::anyhow!(
505 "Stage 1 target time must be greater than 0"
506 ));
507 }
508
509 if config.performance.stage2_target_ms == 0 {
510 return Err(anyhow::anyhow!(
511 "Stage 2 target time must be greater than 0"
512 ));
513 }
514
515 if config.performance.stage3_target_ms == 0 {
516 return Err(anyhow::anyhow!(
517 "Stage 3 target time must be greater than 0"
518 ));
519 }
520
521 if config.stage1.confidence_threshold >= config.stage2.confidence_threshold {
523 warn!(
524 "Stage 1 confidence threshold ({}) should be lower than Stage 2 ({})",
525 config.stage1.confidence_threshold, config.stage2.confidence_threshold
526 );
527 }
528
529 if config.performance.stage1_target_ms >= config.performance.stage2_target_ms {
530 warn!(
531 "Stage 1 target time ({}) should be lower than Stage 2 ({})",
532 config.performance.stage1_target_ms, config.performance.stage2_target_ms
533 );
534 }
535
536 if config.performance.stage2_target_ms >= config.performance.stage3_target_ms {
537 warn!(
538 "Stage 2 target time ({}) should be lower than Stage 3 ({})",
539 config.performance.stage2_target_ms, config.performance.stage3_target_ms
540 );
541 }
542
543 Ok(())
544 }
545}
546
547#[derive(Debug, Serialize, Deserialize)]
549struct ImportanceAssessmentConfigFile {
550 stage1: Stage1ConfigFile,
551 stage2: Stage2ConfigFile,
552 stage3: Stage3ConfigFile,
553 circuit_breaker: CircuitBreakerConfigFile,
554 performance: PerformanceConfigFile,
555}
556
557#[derive(Debug, Serialize, Deserialize)]
558struct Stage1ConfigFile {
559 confidence_threshold: f64,
560 max_processing_time_ms: u64,
561 patterns: Vec<ImportancePatternFile>,
562}
563
564#[derive(Debug, Serialize, Deserialize)]
565struct ImportancePatternFile {
566 name: String,
567 pattern: String,
568 weight: f64,
569 context_boosters: Option<Vec<String>>,
570 category: String,
571}
572
573#[derive(Debug, Serialize, Deserialize)]
574struct Stage2ConfigFile {
575 confidence_threshold: f64,
576 max_processing_time_ms: u64,
577 embedding_cache_ttl_seconds: u64,
578 embedding_cache_max_size: Option<usize>,
579 cache_eviction_threshold: Option<f64>,
580 similarity_threshold: f32,
581 reference_embeddings: Option<Vec<ReferenceEmbeddingFile>>,
582}
583
584#[derive(Debug, Serialize, Deserialize)]
585struct ReferenceEmbeddingFile {
586 name: String,
587 embedding: Vec<f32>,
588 weight: f64,
589 category: String,
590}
591
592#[derive(Debug, Serialize, Deserialize)]
593struct Stage3ConfigFile {
594 max_processing_time_ms: u64,
595 llm_endpoint: String,
596 max_concurrent_requests: usize,
597 prompt_template: String,
598 target_usage_percentage: f64,
599}
600
601#[derive(Debug, Serialize, Deserialize)]
602struct CircuitBreakerConfigFile {
603 failure_threshold: usize,
604 failure_window_seconds: u64,
605 recovery_timeout_seconds: u64,
606 minimum_requests: usize,
607}
608
609#[derive(Debug, Serialize, Deserialize)]
610struct PerformanceConfigFile {
611 stage1_target_ms: u64,
612 stage2_target_ms: u64,
613 stage3_target_ms: u64,
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619 use tempfile::NamedTempFile;
620
621 #[test]
622 fn test_production_config_validation() {
623 let config = ImportanceAssessmentConfigLoader::production_config();
624 assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_ok());
625
626 assert!(config.stage1.confidence_threshold > 0.6);
628 assert!(config.stage2.confidence_threshold > 0.7);
629 assert!(config.stage3.target_usage_percentage < 20.0);
630 assert!(config.circuit_breaker.failure_threshold <= 5);
631 }
632
633 #[test]
634 fn test_development_config_validation() {
635 let config = ImportanceAssessmentConfigLoader::development_config();
636 assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_ok());
637
638 assert!(config.stage1.confidence_threshold <= 0.6);
640 assert!(config.stage2.confidence_threshold <= 0.7);
641 assert!(config.stage3.target_usage_percentage >= 25.0);
642 assert!(config.circuit_breaker.failure_threshold >= 5);
643 }
644
645 #[test]
646 fn test_config_export_import() -> Result<()> {
647 let original_config = ImportanceAssessmentConfigLoader::production_config();
648
649 let temp_file = NamedTempFile::new()?;
650 ImportanceAssessmentConfigLoader::export_to_file(&original_config, temp_file.path())?;
651
652 let loaded_config = ImportanceAssessmentConfigLoader::load_from_file(temp_file.path())?;
653
654 assert_eq!(
656 original_config.stage1.confidence_threshold,
657 loaded_config.stage1.confidence_threshold
658 );
659 assert_eq!(
660 original_config.stage2.confidence_threshold,
661 loaded_config.stage2.confidence_threshold
662 );
663 assert_eq!(
664 original_config.stage3.target_usage_percentage,
665 loaded_config.stage3.target_usage_percentage
666 );
667
668 Ok(())
669 }
670
671 #[test]
672 fn test_invalid_config_validation() {
673 let mut config = ImportanceAssessmentConfig::default();
674
675 config.stage1.confidence_threshold = 1.5;
677 assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_err());
678
679 config.stage1.confidence_threshold = 0.6;
680
681 config.stage2.similarity_threshold = 1.5;
683 assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_err());
684
685 config.stage2.similarity_threshold = 0.7;
686
687 config.stage1.max_processing_time_ms = 0;
689 assert!(ImportanceAssessmentConfigLoader::validate_config(&config).is_err());
690 }
691
692 #[test]
693 fn test_env_config_loading() {
694 std::env::set_var("CODEX_STAGE1_CONFIDENCE_THRESHOLD", "0.8");
696 std::env::set_var("CODEX_STAGE2_MAX_TIME_MS", "150");
697 std::env::set_var("CODEX_STAGE3_TARGET_USAGE_PCT", "25.0");
698
699 let config = ImportanceAssessmentConfigLoader::load_from_env().unwrap();
700
701 assert_eq!(config.stage1.confidence_threshold, 0.8);
702 assert_eq!(config.stage2.max_processing_time_ms, 150);
703 assert_eq!(config.stage3.target_usage_percentage, 25.0);
704
705 std::env::remove_var("CODEX_STAGE1_CONFIDENCE_THRESHOLD");
707 std::env::remove_var("CODEX_STAGE2_MAX_TIME_MS");
708 std::env::remove_var("CODEX_STAGE3_TARGET_USAGE_PCT");
709 }
710}