codex_memory/memory/
insight_loop_prevention.rs

1//! Insight Loop Prevention and Quality Control
2//!
3//! This module implements sophisticated algorithms to prevent insight generation loops,
4//! detect duplicate or low-quality insights, and maintain cognitive system stability.
5//!
6//! ## Cognitive Science Foundation
7//!
8//! ### Research Basis
9//! 1. **Circular Reasoning Prevention (Rips, 1994)**: Avoid self-referential reasoning loops
10//! 2. **Novelty Detection (Boden, 2004)**: Ensure insights provide genuine new knowledge
11//! 3. **Coherence Checking (Thagard, 2000)**: Validate logical consistency of insights
12//! 4. **Semantic Satiation (Severance & Washburn, 1907)**: Prevent concept degradation through repetition
13//! 5. **Forgetting for Creativity (Storm & Angello, 2010)**: Strategic forgetting enhances innovation
14//!
15//! ## Prevention Mechanisms
16//!
17//! ### 1. Semantic Fingerprinting
18//! - Hash insight concepts and relationships
19//! - Detect near-duplicate insights with configurable similarity thresholds
20//! - Track concept evolution over time
21//!
22//! ### 2. Causal Chain Analysis
23//! - Map insight derivation paths
24//! - Detect circular dependencies in reasoning
25//! - Enforce maximum inference depth
26//!
27//! ### 3. Quality Validation
28//! - Evidence strength assessment
29//! - Coherence scoring
30//! - Novelty quantification
31//! - Predictive power evaluation
32//!
33//! ### 4. Temporal Cooling
34//! - Cooldown periods between similar insight types
35//! - Exponential backoff for failed insight attempts
36//! - Strategic forgetting of low-quality insights
37//!
38//! ### 5. Diversity Enforcement
39//! - Encourage insight type diversity
40//! - Penalize over-representation of specific patterns
41//! - Reward novel insight combinations
42
43use 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/// Configuration for insight loop prevention
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct LoopPreventionConfig {
55    /// Minimum semantic distance for considering insights different
56    pub semantic_similarity_threshold: f64,
57
58    /// Maximum depth for causal chain analysis
59    pub max_causal_depth: usize,
60
61    /// Cooldown period between similar insights (hours)
62    pub insight_cooldown_hours: i64,
63
64    /// Minimum novelty score for insight acceptance
65    pub min_novelty_threshold: f64,
66
67    /// Minimum coherence score for insight acceptance
68    pub min_coherence_threshold: f64,
69
70    /// Minimum evidence strength for insight acceptance
71    pub min_evidence_threshold: f64,
72
73    /// Maximum insights per concept cluster
74    pub max_insights_per_cluster: usize,
75
76    /// Window for tracking recent insights (days)
77    pub tracking_window_days: i64,
78
79    /// Diversity bonus for underrepresented insight types
80    pub diversity_bonus_multiplier: f64,
81
82    /// Penalty for overrepresented insight types
83    pub repetition_penalty_factor: f64,
84
85    /// Maximum allowed insight derivation depth
86    pub max_derivation_depth: usize,
87
88    /// Enable automatic quality filtering
89    pub enable_quality_filtering: bool,
90
91    /// Enable semantic fingerprinting
92    pub enable_semantic_fingerprinting: bool,
93
94    /// Enable causal chain analysis
95    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/// Semantic fingerprint for insight deduplication
120#[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/// Causal chain node for dependency tracking
129#[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/// Quality assessment result
139#[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/// Loop detection result
152#[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/// Types of loops that can be detected
162#[derive(Debug, Clone, PartialEq)]
163pub enum LoopType {
164    SemanticDuplication,
165    CausalCircularity,
166    ConceptualSatiation,
167    DerivationLoop,
168    TemporalRepetition,
169}
170
171/// Severity levels for detected loops
172#[derive(Debug, Clone, PartialEq, PartialOrd)]
173pub enum LoopSeverity {
174    Low,
175    Medium,
176    High,
177    Critical,
178}
179
180/// Actions to take when loops are detected
181#[derive(Debug, Clone, PartialEq)]
182pub enum PreventionAction {
183    Allow,
184    DelayGeneration,
185    ModifyInsight,
186    RejectInsight,
187    TriggerCooldown,
188    PruneRedundant,
189}
190
191/// Insight tracking entry for loop prevention
192#[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
203/// Main loop prevention engine
204pub 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    /// Validate insight and check for loops before acceptance
227    pub fn validate_insight(&mut self, insight: &Insight) -> Result<LoopDetectionResult> {
228        // Create semantic fingerprint
229        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        // Check for semantic duplication
241        if let Some(duplicate_result) = self.check_semantic_duplication(&fingerprint)? {
242            return Ok(duplicate_result);
243        }
244
245        // Check for causal loops
246        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        // Check temporal repetition patterns
253        if let Some(temporal_result) = self.check_temporal_repetition(insight)? {
254            return Ok(temporal_result);
255        }
256
257        // Check concept satiation
258        if let Some(satiation_result) = self.check_conceptual_satiation(insight)? {
259            return Ok(satiation_result);
260        }
261
262        // Quality assessment
263        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        // Check quality thresholds
279        if let Some(quality_result) = self.check_quality_thresholds(&quality)? {
280            return Ok(quality_result);
281        }
282
283        // If all checks pass, allow the insight
284        Ok(LoopDetectionResult {
285            has_loop: false,
286            loop_path: Vec::new(),
287            loop_type: LoopType::SemanticDuplication, // Default, not used
288            severity: LoopSeverity::Low,
289            prevention_action: PreventionAction::Allow,
290        })
291    }
292
293    /// Register a validated insight in the tracking system
294    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(), // Would be computed from source memories
316            created_at: insight.generated_at,
317            source_concepts: insight.related_concepts.iter().cloned().collect(),
318        };
319
320        // Add to history
321        self.insight_history.push_back(tracking_entry);
322
323        // Update frequency counters
324        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        // Update causal graph if enabled
333        if self.config.enable_causal_analysis {
334            self.update_causal_graph(insight)?;
335        }
336
337        // Prune old entries
338        self.prune_old_entries();
339
340        info!(
341            "Registered insight {} in loop prevention system",
342            insight.id
343        );
344        Ok(())
345    }
346
347    /// Create semantic fingerprint for insight deduplication
348    fn create_semantic_fingerprint(&self, insight: &Insight) -> Result<SemanticFingerprint> {
349        use std::collections::hash_map::DefaultHasher;
350
351        // Hash related concepts
352        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        // Hash content (simplified - would use semantic hashing in production)
361        let mut content_hasher = DefaultHasher::new();
362        insight.content.to_lowercase().hash(&mut content_hasher);
363        let content_hash = content_hasher.finish();
364
365        // Hash insight type
366        let mut type_hasher = DefaultHasher::new();
367        insight.insight_type.hash(&mut type_hasher);
368        let insight_type_hash = type_hasher.finish();
369
370        // For relationships, we'd hash the knowledge graph connections
371        let relationship_hashes = Vec::new(); // Simplified
372
373        Ok(SemanticFingerprint {
374            concept_hashes,
375            relationship_hashes,
376            content_hash,
377            insight_type_hash,
378        })
379    }
380
381    /// Check for semantic duplication with existing insights
382    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                // Check cooldown period
392                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    /// Check for causal reasoning loops
428    fn check_causal_loops(&self, insight: &Insight) -> Result<Option<LoopDetectionResult>> {
429        // Build derivation path for this insight
430        let mut derivation_path = Vec::new();
431        let mut visited = HashSet::new();
432
433        // Start from source memories and trace backwards
434        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        // Check for circular dependencies
450        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    /// Check for temporal repetition patterns
468    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            // Too many of the same type recently
481            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    /// Check for conceptual satiation (overuse of specific concepts)
494    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                    // Arbitrary threshold for concept overuse
499                    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    /// Assess the quality of an insight
514    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        // Novelty assessment - check against existing insights
519        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        // Coherence assessment - check logical consistency
527        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        // Evidence strength - assess supporting memories
536        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        // Semantic richness - diversity of concepts involved
544        let semantic_richness = self.calculate_semantic_richness(insight)?;
545        quality_factors.push(format!("Semantic richness: {semantic_richness:.2}"));
546
547        // Predictive power - potential for future value
548        let predictive_power = self.calculate_predictive_power(insight)?;
549        quality_factors.push(format!("Predictive power: {predictive_power:.2}"));
550
551        // Overall quality score
552        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    /// Check if insight meets quality thresholds
572    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    // Helper methods for quality assessment
610
611    fn calculate_novelty_score(&self, insight: &Insight) -> Result<f64> {
612        // Calculate novelty based on similarity to existing insights
613        let mut max_similarity: f64 = 0.0;
614
615        for entry in &self.insight_history {
616            // Simple concept overlap calculation
617            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        // Simplified coherence assessment - would use NLP in production
632        Ok(0.8) // Default high coherence
633    }
634
635    fn calculate_evidence_strength(&self, insight: &Insight) -> Result<f64> {
636        // Calculate based on number and quality of source memories
637        let memory_count = insight.source_memory_ids.len() as f64;
638        let evidence_strength = (memory_count / 10.0).min(1.0); // Normalize to [0,1]
639        Ok(evidence_strength)
640    }
641
642    fn calculate_semantic_richness(&self, insight: &Insight) -> Result<f64> {
643        // Calculate based on diversity of concepts
644        let concept_count = insight.related_concepts.len() as f64;
645        let richness = (concept_count / 5.0).min(1.0); // Normalize to [0,1]
646        Ok(richness)
647    }
648
649    fn calculate_predictive_power(&self, insight: &Insight) -> Result<f64> {
650        // Simplified assessment - would use ML models in production
651        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    // Utility methods
661
662    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); // Different types can't be similar
669        }
670
671        // Calculate concept overlap
672        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        // Simple content similarity (would use semantic embeddings in production)
686        let content_similarity = if fp1.content_hash == fp2.content_hash {
687            1.0
688        } else {
689            0.0
690        };
691
692        // Weighted combination
693        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        // Simplified implementation - would trace through actual causal graph
710        Ok(None)
711    }
712
713    fn update_causal_graph(&mut self, _insight: &Insight) -> Result<()> {
714        // Update the causal dependency graph with new insight
715        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        // Clean up frequency counters for pruned entries
730        // (Simplified - would need more sophisticated cleanup)
731    }
732
733    /// Apply diversity bonuses and repetition penalties
734    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; // Assuming 7 insight types
744
745        if frequency_ratio < expected_ratio {
746            // Underrepresented - apply diversity bonus
747            self.config.diversity_bonus_multiplier
748        } else if frequency_ratio > expected_ratio * 2.0 {
749            // Overrepresented - apply penalty
750            self.config.repetition_penalty_factor
751        } else {
752            1.0 // Neutral
753        }
754    }
755
756    /// Get statistics about current prevention state
757    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/// Statistics about the prevention system
793#[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        // First insight should be allowed
867        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        // Register first insight
877        let quality = engine.assess_insight_quality(&insight).unwrap();
878        engine.register_insight(&insight, quality).unwrap();
879
880        // Try to register identical insight
881        let duplicate = insight.clone();
882        let result = engine.validate_insight(&duplicate).unwrap();
883
884        // Should be detected as duplicate
885        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        // Register several insights of the same type
894        for _ in 0..5 {
895            let mut insight = create_test_insight();
896            insight.id = Uuid::new_v4(); // Make unique
897            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        // Should apply penalty for overrepresented type
904        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}