use super::config::ContextAnalysisConfig;
use super::results::{
ContextEvolutionPattern, ContextualAdaptation, ContextualCoherence, ContextualSemanticMetrics,
ContextualTransition, SemanticContext,
};
use crate::error::TextAnalysisError;
use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::hash::{Hash, Hasher};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ContextAnalysisError {
#[error("Invalid context analysis configuration: {0}")]
ConfigError(String),
#[error("Context calculation failed: {0}")]
CalculationError(String),
#[error("Semantic context analysis error: {0}")]
AnalysisError(String),
}
pub type ContextResult<T> = Result<T, ContextAnalysisError>;
#[derive(Debug, Clone)]
pub struct SemanticContextAnalyzer {
config: ContextAnalysisConfig,
context_windows: VecDeque<SemanticContext>,
contextual_mappings: HashMap<String, Vec<SemanticContext>>,
transition_patterns: Vec<ContextualTransition>,
context_cache: HashMap<u64, ContextualSemanticMetrics>,
adaptation_tracker: BTreeMap<usize, ContextualAdaptation>,
evolution_patterns: Vec<ContextEvolutionPattern>,
coherence_tracker: HashMap<String, f64>,
}
impl SemanticContextAnalyzer {
pub fn new(config: ContextAnalysisConfig) -> ContextResult<Self> {
Self::validate_config(&config)?;
Ok(Self {
config,
context_windows: VecDeque::new(),
contextual_mappings: HashMap::new(),
transition_patterns: Vec::new(),
context_cache: HashMap::new(),
adaptation_tracker: BTreeMap::new(),
evolution_patterns: Vec::new(),
coherence_tracker: HashMap::new(),
})
}
pub fn analyze_contextual_semantics(
&mut self,
text: &str,
context_metadata: Option<&HashMap<String, String>>,
) -> ContextResult<ContextualSemanticMetrics> {
let cache_key = self.generate_cache_key(text, context_metadata);
if let Some(cached) = self.context_cache.get(&cache_key) {
return Ok(cached.clone());
}
let sentences = self.extract_sentences(text);
let mut metrics = ContextualSemanticMetrics::default();
let contexts = self.build_semantic_contexts(&sentences, context_metadata)?;
metrics.overall_contextual_score = self.calculate_overall_contextual_score(&contexts)?;
metrics.context_preservation_score = self.analyze_context_preservation(&contexts)?;
metrics.contextual_adaptation_score = self.evaluate_contextual_adaptation(&contexts)?;
metrics.semantic_context_coherence = self.calculate_contextual_coherence(&contexts)?;
if self.config.analyze_advanced_context {
metrics.contextual_transitions = self.analyze_contextual_transitions(&contexts)?;
metrics.adaptation_patterns = self.identify_adaptation_patterns(&contexts)?;
metrics.evolution_patterns = self.detect_evolution_patterns(&contexts)?;
metrics.contextual_coherence_analysis = self.perform_coherence_analysis(&contexts)?;
}
if self.config.multi_scale_analysis {
metrics.local_context_metrics = self.analyze_local_context(&contexts)?;
metrics.global_context_metrics = self.analyze_global_context(&contexts)?;
metrics.hierarchical_context_metrics = self.analyze_hierarchical_context(&contexts)?;
}
if self.config.analyze_context_sensitivity {
metrics.sensitivity_measures = self.calculate_context_sensitivity(&contexts)?;
metrics.adaptation_flexibility = self.measure_adaptation_flexibility(&contexts)?;
}
self.context_cache.insert(cache_key, metrics.clone());
Ok(metrics)
}
fn build_semantic_contexts(
&mut self,
sentences: &[String],
metadata: Option<&HashMap<String, String>>,
) -> ContextResult<Vec<SemanticContext>> {
let mut contexts = Vec::new();
let window_size = self.config.context_window_size;
for (i, sentence) in sentences.iter().enumerate() {
let start_idx = i.saturating_sub(window_size / 2);
let end_idx = (i + window_size / 2 + 1).min(sentences.len());
let preceding_context = if start_idx < i {
sentences[start_idx..i].to_vec()
} else {
vec![]
};
let following_context = if i + 1 < end_idx {
sentences[i + 1..end_idx].to_vec()
} else {
vec![]
};
let context = SemanticContext {
sentence_index: i,
focal_sentence: sentence.clone(),
preceding_context,
following_context,
semantic_features: self
.extract_contextual_features(sentence, &sentences[start_idx..end_idx])?,
context_strength: self.calculate_context_strength(i, sentences)?,
contextual_keywords: self
.identify_contextual_keywords(sentence, &sentences[start_idx..end_idx])?,
semantic_roles: self
.analyze_semantic_roles(sentence, &sentences[start_idx..end_idx])?,
contextual_coherence: self
.measure_local_coherence(sentence, &sentences[start_idx..end_idx])?,
adaptation_indicators: self
.identify_adaptation_indicators(sentence, &sentences[start_idx..end_idx])?,
metadata: metadata.cloned().unwrap_or_default(),
};
contexts.push(context);
}
self.update_context_tracking(&contexts)?;
Ok(contexts)
}
fn calculate_overall_contextual_score(
&self,
contexts: &[SemanticContext],
) -> ContextResult<f64> {
if contexts.is_empty() {
return Ok(0.0);
}
let mut scores = Vec::new();
for context in contexts {
let context_score = self.evaluate_single_context(context)?;
scores.push(context_score);
}
let weighted_score = if self.config.weight_by_context_strength {
self.calculate_strength_weighted_score(&scores, contexts)?
} else {
scores.iter().sum::<f64>() / scores.len() as f64
};
Ok(weighted_score.max(0.0).min(1.0))
}
fn analyze_context_preservation(&self, contexts: &[SemanticContext]) -> ContextResult<f64> {
if contexts.len() < 2 {
return Ok(1.0);
}
let mut preservation_scores = Vec::new();
for i in 1..contexts.len() {
let preservation =
self.calculate_context_preservation(&contexts[i - 1], &contexts[i])?;
preservation_scores.push(preservation);
}
Ok(preservation_scores.iter().sum::<f64>() / preservation_scores.len() as f64)
}
fn evaluate_contextual_adaptation(
&mut self,
contexts: &[SemanticContext],
) -> ContextResult<f64> {
let mut adaptation_scores = Vec::new();
for (i, context) in contexts.iter().enumerate() {
let adaptation_quality = self.assess_adaptation_quality(context, i)?;
adaptation_scores.push(adaptation_quality);
if adaptation_quality != 0.0 {
let adaptation = ContextualAdaptation {
position: i,
adaptation_type: self.classify_adaptation_type(context)?,
strength: adaptation_quality,
triggers: self.identify_adaptation_triggers(context)?,
semantic_shift: self.measure_semantic_shift(context)?,
};
self.adaptation_tracker.insert(i, adaptation);
}
}
Ok(adaptation_scores.iter().sum::<f64>() / adaptation_scores.len() as f64)
}
fn calculate_contextual_coherence(
&mut self,
contexts: &[SemanticContext],
) -> ContextResult<f64> {
let mut coherence_measures = Vec::new();
for context in contexts {
let local_coherence = self.measure_local_contextual_coherence(context)?;
coherence_measures.push(local_coherence);
let context_key = format!("context_{}", context.sentence_index);
self.coherence_tracker.insert(context_key, local_coherence);
}
let global_coherence = self.measure_global_contextual_coherence(contexts)?;
let local_avg = coherence_measures.iter().sum::<f64>() / coherence_measures.len() as f64;
let combined_coherence = (local_avg * self.config.local_coherence_weight
+ global_coherence * self.config.global_coherence_weight)
/ (self.config.local_coherence_weight + self.config.global_coherence_weight);
Ok(combined_coherence)
}
fn analyze_contextual_transitions(
&mut self,
contexts: &[SemanticContext],
) -> ContextResult<Vec<ContextualTransition>> {
let mut transitions = Vec::new();
if contexts.len() < 2 {
return Ok(transitions);
}
for i in 1..contexts.len() {
let transition = self.analyze_single_transition(&contexts[i - 1], &contexts[i])?;
transitions.push(transition);
}
self.transition_patterns = transitions.clone();
Ok(transitions)
}
fn identify_adaptation_patterns(
&self,
contexts: &[SemanticContext],
) -> ContextResult<Vec<ContextualAdaptation>> {
let mut patterns = Vec::new();
let window_size = self.config.adaptation_window_size;
for window_start in 0..=(contexts.len().saturating_sub(window_size)) {
let window = &contexts[window_start..window_start + window_size];
if let Some(pattern) = self.detect_adaptation_pattern(window)? {
patterns.push(pattern);
}
}
Ok(patterns)
}
fn detect_evolution_patterns(
&mut self,
contexts: &[SemanticContext],
) -> ContextResult<Vec<ContextEvolutionPattern>> {
let mut evolution_patterns = Vec::new();
if contexts.len() < self.config.min_evolution_window {
return Ok(evolution_patterns);
}
let evolution_windows = self.create_evolution_windows(contexts)?;
for window in evolution_windows {
let pattern = self.analyze_evolution_window(&window)?;
evolution_patterns.push(pattern);
}
self.evolution_patterns = evolution_patterns.clone();
Ok(evolution_patterns)
}
fn perform_coherence_analysis(
&self,
contexts: &[SemanticContext],
) -> ContextResult<ContextualCoherence> {
let inter_context_coherence = self.analyze_inter_context_coherence(contexts)?;
let intra_context_coherence = self.analyze_intra_context_coherence(contexts)?;
let temporal_coherence = self.analyze_temporal_coherence(contexts)?;
let thematic_coherence = self.analyze_thematic_coherence(contexts)?;
Ok(ContextualCoherence {
inter_context_coherence,
intra_context_coherence,
temporal_coherence,
thematic_coherence,
overall_coherence: self.calculate_overall_coherence(
inter_context_coherence,
intra_context_coherence,
temporal_coherence,
thematic_coherence,
)?,
coherence_stability: self.measure_coherence_stability(contexts)?,
coherence_breakdown_points: self.identify_coherence_breakdowns(contexts)?,
})
}
fn validate_config(config: &ContextAnalysisConfig) -> ContextResult<()> {
if config.context_window_size < 1 {
return Err(ContextAnalysisError::ConfigError(
"context_window_size must be at least 1".to_string(),
));
}
if config.local_coherence_weight < 0.0 || config.global_coherence_weight < 0.0 {
return Err(ContextAnalysisError::ConfigError(
"coherence weights must be non-negative".to_string(),
));
}
Ok(())
}
fn extract_sentences(&self, text: &str) -> Vec<String> {
text.split(&self.config.sentence_delimiters)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
}
fn generate_cache_key(&self, text: &str, metadata: Option<&HashMap<String, String>>) -> u64 {
let mut hasher = DefaultHasher::new();
text.hash(&mut hasher);
if let Some(meta) = metadata {
for (k, v) in meta {
k.hash(&mut hasher);
v.hash(&mut hasher);
}
}
self.config.hash(&mut hasher);
hasher.finish()
}
fn extract_contextual_features(
&self,
sentence: &str,
context_window: &[String],
) -> ContextResult<HashMap<String, f64>> {
let mut features = HashMap::new();
features.insert(
"sentence_length".to_string(),
sentence.split_whitespace().count() as f64,
);
features.insert(
"context_window_size".to_string(),
context_window.len() as f64,
);
let semantic_density = self.calculate_semantic_density(sentence, context_window)?;
features.insert("semantic_density".to_string(), semantic_density);
let contextual_overlap = self.calculate_contextual_overlap(sentence, context_window)?;
features.insert("contextual_overlap".to_string(), contextual_overlap);
let semantic_novelty = self.calculate_semantic_novelty(sentence, context_window)?;
features.insert("semantic_novelty".to_string(), semantic_novelty);
Ok(features)
}
fn calculate_context_strength(
&self,
position: usize,
sentences: &[String],
) -> ContextResult<f64> {
let total_sentences = sentences.len() as f64;
let relative_position = position as f64 / total_sentences;
let position_strength = 1.0 - (2.0 * (relative_position - 0.5)).abs();
let content_strength = if position > 0 && position < sentences.len() - 1 {
self.calculate_content_connectivity(
&sentences[position],
&sentences[position - 1],
&sentences[position + 1],
)?
} else {
0.5 };
Ok((position_strength + content_strength) / 2.0)
}
fn update_context_tracking(&mut self, contexts: &[SemanticContext]) -> ContextResult<()> {
self.context_windows.clear();
for context in contexts {
self.context_windows.push_back(context.clone());
if self.context_windows.len() > self.config.max_tracked_contexts {
self.context_windows.pop_front();
}
}
for context in contexts {
for keyword in &context.contextual_keywords {
self.contextual_mappings
.entry(keyword.clone())
.or_insert_with(Vec::new)
.push(context.clone());
}
}
Ok(())
}
}
impl Default for SemanticContextAnalyzer {
fn default() -> Self {
Self::new(ContextAnalysisConfig::default()).expect("default context config should be valid")
}
}