1use super::error::Result;
44use super::reflection_engine::{Insight, InsightType};
45use chrono::{DateTime, Duration, Utc};
46use serde::{Deserialize, Serialize};
47use std::collections::{HashMap, HashSet, VecDeque};
48use std::hash::{Hash, Hasher};
49use tracing::info;
50use uuid::Uuid;
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct LoopPreventionConfig {
55 pub semantic_similarity_threshold: f64,
57
58 pub max_causal_depth: usize,
60
61 pub insight_cooldown_hours: i64,
63
64 pub min_novelty_threshold: f64,
66
67 pub min_coherence_threshold: f64,
69
70 pub min_evidence_threshold: f64,
72
73 pub max_insights_per_cluster: usize,
75
76 pub tracking_window_days: i64,
78
79 pub diversity_bonus_multiplier: f64,
81
82 pub repetition_penalty_factor: f64,
84
85 pub max_derivation_depth: usize,
87
88 pub enable_quality_filtering: bool,
90
91 pub enable_semantic_fingerprinting: bool,
93
94 pub enable_causal_analysis: bool,
96}
97
98impl Default for LoopPreventionConfig {
99 fn default() -> Self {
100 Self {
101 semantic_similarity_threshold: 0.85,
102 max_causal_depth: 5,
103 insight_cooldown_hours: 2,
104 min_novelty_threshold: 0.3,
105 min_coherence_threshold: 0.5,
106 min_evidence_threshold: 0.4,
107 max_insights_per_cluster: 3,
108 tracking_window_days: 7,
109 diversity_bonus_multiplier: 1.2,
110 repetition_penalty_factor: 0.8,
111 max_derivation_depth: 4,
112 enable_quality_filtering: true,
113 enable_semantic_fingerprinting: true,
114 enable_causal_analysis: true,
115 }
116 }
117}
118
119#[derive(Debug, Clone, Hash, PartialEq, Eq)]
121pub struct SemanticFingerprint {
122 pub concept_hashes: Vec<u64>,
123 pub relationship_hashes: Vec<u64>,
124 pub content_hash: u64,
125 pub insight_type_hash: u64,
126}
127
128#[derive(Debug, Clone)]
130pub struct CausalNode {
131 pub insight_id: Uuid,
132 pub concept: String,
133 pub derivation_sources: Vec<Uuid>,
134 pub derivation_depth: usize,
135 pub created_at: DateTime<Utc>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct QualityAssessment {
141 pub novelty_score: f64,
142 pub coherence_score: f64,
143 pub evidence_strength: f64,
144 pub semantic_richness: f64,
145 pub predictive_power: f64,
146 pub overall_quality: f64,
147 pub quality_factors: Vec<String>,
148 pub deficiency_reasons: Vec<String>,
149}
150
151#[derive(Debug, Clone)]
153pub struct LoopDetectionResult {
154 pub has_loop: bool,
155 pub loop_path: Vec<Uuid>,
156 pub loop_type: LoopType,
157 pub severity: LoopSeverity,
158 pub prevention_action: PreventionAction,
159}
160
161#[derive(Debug, Clone, PartialEq)]
163pub enum LoopType {
164 SemanticDuplication,
165 CausalCircularity,
166 ConceptualSatiation,
167 DerivationLoop,
168 TemporalRepetition,
169}
170
171#[derive(Debug, Clone, PartialEq, PartialOrd)]
173pub enum LoopSeverity {
174 Low,
175 Medium,
176 High,
177 Critical,
178}
179
180#[derive(Debug, Clone, PartialEq)]
182pub enum PreventionAction {
183 Allow,
184 DelayGeneration,
185 ModifyInsight,
186 RejectInsight,
187 TriggerCooldown,
188 PruneRedundant,
189}
190
191#[derive(Debug, Clone)]
193pub struct InsightTrackingEntry {
194 pub insight_id: Uuid,
195 pub fingerprint: SemanticFingerprint,
196 pub insight_type: InsightType,
197 pub quality_assessment: QualityAssessment,
198 pub derivation_path: Vec<Uuid>,
199 pub created_at: DateTime<Utc>,
200 pub source_concepts: HashSet<String>,
201}
202
203pub struct LoopPreventionEngine {
205 config: LoopPreventionConfig,
206 insight_history: VecDeque<InsightTrackingEntry>,
207 concept_frequency: HashMap<String, usize>,
208 type_frequency: HashMap<InsightType, usize>,
209 #[allow(dead_code)]
210 causal_graph: HashMap<Uuid, CausalNode>,
211 cooldown_tracker: HashMap<u64, DateTime<Utc>>,
212}
213
214impl LoopPreventionEngine {
215 pub fn new(config: LoopPreventionConfig) -> Self {
216 Self {
217 config,
218 insight_history: VecDeque::new(),
219 concept_frequency: HashMap::new(),
220 type_frequency: HashMap::new(),
221 causal_graph: HashMap::new(),
222 cooldown_tracker: HashMap::new(),
223 }
224 }
225
226 pub fn validate_insight(&mut self, insight: &Insight) -> Result<LoopDetectionResult> {
228 let fingerprint = if self.config.enable_semantic_fingerprinting {
230 self.create_semantic_fingerprint(insight)?
231 } else {
232 SemanticFingerprint {
233 concept_hashes: Vec::new(),
234 relationship_hashes: Vec::new(),
235 content_hash: 0,
236 insight_type_hash: 0,
237 }
238 };
239
240 if let Some(duplicate_result) = self.check_semantic_duplication(&fingerprint)? {
242 return Ok(duplicate_result);
243 }
244
245 if self.config.enable_causal_analysis {
247 if let Some(causal_result) = self.check_causal_loops(insight)? {
248 return Ok(causal_result);
249 }
250 }
251
252 if let Some(temporal_result) = self.check_temporal_repetition(insight)? {
254 return Ok(temporal_result);
255 }
256
257 if let Some(satiation_result) = self.check_conceptual_satiation(insight)? {
259 return Ok(satiation_result);
260 }
261
262 let quality = if self.config.enable_quality_filtering {
264 self.assess_insight_quality(insight)?
265 } else {
266 QualityAssessment {
267 novelty_score: 1.0,
268 coherence_score: 1.0,
269 evidence_strength: 1.0,
270 semantic_richness: 1.0,
271 predictive_power: 1.0,
272 overall_quality: 1.0,
273 quality_factors: vec!["Quality filtering disabled".to_string()],
274 deficiency_reasons: Vec::new(),
275 }
276 };
277
278 if let Some(quality_result) = self.check_quality_thresholds(&quality)? {
280 return Ok(quality_result);
281 }
282
283 Ok(LoopDetectionResult {
285 has_loop: false,
286 loop_path: Vec::new(),
287 loop_type: LoopType::SemanticDuplication, severity: LoopSeverity::Low,
289 prevention_action: PreventionAction::Allow,
290 })
291 }
292
293 pub fn register_insight(
295 &mut self,
296 insight: &Insight,
297 quality: QualityAssessment,
298 ) -> Result<()> {
299 let fingerprint = if self.config.enable_semantic_fingerprinting {
300 self.create_semantic_fingerprint(insight)?
301 } else {
302 SemanticFingerprint {
303 concept_hashes: Vec::new(),
304 relationship_hashes: Vec::new(),
305 content_hash: 0,
306 insight_type_hash: 0,
307 }
308 };
309
310 let tracking_entry = InsightTrackingEntry {
311 insight_id: insight.id,
312 fingerprint: fingerprint.clone(),
313 insight_type: insight.insight_type.clone(),
314 quality_assessment: quality,
315 derivation_path: Vec::new(), created_at: insight.generated_at,
317 source_concepts: insight.related_concepts.iter().cloned().collect(),
318 };
319
320 self.insight_history.push_back(tracking_entry);
322
323 for concept in &insight.related_concepts {
325 *self.concept_frequency.entry(concept.clone()).or_insert(0) += 1;
326 }
327 *self
328 .type_frequency
329 .entry(insight.insight_type.clone())
330 .or_insert(0) += 1;
331
332 if self.config.enable_causal_analysis {
334 self.update_causal_graph(insight)?;
335 }
336
337 self.prune_old_entries();
339
340 info!(
341 "Registered insight {} in loop prevention system",
342 insight.id
343 );
344 Ok(())
345 }
346
347 fn create_semantic_fingerprint(&self, insight: &Insight) -> Result<SemanticFingerprint> {
349 use std::collections::hash_map::DefaultHasher;
350
351 let mut concept_hashes = Vec::new();
353 for concept in &insight.related_concepts {
354 let mut hasher = DefaultHasher::new();
355 concept.to_lowercase().hash(&mut hasher);
356 concept_hashes.push(hasher.finish());
357 }
358 concept_hashes.sort_unstable();
359
360 let mut content_hasher = DefaultHasher::new();
362 insight.content.to_lowercase().hash(&mut content_hasher);
363 let content_hash = content_hasher.finish();
364
365 let mut type_hasher = DefaultHasher::new();
367 insight.insight_type.hash(&mut type_hasher);
368 let insight_type_hash = type_hasher.finish();
369
370 let relationship_hashes = Vec::new(); Ok(SemanticFingerprint {
374 concept_hashes,
375 relationship_hashes,
376 content_hash,
377 insight_type_hash,
378 })
379 }
380
381 fn check_semantic_duplication(
383 &self,
384 fingerprint: &SemanticFingerprint,
385 ) -> Result<Option<LoopDetectionResult>> {
386 for entry in &self.insight_history {
387 let similarity =
388 self.calculate_fingerprint_similarity(fingerprint, &entry.fingerprint)?;
389
390 if similarity >= self.config.semantic_similarity_threshold {
391 let fingerprint_hash = self.hash_fingerprint(fingerprint);
393 if let Some(last_time) = self.cooldown_tracker.get(&fingerprint_hash) {
394 let hours_since = Utc::now().signed_duration_since(*last_time).num_hours();
395 if hours_since < self.config.insight_cooldown_hours {
396 return Ok(Some(LoopDetectionResult {
397 has_loop: true,
398 loop_path: vec![entry.insight_id],
399 loop_type: LoopType::SemanticDuplication,
400 severity: LoopSeverity::Medium,
401 prevention_action: PreventionAction::DelayGeneration,
402 }));
403 }
404 }
405
406 return Ok(Some(LoopDetectionResult {
407 has_loop: true,
408 loop_path: vec![entry.insight_id],
409 loop_type: LoopType::SemanticDuplication,
410 severity: if similarity > 0.95 {
411 LoopSeverity::High
412 } else {
413 LoopSeverity::Medium
414 },
415 prevention_action: if similarity > 0.95 {
416 PreventionAction::RejectInsight
417 } else {
418 PreventionAction::ModifyInsight
419 },
420 }));
421 }
422 }
423
424 Ok(None)
425 }
426
427 fn check_causal_loops(&self, insight: &Insight) -> Result<Option<LoopDetectionResult>> {
429 let mut derivation_path = Vec::new();
431 let mut visited = HashSet::new();
432
433 for &source_id in &insight.source_memory_ids {
435 if let Some(path) = self.trace_causal_path(source_id, &mut visited, 0)? {
436 if path.len() > self.config.max_derivation_depth {
437 return Ok(Some(LoopDetectionResult {
438 has_loop: true,
439 loop_path: path,
440 loop_type: LoopType::CausalCircularity,
441 severity: LoopSeverity::High,
442 prevention_action: PreventionAction::RejectInsight,
443 }));
444 }
445 derivation_path.extend(path);
446 }
447 }
448
449 for i in 0..derivation_path.len() {
451 for j in i + 1..derivation_path.len() {
452 if derivation_path[i] == derivation_path[j] {
453 return Ok(Some(LoopDetectionResult {
454 has_loop: true,
455 loop_path: derivation_path[i..=j].to_vec(),
456 loop_type: LoopType::DerivationLoop,
457 severity: LoopSeverity::Medium,
458 prevention_action: PreventionAction::ModifyInsight,
459 }));
460 }
461 }
462 }
463
464 Ok(None)
465 }
466
467 fn check_temporal_repetition(&self, insight: &Insight) -> Result<Option<LoopDetectionResult>> {
469 let recent_cutoff = Utc::now() - Duration::hours(self.config.insight_cooldown_hours);
470
471 let recent_same_type = self
472 .insight_history
473 .iter()
474 .filter(|entry| {
475 entry.insight_type == insight.insight_type && entry.created_at > recent_cutoff
476 })
477 .count();
478
479 if recent_same_type >= 3 {
480 return Ok(Some(LoopDetectionResult {
482 has_loop: true,
483 loop_path: Vec::new(),
484 loop_type: LoopType::TemporalRepetition,
485 severity: LoopSeverity::Medium,
486 prevention_action: PreventionAction::TriggerCooldown,
487 }));
488 }
489
490 Ok(None)
491 }
492
493 fn check_conceptual_satiation(&self, insight: &Insight) -> Result<Option<LoopDetectionResult>> {
495 for concept in &insight.related_concepts {
496 if let Some(&frequency) = self.concept_frequency.get(concept) {
497 if frequency > 10 {
498 return Ok(Some(LoopDetectionResult {
500 has_loop: true,
501 loop_path: Vec::new(),
502 loop_type: LoopType::ConceptualSatiation,
503 severity: LoopSeverity::Low,
504 prevention_action: PreventionAction::ModifyInsight,
505 }));
506 }
507 }
508 }
509
510 Ok(None)
511 }
512
513 fn assess_insight_quality(&self, insight: &Insight) -> Result<QualityAssessment> {
515 let mut quality_factors = Vec::new();
516 let mut deficiency_reasons = Vec::new();
517
518 let novelty_score = self.calculate_novelty_score(insight)?;
520 if novelty_score < self.config.min_novelty_threshold {
521 deficiency_reasons.push("Low novelty - similar insights already exist".to_string());
522 } else {
523 quality_factors.push("High novelty".to_string());
524 }
525
526 let coherence_score = self.calculate_coherence_score(insight)?;
528 if coherence_score < self.config.min_coherence_threshold {
529 deficiency_reasons
530 .push("Low coherence - insight lacks logical consistency".to_string());
531 } else {
532 quality_factors.push("High coherence".to_string());
533 }
534
535 let evidence_strength = self.calculate_evidence_strength(insight)?;
537 if evidence_strength < self.config.min_evidence_threshold {
538 deficiency_reasons.push("Weak evidence - insufficient supporting memories".to_string());
539 } else {
540 quality_factors.push("Strong evidence".to_string());
541 }
542
543 let semantic_richness = self.calculate_semantic_richness(insight)?;
545 quality_factors.push(format!("Semantic richness: {semantic_richness:.2}"));
546
547 let predictive_power = self.calculate_predictive_power(insight)?;
549 quality_factors.push(format!("Predictive power: {predictive_power:.2}"));
550
551 let overall_quality = (novelty_score
553 + coherence_score
554 + evidence_strength
555 + semantic_richness
556 + predictive_power)
557 / 5.0;
558
559 Ok(QualityAssessment {
560 novelty_score,
561 coherence_score,
562 evidence_strength,
563 semantic_richness,
564 predictive_power,
565 overall_quality,
566 quality_factors,
567 deficiency_reasons,
568 })
569 }
570
571 fn check_quality_thresholds(
573 &self,
574 quality: &QualityAssessment,
575 ) -> Result<Option<LoopDetectionResult>> {
576 if quality.novelty_score < self.config.min_novelty_threshold {
577 return Ok(Some(LoopDetectionResult {
578 has_loop: false,
579 loop_path: Vec::new(),
580 loop_type: LoopType::SemanticDuplication,
581 severity: LoopSeverity::Medium,
582 prevention_action: PreventionAction::RejectInsight,
583 }));
584 }
585
586 if quality.coherence_score < self.config.min_coherence_threshold {
587 return Ok(Some(LoopDetectionResult {
588 has_loop: false,
589 loop_path: Vec::new(),
590 loop_type: LoopType::SemanticDuplication,
591 severity: LoopSeverity::Medium,
592 prevention_action: PreventionAction::RejectInsight,
593 }));
594 }
595
596 if quality.evidence_strength < self.config.min_evidence_threshold {
597 return Ok(Some(LoopDetectionResult {
598 has_loop: false,
599 loop_path: Vec::new(),
600 loop_type: LoopType::SemanticDuplication,
601 severity: LoopSeverity::Low,
602 prevention_action: PreventionAction::ModifyInsight,
603 }));
604 }
605
606 Ok(None)
607 }
608
609 fn calculate_novelty_score(&self, insight: &Insight) -> Result<f64> {
612 let mut max_similarity: f64 = 0.0;
614
615 for entry in &self.insight_history {
616 let overlap = insight
618 .related_concepts
619 .iter()
620 .filter(|concept| entry.source_concepts.contains(*concept))
621 .count();
622
623 let similarity = overlap as f64 / insight.related_concepts.len().max(1) as f64;
624 max_similarity = max_similarity.max(similarity);
625 }
626
627 Ok(1.0 - max_similarity)
628 }
629
630 fn calculate_coherence_score(&self, _insight: &Insight) -> Result<f64> {
631 Ok(0.8) }
634
635 fn calculate_evidence_strength(&self, insight: &Insight) -> Result<f64> {
636 let memory_count = insight.source_memory_ids.len() as f64;
638 let evidence_strength = (memory_count / 10.0).min(1.0); Ok(evidence_strength)
640 }
641
642 fn calculate_semantic_richness(&self, insight: &Insight) -> Result<f64> {
643 let concept_count = insight.related_concepts.len() as f64;
645 let richness = (concept_count / 5.0).min(1.0); Ok(richness)
647 }
648
649 fn calculate_predictive_power(&self, insight: &Insight) -> Result<f64> {
650 match insight.insight_type {
652 InsightType::Causality => Ok(0.9),
653 InsightType::Trend => Ok(0.8),
654 InsightType::Pattern => Ok(0.7),
655 InsightType::Synthesis => Ok(0.6),
656 _ => Ok(0.5),
657 }
658 }
659
660 fn calculate_fingerprint_similarity(
663 &self,
664 fp1: &SemanticFingerprint,
665 fp2: &SemanticFingerprint,
666 ) -> Result<f64> {
667 if fp1.insight_type_hash != fp2.insight_type_hash {
668 return Ok(0.0); }
670
671 let common_concepts = fp1
673 .concept_hashes
674 .iter()
675 .filter(|hash| fp2.concept_hashes.contains(hash))
676 .count();
677
678 let total_concepts = fp1.concept_hashes.len().max(fp2.concept_hashes.len());
679 let concept_similarity = if total_concepts > 0 {
680 common_concepts as f64 / total_concepts as f64
681 } else {
682 0.0
683 };
684
685 let content_similarity = if fp1.content_hash == fp2.content_hash {
687 1.0
688 } else {
689 0.0
690 };
691
692 Ok(0.7 * concept_similarity + 0.3 * content_similarity)
694 }
695
696 fn hash_fingerprint(&self, fingerprint: &SemanticFingerprint) -> u64 {
697 use std::collections::hash_map::DefaultHasher;
698 let mut hasher = DefaultHasher::new();
699 fingerprint.hash(&mut hasher);
700 hasher.finish()
701 }
702
703 fn trace_causal_path(
704 &self,
705 _source_id: Uuid,
706 _visited: &mut HashSet<Uuid>,
707 _depth: usize,
708 ) -> Result<Option<Vec<Uuid>>> {
709 Ok(None)
711 }
712
713 fn update_causal_graph(&mut self, _insight: &Insight) -> Result<()> {
714 Ok(())
716 }
717
718 fn prune_old_entries(&mut self) {
719 let cutoff = Utc::now() - Duration::days(self.config.tracking_window_days);
720
721 while let Some(entry) = self.insight_history.front() {
722 if entry.created_at < cutoff {
723 self.insight_history.pop_front();
724 } else {
725 break;
726 }
727 }
728
729 }
732
733 pub fn calculate_diversity_adjustment(&self, insight_type: &InsightType) -> f64 {
735 let type_frequency = self.type_frequency.get(insight_type).unwrap_or(&0);
736 let total_insights = self.insight_history.len();
737
738 if total_insights == 0 {
739 return 1.0;
740 }
741
742 let frequency_ratio = *type_frequency as f64 / total_insights as f64;
743 let expected_ratio = 1.0 / 7.0; if frequency_ratio < expected_ratio {
746 self.config.diversity_bonus_multiplier
748 } else if frequency_ratio > expected_ratio * 2.0 {
749 self.config.repetition_penalty_factor
751 } else {
752 1.0 }
754 }
755
756 pub fn get_prevention_statistics(&self) -> PreventionStatistics {
758 PreventionStatistics {
759 total_insights_tracked: self.insight_history.len(),
760 active_cooldowns: self.cooldown_tracker.len(),
761 most_frequent_concepts: self.get_top_concepts(5),
762 insight_type_distribution: self.type_frequency.clone(),
763 average_quality_score: self.calculate_average_quality(),
764 }
765 }
766
767 fn get_top_concepts(&self, limit: usize) -> Vec<(String, usize)> {
768 let mut concepts: Vec<_> = self.concept_frequency.iter().collect();
769 concepts.sort_by(|a, b| b.1.cmp(a.1));
770 concepts
771 .into_iter()
772 .take(limit)
773 .map(|(k, v)| (k.clone(), *v))
774 .collect()
775 }
776
777 fn calculate_average_quality(&self) -> f64 {
778 if self.insight_history.is_empty() {
779 return 0.0;
780 }
781
782 let total_quality: f64 = self
783 .insight_history
784 .iter()
785 .map(|entry| entry.quality_assessment.overall_quality)
786 .sum();
787
788 total_quality / self.insight_history.len() as f64
789 }
790}
791
792#[derive(Debug, Clone, Serialize, Deserialize)]
794pub struct PreventionStatistics {
795 pub total_insights_tracked: usize,
796 pub active_cooldowns: usize,
797 pub most_frequent_concepts: Vec<(String, usize)>,
798 pub insight_type_distribution: HashMap<InsightType, usize>,
799 pub average_quality_score: f64,
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805
806 fn create_test_insight() -> Insight {
807 Insight {
808 id: Uuid::new_v4(),
809 insight_type: InsightType::Pattern,
810 content: "Test insight content".to_string(),
811 confidence_score: 0.8,
812 source_memory_ids: vec![
813 Uuid::new_v4(),
814 Uuid::new_v4(),
815 Uuid::new_v4(),
816 Uuid::new_v4(),
817 Uuid::new_v4(),
818 ],
819 related_concepts: vec!["concept1".to_string(), "concept2".to_string()],
820 knowledge_graph_nodes: Vec::new(),
821 importance_score: 0.7,
822 generated_at: Utc::now(),
823 validation_metrics: super::super::reflection_engine::ValidationMetrics {
824 novelty_score: 0.8,
825 coherence_score: 0.9,
826 evidence_strength: 0.7,
827 semantic_richness: 0.6,
828 predictive_power: 0.5,
829 },
830 }
831 }
832
833 #[test]
834 fn test_semantic_fingerprint_creation() {
835 let engine = LoopPreventionEngine::new(LoopPreventionConfig::default());
836 let insight = create_test_insight();
837
838 let fingerprint = engine.create_semantic_fingerprint(&insight).unwrap();
839
840 assert_eq!(fingerprint.concept_hashes.len(), 2);
841 assert!(fingerprint.content_hash != 0);
842 assert!(fingerprint.insight_type_hash != 0);
843 }
844
845 #[test]
846 fn test_quality_assessment() {
847 let engine = LoopPreventionEngine::new(LoopPreventionConfig::default());
848 let insight = create_test_insight();
849
850 let quality = engine.assess_insight_quality(&insight).unwrap();
851
852 assert!(quality.overall_quality >= 0.0);
853 assert!(quality.overall_quality <= 1.0);
854 assert!(quality.novelty_score >= 0.0);
855 assert!(quality.coherence_score >= 0.0);
856 assert!(quality.evidence_strength >= 0.0);
857 }
858
859 #[test]
860 fn test_insight_validation() {
861 let mut engine = LoopPreventionEngine::new(LoopPreventionConfig::default());
862 let insight = create_test_insight();
863
864 let result = engine.validate_insight(&insight).unwrap();
865
866 assert!(!result.has_loop);
868 assert_eq!(result.prevention_action, PreventionAction::Allow);
869 }
870
871 #[test]
872 fn test_duplicate_detection() {
873 let mut engine = LoopPreventionEngine::new(LoopPreventionConfig::default());
874 let insight = create_test_insight();
875
876 let quality = engine.assess_insight_quality(&insight).unwrap();
878 engine.register_insight(&insight, quality).unwrap();
879
880 let duplicate = insight.clone();
882 let result = engine.validate_insight(&duplicate).unwrap();
883
884 assert!(result.has_loop);
886 assert_eq!(result.loop_type, LoopType::SemanticDuplication);
887 }
888
889 #[test]
890 fn test_diversity_adjustment() {
891 let mut engine = LoopPreventionEngine::new(LoopPreventionConfig::default());
892
893 for _ in 0..5 {
895 let mut insight = create_test_insight();
896 insight.id = Uuid::new_v4(); let quality = engine.assess_insight_quality(&insight).unwrap();
898 engine.register_insight(&insight, quality).unwrap();
899 }
900
901 let adjustment = engine.calculate_diversity_adjustment(&InsightType::Pattern);
902
903 assert!(adjustment < 1.0);
905 }
906
907 #[test]
908 fn test_prevention_statistics() {
909 let mut engine = LoopPreventionEngine::new(LoopPreventionConfig::default());
910 let insight = create_test_insight();
911
912 let quality = engine.assess_insight_quality(&insight).unwrap();
913 engine.register_insight(&insight, quality).unwrap();
914
915 let stats = engine.get_prevention_statistics();
916
917 assert_eq!(stats.total_insights_tracked, 1);
918 assert!(!stats.most_frequent_concepts.is_empty());
919 assert!(!stats.insight_type_distribution.is_empty());
920 }
921}