1use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9use std::time::{SystemTime, UNIX_EPOCH};
10
11use crate::architecture_simulator::{ArchitectureSimulator, SimulationConfig, SimulationMetrics};
12use crate::eureka::EurekaLog;
13use crate::knowledge_base::KnowledgeBase;
14use crate::meta_supervision::MetaSupervisor;
15use crate::salience::SalienceState;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub enum DecisionType {
22 ArchitectureSelection,
24 ResourceAllocation,
26 OptimizationStrategy,
28 RiskManagement,
30 LearningStrategy,
32 Custom(String),
34}
35
36#[derive(Debug, Clone)]
38pub struct DecisionOption {
39 pub id: String,
40 pub description: String,
41 pub decision_type: DecisionType,
42 pub expected_utility: f64,
44 pub risk_level: f64,
46 pub execution_cost: f64,
48 pub confidence: f64,
50 pub salience_score: f64,
52 pub eureka_score: f64,
54 pub meta_score: f64,
56 pub simulation_prediction: Option<SimulationMetrics>,
58}
59
60#[derive(Debug, Clone)]
62pub struct Decision {
63 pub id: String,
64 pub timestamp: u64,
65 pub selected_option: DecisionOption,
66 pub alternatives: Vec<DecisionOption>,
67 pub reasoning: Vec<String>,
68 pub confidence_level: f64,
69 pub expected_outcome: String,
70}
71
72#[derive(Debug, Clone)]
74pub struct HeuristicPattern {
75 pub id: String,
76 pub name: String,
77 pub pattern_type: String,
78 pub context: String,
80 pub success_rate: f64,
82 pub usage_count: u64,
84 pub last_used: u64,
86 pub weight: f64,
88}
89
90#[derive(Debug, Clone)]
92pub struct DecisionLogEntry {
93 pub timestamp: u64,
94 pub decision_id: String,
95 pub decision_type: DecisionType,
96 pub selected_option: String,
97 pub outcome_score: f64,
98 pub reflection: String,
99}
100
101pub struct HeuristicDecisionMaker {
105 salience: Arc<RwLock<SalienceState>>,
107 eureka: Arc<RwLock<EurekaLog>>,
109 meta_supervisor: Arc<RwLock<MetaSupervisor>>,
111 simulator: Arc<ArchitectureSimulator>,
113 knowledge_base: Arc<KnowledgeBase>,
115 patterns: Arc<RwLock<HashMap<String, HeuristicPattern>>>,
117 decision_log: Arc<RwLock<Vec<DecisionLogEntry>>>,
119 preference: String,
121 learning_rate: f64,
123}
124
125impl HeuristicDecisionMaker {
126 pub fn new(
128 salience: Arc<RwLock<SalienceState>>,
129 eureka: Arc<RwLock<EurekaLog>>,
130 meta_supervisor: Arc<RwLock<MetaSupervisor>>,
131 simulator: Arc<ArchitectureSimulator>,
132 knowledge_base: Arc<KnowledgeBase>,
133 ) -> Self {
134 Self {
135 salience,
136 eureka,
137 meta_supervisor,
138 simulator,
139 knowledge_base,
140 patterns: Arc::new(RwLock::new(HashMap::new())),
141 decision_log: Arc::new(RwLock::new(Vec::new())),
142 preference: "balanced".to_string(),
143 learning_rate: 0.1,
144 }
145 }
146
147 pub fn set_preference(&mut self, preference: &str) {
149 self.preference = preference.to_string();
150 }
151
152 pub fn set_learning_rate(&mut self, rate: f64) {
154 self.learning_rate = rate.clamp(0.0, 1.0);
155 }
156
157 pub fn evaluate_options(&self, options: Vec<DecisionOption>) -> Vec<DecisionOption> {
159 let mut scored_options: Vec<(f64, DecisionOption)> = options
160 .into_iter()
161 .map(|opt| {
162 let score = self.calculate_option_score(&opt);
163 (score, opt)
164 })
165 .collect();
166
167 scored_options.sort_by(|a, b| b.0.total_cmp(&a.0));
169
170 scored_options.into_iter().map(|(_, opt)| opt).collect()
171 }
172
173 fn calculate_option_score(&self, option: &DecisionOption) -> f64 {
175 let mut score = 0.0;
176
177 score += option.expected_utility * 0.25;
179
180 let risk_factor = match self.preference.as_str() {
182 "risk_averse" => 1.0 - option.risk_level,
183 "aggressive" => option.risk_level,
184 _ => 0.5 + (0.5 - option.risk_level), };
186 score += risk_factor * 0.15;
187
188 score += (1.0 - option.execution_cost) * 0.1;
190
191 score += option.confidence * 0.1;
193
194 let salience_score = {
197 let salience = crate::sync_guard::read_lock(&self.salience);
198 let hash = SalienceState::topic_hash(&option.description);
199 let computed = salience.compute_salience(0.5f32, 0.5f32, 0.5f32, hash);
200 computed as f64
201 };
202 score += salience_score;
203
204 let eureka_score = {
207 let eureka = crate::sync_guard::read_lock(&self.eureka);
208 let events = &eureka.events;
209 if events.is_empty() {
210 0.0
211 } else {
212 let total: f32 = events.iter().map(|e| e.insight_score()).sum();
213 (total / events.len() as f32) as f64
214 }
215 };
216 score += eureka_score * 0.1;
217
218 let meta_score = {
220 let meta = crate::sync_guard::read_lock(&self.meta_supervisor);
221 let (current, _trend, _volatility) = meta.get_summary();
222 current as f64
223 };
224 score += meta_score * 0.1;
225
226 if let Some(ref sim) = option.simulation_prediction {
228 let stability_bonus = sim.stability_score * 0.05;
229 let resilience_bonus = sim.resilience_score * 0.05;
230 score += stability_bonus + resilience_bonus;
231 }
232
233 let pattern_correction = self.apply_learned_patterns(option);
235 score += pattern_correction * 0.1;
236
237 let kb_boost = {
239 let results = self.knowledge_base.search(&option.description, 3);
240 let boost: f64 = results
241 .iter()
242 .map(|r| r.entry.confidence * r.relevance_score.min(1.0) / 10.0)
243 .sum();
244 boost.min(0.2)
245 };
246 score += kb_boost;
247
248 score
249 }
250
251 fn apply_learned_patterns(&self, option: &DecisionOption) -> f64 {
253 let patterns = crate::sync_guard::read_lock(&self.patterns);
254 let mut correction = 0.0;
255
256 for pattern in patterns.values() {
257 if option.description.contains(&pattern.context)
259 || option
260 .decision_type
261 .to_string()
262 .contains(&pattern.pattern_type)
263 {
264 let recency_factor = if pattern.last_used > 0 {
265 let now = SystemTime::now()
266 .duration_since(UNIX_EPOCH)
267 .unwrap()
268 .as_secs();
269 let hours_since = (now - pattern.last_used) as f64 / 3600.0;
270 (-hours_since / 24.0).exp() } else {
272 0.0
273 };
274
275 correction += pattern.success_rate * pattern.weight * recency_factor;
276 }
277 }
278
279 correction
280 }
281
282 pub fn make_decision(&self, options: Vec<DecisionOption>) -> Option<Decision> {
284 if options.is_empty() {
285 return None;
286 }
287
288 let ranked = self.evaluate_options(options);
289 let best = ranked.first().cloned()?;
290 let alternatives: Vec<DecisionOption> = ranked.into_iter().skip(1).collect();
291
292 let reasoning = self.generate_reasoning(&best);
294
295 let decision = Decision {
296 id: format!("decision_{}", rand::random::<u32>()),
297 timestamp: SystemTime::now()
298 .duration_since(UNIX_EPOCH)
299 .unwrap()
300 .as_secs(),
301 selected_option: best.clone(),
302 alternatives,
303 reasoning,
304 confidence_level: best.confidence,
305 expected_outcome: format!(
306 "Expected utility: {:.2}, Risk: {:.2}, Confidence: {:.2}",
307 best.expected_utility, best.risk_level, best.confidence
308 ),
309 };
310
311 self.log_decision(&decision);
313
314 Some(decision)
315 }
316
317 fn generate_reasoning(&self, option: &DecisionOption) -> Vec<String> {
319 let mut reasoning = Vec::new();
320
321 reasoning.push(format!(
322 "Selected option: {} (type: {:?})",
323 option.description, option.decision_type
324 ));
325
326 if option.salience_score > 0.7 {
327 reasoning.push(format!(
328 "High salience detected ({:.2}) — option stands out from alternatives",
329 option.salience_score
330 ));
331 }
332
333 if option.eureka_score > 0.5 {
334 reasoning.push(format!(
335 "Eureka insight detected ({:.2}) — unexpected connection found",
336 option.eureka_score
337 ));
338 }
339
340 if let Some(ref sim) = option.simulation_prediction {
341 reasoning.push(format!(
342 "Simulation predicts: stability={:.2}, resilience={:.2}, bottleneck={}",
343 sim.stability_score,
344 sim.resilience_score,
345 sim.bottleneck_components.len()
346 ));
347 }
348
349 reasoning.push(format!(
350 "Risk/reward ratio: {:.2}",
351 option.expected_utility / (option.risk_level + 0.01)
352 ));
353
354 reasoning
355 }
356
357 fn log_decision(&self, decision: &Decision) {
359 let mut log = crate::sync_guard::write_lock(&self.decision_log);
360 log.push(DecisionLogEntry {
361 timestamp: decision.timestamp,
362 decision_id: decision.id.clone(),
363 decision_type: decision.selected_option.decision_type.clone(),
364 selected_option: decision.selected_option.description.clone(),
365 outcome_score: 0.0, reflection: String::new(),
367 });
368 }
369
370 pub fn evaluate_decision_outcome(
372 &self,
373 decision_id: &str,
374 outcome_score: f64,
375 reflection: &str,
376 ) {
377 let mut log = crate::sync_guard::write_lock(&self.decision_log);
378
379 if let Some(entry) = log.iter_mut().find(|e| e.decision_id == decision_id) {
380 entry.outcome_score = outcome_score;
381 entry.reflection = reflection.to_string();
382
383 self.learn_from_outcome(entry, outcome_score);
385 }
386 }
387
388 fn learn_from_outcome(&self, entry: &DecisionLogEntry, outcome_score: f64) {
390 let mut patterns = crate::sync_guard::write_lock(&self.patterns);
391 let pattern_id = format!("pattern_{}", entry.decision_type);
392
393 let pattern = patterns.entry(pattern_id).or_insert(HeuristicPattern {
394 id: format!("pattern_{}", rand::random::<u32>()),
395 name: format!("{}_pattern", entry.decision_type),
396 pattern_type: entry.decision_type.to_string(),
397 context: entry.selected_option.clone(),
398 success_rate: 0.5,
399 usage_count: 0,
400 last_used: entry.timestamp,
401 weight: 0.5,
402 });
403
404 pattern.usage_count += 1;
405 pattern.last_used = entry.timestamp;
406
407 let learning = self.learning_rate;
409 pattern.success_rate = pattern.success_rate * (1.0 - learning) + outcome_score * learning;
410
411 if outcome_score > 0.7 {
413 pattern.weight = (pattern.weight + learning).min(1.0);
414 } else if outcome_score < 0.3 {
415 pattern.weight = (pattern.weight - learning).max(0.0);
416 }
417 }
418
419 pub fn recommend_architecture(&self, requirements: &str) -> Option<Decision> {
421 let architectures = self.simulator.list_architectures();
422
423 if architectures.is_empty() {
424 return None;
425 }
426
427 let options: Vec<DecisionOption> = architectures
428 .iter()
429 .map(|arch| {
430 let config = SimulationConfig {
432 duration_secs: 30.0,
433 time_step_ms: 100.0,
434 max_concurrent_requests: 500,
435 load_pattern: "sine".to_string(),
436 peak_load: 0.7,
437 enable_fault_injection: true,
438 fault_rate: 0.01,
439 };
440
441 let sim_result = self.simulator.run_simulation(&arch.id, &config);
442
443 let salience_score = {
445 let salience = crate::sync_guard::read_lock(&self.salience);
446 let hash =
447 SalienceState::topic_hash(&format!("{} {}", arch.name, requirements));
448 salience.compute_salience(0.5f32, 0.5f32, 0.5f32, hash) as f64
449 };
450
451 let eureka_score = {
453 let eureka = crate::sync_guard::read_lock(&self.eureka);
454 let events = &eureka.events;
455 if events.is_empty() {
456 0.0
457 } else {
458 let total: f32 = events.iter().map(|e| e.insight_score()).sum();
459 (total / events.len() as f32) as f64
460 }
461 };
462
463 let meta_score = {
465 let meta = crate::sync_guard::read_lock(&self.meta_supervisor);
466 let (current, _trend, _volatility) = meta.get_summary();
467 current as f64
468 };
469
470 DecisionOption {
471 id: arch.id.clone(),
472 description: format!("{}: {}", arch.name, arch.description),
473 decision_type: DecisionType::ArchitectureSelection,
474 expected_utility: arch.cohesion_score,
475 risk_level: 1.0 - arch.cohesion_score,
476 execution_cost: 0.3,
477 confidence: 0.7,
478 salience_score,
479 eureka_score,
480 meta_score,
481 simulation_prediction: sim_result,
482 }
483 })
484 .collect();
485
486 self.make_decision(options)
487 }
488
489 pub fn quick_decision(
491 &self,
492 options: Vec<DecisionOption>,
493 time_budget_ms: u64,
494 ) -> Option<Decision> {
495 let start = std::time::Instant::now();
496
497 let scored: Vec<(f64, DecisionOption)> = options
499 .into_iter()
500 .map(|opt| {
501 let elapsed = start.elapsed().as_millis() as u64;
502 if elapsed > time_budget_ms {
503 return (opt.expected_utility, opt);
505 }
506
507 let score = opt.expected_utility * 0.4
509 + (1.0 - opt.risk_level) * 0.3
510 + opt.confidence * 0.3;
511 (score, opt)
512 })
513 .collect();
514
515 let best = scored.into_iter().max_by(|a, b| a.0.total_cmp(&b.0))?;
516
517 Some(Decision {
518 id: format!("quick_decision_{}", rand::random::<u32>()),
519 timestamp: SystemTime::now()
520 .duration_since(UNIX_EPOCH)
521 .unwrap()
522 .as_secs(),
523 selected_option: best.1,
524 alternatives: Vec::new(),
525 reasoning: vec!["Quick decision — limited evaluation".to_string()],
526 confidence_level: 0.5,
527 expected_outcome: "Quick heuristic assessment".to_string(),
528 })
529 }
530
531 pub fn recognize_patterns(&self) -> Vec<HeuristicPattern> {
533 let log = crate::sync_guard::read_lock(&self.decision_log);
534 let mut pattern_map: HashMap<String, Vec<f64>> = HashMap::new();
535
536 for entry in log.iter() {
537 let key = format!("{:?}", entry.decision_type);
538 pattern_map
539 .entry(key)
540 .or_default()
541 .push(entry.outcome_score);
542 }
543
544 pattern_map
545 .into_iter()
546 .map(|(key, scores)| {
547 let avg_score = scores.iter().sum::<f64>() / scores.len() as f64;
548 HeuristicPattern {
549 id: format!("recognized_{}", rand::random::<u32>()),
550 name: format!("{}_pattern", key),
551 pattern_type: key.clone(),
552 context: "general".to_string(),
553 success_rate: avg_score,
554 usage_count: scores.len() as u64,
555 last_used: SystemTime::now()
556 .duration_since(UNIX_EPOCH)
557 .unwrap()
558 .as_secs(),
559 weight: avg_score,
560 }
561 })
562 .collect()
563 }
564
565 pub fn get_statistics(&self) -> DecisionStatistics {
567 let log = crate::sync_guard::read_lock(&self.decision_log);
568 let patterns = crate::sync_guard::read_lock(&self.patterns);
569
570 let total_decisions = log.len() as u64;
571 let successful = log.iter().filter(|e| e.outcome_score > 0.7).count() as u64;
572 let failed = log.iter().filter(|e| e.outcome_score < 0.3).count() as u64;
573
574 DecisionStatistics {
575 total_decisions,
576 successful_decisions: successful,
577 failed_decisions: failed,
578 success_rate: if total_decisions > 0 {
579 successful as f64 / total_decisions as f64
580 } else {
581 0.0
582 },
583 learned_patterns: patterns.len() as u64,
584 current_preference: self.preference.clone(),
585 learning_rate: self.learning_rate,
586 }
587 }
588
589 pub fn export_decision_log(&self) -> Vec<DecisionLogEntry> {
591 let log = crate::sync_guard::read_lock(&self.decision_log);
592 log.clone()
593 }
594
595 pub fn export_patterns(&self) -> Vec<HeuristicPattern> {
597 let patterns = crate::sync_guard::read_lock(&self.patterns);
598 patterns.values().cloned().collect()
599 }
600}
601
602#[derive(Debug, Clone)]
604pub struct DecisionStatistics {
605 pub total_decisions: u64,
606 pub successful_decisions: u64,
607 pub failed_decisions: u64,
608 pub success_rate: f64,
609 pub learned_patterns: u64,
610 pub current_preference: String,
611 pub learning_rate: f64,
612}
613
614pub fn create_option(
618 description: &str,
619 decision_type: DecisionType,
620 expected_utility: f64,
621 risk_level: f64,
622) -> DecisionOption {
623 DecisionOption {
624 id: format!("opt_{}", rand::random::<u32>()),
625 description: description.to_string(),
626 decision_type,
627 expected_utility,
628 risk_level,
629 execution_cost: 0.5,
630 confidence: 0.7,
631 salience_score: 0.5,
632 eureka_score: 0.0,
633 meta_score: 0.5,
634 simulation_prediction: None,
635 }
636}
637
638impl std::fmt::Display for DecisionType {
640 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
641 match self {
642 DecisionType::ArchitectureSelection => write!(f, "architecture_selection"),
643 DecisionType::ResourceAllocation => write!(f, "resource_allocation"),
644 DecisionType::OptimizationStrategy => write!(f, "optimization_strategy"),
645 DecisionType::RiskManagement => write!(f, "risk_management"),
646 DecisionType::LearningStrategy => write!(f, "learning_strategy"),
647 DecisionType::Custom(s) => write!(f, "custom_{}", s),
648 }
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655 use crate::architecture_simulator::ArchitectureSimulator;
656 use crate::eureka::EurekaLog;
657 use crate::knowledge_base::KnowledgeBase;
658 use crate::meta_supervision::MetaSupervisor;
659 use crate::salience::SalienceState;
660 use std::path::Path;
661
662 fn create_test_decision_maker() -> HeuristicDecisionMaker {
663 let data_dir = Path::new("data");
664 let salience = Arc::new(RwLock::new(SalienceState::load_or_init(data_dir)));
665 let eureka = Arc::new(RwLock::new(EurekaLog::load_or_init(data_dir)));
666 let meta = Arc::new(RwLock::new(MetaSupervisor::new()));
667 let simulator = Arc::new(ArchitectureSimulator::new());
668 let kb = Arc::new(KnowledgeBase::new());
669
670 HeuristicDecisionMaker::new(salience, eureka, meta, simulator, kb)
671 }
672
673 #[test]
674 fn test_make_decision() {
675 let dm = create_test_decision_maker();
676
677 let options = vec![
678 create_option(
679 "Use microservices architecture",
680 DecisionType::ArchitectureSelection,
681 0.8,
682 0.3,
683 ),
684 create_option(
685 "Use monolithic architecture",
686 DecisionType::ArchitectureSelection,
687 0.5,
688 0.1,
689 ),
690 create_option(
691 "Use serverless architecture",
692 DecisionType::ArchitectureSelection,
693 0.6,
694 0.5,
695 ),
696 ];
697
698 let decision = dm.make_decision(options);
699 assert!(decision.is_some());
700 let decision = decision.unwrap();
701 assert!(!decision.reasoning.is_empty());
702 assert!(decision.confidence_level > 0.0);
703 }
704
705 #[test]
706 fn test_quick_decision() {
707 let dm = create_test_decision_maker();
708
709 let options = vec![
710 create_option("Option A", DecisionType::ResourceAllocation, 0.9, 0.2),
711 create_option("Option B", DecisionType::ResourceAllocation, 0.6, 0.4),
712 ];
713
714 let decision = dm.quick_decision(options, 100);
715 assert!(decision.is_some());
716 }
717
718 #[test]
719 fn test_learn_from_outcome() {
720 let dm = create_test_decision_maker();
721
722 let options = vec![create_option(
723 "Test option",
724 DecisionType::LearningStrategy,
725 0.7,
726 0.3,
727 )];
728
729 let decision = dm.make_decision(options).unwrap();
730 dm.evaluate_decision_outcome(&decision.id, 0.9, "Good decision");
731
732 let stats = dm.get_statistics();
733 assert!(stats.total_decisions > 0);
734 assert!(stats.success_rate > 0.0);
735 }
736
737 #[test]
738 fn test_pattern_recognition() {
739 let dm = create_test_decision_maker();
740
741 for i in 0..5 {
743 let options = vec![create_option(
744 &format!("Pattern option {}", i),
745 DecisionType::ArchitectureSelection,
746 0.7,
747 0.3,
748 )];
749 if let Some(decision) = dm.make_decision(options) {
750 dm.evaluate_decision_outcome(
751 &decision.id,
752 0.8 + (i as f64 * 0.02),
753 "Consistent pattern",
754 );
755 }
756 }
757
758 let patterns = dm.recognize_patterns();
759 assert!(!patterns.is_empty());
760 }
761}