Skip to main content
Glama
by 8b-is
temporal.rs13.1 kB
use chrono::{DateTime, Utc, Timelike, Datelike}; use serde::{Deserialize, Serialize}; use std::collections::VecDeque; /// Temporal analysis of signature evolution pub struct TemporalAnalyzer { /// Window size for moving averages window_size: usize, /// History buffer for recent signatures history: VecDeque<TimestampedSignature>, /// Detected patterns patterns: Vec<TemporalPattern>, } #[derive(Debug, Clone)] struct TimestampedSignature { timestamp: DateTime<Utc>, vectors: super::SignatureVectors, context: String, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TemporalPattern { pub pattern_type: PatternType, pub period: Option<i64>, // Duration in seconds pub strength: f32, pub first_seen: DateTime<Utc>, pub last_seen: DateTime<Utc>, } #[derive(Debug, Serialize, Deserialize, Clone)] pub enum PatternType { /// Daily rhythm (morning vs evening behavior) Circadian, /// Weekly patterns (weekday vs weekend) Weekly, /// Project-based shifts Contextual, /// Gradual drift over time LongTermDrift, /// Sudden spikes or drops Anomaly, } impl TemporalAnalyzer { pub fn new(window_size: usize) -> Self { Self { window_size, history: VecDeque::with_capacity(window_size * 2), patterns: Vec::new(), } } /// Add a new signature to the temporal analysis pub fn add_signature( &mut self, vectors: super::SignatureVectors, context: String ) { let timestamped = TimestampedSignature { timestamp: Utc::now(), vectors, context, }; self.history.push_back(timestamped); // Keep history bounded while self.history.len() > self.window_size * 2 { self.history.pop_front(); } // Analyze for patterns self.detect_patterns(); } /// Detect temporal patterns in the signature evolution fn detect_patterns(&mut self) { if self.history.len() < 10 { return; // Need enough data } // Check for circadian patterns if let Some(pattern) = self.detect_circadian() { self.update_or_add_pattern(pattern); } // Check for weekly patterns if let Some(pattern) = self.detect_weekly() { self.update_or_add_pattern(pattern); } // Check for contextual shifts if let Some(pattern) = self.detect_contextual() { self.update_or_add_pattern(pattern); } // Check for long-term drift if let Some(pattern) = self.detect_drift() { self.update_or_add_pattern(pattern); } } fn detect_circadian(&self) -> Option<TemporalPattern> { // Group signatures by hour of day let mut hourly_groups: Vec<Vec<&TimestampedSignature>> = vec![vec![]; 24]; for sig in &self.history { let hour = sig.timestamp.hour() as usize; hourly_groups[hour].push(sig); } // Find hours with significant behavioral differences let mut max_variance = 0.0; let mut _peak_hours = (0, 12); // Default morning vs evening for morning in 6..12 { for evening in 18..23 { if hourly_groups[morning].is_empty() || hourly_groups[evening].is_empty() { continue; } let variance = self.calculate_group_variance( &hourly_groups[morning], &hourly_groups[evening] ); if variance > max_variance { max_variance = variance; _peak_hours = (morning, evening); } } } if max_variance > 0.2 { // Threshold for significant pattern Some(TemporalPattern { pattern_type: PatternType::Circadian, period: Some(24 * 3600), // 24 hours in seconds strength: max_variance, first_seen: self.history.front()?.timestamp, last_seen: self.history.back()?.timestamp, }) } else { None } } fn detect_weekly(&self) -> Option<TemporalPattern> { use chrono::Datelike; // Group by weekday vs weekend let mut weekday_sigs = Vec::new(); let mut weekend_sigs = Vec::new(); for sig in &self.history { let weekday = sig.timestamp.weekday(); match weekday { chrono::Weekday::Sat | chrono::Weekday::Sun => weekend_sigs.push(sig), _ => weekday_sigs.push(sig), } } if weekday_sigs.len() < 5 || weekend_sigs.len() < 2 { return None; // Not enough data } let variance = self.calculate_group_variance(&weekday_sigs, &weekend_sigs); if variance > 0.15 { Some(TemporalPattern { pattern_type: PatternType::Weekly, period: Some(7 * 24 * 3600), // 7 days in seconds strength: variance, first_seen: self.history.front()?.timestamp, last_seen: self.history.back()?.timestamp, }) } else { None } } fn detect_contextual(&self) -> Option<TemporalPattern> { // Group by context let mut context_groups: std::collections::HashMap<String, Vec<&TimestampedSignature>> = std::collections::HashMap::new(); for sig in &self.history { context_groups.entry(sig.context.clone()) .or_insert_with(Vec::new) .push(sig); } // Find contexts with most variance let mut max_variance: f32 = 0.0; for (ctx1, sigs1) in &context_groups { for (ctx2, sigs2) in &context_groups { if ctx1 == ctx2 || sigs1.len() < 3 || sigs2.len() < 3 { continue; } let variance = self.calculate_group_variance(sigs1, sigs2); max_variance = max_variance.max(variance); } } if max_variance > 0.25 { Some(TemporalPattern { pattern_type: PatternType::Contextual, period: None, strength: max_variance, first_seen: self.history.front()?.timestamp, last_seen: self.history.back()?.timestamp, }) } else { None } } fn detect_drift(&self) -> Option<TemporalPattern> { if self.history.len() < 20 { return None; } // Compare early signatures to recent ones let early_count = self.history.len() / 4; let recent_count = self.history.len() / 4; let early_sigs: Vec<_> = self.history.iter().take(early_count).collect(); let recent_sigs: Vec<_> = self.history.iter().rev().take(recent_count).collect(); let drift = self.calculate_group_variance(&early_sigs, &recent_sigs); if drift > 0.1 { Some(TemporalPattern { pattern_type: PatternType::LongTermDrift, period: None, strength: drift, first_seen: self.history.front()?.timestamp, last_seen: self.history.back()?.timestamp, }) } else { None } } fn calculate_group_variance( &self, group1: &[&TimestampedSignature], group2: &[&TimestampedSignature] ) -> f32 { if group1.is_empty() || group2.is_empty() { return 0.0; } // Calculate average vectors for each group let avg1 = self.average_signatures(group1); let avg2 = self.average_signatures(group2); // Calculate distance between averages self.signature_distance(&avg1, &avg2) } fn average_signatures(&self, sigs: &[&TimestampedSignature]) -> super::SignatureVectors { let count = sigs.len() as f32; // Sum all components let mut avg_style = super::StyleVector { terseness: 0.0, humor_density: 0.0, technicality: 0.0, formality: 0.0, bullet_preference: 0.0, }; for sig in sigs { avg_style.terseness += sig.vectors.style.terseness; avg_style.humor_density += sig.vectors.style.humor_density; avg_style.technicality += sig.vectors.style.technicality; avg_style.formality += sig.vectors.style.formality; avg_style.bullet_preference += sig.vectors.style.bullet_preference; } // Divide by count avg_style.terseness /= count; avg_style.humor_density /= count; avg_style.technicality /= count; avg_style.formality /= count; avg_style.bullet_preference /= count; // For brevity, just using style vector here // In real implementation, would average all vectors super::SignatureVectors { style: avg_style, behavior: sigs[0].vectors.behavior.clone(), concepts: sigs[0].vectors.concepts.clone(), linguistic: sigs[0].vectors.linguistic.clone(), emotional: sigs[0].vectors.emotional.clone(), } } fn signature_distance(&self, sig1: &super::SignatureVectors, sig2: &super::SignatureVectors) -> f32 { use super::VectorDistance; let style_dist = sig1.style.distance(&sig2.style); let behavior_dist = sig1.behavior.distance(&sig2.behavior); let emotional_dist = sig1.emotional.distance(&sig2.emotional); (style_dist + behavior_dist + emotional_dist) / 3.0 } fn update_or_add_pattern(&mut self, pattern: TemporalPattern) { // Update existing pattern or add new one if let Some(existing) = self.patterns.iter_mut() .find(|p| std::mem::discriminant(&p.pattern_type) == std::mem::discriminant(&pattern.pattern_type)) { existing.strength = pattern.strength; existing.last_seen = pattern.last_seen; } else { self.patterns.push(pattern); } } /// Get current temporal state pub fn get_temporal_state(&self) -> TemporalState { TemporalState { current_phase: self.determine_current_phase(), active_patterns: self.patterns.clone(), stability_score: self.calculate_stability(), predicted_next: self.predict_next_state(), } } fn determine_current_phase(&self) -> String { // Based on time of day and recent patterns let now = Utc::now(); let hour = now.hour(); match hour { 6..=11 => "morning_flow".to_string(), 12..=17 => "afternoon_focus".to_string(), 18..=23 => "evening_wind_down".to_string(), _ => "night_owl".to_string(), } } fn calculate_stability(&self) -> f32 { if self.history.len() < 5 { return 1.0; // Not enough data } // Calculate variance in recent signatures let recent: Vec<_> = self.history.iter().rev().take(5).collect(); let mut total_variance = 0.0; for i in 1..recent.len() { let dist = self.signature_distance(&recent[i-1].vectors, &recent[i].vectors); total_variance += dist; } // Convert to stability score (inverse of variance) 1.0 - (total_variance / 4.0).min(1.0) } fn predict_next_state(&self) -> Option<String> { // Simple prediction based on patterns if let Some(circadian) = self.patterns.iter() .find(|p| matches!(p.pattern_type, PatternType::Circadian)) { if circadian.strength > 0.3 { return Some("Circadian shift expected".to_string()); } } None } } #[derive(Debug, Serialize, Deserialize)] pub struct TemporalState { pub current_phase: String, pub active_patterns: Vec<TemporalPattern>, pub stability_score: f32, pub predicted_next: Option<String>, } /// Calculate temporal velocity (rate of change) pub fn calculate_velocity( history: &[super::SignatureBlock], window: usize ) -> f32 { if history.len() < window + 1 { return 0.0; } let recent = &history[history.len() - window..]; let mut total_change = 0.0; for i in 1..recent.len() { total_change += recent[i].divergence.delta_magnitude; } total_change / (window - 1) as f32 }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server