writing_analysis/
readability.rs1use crate::error::{Result, WritingAnalysisError};
2use crate::utils::{compute_statistics, TextStatistics};
3
4#[derive(Debug, Clone, PartialEq)]
6pub struct ReadabilityScores {
7 pub flesch_kincaid_grade: f64,
9 pub flesch_reading_ease: f64,
11 pub smog_index: f64,
15 pub coleman_liau_index: f64,
17 pub automated_readability_index: f64,
19}
20
21fn flesch_kincaid_grade(stats: &TextStatistics) -> f64 {
22 let words = stats.word_count as f64;
23 let sentences = stats.sentence_count as f64;
24 let syllables = stats.syllable_count as f64;
25 0.39 * (words / sentences) + 11.8 * (syllables / words) - 15.59
26}
27
28fn flesch_reading_ease(stats: &TextStatistics) -> f64 {
29 let words = stats.word_count as f64;
30 let sentences = stats.sentence_count as f64;
31 let syllables = stats.syllable_count as f64;
32 206.835 - 1.015 * (words / sentences) - 84.6 * (syllables / words)
33}
34
35fn smog_index(stats: &TextStatistics) -> f64 {
36 let polysyllables = stats.polysyllable_count as f64;
37 let sentences = stats.sentence_count as f64;
38 3.0 + (polysyllables * 30.0 / sentences).sqrt()
39}
40
41fn coleman_liau_index(stats: &TextStatistics) -> f64 {
42 let words = stats.word_count as f64;
43 let l = (stats.character_count as f64 / words) * 100.0;
44 let s = (stats.sentence_count as f64 / words) * 100.0;
45 0.0588 * l - 0.296 * s - 15.8
46}
47
48fn automated_readability_index(stats: &TextStatistics) -> f64 {
49 let words = stats.word_count as f64;
50 let sentences = stats.sentence_count as f64;
51 let characters = stats.character_count as f64;
52 4.71 * (characters / words) + 0.5 * (words / sentences) - 21.43
53}
54
55pub fn analyze_readability(text: &str) -> Result<ReadabilityScores> {
57 let stats = compute_statistics(text);
58
59 if stats.word_count == 0 {
60 return Err(WritingAnalysisError::EmptyText);
61 }
62 if stats.word_count < 2 {
63 return Err(WritingAnalysisError::TextTooShort {
64 min_words: 2,
65 found: stats.word_count,
66 });
67 }
68 if stats.sentence_count == 0 {
69 return Err(WritingAnalysisError::NoSentences);
70 }
71
72 Ok(ReadabilityScores {
73 flesch_kincaid_grade: flesch_kincaid_grade(&stats),
74 flesch_reading_ease: flesch_reading_ease(&stats),
75 smog_index: smog_index(&stats),
76 coleman_liau_index: coleman_liau_index(&stats),
77 automated_readability_index: automated_readability_index(&stats),
78 })
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn readability_simple_text() {
87 let scores = analyze_readability("The cat sat on the mat.").unwrap();
88 assert!(scores.flesch_kincaid_grade < 5.0);
89 assert!(scores.flesch_reading_ease > 80.0);
90 }
91
92 #[test]
93 fn readability_complex_text() {
94 let text = "The implementation of sophisticated algorithms \
95 necessitates comprehensive understanding of computational \
96 complexity and mathematical abstractions.";
97 let scores = analyze_readability(text).unwrap();
98 assert!(scores.flesch_kincaid_grade > 12.0);
99 assert!(scores.flesch_reading_ease < 30.0);
100 }
101
102 #[test]
103 fn readability_empty_text() {
104 let result = analyze_readability("");
105 assert!(result.is_err());
106 }
107
108 #[test]
109 fn readability_all_scores_finite() {
110 let scores = analyze_readability("Hello world. This is a test.").unwrap();
111 assert!(scores.flesch_kincaid_grade.is_finite());
112 assert!(scores.flesch_reading_ease.is_finite());
113 assert!(scores.smog_index.is_finite());
114 assert!(scores.coleman_liau_index.is_finite());
115 assert!(scores.automated_readability_index.is_finite());
116 }
117}