writing_analysis/
sentiment.rs1use crate::error::{Result, WritingAnalysisError};
2use crate::lexicon::get_score;
3use crate::utils::split_words;
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct SentimentResult {
8 pub score: f64,
10 pub comparative: f64,
12 pub tokens: Vec<TokenSentiment>,
14}
15
16#[derive(Debug, Clone, PartialEq)]
18pub struct TokenSentiment {
19 pub word: String,
21 pub score: i32,
23}
24
25pub fn analyze_sentiment(text: &str) -> Result<SentimentResult> {
27 let words = split_words(text);
28 if words.is_empty() {
29 return Err(WritingAnalysisError::EmptyText);
30 }
31
32 let mut tokens = Vec::new();
33 let mut total_score: i32 = 0;
34
35 for word in &words {
36 let lower = word.to_lowercase();
37 let score = get_score(&lower).unwrap_or(0);
38 total_score += score;
39 tokens.push(TokenSentiment {
40 word: word.to_string(),
41 score,
42 });
43 }
44
45 let token_count = tokens.len() as f64;
46 let comparative = total_score as f64 / token_count;
47 let score = (comparative * 2.0).clamp(-1.0, 1.0);
48
49 Ok(SentimentResult {
50 score,
51 comparative,
52 tokens,
53 })
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn positive_sentiment() {
62 let result = analyze_sentiment("I love this wonderful movie.").unwrap();
63 assert!(result.score > 0.0);
64 assert!(result.comparative > 0.0);
65 }
66
67 #[test]
68 fn negative_sentiment() {
69 let result = analyze_sentiment("This is a terrible awful disaster.").unwrap();
70 assert!(result.score < 0.0);
71 assert!(result.comparative < 0.0);
72 }
73
74 #[test]
75 fn neutral_sentiment() {
76 let result = analyze_sentiment("The table is in the room.").unwrap();
77 assert!(result.score.abs() < 0.3);
78 }
79
80 #[test]
81 fn token_level_scores() {
82 let result = analyze_sentiment("I love hate things.").unwrap();
83 let love_token = result.tokens.iter().find(|t| t.word == "love").unwrap();
84 let hate_token = result.tokens.iter().find(|t| t.word == "hate").unwrap();
85 assert!(love_token.score > 0);
86 assert!(hate_token.score < 0);
87 }
88
89 #[test]
90 fn empty_text_error() {
91 let result = analyze_sentiment("");
92 assert!(result.is_err());
93 }
94
95 #[test]
96 fn comparative_normalization() {
97 let short = analyze_sentiment("I love this.").unwrap();
98 let long = analyze_sentiment("I love this thing that is here today.").unwrap();
99 assert!(short.comparative.abs() > long.comparative.abs());
100 }
101}