Skip to main content

oxirs_core/ai/
temporal_reasoning.rs

1//! Temporal Knowledge Graph Reasoning
2//!
3//! This module provides temporal reasoning capabilities for knowledge graphs,
4//! including temporal logic, time-aware inference, and temporal query processing.
5
6use crate::ai::AiConfig;
7use anyhow::Result;
8use scirs2_core::random::{Random, RngExt};
9use serde::{Deserialize, Serialize};
10use std::collections::{BTreeMap, HashMap};
11use std::time::{SystemTime, UNIX_EPOCH};
12
13/// Temporal reasoning module
14pub struct TemporalReasoner {
15    /// Configuration
16    config: TemporalConfig,
17
18    /// Temporal knowledge base
19    temporal_kb: TemporalKnowledgeBase,
20
21    /// Temporal inference engine
22    inference_engine: Box<dyn TemporalInferenceEngine>,
23
24    /// Event detection module
25    event_detector: Box<dyn EventDetector>,
26
27    /// Temporal constraint solver
28    #[allow(dead_code)]
29    constraint_solver: Box<dyn TemporalConstraintSolver>,
30}
31
32/// Temporal reasoning configuration
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct TemporalConfig {
35    /// Enable temporal inference
36    pub enable_inference: bool,
37
38    /// Enable event detection
39    pub enable_event_detection: bool,
40
41    /// Temporal resolution (granularity)
42    pub temporal_resolution: TemporalResolution,
43
44    /// Maximum inference depth
45    pub max_inference_depth: usize,
46
47    /// Confidence threshold for temporal inferences
48    pub inference_confidence_threshold: f32,
49
50    /// Enable temporal constraint solving
51    pub enable_constraint_solving: bool,
52
53    /// Supported temporal relations
54    pub supported_relations: Vec<TemporalRelation>,
55}
56
57impl Default for TemporalConfig {
58    fn default() -> Self {
59        Self {
60            enable_inference: true,
61            enable_event_detection: true,
62            temporal_resolution: TemporalResolution::Day,
63            max_inference_depth: 5,
64            inference_confidence_threshold: 0.7,
65            enable_constraint_solving: true,
66            supported_relations: vec![
67                TemporalRelation::Before,
68                TemporalRelation::After,
69                TemporalRelation::During,
70                TemporalRelation::Overlaps,
71                TemporalRelation::Meets,
72                TemporalRelation::Starts,
73                TemporalRelation::Finishes,
74                TemporalRelation::Equals,
75            ],
76        }
77    }
78}
79
80/// Temporal resolution granularity
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub enum TemporalResolution {
83    Millisecond,
84    Second,
85    Minute,
86    Hour,
87    Day,
88    Week,
89    Month,
90    Year,
91    Decade,
92    Century,
93}
94
95/// Temporal relations (Allen's interval algebra)
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
97pub enum TemporalRelation {
98    Before,
99    After,
100    During,
101    Contains,
102    Overlaps,
103    OverlappedBy,
104    Meets,
105    MetBy,
106    Starts,
107    StartedBy,
108    Finishes,
109    FinishedBy,
110    Equals,
111}
112
113/// Temporal query
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct TemporalQuery {
116    /// Query type
117    pub query_type: TemporalQueryType,
118
119    /// Target entities
120    pub entities: Vec<String>,
121
122    /// Temporal constraints
123    pub constraints: Vec<TemporalConstraint>,
124
125    /// Time window
126    pub time_window: Option<TimeInterval>,
127
128    /// Include derived facts
129    pub include_inferred: bool,
130}
131
132/// Temporal query types
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub enum TemporalQueryType {
135    /// Find facts valid at specific time
136    ValidAt { time: Timestamp },
137
138    /// Find facts valid during interval
139    ValidDuring { interval: TimeInterval },
140
141    /// Find temporal relations between events
142    TemporalRelations { entity1: String, entity2: String },
143
144    /// Event sequence queries
145    EventSequence { pattern: Vec<EventPattern> },
146
147    /// Temporal aggregation
148    Aggregation {
149        function: AggregationFunction,
150        grouping: TemporalGrouping,
151    },
152
153    /// Change detection
154    ChangeDetection { entity: String, property: String },
155}
156
157/// Temporal query result
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct TemporalResult {
160    /// Query ID
161    pub query_id: String,
162
163    /// Results
164    pub results: Vec<TemporalFact>,
165
166    /// Inference trace (if requested)
167    pub inference_trace: Option<Vec<InferenceStep>>,
168
169    /// Execution time
170    pub execution_time: std::time::Duration,
171
172    /// Result confidence
173    pub confidence: f32,
174}
175
176/// Temporal fact
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct TemporalFact {
179    /// Subject
180    pub subject: String,
181
182    /// Predicate
183    pub predicate: String,
184
185    /// Object
186    pub object: String,
187
188    /// Validity interval
189    pub validity: TimeInterval,
190
191    /// Confidence score
192    pub confidence: f32,
193
194    /// Source information
195    pub source: FactSource,
196
197    /// Temporal annotations
198    pub annotations: HashMap<String, String>,
199}
200
201/// Fact source
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub enum FactSource {
204    /// Asserted fact
205    Asserted,
206
207    /// Inferred fact
208    Inferred { rule: String, premises: Vec<String> },
209
210    /// Derived from temporal reasoning
211    TemporalInference { reasoning_type: String },
212
213    /// Event detection
214    EventDetection { detector: String },
215}
216
217/// Time interval
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct TimeInterval {
220    /// Start time
221    pub start: Timestamp,
222
223    /// End time
224    pub end: Timestamp,
225
226    /// Interval type
227    pub interval_type: IntervalType,
228}
229
230impl TimeInterval {
231    /// Check if this interval contains a timestamp
232    pub fn contains(&self, timestamp: Timestamp) -> bool {
233        match self.interval_type {
234            IntervalType::Closed => timestamp >= self.start && timestamp <= self.end,
235            IntervalType::Open => timestamp > self.start && timestamp < self.end,
236            IntervalType::LeftOpen => timestamp > self.start && timestamp <= self.end,
237            IntervalType::RightOpen => timestamp >= self.start && timestamp < self.end,
238        }
239    }
240
241    /// Check if this interval overlaps with another
242    pub fn overlaps(&self, other: &TimeInterval) -> bool {
243        self.start < other.end && other.start < self.end
244    }
245
246    /// Get temporal relation with another interval
247    pub fn relation_to(&self, other: &TimeInterval) -> TemporalRelation {
248        if self.end < other.start {
249            TemporalRelation::Before
250        } else if self.start > other.end {
251            TemporalRelation::After
252        } else if self.start == other.start && self.end == other.end {
253            TemporalRelation::Equals
254        } else if self.start >= other.start && self.end <= other.end {
255            TemporalRelation::During
256        } else if self.start <= other.start && self.end >= other.end {
257            TemporalRelation::Contains
258        } else if self.end == other.start {
259            TemporalRelation::Meets
260        } else if self.start == other.end {
261            TemporalRelation::MetBy
262        } else if self.start == other.start && self.end < other.end {
263            TemporalRelation::Starts
264        } else if self.start == other.start && self.end > other.end {
265            TemporalRelation::StartedBy
266        } else if self.end == other.end && self.start > other.start {
267            TemporalRelation::Finishes
268        } else if self.end == other.end && self.start < other.start {
269            TemporalRelation::FinishedBy
270        } else if self.overlaps(other) && self.start < other.start {
271            TemporalRelation::Overlaps
272        } else {
273            TemporalRelation::OverlappedBy
274        }
275    }
276}
277
278/// Interval type
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub enum IntervalType {
281    /// [start, end]
282    Closed,
283
284    /// (start, end)
285    Open,
286
287    /// (start, end]
288    LeftOpen,
289
290    /// [start, end)
291    RightOpen,
292}
293
294/// Timestamp (Unix timestamp in seconds)
295pub type Timestamp = u64;
296
297/// Temporal constraint
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct TemporalConstraint {
300    /// Constraint type
301    pub constraint_type: ConstraintType,
302
303    /// Entity or event involved
304    pub entity: String,
305
306    /// Temporal relation
307    pub relation: TemporalRelation,
308
309    /// Reference time or interval
310    pub reference: TemporalReference,
311
312    /// Constraint strength
313    pub strength: ConstraintStrength,
314}
315
316/// Constraint types
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub enum ConstraintType {
319    /// Hard constraint (must be satisfied)
320    Hard,
321
322    /// Soft constraint (preferred)
323    Soft { weight: f32 },
324
325    /// Conditional constraint
326    Conditional { condition: String },
327}
328
329/// Temporal reference
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub enum TemporalReference {
332    /// Absolute timestamp
333    Absolute(Timestamp),
334
335    /// Time interval
336    Interval(TimeInterval),
337
338    /// Relative to another entity/event
339    Relative { entity: String, offset: Option<i64> },
340
341    /// Now (current time)
342    Now,
343}
344
345/// Constraint strength
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub enum ConstraintStrength {
348    Required,
349    Strong,
350    Medium,
351    Weak,
352}
353
354/// Event pattern for sequence queries
355#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct EventPattern {
357    /// Event type
358    pub event_type: String,
359
360    /// Entities involved
361    pub entities: Vec<String>,
362
363    /// Temporal constraints
364    pub constraints: Vec<TemporalConstraint>,
365
366    /// Optional flag
367    pub optional: bool,
368}
369
370/// Aggregation functions
371#[derive(Debug, Clone, Serialize, Deserialize)]
372pub enum AggregationFunction {
373    Count,
374    Sum,
375    Average,
376    Min,
377    Max,
378    Duration,
379    Frequency,
380}
381
382/// Temporal grouping
383#[derive(Debug, Clone, Serialize, Deserialize)]
384pub enum TemporalGrouping {
385    ByHour,
386    ByDay,
387    ByWeek,
388    ByMonth,
389    ByYear,
390    ByInterval { duration: u64 },
391}
392
393/// Inference step
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct InferenceStep {
396    /// Step number
397    pub step: usize,
398
399    /// Rule applied
400    pub rule: String,
401
402    /// Input facts
403    pub inputs: Vec<TemporalFact>,
404
405    /// Output fact
406    pub output: TemporalFact,
407
408    /// Confidence score
409    pub confidence: f32,
410}
411
412/// Temporal knowledge base
413pub struct TemporalKnowledgeBase {
414    /// Temporal facts indexed by time
415    facts_by_time: BTreeMap<Timestamp, Vec<TemporalFact>>,
416
417    /// Facts indexed by entity
418    facts_by_entity: HashMap<String, Vec<TemporalFact>>,
419
420    /// Temporal rules
421    #[allow(dead_code)]
422    temporal_rules: Vec<TemporalRule>,
423
424    /// Event definitions
425    event_definitions: HashMap<String, EventDefinition>,
426}
427
428/// Temporal rule
429#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct TemporalRule {
431    /// Rule ID
432    pub id: String,
433
434    /// Rule name
435    pub name: String,
436
437    /// Premises
438    pub premises: Vec<TemporalPattern>,
439
440    /// Conclusion
441    pub conclusion: TemporalPattern,
442
443    /// Rule confidence
444    pub confidence: f32,
445
446    /// Temporal constraints
447    pub temporal_constraints: Vec<TemporalConstraint>,
448}
449
450/// Temporal pattern in rules
451#[derive(Debug, Clone, Serialize, Deserialize)]
452pub struct TemporalPattern {
453    /// Pattern variables
454    pub variables: HashMap<String, String>,
455
456    /// Temporal conditions
457    pub temporal_conditions: Vec<TemporalCondition>,
458
459    /// Pattern confidence
460    pub confidence: f32,
461}
462
463/// Temporal condition
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct TemporalCondition {
466    /// Subject variable
467    pub subject: String,
468
469    /// Predicate
470    pub predicate: String,
471
472    /// Object variable
473    pub object: String,
474
475    /// Temporal validity
476    pub validity: TemporalValidity,
477}
478
479/// Temporal validity specification
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub enum TemporalValidity {
482    /// Always valid
483    Always,
484
485    /// Valid during specific interval
486    During(TimeInterval),
487
488    /// Valid at specific time
489    At(Timestamp),
490
491    /// Valid relative to another fact
492    Relative {
493        reference: String,
494        relation: TemporalRelation,
495    },
496}
497
498/// Event definition
499#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct EventDefinition {
501    /// Event type
502    pub event_type: String,
503
504    /// Event patterns to detect
505    pub patterns: Vec<EventDetectionPattern>,
506
507    /// Duration constraints
508    pub duration_constraints: Option<TimeInterval>,
509
510    /// Participants
511    pub participants: Vec<ParticipantRole>,
512}
513
514/// Event detection pattern
515#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct EventDetectionPattern {
517    /// Pattern conditions
518    pub conditions: Vec<TemporalCondition>,
519
520    /// Temporal ordering
521    pub ordering: Vec<TemporalOrdering>,
522
523    /// Pattern confidence
524    pub confidence: f32,
525}
526
527/// Temporal ordering constraint
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct TemporalOrdering {
530    /// First event
531    pub first: String,
532
533    /// Second event
534    pub second: String,
535
536    /// Temporal relation
537    pub relation: TemporalRelation,
538
539    /// Time bounds
540    pub bounds: Option<TimeInterval>,
541}
542
543/// Participant role in events
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct ParticipantRole {
546    /// Role name
547    pub role: String,
548
549    /// Entity type
550    pub entity_type: String,
551
552    /// Required flag
553    pub required: bool,
554}
555
556/// Temporal inference engine trait
557pub trait TemporalInferenceEngine: Send + Sync {
558    /// Perform temporal inference
559    fn infer(&self, kb: &TemporalKnowledgeBase, query: &TemporalQuery)
560        -> Result<Vec<TemporalFact>>;
561
562    /// Apply temporal rules
563    fn apply_rules(
564        &self,
565        facts: &[TemporalFact],
566        rules: &[TemporalRule],
567    ) -> Result<Vec<TemporalFact>>;
568}
569
570/// Event detector trait
571pub trait EventDetector: Send + Sync {
572    /// Detect events from temporal facts
573    fn detect_events(
574        &self,
575        facts: &[TemporalFact],
576        event_definitions: &[EventDefinition],
577    ) -> Result<Vec<DetectedEvent>>;
578
579    /// Get event patterns
580    fn get_patterns(&self) -> Vec<EventDetectionPattern>;
581}
582
583/// Temporal constraint solver trait
584pub trait TemporalConstraintSolver: Send + Sync {
585    /// Solve temporal constraints
586    fn solve_constraints(&self, constraints: &[TemporalConstraint]) -> Result<ConstraintSolution>;
587
588    /// Check constraint satisfaction
589    fn check_satisfaction(
590        &self,
591        constraints: &[TemporalConstraint],
592        assignments: &HashMap<String, Timestamp>,
593    ) -> Result<bool>;
594}
595
596/// Detected event
597#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct DetectedEvent {
599    /// Event type
600    pub event_type: String,
601
602    /// Event interval
603    pub interval: TimeInterval,
604
605    /// Participants
606    pub participants: HashMap<String, String>,
607
608    /// Supporting facts
609    pub supporting_facts: Vec<TemporalFact>,
610
611    /// Detection confidence
612    pub confidence: f32,
613}
614
615/// Constraint solution
616#[derive(Debug, Clone, Serialize, Deserialize)]
617pub struct ConstraintSolution {
618    /// Variable assignments
619    pub assignments: HashMap<String, Timestamp>,
620
621    /// Satisfaction score
622    pub satisfaction_score: f32,
623
624    /// Unsatisfied constraints
625    pub unsatisfied: Vec<String>,
626}
627
628impl TemporalReasoner {
629    /// Create new temporal reasoner
630    pub fn new(_config: &AiConfig) -> Result<Self> {
631        let temporal_config = TemporalConfig::default();
632
633        Ok(Self {
634            config: temporal_config,
635            temporal_kb: TemporalKnowledgeBase::new(),
636            inference_engine: Box::new(DefaultTemporalInferenceEngine::new()),
637            event_detector: Box::new(DefaultEventDetector::new()),
638            constraint_solver: Box::new(DefaultConstraintSolver::new()),
639        })
640    }
641
642    /// Perform temporal reasoning
643    pub async fn reason(&self, query: &TemporalQuery) -> Result<TemporalResult> {
644        let start_time = std::time::Instant::now();
645        let mut inference_steps = Vec::new();
646
647        // Step 1: Retrieve relevant facts
648        let mut facts = self.retrieve_facts(query)?;
649
650        // Step 2: Apply temporal inference if enabled
651        if self.config.enable_inference && query.include_inferred {
652            let inferred_facts = self.inference_engine.infer(&self.temporal_kb, query)?;
653
654            // Track inference steps for each inferred fact
655            for (idx, inferred_fact) in inferred_facts.iter().enumerate() {
656                if let FactSource::Inferred { rule, premises } = &inferred_fact.source {
657                    let step = InferenceStep {
658                        step: idx + 1,
659                        rule: rule.clone(),
660                        inputs: premises
661                            .iter()
662                            .filter_map(|premise_id| {
663                                facts
664                                    .iter()
665                                    .find(|f| {
666                                        format!("{}:{}:{}", f.subject, f.predicate, f.object)
667                                            == *premise_id
668                                    })
669                                    .cloned()
670                            })
671                            .collect(),
672                        output: inferred_fact.clone(),
673                        confidence: inferred_fact.confidence,
674                    };
675                    inference_steps.push(step);
676                }
677            }
678
679            facts.extend(inferred_facts);
680        }
681
682        // Step 3: Detect events if enabled
683        if self.config.enable_event_detection {
684            let events = self.event_detector.detect_events(
685                &facts,
686                &self
687                    .temporal_kb
688                    .event_definitions
689                    .values()
690                    .cloned()
691                    .collect::<Vec<_>>(),
692            )?;
693
694            // Convert events to facts
695            for event in events {
696                let event_fact = self.event_to_fact(event)?;
697                facts.push(event_fact);
698            }
699        }
700
701        // Step 4: Filter and rank results
702        let filtered_facts = self.filter_and_rank_facts(facts, query)?;
703
704        // Compute overall confidence from filtered facts
705        let overall_confidence = if filtered_facts.is_empty() {
706            0.0
707        } else {
708            let sum: f32 = filtered_facts.iter().map(|f| f.confidence).sum();
709            let count = filtered_facts.len() as f32;
710            (sum / count).min(1.0) // Average confidence, capped at 1.0
711        };
712
713        let execution_time = start_time.elapsed();
714
715        Ok(TemporalResult {
716            query_id: format!("query_{}", {
717                let mut rng = Random::default();
718                rng.random::<u32>()
719            }),
720            results: filtered_facts,
721            inference_trace: if inference_steps.is_empty() {
722                None
723            } else {
724                Some(inference_steps)
725            },
726            execution_time,
727            confidence: overall_confidence,
728        })
729    }
730
731    /// Add temporal fact to knowledge base
732    pub fn add_fact(&mut self, fact: TemporalFact) -> Result<()> {
733        // Add to time index
734        self.temporal_kb
735            .facts_by_time
736            .entry(fact.validity.start)
737            .or_default()
738            .push(fact.clone());
739
740        // Add to entity index
741        self.temporal_kb
742            .facts_by_entity
743            .entry(fact.subject.clone())
744            .or_default()
745            .push(fact.clone());
746
747        self.temporal_kb
748            .facts_by_entity
749            .entry(fact.object.clone())
750            .or_default()
751            .push(fact);
752
753        Ok(())
754    }
755
756    /// Retrieve facts relevant to query
757    fn retrieve_facts(&self, query: &TemporalQuery) -> Result<Vec<TemporalFact>> {
758        let mut facts = Vec::new();
759
760        // Get facts based on query type
761        match &query.query_type {
762            TemporalQueryType::ValidAt { time } => {
763                for time_facts in self.temporal_kb.facts_by_time.values() {
764                    for fact in time_facts {
765                        if fact.validity.contains(*time) {
766                            facts.push(fact.clone());
767                        }
768                    }
769                }
770            }
771            TemporalQueryType::ValidDuring { interval } => {
772                for time_facts in self.temporal_kb.facts_by_time.values() {
773                    for fact in time_facts {
774                        if fact.validity.overlaps(interval) {
775                            facts.push(fact.clone());
776                        }
777                    }
778                }
779            }
780            TemporalQueryType::TemporalRelations { entity1, entity2 } => {
781                // Retrieve facts that involve either entity (as subject or object).
782                // This is the candidate set over which Allen-interval relations
783                // between the two entities' facts can be computed downstream.
784                for time_facts in self.temporal_kb.facts_by_time.values() {
785                    for fact in time_facts {
786                        let involves_e1 = &fact.subject == entity1 || &fact.object == entity1;
787                        let involves_e2 = &fact.subject == entity2 || &fact.object == entity2;
788                        if involves_e1 || involves_e2 {
789                            facts.push(fact.clone());
790                        }
791                    }
792                }
793            }
794            TemporalQueryType::ChangeDetection { entity, property } => {
795                // Retrieve facts asserting `property` about `entity`, i.e. the
796                // value history whose changes over time we want to detect.
797                for time_facts in self.temporal_kb.facts_by_time.values() {
798                    for fact in time_facts {
799                        if &fact.subject == entity && &fact.predicate == property {
800                            facts.push(fact.clone());
801                        }
802                    }
803                }
804            }
805            TemporalQueryType::EventSequence { pattern } => {
806                // Retrieve facts that match at least one event pattern (by event
807                // type and/or involved entities). An empty pattern list matches
808                // nothing rather than everything.
809                for time_facts in self.temporal_kb.facts_by_time.values() {
810                    for fact in time_facts {
811                        if pattern
812                            .iter()
813                            .any(|p| Self::fact_matches_event_pattern(fact, p))
814                        {
815                            facts.push(fact.clone());
816                        }
817                    }
818                }
819            }
820            TemporalQueryType::Aggregation { function, grouping } => {
821                // Aggregation queries require computing grouped aggregate values
822                // (e.g. counts per day), which the fact-retrieval/ranking pipeline
823                // does not synthesize. Returning the raw underlying facts would
824                // misrepresent them as the aggregate result, so fail loudly.
825                return Err(anyhow::anyhow!(
826                    "Temporal aggregation queries (function {:?}, grouping {:?}) are not \
827                     supported by TemporalReasoner::reason; no aggregate result is computed",
828                    function,
829                    grouping
830                ));
831            }
832        }
833
834        Ok(facts)
835    }
836
837    /// Returns true if `fact` matches the given event pattern.
838    ///
839    /// A fact matches when it is compatible with the pattern's `event_type`
840    /// (checked against the fact's object, predicate, or `event:<type>` subject)
841    /// and, when the pattern names entities, involves at least one of them.
842    fn fact_matches_event_pattern(fact: &TemporalFact, pattern: &EventPattern) -> bool {
843        let type_matches = pattern.event_type.is_empty()
844            || fact.object == pattern.event_type
845            || fact.predicate == pattern.event_type
846            || fact.subject == format!("event:{}", pattern.event_type);
847
848        let entity_matches = pattern.entities.is_empty()
849            || pattern
850                .entities
851                .iter()
852                .any(|e| e == &fact.subject || e == &fact.object);
853
854        type_matches && entity_matches
855    }
856
857    /// Convert detected event to temporal fact
858    fn event_to_fact(&self, event: DetectedEvent) -> Result<TemporalFact> {
859        Ok(TemporalFact {
860            subject: format!("event:{}", event.event_type),
861            predicate: "hasEventType".to_string(),
862            object: event.event_type,
863            validity: event.interval,
864            confidence: event.confidence,
865            source: FactSource::EventDetection {
866                detector: "default".to_string(),
867            },
868            annotations: HashMap::new(),
869        })
870    }
871
872    /// Filter and rank facts based on query
873    fn filter_and_rank_facts(
874        &self,
875        mut facts: Vec<TemporalFact>,
876        query: &TemporalQuery,
877    ) -> Result<Vec<TemporalFact>> {
878        // Apply entity filters
879        if !query.entities.is_empty() {
880            facts.retain(|fact| {
881                query.entities.contains(&fact.subject) || query.entities.contains(&fact.object)
882            });
883        }
884
885        // Apply time window filter
886        if let Some(window) = &query.time_window {
887            facts.retain(|fact| fact.validity.overlaps(window));
888        }
889
890        // Sort by confidence (descending)
891        facts.sort_by(|a, b| {
892            b.confidence
893                .partial_cmp(&a.confidence)
894                .unwrap_or(std::cmp::Ordering::Equal)
895        });
896
897        Ok(facts)
898    }
899}
900
901impl TemporalKnowledgeBase {
902    fn new() -> Self {
903        Self {
904            facts_by_time: BTreeMap::new(),
905            facts_by_entity: HashMap::new(),
906            temporal_rules: Vec::new(),
907            event_definitions: HashMap::new(),
908        }
909    }
910}
911
912/// Default temporal inference engine
913struct DefaultTemporalInferenceEngine;
914
915impl DefaultTemporalInferenceEngine {
916    fn new() -> Self {
917        Self
918    }
919}
920
921impl TemporalInferenceEngine for DefaultTemporalInferenceEngine {
922    fn infer(
923        &self,
924        _kb: &TemporalKnowledgeBase,
925        _query: &TemporalQuery,
926    ) -> Result<Vec<TemporalFact>> {
927        // Placeholder implementation
928        Ok(Vec::new())
929    }
930
931    fn apply_rules(
932        &self,
933        _facts: &[TemporalFact],
934        _rules: &[TemporalRule],
935    ) -> Result<Vec<TemporalFact>> {
936        // Placeholder implementation
937        Ok(Vec::new())
938    }
939}
940
941/// Default event detector
942struct DefaultEventDetector;
943
944impl DefaultEventDetector {
945    fn new() -> Self {
946        Self
947    }
948}
949
950impl EventDetector for DefaultEventDetector {
951    fn detect_events(
952        &self,
953        _facts: &[TemporalFact],
954        _event_definitions: &[EventDefinition],
955    ) -> Result<Vec<DetectedEvent>> {
956        // Placeholder implementation
957        Ok(Vec::new())
958    }
959
960    fn get_patterns(&self) -> Vec<EventDetectionPattern> {
961        Vec::new()
962    }
963}
964
965/// Default constraint solver
966struct DefaultConstraintSolver;
967
968impl DefaultConstraintSolver {
969    fn new() -> Self {
970        Self
971    }
972}
973
974impl TemporalConstraintSolver for DefaultConstraintSolver {
975    fn solve_constraints(&self, _constraints: &[TemporalConstraint]) -> Result<ConstraintSolution> {
976        // Placeholder implementation
977        Ok(ConstraintSolution {
978            assignments: HashMap::new(),
979            satisfaction_score: 1.0,
980            unsatisfied: Vec::new(),
981        })
982    }
983
984    fn check_satisfaction(
985        &self,
986        _constraints: &[TemporalConstraint],
987        _assignments: &HashMap<String, Timestamp>,
988    ) -> Result<bool> {
989        // Placeholder implementation
990        Ok(true)
991    }
992}
993
994/// Get current timestamp
995pub fn current_timestamp() -> Timestamp {
996    SystemTime::now()
997        .duration_since(UNIX_EPOCH)
998        .expect("SystemTime should be after UNIX_EPOCH")
999        .as_secs()
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004    use super::*;
1005    use crate::ai::AiConfig;
1006
1007    #[test]
1008    fn test_temporal_reasoner_creation() {
1009        let config = AiConfig::default();
1010        let reasoner = TemporalReasoner::new(&config);
1011        assert!(reasoner.is_ok());
1012    }
1013
1014    #[test]
1015    fn test_time_interval_operations() {
1016        let interval1 = TimeInterval {
1017            start: 100,
1018            end: 200,
1019            interval_type: IntervalType::Closed,
1020        };
1021
1022        let interval2 = TimeInterval {
1023            start: 150,
1024            end: 250,
1025            interval_type: IntervalType::Closed,
1026        };
1027
1028        assert!(interval1.overlaps(&interval2));
1029        assert_eq!(
1030            interval1.relation_to(&interval2),
1031            TemporalRelation::Overlaps
1032        );
1033    }
1034
1035    #[test]
1036    fn test_temporal_fact_creation() {
1037        let fact = TemporalFact {
1038            subject: "http://example.org/person1".to_string(),
1039            predicate: "worksFor".to_string(),
1040            object: "http://example.org/company1".to_string(),
1041            validity: TimeInterval {
1042                start: 1000,
1043                end: 2000,
1044                interval_type: IntervalType::Closed,
1045            },
1046            confidence: 0.9,
1047            source: FactSource::Asserted,
1048            annotations: HashMap::new(),
1049        };
1050
1051        assert_eq!(fact.confidence, 0.9);
1052        assert!(fact.validity.contains(1500));
1053        assert!(!fact.validity.contains(2500));
1054    }
1055
1056    #[tokio::test]
1057    async fn test_temporal_query() {
1058        let config = AiConfig::default();
1059        let reasoner = TemporalReasoner::new(&config).expect("construction should succeed");
1060
1061        let query = TemporalQuery {
1062            query_type: TemporalQueryType::ValidAt {
1063                time: current_timestamp(),
1064            },
1065            entities: vec!["http://example.org/person1".to_string()],
1066            constraints: Vec::new(),
1067            time_window: None,
1068            include_inferred: false,
1069        };
1070
1071        let result = reasoner
1072            .reason(&query)
1073            .await
1074            .expect("async operation should succeed");
1075        assert!(!result.query_id.is_empty());
1076    }
1077
1078    fn fact(subject: &str, predicate: &str, object: &str, start: Timestamp) -> TemporalFact {
1079        TemporalFact {
1080            subject: subject.to_string(),
1081            predicate: predicate.to_string(),
1082            object: object.to_string(),
1083            validity: TimeInterval {
1084                start,
1085                end: start + 1000,
1086                interval_type: IntervalType::Closed,
1087            },
1088            confidence: 0.9,
1089            source: FactSource::Asserted,
1090            annotations: HashMap::new(),
1091        }
1092    }
1093
1094    #[tokio::test]
1095    async fn regression_change_detection_filters_by_entity_and_property() {
1096        let config = AiConfig::default();
1097        let mut reasoner = TemporalReasoner::new(&config).expect("construction should succeed");
1098
1099        reasoner
1100            .add_fact(fact("ent:a", "prop:title", "Engineer", 1000))
1101            .expect("add");
1102        reasoner
1103            .add_fact(fact("ent:a", "prop:title", "Manager", 2000))
1104            .expect("add");
1105        reasoner
1106            .add_fact(fact("ent:a", "prop:city", "Seattle", 1500))
1107            .expect("add");
1108        reasoner
1109            .add_fact(fact("ent:b", "prop:title", "Director", 1200))
1110            .expect("add");
1111
1112        let query = TemporalQuery {
1113            query_type: TemporalQueryType::ChangeDetection {
1114                entity: "ent:a".to_string(),
1115                property: "prop:title".to_string(),
1116            },
1117            entities: Vec::new(),
1118            constraints: Vec::new(),
1119            time_window: None,
1120            include_inferred: false,
1121        };
1122        let result = reasoner.reason(&query).await.expect("reason");
1123        // Exactly the two title facts about ent:a, not the city fact nor ent:b.
1124        assert_eq!(result.results.len(), 2);
1125        assert!(result
1126            .results
1127            .iter()
1128            .all(|f| f.subject == "ent:a" && f.predicate == "prop:title"));
1129    }
1130
1131    #[tokio::test]
1132    async fn regression_temporal_relations_filters_by_entity_pair() {
1133        let config = AiConfig::default();
1134        let mut reasoner = TemporalReasoner::new(&config).expect("construction should succeed");
1135
1136        reasoner
1137            .add_fact(fact("ent:a", "p", "ent:x", 1000))
1138            .expect("add");
1139        reasoner
1140            .add_fact(fact("ent:b", "p", "ent:y", 1000))
1141            .expect("add");
1142        reasoner
1143            .add_fact(fact("ent:c", "p", "ent:z", 1000))
1144            .expect("add");
1145
1146        let query = TemporalQuery {
1147            query_type: TemporalQueryType::TemporalRelations {
1148                entity1: "ent:a".to_string(),
1149                entity2: "ent:b".to_string(),
1150            },
1151            entities: Vec::new(),
1152            constraints: Vec::new(),
1153            time_window: None,
1154            include_inferred: false,
1155        };
1156        let result = reasoner.reason(&query).await.expect("reason");
1157        // Only facts involving ent:a or ent:b, never the ent:c fact.
1158        assert!(!result.results.is_empty());
1159        assert!(result
1160            .results
1161            .iter()
1162            .all(|f| f.subject != "ent:c" && f.object != "ent:z"));
1163    }
1164
1165    #[tokio::test]
1166    async fn regression_aggregation_query_fails_loud() {
1167        let config = AiConfig::default();
1168        let mut reasoner = TemporalReasoner::new(&config).expect("construction should succeed");
1169        reasoner
1170            .add_fact(fact("ent:a", "p", "o", 1000))
1171            .expect("add");
1172
1173        let query = TemporalQuery {
1174            query_type: TemporalQueryType::Aggregation {
1175                function: AggregationFunction::Count,
1176                grouping: TemporalGrouping::ByDay,
1177            },
1178            entities: Vec::new(),
1179            constraints: Vec::new(),
1180            time_window: None,
1181            include_inferred: false,
1182        };
1183        // Must not silently return all facts as a fake aggregate result.
1184        assert!(reasoner.reason(&query).await.is_err());
1185    }
1186}