Skip to main content

brainwires_seal/
reflection.rs

1//! Reflection Module for Error Detection and Correction
2//!
3//! Provides post-execution analysis to detect issues and suggest corrections.
4//! Implements error classification, root cause analysis, and correction strategies.
5//!
6//! ## Reflection Flow
7//!
8//! ```text
9//! Query Execution Result
10//!         │
11//!         ▼
12//! ┌───────────────────┐
13//! │  Analyze Result   │ ◄── Check for empty results, overflow, etc.
14//! └─────────┬─────────┘
15//!           │
16//!           ▼
17//! ┌───────────────────┐
18//! │  Classify Issues  │ ◄── Determine error types and severity
19//! └─────────┬─────────┘
20//!           │
21//!           ▼
22//! ┌───────────────────┐
23//! │  Suggest Fixes    │ ◄── Generate correction strategies
24//! └─────────┬─────────┘
25//!           │
26//!           ▼
27//! ┌───────────────────┐
28//! │ Attempt Correction│ ◄── Try fixes with retry limit
29//! └───────────────────┘
30//! ```
31//!
32//! ## Error Types
33//!
34//! - `EmptyResult`: Query returned no results
35//! - `ResultOverflow`: Too many results to be useful
36//! - `EntityNotFound`: Referenced entity doesn't exist
37//! - `RelationMismatch`: Relationship type doesn't apply
38//! - `CoreferenceFailure`: Could not resolve reference
39//! - `SchemaAlignment`: Query structure doesn't match data
40//!
41//! ## Example
42//!
43//! ```rust,ignore
44//! let mut reflection = ReflectionModule::new(ReflectionConfig::default());
45//!
46//! let report = reflection.analyze(&query_core, &result, &graph);
47//! if !report.issues.is_empty() {
48//!     if reflection.attempt_correction(&mut report, &graph, &executor) {
49//!         // Use corrected_result
50//!     }
51//! }
52//! ```
53
54use super::learning::LearningCoordinator;
55use super::query_core::{QueryCore, QueryExpr, QueryOp, QueryResult, RelationType};
56use brainwires_core::graph::RelationshipGraphT;
57use std::collections::HashMap;
58
59/// Types of errors that can be detected
60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub enum ErrorType {
62    /// Query returned no results
63    EmptyResult,
64    /// Too many results (overflow threshold exceeded)
65    ResultOverflow,
66    /// Referenced entity was not found in the graph
67    EntityNotFound(String),
68    /// Relationship type doesn't match entities
69    RelationMismatch(String),
70    /// Coreference resolution failed
71    CoreferenceFailure(String),
72    /// Query structure doesn't align with schema
73    SchemaAlignment(String),
74    /// Execution timeout
75    Timeout,
76    /// Unknown error
77    Unknown(String),
78}
79
80impl ErrorType {
81    /// Get a human-readable description
82    pub fn description(&self) -> String {
83        match self {
84            ErrorType::EmptyResult => "Query returned no results".to_string(),
85            ErrorType::ResultOverflow => "Query returned too many results".to_string(),
86            ErrorType::EntityNotFound(name) => format!("Entity '{}' not found", name),
87            ErrorType::RelationMismatch(rel) => format!("Relationship '{}' does not apply", rel),
88            ErrorType::CoreferenceFailure(ref_text) => {
89                format!("Could not resolve reference '{}'", ref_text)
90            }
91            ErrorType::SchemaAlignment(msg) => format!("Schema alignment issue: {}", msg),
92            ErrorType::Timeout => "Query execution timed out".to_string(),
93            ErrorType::Unknown(msg) => format!("Unknown error: {}", msg),
94        }
95    }
96}
97
98/// Severity level for issues
99#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
100pub enum Severity {
101    /// Informational - not a problem
102    Info,
103    /// Warning - may indicate a problem
104    Warning,
105    /// Error - query failed but may be recoverable
106    Error,
107    /// Critical - unrecoverable error
108    Critical,
109}
110
111impl Severity {
112    /// Get the severity as a string
113    pub fn as_str(&self) -> &'static str {
114        match self {
115            Severity::Info => "info",
116            Severity::Warning => "warning",
117            Severity::Error => "error",
118            Severity::Critical => "critical",
119        }
120    }
121}
122
123/// An issue detected during reflection
124#[derive(Debug, Clone)]
125pub struct Issue {
126    /// Type of error
127    pub error_type: ErrorType,
128    /// Severity level
129    pub severity: Severity,
130    /// Human-readable message
131    pub message: String,
132    /// Suggested fixes
133    pub suggested_fixes: Vec<SuggestedFix>,
134    /// Source location in query (if applicable)
135    pub source: Option<String>,
136}
137
138impl Issue {
139    /// Create a new issue
140    pub fn new(error_type: ErrorType, severity: Severity, message: &str) -> Self {
141        Self {
142            error_type,
143            severity,
144            message: message.to_string(),
145            suggested_fixes: Vec::new(),
146            source: None,
147        }
148    }
149
150    /// Add a suggested fix
151    pub fn with_fix(mut self, fix: SuggestedFix) -> Self {
152        self.suggested_fixes.push(fix);
153        self
154    }
155
156    /// Set the source location
157    pub fn with_source(mut self, source: &str) -> Self {
158        self.source = Some(source.to_string());
159        self
160    }
161}
162
163/// Suggested fix for an issue
164#[derive(Debug, Clone)]
165pub enum SuggestedFix {
166    /// Retry with a modified query
167    RetryWithQuery(QueryCore),
168    /// Expand search scope by adding relationships
169    ExpandScope {
170        /// The relationship to expand search by.
171        relation: String,
172    },
173    /// Narrow scope with a filter
174    NarrowScope {
175        /// The filter expression to apply.
176        filter: String,
177    },
178    /// Suggest a different entity resolution
179    ResolveEntity {
180        /// The original entity name.
181        original: String,
182        /// The suggested replacement entity name.
183        suggested: String,
184    },
185    /// Add a relationship that might be missing
186    AddRelation {
187        /// Source entity.
188        from: String,
189        /// Target entity.
190        to: String,
191        /// Relationship type.
192        relation: String,
193    },
194    /// Requires manual intervention
195    ManualIntervention(String),
196}
197
198impl SuggestedFix {
199    /// Get a description of the fix
200    pub fn description(&self) -> String {
201        match self {
202            SuggestedFix::RetryWithQuery(_) => "Retry with modified query".to_string(),
203            SuggestedFix::ExpandScope { relation } => {
204                format!("Expand scope to include {} relationships", relation)
205            }
206            SuggestedFix::NarrowScope { filter } => {
207                format!("Narrow scope with filter: {}", filter)
208            }
209            SuggestedFix::ResolveEntity {
210                original,
211                suggested,
212            } => {
213                format!("Resolve '{}' as '{}'", original, suggested)
214            }
215            SuggestedFix::AddRelation { from, to, relation } => {
216                format!("Add {} relationship from {} to {}", relation, from, to)
217            }
218            SuggestedFix::ManualIntervention(msg) => format!("Manual intervention: {}", msg),
219        }
220    }
221}
222
223/// Record of a correction attempt
224#[derive(Debug, Clone)]
225pub struct CorrectionRecord {
226    /// The original issue
227    pub issue: Issue,
228    /// The fix that was attempted
229    pub fix_applied: SuggestedFix,
230    /// Whether the correction was successful
231    pub success: bool,
232    /// Timestamp
233    pub timestamp: i64,
234}
235
236/// Reflection report
237#[derive(Debug, Clone)]
238pub struct ReflectionReport {
239    /// Original query core
240    pub query: QueryCore,
241    /// Original result
242    pub result: QueryResult,
243    /// Issues detected
244    pub issues: Vec<Issue>,
245    /// Overall quality score (0.0 - 1.0)
246    pub quality_score: f32,
247    /// Whether correction was attempted
248    pub correction_attempted: bool,
249    /// Corrected query (if correction was attempted)
250    pub corrected_query: Option<QueryCore>,
251    /// Corrected result (if correction succeeded)
252    pub corrected_result: Option<QueryResult>,
253}
254
255impl ReflectionReport {
256    /// Create a new reflection report
257    pub fn new(query: QueryCore, result: QueryResult) -> Self {
258        Self {
259            query,
260            result,
261            issues: Vec::new(),
262            quality_score: 1.0,
263            correction_attempted: false,
264            corrected_query: None,
265            corrected_result: None,
266        }
267    }
268
269    /// Check if the result is acceptable
270    pub fn is_acceptable(&self) -> bool {
271        self.quality_score >= 0.5 && !self.issues.iter().any(|i| i.severity >= Severity::Error)
272    }
273
274    /// Get the highest severity issue
275    pub fn max_severity(&self) -> Option<Severity> {
276        self.issues.iter().map(|i| i.severity).max()
277    }
278}
279
280/// Configuration for reflection
281#[derive(Debug, Clone)]
282pub struct ReflectionConfig {
283    /// Maximum results before overflow warning
284    pub max_results: usize,
285    /// Minimum results before empty warning (if expecting results)
286    pub min_results: usize,
287    /// Maximum correction retries
288    pub max_retries: u32,
289    /// Whether to auto-apply simple corrections
290    pub auto_correct: bool,
291}
292
293impl Default for ReflectionConfig {
294    fn default() -> Self {
295        Self {
296            max_results: 100,
297            min_results: 1,
298            max_retries: 2,
299            auto_correct: true,
300        }
301    }
302}
303
304/// Reflection module for analyzing and correcting query results
305pub struct ReflectionModule {
306    /// Configuration
307    config: ReflectionConfig,
308    /// Error pattern counts for learning
309    error_patterns: HashMap<ErrorType, u32>,
310    /// History of correction attempts
311    correction_history: Vec<CorrectionRecord>,
312}
313
314impl ReflectionModule {
315    /// Create a new reflection module
316    pub fn new(config: ReflectionConfig) -> Self {
317        Self {
318            config,
319            error_patterns: HashMap::new(),
320            correction_history: Vec::new(),
321        }
322    }
323
324    /// Analyze a query result and generate a reflection report
325    pub fn analyze(
326        &mut self,
327        query: &QueryCore,
328        result: &QueryResult,
329        graph: &dyn RelationshipGraphT,
330    ) -> ReflectionReport {
331        let mut report = ReflectionReport::new(query.clone(), result.clone());
332
333        // Check for execution errors
334        if let Some(ref error) = result.error {
335            report.issues.push(Issue::new(
336                ErrorType::Unknown(error.clone()),
337                Severity::Error,
338                error,
339            ));
340            report.quality_score = 0.0;
341            return report;
342        }
343
344        // Check for empty results
345        if result.values.is_empty() && result.count != Some(0) {
346            let issue = self.analyze_empty_result(query, graph);
347            report.issues.push(issue);
348            report.quality_score = 0.3;
349        }
350
351        // Check for result overflow
352        if result.values.len() > self.config.max_results {
353            let issue = Issue::new(
354                ErrorType::ResultOverflow,
355                Severity::Warning,
356                &format!(
357                    "Query returned {} results (max: {})",
358                    result.values.len(),
359                    self.config.max_results
360                ),
361            )
362            .with_fix(SuggestedFix::NarrowScope {
363                filter: "Add type or name filter".to_string(),
364            });
365            report.issues.push(issue);
366            report.quality_score = 0.6;
367        }
368
369        // Validate entities in the query
370        for (entity_name, _entity_type) in &query.entities {
371            if graph.get_node(entity_name).is_none() {
372                let similar = self.find_similar_entities(entity_name, graph);
373                let mut issue = Issue::new(
374                    ErrorType::EntityNotFound(entity_name.clone()),
375                    Severity::Warning,
376                    &format!("Entity '{}' not found in graph", entity_name),
377                );
378
379                if let Some(suggestion) = similar.first() {
380                    issue = issue.with_fix(SuggestedFix::ResolveEntity {
381                        original: entity_name.clone(),
382                        suggested: suggestion.clone(),
383                    });
384                }
385
386                report.issues.push(issue);
387                report.quality_score = (report.quality_score - 0.2).max(0.0);
388            }
389        }
390
391        // Validate relationships
392        self.validate_relationships(query, graph, &mut report);
393
394        // Update error pattern counts
395        for issue in &report.issues {
396            *self
397                .error_patterns
398                .entry(issue.error_type.clone())
399                .or_insert(0) += 1;
400        }
401
402        report
403    }
404
405    /// Analyze why a result is empty
406    fn analyze_empty_result(&self, query: &QueryCore, graph: &dyn RelationshipGraphT) -> Issue {
407        // Check if entities exist
408        for (entity_name, _) in &query.entities {
409            if graph.get_node(entity_name).is_none() {
410                return Issue::new(
411                    ErrorType::EntityNotFound(entity_name.clone()),
412                    Severity::Error,
413                    &format!(
414                        "Entity '{}' not found - query cannot return results",
415                        entity_name
416                    ),
417                );
418            }
419        }
420
421        // Check if the relationship type applies
422        if let Some(relation_msg) = self.check_relationship_applicability(&query.root, graph) {
423            return Issue::new(
424                ErrorType::RelationMismatch(relation_msg.clone()),
425                Severity::Error,
426                &relation_msg,
427            )
428            .with_fix(SuggestedFix::ExpandScope {
429                relation: "CoOccurs".to_string(),
430            });
431        }
432
433        // Generic empty result
434        Issue::new(
435            ErrorType::EmptyResult,
436            Severity::Warning,
437            "Query returned no results",
438        )
439        .with_fix(SuggestedFix::ExpandScope {
440            relation: "All".to_string(),
441        })
442    }
443
444    /// Check if a relationship type applies to the entities
445    fn check_relationship_applicability(
446        &self,
447        expr: &QueryExpr,
448        graph: &dyn RelationshipGraphT,
449    ) -> Option<String> {
450        match expr {
451            QueryExpr::Op(QueryOp::Join {
452                relation,
453                subject,
454                object,
455            }) => {
456                // Get entity names
457                let subject_name = match subject.as_ref() {
458                    QueryExpr::Constant(name, _) => Some(name.as_str()),
459                    _ => None,
460                };
461                let object_name = match object.as_ref() {
462                    QueryExpr::Constant(name, _) => Some(name.as_str()),
463                    _ => None,
464                };
465
466                // Check if any edges of this type exist for the entities
467                if let Some(name) = subject_name.or(object_name) {
468                    let edges = graph.get_edges(name);
469                    if let Some(edge_type) = relation.to_edge_type()
470                        && !edges.iter().any(|e| e.edge_type == edge_type)
471                    {
472                        return Some(format!(
473                            "No {:?} relationships found for '{}'",
474                            relation, name
475                        ));
476                    }
477                }
478
479                None
480            }
481            _ => None,
482        }
483    }
484
485    /// Find similar entity names
486    fn find_similar_entities(&self, name: &str, graph: &dyn RelationshipGraphT) -> Vec<String> {
487        let candidates = graph.search(name, 5);
488        candidates
489            .iter()
490            .map(|node| node.entity_name.clone())
491            .collect()
492    }
493
494    /// Validate relationships in the query
495    fn validate_relationships(
496        &self,
497        query: &QueryCore,
498        graph: &dyn RelationshipGraphT,
499        report: &mut ReflectionReport,
500    ) {
501        self.validate_expr(&query.root, graph, report);
502    }
503
504    #[allow(clippy::only_used_in_recursion)]
505    fn validate_expr(
506        &self,
507        expr: &QueryExpr,
508        graph: &dyn RelationshipGraphT,
509        report: &mut ReflectionReport,
510    ) {
511        match expr {
512            QueryExpr::Op(QueryOp::Join {
513                relation,
514                subject,
515                object,
516            }) => {
517                // Check if the relation type is valid for the entity types
518                if relation.to_edge_type().is_none()
519                    && !matches!(
520                        relation,
521                        RelationType::HasType
522                            | RelationType::HasError
523                            | RelationType::CreatedAt
524                            | RelationType::ModifiedAt
525                    )
526                    && let RelationType::Custom(name) = relation
527                {
528                    report.issues.push(
529                        Issue::new(
530                            ErrorType::RelationMismatch(name.clone()),
531                            Severity::Warning,
532                            &format!("Custom relationship '{}' may not exist", name),
533                        )
534                        .with_source(&format!("{:?}", relation)),
535                    );
536                }
537
538                // Recursively validate sub-expressions
539                self.validate_expr(subject, graph, report);
540                self.validate_expr(object, graph, report);
541            }
542            QueryExpr::Op(QueryOp::And(exprs)) | QueryExpr::Op(QueryOp::Or(exprs)) => {
543                for e in exprs {
544                    self.validate_expr(e, graph, report);
545                }
546            }
547            QueryExpr::Op(QueryOp::Filter { source, .. }) => {
548                self.validate_expr(source, graph, report);
549            }
550            QueryExpr::Op(QueryOp::Count(inner)) => {
551                self.validate_expr(inner, graph, report);
552            }
553            QueryExpr::Op(QueryOp::Superlative { source, .. }) => {
554                self.validate_expr(source, graph, report);
555            }
556            _ => {}
557        }
558    }
559
560    /// Validate a query core structure (before execution)
561    pub fn validate_query_core(&self, query: &QueryCore) -> Vec<Issue> {
562        let mut issues = Vec::new();
563
564        // Check for missing entities
565        if query.entities.is_empty() {
566            issues.push(Issue::new(
567                ErrorType::SchemaAlignment("No entities in query".to_string()),
568                Severity::Warning,
569                "Query does not reference any entities",
570            ));
571        }
572
573        // Check for valid question type
574        if matches!(
575            query.question_type,
576            super::query_core::QuestionType::Unknown
577        ) {
578            issues.push(Issue::new(
579                ErrorType::SchemaAlignment("Unknown question type".to_string()),
580                Severity::Info,
581                "Could not determine question type",
582            ));
583        }
584
585        issues
586    }
587
588    /// Attempt to correct issues in a report
589    pub fn attempt_correction(
590        &mut self,
591        report: &mut ReflectionReport,
592        graph: &dyn RelationshipGraphT,
593        _executor: &super::query_core::QueryExecutor,
594    ) -> bool {
595        if !self.config.auto_correct {
596            return false;
597        }
598
599        if report.issues.is_empty() {
600            return true; // Nothing to correct
601        }
602
603        report.correction_attempted = true;
604
605        // Try to apply fixes for each issue
606        for issue in &report.issues {
607            if issue.severity < Severity::Warning {
608                continue;
609            }
610
611            for fix in &issue.suggested_fixes {
612                match fix {
613                    SuggestedFix::ResolveEntity {
614                        original,
615                        suggested,
616                    } => {
617                        // Try resolving to a different entity
618                        if graph.get_node(suggested).is_some() {
619                            // Create a corrected query by substituting the entity
620                            let corrected =
621                                self.substitute_entity(&report.query, original, suggested);
622                            if let Some(corrected) = corrected {
623                                report.corrected_query = Some(corrected);
624                                // Note: Would re-execute here, but we don't have executor access
625                                // in a way that allows us to return the result
626                                self.record_correction(issue.clone(), fix.clone(), true);
627                                return true;
628                            }
629                        }
630                    }
631                    SuggestedFix::ExpandScope { .. } => {
632                        // Would need to modify query to use different relationship
633                        // This is more complex and requires query rewriting
634                    }
635                    _ => {}
636                }
637            }
638        }
639
640        false
641    }
642
643    /// Substitute an entity in a query
644    fn substitute_entity(
645        &self,
646        query: &QueryCore,
647        original: &str,
648        replacement: &str,
649    ) -> Option<QueryCore> {
650        let mut corrected = query.clone();
651
652        // Update entities list
653        for (name, _) in &mut corrected.entities {
654            if name == original {
655                *name = replacement.to_string();
656            }
657        }
658
659        // Update the expression tree
660        corrected.root = Self::substitute_in_expr(&query.root, original, replacement);
661
662        Some(corrected)
663    }
664
665    fn substitute_in_expr(expr: &QueryExpr, original: &str, replacement: &str) -> QueryExpr {
666        match expr {
667            QueryExpr::Constant(name, entity_type) => {
668                if name == original {
669                    QueryExpr::Constant(replacement.to_string(), entity_type.clone())
670                } else {
671                    expr.clone()
672                }
673            }
674            QueryExpr::Op(op) => QueryExpr::Op(match op {
675                QueryOp::Join {
676                    relation,
677                    subject,
678                    object,
679                } => QueryOp::Join {
680                    relation: relation.clone(),
681                    subject: Box::new(Self::substitute_in_expr(subject, original, replacement)),
682                    object: Box::new(Self::substitute_in_expr(object, original, replacement)),
683                },
684                QueryOp::And(exprs) => QueryOp::And(
685                    exprs
686                        .iter()
687                        .map(|e| Self::substitute_in_expr(e, original, replacement))
688                        .collect(),
689                ),
690                QueryOp::Or(exprs) => QueryOp::Or(
691                    exprs
692                        .iter()
693                        .map(|e| Self::substitute_in_expr(e, original, replacement))
694                        .collect(),
695                ),
696                QueryOp::Filter { source, predicate } => QueryOp::Filter {
697                    source: Box::new(Self::substitute_in_expr(source, original, replacement)),
698                    predicate: predicate.clone(),
699                },
700                QueryOp::Count(inner) => QueryOp::Count(Box::new(Self::substitute_in_expr(
701                    inner,
702                    original,
703                    replacement,
704                ))),
705                QueryOp::Superlative {
706                    source,
707                    property,
708                    direction,
709                } => QueryOp::Superlative {
710                    source: Box::new(Self::substitute_in_expr(source, original, replacement)),
711                    property: property.clone(),
712                    direction: direction.clone(),
713                },
714                _ => op.clone(),
715            }),
716            _ => expr.clone(),
717        }
718    }
719
720    /// Record a correction attempt
721    fn record_correction(&mut self, issue: Issue, fix: SuggestedFix, success: bool) {
722        self.correction_history.push(CorrectionRecord {
723            issue,
724            fix_applied: fix,
725            success,
726            timestamp: chrono::Utc::now().timestamp(),
727        });
728    }
729
730    /// Provide feedback to the learning coordinator
731    pub fn provide_feedback(
732        &self,
733        report: &ReflectionReport,
734        coordinator: &mut LearningCoordinator,
735    ) {
736        // Record the query outcome
737        let success = report.is_acceptable();
738        let result_count = report.result.values.len();
739
740        coordinator.record_outcome(
741            None, // Pattern ID not tracked here
742            success,
743            result_count,
744            Some(&report.query),
745            0, // Reflection doesn't track query execution timing
746        );
747
748        // Track specific error patterns for future avoidance
749        for issue in &report.issues {
750            if issue.severity >= Severity::Error {
751                // This could be used to create negative patterns
752                // that the learning system avoids
753            }
754        }
755    }
756
757    /// Get error statistics
758    pub fn get_error_stats(&self) -> HashMap<ErrorType, u32> {
759        self.error_patterns.clone()
760    }
761
762    /// Get correction success rate
763    pub fn correction_success_rate(&self) -> f32 {
764        if self.correction_history.is_empty() {
765            return 0.0;
766        }
767
768        let successes = self.correction_history.iter().filter(|r| r.success).count();
769        successes as f32 / self.correction_history.len() as f32
770    }
771}
772
773impl Default for ReflectionModule {
774    fn default() -> Self {
775        Self::new(ReflectionConfig::default())
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use super::*;
782    use crate::query_core::{QueryExpr, QueryResultValue, QuestionType};
783    use brainwires_core::graph::EntityType;
784    use brainwires_knowledge::RelationshipGraph;
785
786    fn create_test_query() -> QueryCore {
787        QueryCore::new(
788            QuestionType::Definition,
789            QueryExpr::var("x"),
790            vec![("main.rs".to_string(), EntityType::File)],
791            "What is main.rs?".to_string(),
792        )
793    }
794
795    #[test]
796    fn test_analyze_empty_result() {
797        let mut reflection = ReflectionModule::new(ReflectionConfig::default());
798        let query = create_test_query();
799        let result = QueryResult::empty();
800        let graph = RelationshipGraph::new();
801
802        let report = reflection.analyze(&query, &result, &graph);
803
804        assert!(!report.issues.is_empty());
805        assert!(report.issues.iter().any(|i| matches!(
806            i.error_type,
807            ErrorType::EmptyResult | ErrorType::EntityNotFound(_)
808        )));
809    }
810
811    #[test]
812    fn test_analyze_overflow_result() {
813        let mut reflection = ReflectionModule::new(ReflectionConfig {
814            max_results: 10,
815            ..Default::default()
816        });
817        let query = create_test_query();
818
819        // Create result with many values
820        let mut values = Vec::new();
821        for i in 0..20 {
822            values.push(QueryResultValue {
823                value: format!("entity_{}", i),
824                entity_type: Some(EntityType::File),
825                score: 0.8,
826                metadata: std::collections::HashMap::new(),
827            });
828        }
829        let result = crate::query_core::QueryResult::with_values(values);
830        let graph = RelationshipGraph::new();
831
832        let report = reflection.analyze(&query, &result, &graph);
833
834        assert!(
835            report
836                .issues
837                .iter()
838                .any(|i| i.error_type == ErrorType::ResultOverflow)
839        );
840    }
841
842    #[test]
843    fn test_validate_query_core() {
844        let reflection = ReflectionModule::new(ReflectionConfig::default());
845
846        // Query with no entities
847        let query = QueryCore::new(
848            QuestionType::Unknown,
849            QueryExpr::var("x"),
850            vec![],
851            "Test".to_string(),
852        );
853
854        let issues = reflection.validate_query_core(&query);
855        assert!(!issues.is_empty());
856    }
857
858    #[test]
859    fn test_issue_creation() {
860        let issue = Issue::new(ErrorType::EmptyResult, Severity::Warning, "No results")
861            .with_fix(SuggestedFix::ExpandScope {
862                relation: "All".to_string(),
863            })
864            .with_source("query_root");
865
866        assert_eq!(issue.severity, Severity::Warning);
867        assert_eq!(issue.suggested_fixes.len(), 1);
868        assert!(issue.source.is_some());
869    }
870
871    #[test]
872    fn test_severity_ordering() {
873        assert!(Severity::Info < Severity::Warning);
874        assert!(Severity::Warning < Severity::Error);
875        assert!(Severity::Error < Severity::Critical);
876    }
877
878    #[test]
879    fn test_reflection_report_acceptable() {
880        let query = create_test_query();
881        let result = QueryResult::empty();
882        let mut report = ReflectionReport::new(query, result);
883
884        report.quality_score = 0.7;
885        assert!(report.is_acceptable());
886
887        report.quality_score = 0.3;
888        assert!(!report.is_acceptable());
889
890        report.quality_score = 0.7;
891        report
892            .issues
893            .push(Issue::new(ErrorType::EmptyResult, Severity::Error, "Error"));
894        assert!(!report.is_acceptable());
895    }
896
897    #[test]
898    fn test_suggested_fix_description() {
899        let fix = SuggestedFix::ResolveEntity {
900            original: "main".to_string(),
901            suggested: "main.rs".to_string(),
902        };
903
904        let desc = fix.description();
905        assert!(desc.contains("main"));
906        assert!(desc.contains("main.rs"));
907    }
908
909    #[test]
910    fn test_error_type_description() {
911        let error = ErrorType::EntityNotFound("test.rs".to_string());
912        let desc = error.description();
913        assert!(desc.contains("test.rs"));
914    }
915
916    #[test]
917    fn test_substitute_entity() {
918        let reflection = ReflectionModule::new(ReflectionConfig::default());
919
920        let query = QueryCore::new(
921            QuestionType::Definition,
922            QueryExpr::constant("main", EntityType::File),
923            vec![("main".to_string(), EntityType::File)],
924            "What is main?".to_string(),
925        );
926
927        let corrected = reflection.substitute_entity(&query, "main", "main.rs");
928        assert!(corrected.is_some());
929
930        let corrected = corrected.unwrap();
931        assert!(corrected.entities.iter().any(|(name, _)| name == "main.rs"));
932    }
933
934    #[test]
935    fn test_correction_success_rate() {
936        let mut reflection = ReflectionModule::new(ReflectionConfig::default());
937
938        assert_eq!(reflection.correction_success_rate(), 0.0);
939
940        reflection.record_correction(
941            Issue::new(ErrorType::EmptyResult, Severity::Warning, "test"),
942            SuggestedFix::ExpandScope {
943                relation: "All".to_string(),
944            },
945            true,
946        );
947
948        reflection.record_correction(
949            Issue::new(ErrorType::EmptyResult, Severity::Warning, "test"),
950            SuggestedFix::ExpandScope {
951                relation: "All".to_string(),
952            },
953            false,
954        );
955
956        assert_eq!(reflection.correction_success_rate(), 0.5);
957    }
958}