Skip to main content

voirs_g2p/preprocessing/
semantic_analysis.rs

1//! Semantic analysis functionality for context-aware preprocessing.
2
3use crate::{LanguageCode, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::{HashMap, HashSet};
6
7/// Semantic context information for text
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct SemanticContext {
10    /// Detected topics with confidence scores
11    pub topics: HashMap<String, f32>,
12    /// Sentiment polarity (-1.0 to 1.0)
13    pub sentiment_polarity: f32,
14    /// Formality level (0.0 to 1.0, higher = more formal)
15    pub formality_level: f32,
16    /// Technical complexity score (0.0 to 1.0)
17    pub technical_complexity: f32,
18    /// Domain/field classification
19    pub domain: Option<String>,
20    /// Emotional tone indicators
21    pub emotion_indicators: Vec<String>,
22    /// Linguistic register (formal, informal, technical, etc.)
23    pub register: String,
24}
25
26impl Default for SemanticContext {
27    fn default() -> Self {
28        Self {
29            topics: HashMap::new(),
30            sentiment_polarity: 0.0,
31            formality_level: 0.5,
32            technical_complexity: 0.0,
33            domain: None,
34            emotion_indicators: Vec::new(),
35            register: "neutral".to_string(),
36        }
37    }
38}
39
40/// Trait for semantic analysis functionality
41pub trait SemanticAnalysis: Send + Sync {
42    /// Analyze semantic context of text
43    fn analyze_context(&self, text: &str) -> Result<SemanticContext>;
44
45    /// Detect topics in text
46    fn detect_topics(&self, text: &str) -> Result<HashMap<String, f32>>;
47
48    /// Analyze sentiment
49    fn analyze_sentiment(&self, text: &str) -> Result<f32>;
50
51    /// Assess formality level
52    fn assess_formality(&self, text: &str) -> Result<f32>;
53
54    /// Get supported languages
55    fn supported_languages(&self) -> Vec<LanguageCode>;
56}
57
58/// Basic semantic analyzer
59#[derive(Debug, Clone)]
60pub struct BasicSemanticAnalyzer {
61    /// Topic models
62    pub topic_models: HashMap<String, TopicModel>,
63    /// Sentiment lexicons
64    pub sentiment_lexicons: HashMap<LanguageCode, SentimentLexicon>,
65    /// Formality indicators
66    pub formality_indicators: HashMap<LanguageCode, FormalityIndicators>,
67}
68
69/// Simple topic model
70#[derive(Debug, Clone)]
71pub struct TopicModel {
72    /// Topic keywords with weights
73    pub keywords: HashMap<String, f32>,
74    /// Topic name
75    pub name: String,
76    /// Topic confidence threshold
77    pub threshold: f32,
78}
79
80/// Sentiment lexicon
81#[derive(Debug, Clone)]
82pub struct SentimentLexicon {
83    /// Word sentiment scores (-1.0 to 1.0)
84    pub word_scores: HashMap<String, f32>,
85    /// Negation patterns
86    pub negation_patterns: Vec<String>,
87    /// Intensifier patterns with multipliers
88    pub intensifier_patterns: Vec<(String, f32)>,
89}
90
91/// Formality indicators
92#[derive(Debug, Clone)]
93pub struct FormalityIndicators {
94    /// Formal words/phrases with scores
95    pub formal_indicators: HashMap<String, f32>,
96    /// Informal words/phrases with scores
97    pub informal_indicators: HashMap<String, f32>,
98    /// Technical terms
99    pub technical_terms: HashSet<String>,
100}
101
102impl SemanticAnalysis for BasicSemanticAnalyzer {
103    fn analyze_context(&self, text: &str) -> Result<SemanticContext> {
104        let words: Vec<&str> = text.split_whitespace().collect();
105
106        // Topic detection
107        let topics = self.detect_topics(text)?;
108
109        // Sentiment analysis
110        let sentiment_polarity = self.analyze_sentiment(text)?;
111
112        // Formality assessment
113        let formality_level = self.assess_formality(text)?;
114
115        // Technical complexity assessment
116        let technical_complexity = self.assess_technical_complexity(&words);
117
118        // Domain classification
119        let domain = self.classify_domain(&topics);
120
121        // Emotion indicators
122        let emotion_indicators = self.detect_emotion_indicators(&words);
123
124        // Register determination
125        let register = self.determine_register(formality_level, technical_complexity);
126
127        Ok(SemanticContext {
128            topics,
129            sentiment_polarity,
130            formality_level,
131            technical_complexity,
132            domain,
133            emotion_indicators,
134            register,
135        })
136    }
137
138    fn detect_topics(&self, text: &str) -> Result<HashMap<String, f32>> {
139        let words: Vec<&str> = text.split_whitespace().collect();
140        let mut topic_scores = HashMap::new();
141
142        for (topic_name, topic_model) in &self.topic_models {
143            let mut score = 0.0;
144            let mut word_count = 0;
145
146            for word in &words {
147                if let Some(word_score) = topic_model.keywords.get(&word.to_lowercase()) {
148                    score += word_score;
149                    word_count += 1;
150                }
151            }
152
153            if word_count > 0 {
154                let normalized_score = score / words.len() as f32;
155                if normalized_score >= topic_model.threshold {
156                    topic_scores.insert(topic_name.clone(), normalized_score);
157                }
158            }
159        }
160
161        Ok(topic_scores)
162    }
163
164    fn analyze_sentiment(&self, text: &str) -> Result<f32> {
165        let words: Vec<&str> = text.split_whitespace().collect();
166
167        // Use English lexicon as default
168        let lexicon = self.sentiment_lexicons.get(&LanguageCode::EnUs);
169
170        if let Some(lexicon) = lexicon {
171            let mut total_score = 0.0;
172            let mut scored_words = 0;
173            let mut negation_active = false;
174
175            for word in &words {
176                let word_lower = word.to_lowercase();
177
178                // Check for negation
179                if lexicon
180                    .negation_patterns
181                    .iter()
182                    .any(|pattern| word_lower.contains(pattern))
183                {
184                    negation_active = true;
185                    continue;
186                }
187
188                // Get sentiment score
189                if let Some(score) = lexicon.word_scores.get(&word_lower) {
190                    let final_score = if negation_active { -score } else { *score };
191                    total_score += final_score;
192                    scored_words += 1;
193                    negation_active = false; // Reset negation after applying
194                }
195            }
196
197            if scored_words > 0 {
198                Ok(total_score / scored_words as f32)
199            } else {
200                Ok(0.0)
201            }
202        } else {
203            Ok(0.0) // Neutral if no lexicon available
204        }
205    }
206
207    fn assess_formality(&self, text: &str) -> Result<f32> {
208        let words: Vec<&str> = text.split_whitespace().collect();
209
210        // Use English indicators as default
211        let indicators = self.formality_indicators.get(&LanguageCode::EnUs);
212
213        if let Some(indicators) = indicators {
214            let mut formal_score = 0.0;
215            let mut informal_score = 0.0;
216
217            for word in &words {
218                let word_lower = word.to_lowercase();
219
220                if let Some(score) = indicators.formal_indicators.get(&word_lower) {
221                    formal_score += score;
222                }
223
224                if let Some(score) = indicators.informal_indicators.get(&word_lower) {
225                    informal_score += score;
226                }
227            }
228
229            let total_score = formal_score + informal_score;
230            if total_score > 0.0 {
231                Ok(formal_score / total_score)
232            } else {
233                Ok(0.5) // Neutral
234            }
235        } else {
236            Ok(0.5) // Neutral if no indicators available
237        }
238    }
239
240    fn supported_languages(&self) -> Vec<LanguageCode> {
241        self.sentiment_lexicons.keys().copied().collect()
242    }
243}
244
245impl BasicSemanticAnalyzer {
246    /// Create a new basic semantic analyzer with comprehensive defaults
247    pub fn new() -> Self {
248        let mut analyzer = Self {
249            topic_models: HashMap::new(),
250            sentiment_lexicons: HashMap::new(),
251            formality_indicators: HashMap::new(),
252        };
253
254        // Initialize with comprehensive defaults
255        analyzer.initialize_default_topic_models();
256        analyzer.initialize_default_sentiment_lexicons();
257        analyzer.initialize_default_formality_indicators();
258
259        analyzer
260    }
261
262    /// Add a topic model
263    pub fn add_topic_model(&mut self, name: String, model: TopicModel) {
264        self.topic_models.insert(name, model);
265    }
266
267    /// Add a sentiment lexicon for a language
268    pub fn add_sentiment_lexicon(&mut self, language: LanguageCode, lexicon: SentimentLexicon) {
269        self.sentiment_lexicons.insert(language, lexicon);
270    }
271
272    /// Add formality indicators for a language
273    pub fn add_formality_indicators(
274        &mut self,
275        language: LanguageCode,
276        indicators: FormalityIndicators,
277    ) {
278        self.formality_indicators.insert(language, indicators);
279    }
280
281    /// Initialize comprehensive default topic models
282    fn initialize_default_topic_models(&mut self) {
283        // Technology topic model
284        let mut tech_model = TopicModel::new("technology".to_string(), 0.1);
285        let tech_keywords = vec![
286            ("computer", 0.9),
287            ("software", 0.8),
288            ("algorithm", 0.8),
289            ("programming", 0.9),
290            ("code", 0.7),
291            ("development", 0.7),
292            ("system", 0.6),
293            ("network", 0.7),
294            ("database", 0.8),
295            ("server", 0.7),
296            ("application", 0.6),
297            ("digital", 0.6),
298            ("internet", 0.7),
299            ("web", 0.6),
300            ("mobile", 0.6),
301            ("cloud", 0.7),
302            ("artificial", 0.8),
303            ("intelligence", 0.8),
304            ("machine", 0.7),
305            ("learning", 0.8),
306            ("data", 0.6),
307            ("analytics", 0.7),
308            ("security", 0.7),
309            ("encryption", 0.8),
310        ];
311        for (word, weight) in tech_keywords {
312            tech_model.add_keyword(word.to_string(), weight);
313        }
314        self.add_topic_model("technology".to_string(), tech_model);
315
316        // Business topic model
317        let mut business_model = TopicModel::new("business".to_string(), 0.1);
318        let business_keywords = vec![
319            ("company", 0.8),
320            ("market", 0.8),
321            ("customer", 0.7),
322            ("sales", 0.8),
323            ("revenue", 0.9),
324            ("profit", 0.9),
325            ("investment", 0.8),
326            ("strategy", 0.7),
327            ("management", 0.7),
328            ("finance", 0.8),
329            ("marketing", 0.8),
330            ("brand", 0.7),
331            ("product", 0.6),
332            ("service", 0.6),
333            ("business", 0.9),
334            ("enterprise", 0.8),
335            ("corporate", 0.8),
336            ("commercial", 0.7),
337            ("economic", 0.7),
338            ("financial", 0.8),
339            ("industry", 0.7),
340            ("sector", 0.7),
341            ("competition", 0.7),
342            ("growth", 0.7),
343        ];
344        for (word, weight) in business_keywords {
345            business_model.add_keyword(word.to_string(), weight);
346        }
347        self.add_topic_model("business".to_string(), business_model);
348
349        // Health topic model
350        let mut health_model = TopicModel::new("health".to_string(), 0.1);
351        let health_keywords = vec![
352            ("medical", 0.9),
353            ("health", 0.9),
354            ("doctor", 0.8),
355            ("patient", 0.8),
356            ("treatment", 0.8),
357            ("medicine", 0.8),
358            ("hospital", 0.8),
359            ("clinic", 0.7),
360            ("diagnosis", 0.8),
361            ("therapy", 0.7),
362            ("surgery", 0.8),
363            ("disease", 0.7),
364            ("symptoms", 0.7),
365            ("prevention", 0.7),
366            ("wellness", 0.7),
367            ("fitness", 0.6),
368            ("nutrition", 0.7),
369            ("pharmaceutical", 0.8),
370            ("research", 0.6),
371            ("clinical", 0.8),
372            ("healthcare", 0.9),
373            ("nursing", 0.7),
374            ("emergency", 0.7),
375            ("recovery", 0.6),
376        ];
377        for (word, weight) in health_keywords {
378            health_model.add_keyword(word.to_string(), weight);
379        }
380        self.add_topic_model("health".to_string(), health_model);
381
382        // Education topic model
383        let mut education_model = TopicModel::new("education".to_string(), 0.1);
384        let education_keywords = vec![
385            ("school", 0.8),
386            ("university", 0.8),
387            ("student", 0.8),
388            ("teacher", 0.8),
389            ("learning", 0.9),
390            ("education", 0.9),
391            ("academic", 0.8),
392            ("study", 0.7),
393            ("research", 0.7),
394            ("curriculum", 0.8),
395            ("course", 0.7),
396            ("degree", 0.7),
397            ("knowledge", 0.7),
398            ("scholarship", 0.8),
399            ("tuition", 0.7),
400            ("exam", 0.6),
401            ("grade", 0.6),
402            ("class", 0.6),
403            ("lecture", 0.7),
404            ("professor", 0.8),
405            ("college", 0.8),
406            ("training", 0.7),
407            ("skill", 0.6),
408            ("instruction", 0.7),
409        ];
410        for (word, weight) in education_keywords {
411            education_model.add_keyword(word.to_string(), weight);
412        }
413        self.add_topic_model("education".to_string(), education_model);
414
415        // Science topic model
416        let mut science_model = TopicModel::new("science".to_string(), 0.1);
417        let science_keywords = vec![
418            ("science", 0.9),
419            ("research", 0.8),
420            ("experiment", 0.8),
421            ("theory", 0.7),
422            ("hypothesis", 0.8),
423            ("analysis", 0.7),
424            ("method", 0.6),
425            ("result", 0.6),
426            ("conclusion", 0.7),
427            ("discovery", 0.8),
428            ("innovation", 0.7),
429            ("laboratory", 0.8),
430            ("physics", 0.8),
431            ("chemistry", 0.8),
432            ("biology", 0.8),
433            ("mathematics", 0.8),
434            ("engineering", 0.8),
435            ("technology", 0.7),
436            ("scientific", 0.8),
437            ("academic", 0.7),
438            ("publication", 0.7),
439            ("journal", 0.7),
440            ("peer", 0.6),
441            ("review", 0.6),
442        ];
443        for (word, weight) in science_keywords {
444            science_model.add_keyword(word.to_string(), weight);
445        }
446        self.add_topic_model("science".to_string(), science_model);
447    }
448
449    /// Initialize comprehensive default sentiment lexicons
450    fn initialize_default_sentiment_lexicons(&mut self) {
451        // English sentiment lexicon
452        let mut en_lexicon = SentimentLexicon::new();
453
454        // Positive words
455        let positive_words = vec![
456            ("excellent", 0.9),
457            ("amazing", 0.9),
458            ("wonderful", 0.8),
459            ("fantastic", 0.9),
460            ("great", 0.7),
461            ("good", 0.6),
462            ("nice", 0.5),
463            ("beautiful", 0.7),
464            ("perfect", 0.9),
465            ("outstanding", 0.9),
466            ("superb", 0.8),
467            ("brilliant", 0.8),
468            ("awesome", 0.8),
469            ("terrific", 0.8),
470            ("marvelous", 0.8),
471            ("spectacular", 0.8),
472            ("love", 0.8),
473            ("like", 0.4),
474            ("enjoy", 0.6),
475            ("happy", 0.7),
476            ("pleased", 0.6),
477            ("satisfied", 0.6),
478            ("delighted", 0.8),
479            ("thrilled", 0.8),
480            ("excited", 0.7),
481            ("positive", 0.6),
482            ("successful", 0.7),
483            ("effective", 0.5),
484            ("impressive", 0.7),
485            ("remarkable", 0.7),
486            ("exceptional", 0.8),
487            ("superior", 0.7),
488        ];
489        for (word, score) in positive_words {
490            en_lexicon.add_word(word.to_string(), score);
491        }
492
493        // Negative words
494        let negative_words = vec![
495            ("terrible", -0.9),
496            ("awful", -0.9),
497            ("horrible", -0.9),
498            ("disgusting", -0.9),
499            ("bad", -0.6),
500            ("poor", -0.5),
501            ("worst", -0.9),
502            ("hate", -0.8),
503            ("dislike", -0.5),
504            ("disappointing", -0.7),
505            ("frustrated", -0.7),
506            ("angry", -0.7),
507            ("sad", -0.6),
508            ("upset", -0.6),
509            ("annoyed", -0.5),
510            ("irritated", -0.5),
511            ("terrible", -0.9),
512            ("dreadful", -0.8),
513            ("appalling", -0.9),
514            ("shocking", -0.7),
515            ("unacceptable", -0.8),
516            ("inadequate", -0.6),
517            ("insufficient", -0.5),
518            ("useless", -0.8),
519            ("worthless", -0.8),
520            ("pathetic", -0.8),
521            ("ridiculous", -0.6),
522            ("absurd", -0.6),
523            ("stupid", -0.7),
524            ("foolish", -0.6),
525            ("nonsense", -0.6),
526            ("wrong", -0.4),
527        ];
528        for (word, score) in negative_words {
529            en_lexicon.add_word(word.to_string(), score);
530        }
531
532        self.add_sentiment_lexicon(LanguageCode::EnUs, en_lexicon);
533    }
534
535    /// Initialize comprehensive default formality indicators
536    fn initialize_default_formality_indicators(&mut self) {
537        // English formality indicators
538        let mut en_indicators = FormalityIndicators::new();
539
540        // Formal indicators
541        let formal_words = vec![
542            ("therefore", 0.9),
543            ("furthermore", 0.9),
544            ("however", 0.8),
545            ("nevertheless", 0.9),
546            ("consequently", 0.9),
547            ("accordingly", 0.8),
548            ("subsequently", 0.8),
549            ("moreover", 0.8),
550            ("additionally", 0.8),
551            ("likewise", 0.7),
552            ("nonetheless", 0.8),
553            ("whereas", 0.8),
554            ("regarding", 0.7),
555            ("concerning", 0.7),
556            ("pursuant", 0.9),
557            ("henceforth", 0.9),
558            ("heretofore", 0.9),
559            ("notwithstanding", 0.9),
560            ("aforementioned", 0.9),
561            ("herewith", 0.8),
562            ("kindly", 0.7),
563            ("please", 0.5),
564            ("respectfully", 0.8),
565            ("sincerely", 0.8),
566            ("cordially", 0.8),
567            ("gratefully", 0.7),
568            ("appreciate", 0.6),
569            ("acknowledge", 0.7),
570            ("endeavor", 0.8),
571            ("utilize", 0.7),
572            ("implement", 0.6),
573            ("establish", 0.6),
574        ];
575        for (word, score) in formal_words {
576            en_indicators.add_formal_indicator(word.to_string(), score);
577        }
578
579        // Informal indicators
580        let informal_words = vec![
581            ("yeah", 0.9),
582            ("yep", 0.8),
583            ("nope", 0.8),
584            ("gonna", 0.9),
585            ("wanna", 0.9),
586            ("gotta", 0.9),
587            ("kinda", 0.8),
588            ("sorta", 0.8),
589            ("dunno", 0.9),
590            ("ain't", 0.9),
591            ("can't", 0.4),
592            ("won't", 0.4),
593            ("don't", 0.4),
594            ("isn't", 0.4),
595            ("wasn't", 0.4),
596            ("weren't", 0.4),
597            ("cool", 0.6),
598            ("awesome", 0.6),
599            ("sweet", 0.7),
600            ("neat", 0.6),
601            ("stuff", 0.6),
602            ("things", 0.4),
603            ("guys", 0.7),
604            ("folks", 0.6),
605            ("ok", 0.7),
606            ("okay", 0.6),
607            ("alright", 0.7),
608            ("sure", 0.5),
609            ("totally", 0.7),
610            ("really", 0.4),
611            ("pretty", 0.4),
612            ("super", 0.6),
613        ];
614        for (word, score) in informal_words {
615            en_indicators.add_informal_indicator(word.to_string(), score);
616        }
617
618        // Technical terms
619        let technical_terms = vec![
620            "algorithm",
621            "methodology",
622            "implementation",
623            "optimization",
624            "configuration",
625            "specification",
626            "architecture",
627            "framework",
628            "paradigm",
629            "protocol",
630            "interface",
631            "abstraction",
632            "encapsulation",
633            "polymorphism",
634            "inheritance",
635            "instantiation",
636            "initialization",
637            "synchronization",
638            "asynchronous",
639            "concurrent",
640            "distributed",
641            "scalable",
642            "modular",
643            "extensible",
644            "maintainable",
645        ];
646        for term in technical_terms {
647            en_indicators.add_technical_term(term.to_string());
648        }
649
650        self.add_formality_indicators(LanguageCode::EnUs, en_indicators);
651    }
652
653    /// Assess technical complexity based on word complexity
654    fn assess_technical_complexity(&self, words: &[&str]) -> f32 {
655        let mut _technical_count = 0;
656        let mut total_complexity = 0.0;
657
658        for word in words {
659            let word_len = word.len();
660
661            // Simple heuristics for technical complexity
662            if word_len > 10 {
663                _technical_count += 1;
664                total_complexity += 0.3;
665            }
666
667            if word.contains('_') || word.contains('-') {
668                _technical_count += 1;
669                total_complexity += 0.2;
670            }
671
672            if word.chars().any(|c| c.is_uppercase()) && word.len() > 3 {
673                _technical_count += 1;
674                total_complexity += 0.1;
675            }
676        }
677
678        if words.is_empty() {
679            0.0
680        } else {
681            total_complexity / words.len() as f32
682        }
683    }
684
685    /// Classify domain based on topic scores
686    fn classify_domain(&self, topics: &HashMap<String, f32>) -> Option<String> {
687        topics
688            .iter()
689            .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
690            .map(|(domain, _)| domain.clone())
691    }
692
693    /// Detect emotion indicators in text
694    fn detect_emotion_indicators(&self, words: &[&str]) -> Vec<String> {
695        let emotion_words = [
696            "happy",
697            "sad",
698            "angry",
699            "excited",
700            "worried",
701            "surprised",
702            "disappointed",
703            "frustrated",
704            "delighted",
705            "anxious",
706        ];
707
708        words
709            .iter()
710            .filter_map(|word| {
711                let word_lower = word.to_lowercase();
712                if emotion_words.contains(&word_lower.as_str()) {
713                    Some(word_lower)
714                } else {
715                    None
716                }
717            })
718            .collect()
719    }
720
721    /// Determine linguistic register
722    fn determine_register(&self, formality_level: f32, technical_complexity: f32) -> String {
723        match (formality_level, technical_complexity) {
724            (f, t) if f > 0.7 && t > 0.5 => "academic".to_string(),
725            (f, _) if f > 0.7 => "formal".to_string(),
726            (f, _) if f < 0.3 => "informal".to_string(),
727            (_, t) if t > 0.6 => "technical".to_string(),
728            _ => "neutral".to_string(),
729        }
730    }
731}
732
733impl Default for BasicSemanticAnalyzer {
734    fn default() -> Self {
735        Self::new()
736    }
737}
738
739impl TopicModel {
740    /// Create a new topic model
741    pub fn new(name: String, threshold: f32) -> Self {
742        Self {
743            keywords: HashMap::new(),
744            name,
745            threshold,
746        }
747    }
748
749    /// Add a keyword with weight
750    pub fn add_keyword(&mut self, keyword: String, weight: f32) {
751        self.keywords.insert(keyword, weight);
752    }
753}
754
755impl SentimentLexicon {
756    /// Create a new sentiment lexicon
757    pub fn new() -> Self {
758        Self {
759            word_scores: HashMap::new(),
760            negation_patterns: vec!["not".to_string(), "no".to_string(), "never".to_string()],
761            intensifier_patterns: vec![
762                ("very".to_string(), 1.5),
763                ("extremely".to_string(), 2.0),
764                ("quite".to_string(), 1.2),
765            ],
766        }
767    }
768
769    /// Add a word with sentiment score
770    pub fn add_word(&mut self, word: String, score: f32) {
771        self.word_scores.insert(word, score.clamp(-1.0, 1.0));
772    }
773}
774
775impl Default for SentimentLexicon {
776    fn default() -> Self {
777        Self::new()
778    }
779}
780
781impl FormalityIndicators {
782    /// Create new formality indicators
783    pub fn new() -> Self {
784        Self {
785            formal_indicators: HashMap::new(),
786            informal_indicators: HashMap::new(),
787            technical_terms: HashSet::new(),
788        }
789    }
790
791    /// Add a formal indicator
792    pub fn add_formal_indicator(&mut self, word: String, score: f32) {
793        self.formal_indicators.insert(word, score);
794    }
795
796    /// Add an informal indicator
797    pub fn add_informal_indicator(&mut self, word: String, score: f32) {
798        self.informal_indicators.insert(word, score);
799    }
800
801    /// Add a technical term
802    pub fn add_technical_term(&mut self, term: String) {
803        self.technical_terms.insert(term);
804    }
805}
806
807impl Default for FormalityIndicators {
808    fn default() -> Self {
809        Self::new()
810    }
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816
817    #[test]
818    fn test_semantic_context_default() {
819        let context = SemanticContext::default();
820        assert_eq!(context.sentiment_polarity, 0.0);
821        assert_eq!(context.formality_level, 0.5);
822        assert_eq!(context.technical_complexity, 0.0);
823        assert_eq!(context.register, "neutral");
824    }
825
826    #[test]
827    fn test_topic_model() {
828        let mut model = TopicModel::new("technology".to_string(), 0.1);
829        model.add_keyword("computer".to_string(), 0.8);
830        model.add_keyword("software".to_string(), 0.7);
831
832        assert_eq!(model.keywords.len(), 2);
833        assert_eq!(model.keywords.get("computer"), Some(&0.8));
834    }
835
836    #[test]
837    fn test_sentiment_lexicon() {
838        let mut lexicon = SentimentLexicon::new();
839        lexicon.add_word("happy".to_string(), 0.8);
840        lexicon.add_word("sad".to_string(), -0.6);
841
842        assert_eq!(lexicon.word_scores.get("happy"), Some(&0.8));
843        assert_eq!(lexicon.word_scores.get("sad"), Some(&-0.6));
844    }
845
846    #[test]
847    fn test_formality_indicators() {
848        let mut indicators = FormalityIndicators::new();
849        indicators.add_formal_indicator("therefore".to_string(), 0.8);
850        indicators.add_informal_indicator("yeah".to_string(), 0.9);
851        indicators.add_technical_term("algorithm".to_string());
852
853        assert_eq!(indicators.formal_indicators.len(), 1);
854        assert_eq!(indicators.informal_indicators.len(), 1);
855        assert!(indicators.technical_terms.contains("algorithm"));
856    }
857
858    #[test]
859    fn test_basic_semantic_analyzer() {
860        let analyzer = BasicSemanticAnalyzer::new();
861
862        // Test with empty text
863        let context = analyzer.analyze_context("").unwrap();
864        assert_eq!(context.topics.len(), 0);
865        assert_eq!(context.sentiment_polarity, 0.0);
866    }
867
868    #[test]
869    fn test_technical_complexity() {
870        let analyzer = BasicSemanticAnalyzer::new();
871        let words = vec!["very_complex_function", "algorithm", "data"];
872        let complexity = analyzer.assess_technical_complexity(&words);
873        assert!(complexity > 0.0);
874    }
875
876    #[test]
877    fn test_emotion_indicators() {
878        let analyzer = BasicSemanticAnalyzer::new();
879        let words = vec!["I", "am", "very", "happy", "today"];
880        let emotions = analyzer.detect_emotion_indicators(&words);
881        assert!(emotions.contains(&"happy".to_string()));
882    }
883
884    #[test]
885    fn test_register_determination() {
886        let analyzer = BasicSemanticAnalyzer::new();
887
888        assert_eq!(analyzer.determine_register(0.8, 0.6), "academic");
889        assert_eq!(analyzer.determine_register(0.8, 0.2), "formal");
890        assert_eq!(analyzer.determine_register(0.2, 0.1), "informal");
891        assert_eq!(analyzer.determine_register(0.5, 0.7), "technical");
892        assert_eq!(analyzer.determine_register(0.5, 0.3), "neutral");
893    }
894}