readsight 1.0.0

Multilingual readability library — 86 languages, 17 formulas, TeX-based syllable counting via the Frank M. Liang algorithm.
Documentation
//! Remaining language-specific formulas: Gulpease, FOG-PL, Dale-Chall, Spache, OSMAN.

use crate::formula::{grade_clamp, inputs, round_to, Formula, FormulaResult, TextStatisticsHelper};
use crate::language::Language;
use crate::text::TextStatistics;

/// Gulpease Index (Italian).
pub struct Gulpease;

impl Formula for Gulpease {
    fn name(&self) -> &'static str {
        "gulpease"
    }
    fn description(&self) -> &'static str {
        "Gulpease Index - Italian readability formula. Uses letter count instead of syllables."
    }
    fn supported_languages(&self) -> &'static [&'static str] {
        &["it"]
    }
    fn calculate(&self, stats: &TextStatistics, language: &Language) -> FormulaResult {
        let w = stats.word_count.max(1) as f64;
        let score =
            89.0 + (300.0 * stats.sentence_count as f64 - 10.0 * stats.letter_count as f64) / w;
        FormulaResult {
            formula_name: self.name().to_string(),
            language_code: language.code.clone(),
            score: round_to(score, 1),
            grade_level: None,
            interpretation: interpret_gulpease(score).to_string(),
            inputs: inputs(&[
                ("letterCount", stats.letter_count as f64),
                ("wordCount", stats.word_count as f64),
                ("sentenceCount", stats.sentence_count as f64),
            ]),
        }
    }
}

fn interpret_gulpease(score: f64) -> &'static str {
    if score >= 80.0 {
        "Easy for elementary school"
    } else if score >= 60.0 {
        "Easy for middle school"
    } else if score >= 40.0 {
        "Easy for high school"
    } else {
        "Difficult for high school"
    }
}

/// FOG-PL (Polish adaptation of Gunning Fog).
pub struct FogPL;

impl Formula for FogPL {
    fn name(&self) -> &'static str {
        "fog_pl"
    }
    fn description(&self) -> &'static str {
        "FOG-PL - Polish adaptation of Gunning Fog Index."
    }
    fn supported_languages(&self) -> &'static [&'static str] {
        &["pl"]
    }
    fn calculate(&self, stats: &TextStatistics, language: &Language) -> FormulaResult {
        let w = stats.word_count.max(1) as f64;
        let s = stats.sentence_count.max(1) as f64;
        let hard_words_pct = (stats.polysyllable_count as f64 / w) * 100.0;
        let asl = w / s;
        let score = 0.4 * (asl + hard_words_pct);
        FormulaResult {
            formula_name: self.name().to_string(),
            language_code: language.code.clone(),
            score: round_to(score, 1),
            grade_level: Some(grade_clamp(score, 0.0, 19.0)),
            interpretation: interpret_fog_pl(score).to_string(),
            inputs: inputs(&[
                ("asl", round_to(asl, 2)),
                ("hardWordsPct", round_to(hard_words_pct, 2)),
                ("polysyllableCount", stats.polysyllable_count as f64),
            ]),
        }
    }
}

fn interpret_fog_pl(score: f64) -> &'static str {
    if score < 6.0 {
        "Very Easy"
    } else if score < 8.0 {
        "Easy"
    } else if score < 12.0 {
        "Standard"
    } else if score < 14.0 {
        "Hard"
    } else {
        "Very Hard"
    }
}

/// Dale-Chall readability (English).
pub struct DaleChall;

impl Formula for DaleChall {
    fn name(&self) -> &'static str {
        "dale_chall"
    }
    fn description(&self) -> &'static str {
        "Dale-Chall Readability Score - estimates difficult words via syllable heuristic (1-syllable = easy). NOTE: This is a simplified estimation, not the original 3000-word Dale list."
    }
    fn supported_languages(&self) -> &'static [&'static str] {
        &["en-us", "en-gb"]
    }
    fn calculate(&self, stats: &TextStatistics, language: &Language) -> FormulaResult {
        let difficult_pct = TextStatisticsHelper::estimate_difficult_percentage(stats);
        let raw_score = 0.1579 * difficult_pct + 0.0496 * stats.average_words_per_sentence;
        let adjusted = if difficult_pct > 5.0 {
            raw_score + 3.6365
        } else {
            raw_score
        };
        FormulaResult {
            formula_name: self.name().to_string(),
            language_code: language.code.clone(),
            score: round_to(adjusted, 1),
            grade_level: None,
            interpretation: interpret_dale_chall(adjusted).to_string(),
            inputs: inputs(&[
                ("difficultWordPct", round_to(difficult_pct, 1)),
                ("rawScore", round_to(raw_score, 4)),
                ("averageWordsPerSentence", stats.average_words_per_sentence),
            ]),
        }
    }
}

fn interpret_dale_chall(score: f64) -> &'static str {
    if score <= 4.9 {
        "4th grade or below"
    } else if score <= 5.9 {
        "5th-6th grade"
    } else if score <= 6.9 {
        "7th-8th grade"
    } else if score <= 7.9 {
        "9th-10th grade"
    } else if score <= 8.9 {
        "11th-12th grade"
    } else if score <= 9.9 {
        "College"
    } else {
        "Graduate"
    }
}

/// Spache readability (English, primary grades).
pub struct Spache;

impl Formula for Spache {
    fn name(&self) -> &'static str {
        "spache"
    }
    fn description(&self) -> &'static str {
        "Spache Readability Score - for primary-grade texts (K-4). Estimates difficult words via syllable heuristic (1-syllable = easy). NOTE: This is a simplified estimation, not the original Spache word list."
    }
    fn supported_languages(&self) -> &'static [&'static str] {
        &["en-us", "en-gb"]
    }
    fn calculate(&self, stats: &TextStatistics, language: &Language) -> FormulaResult {
        let difficult_pct = TextStatisticsHelper::estimate_difficult_percentage(stats);
        let score = 0.121 * stats.average_words_per_sentence + 0.082 * difficult_pct + 0.659;
        FormulaResult {
            formula_name: self.name().to_string(),
            language_code: language.code.clone(),
            score: round_to(score, 1),
            grade_level: Some(grade_clamp(score, 0.0, 5.0)),
            interpretation: interpret_spache(score).to_string(),
            inputs: inputs(&[
                ("averageWordsPerSentence", stats.average_words_per_sentence),
                ("difficultWordPct", round_to(difficult_pct, 2)),
            ]),
        }
    }
}

fn interpret_spache(score: f64) -> &'static str {
    if score <= 2.0 {
        "1st Grade"
    } else if score <= 2.5 {
        "2nd Grade"
    } else if score <= 3.0 {
        "3rd Grade"
    } else if score <= 3.5 {
        "4th Grade"
    } else {
        "Above 4th Grade"
    }
}

/// OSMAN (Arabic).
pub struct Osman;

impl Formula for Osman {
    fn name(&self) -> &'static str {
        "osman"
    }
    fn description(&self) -> &'static str {
        "OSMAN - Arabic readability formula combining Flesch and Fog adaptations."
    }
    fn supported_languages(&self) -> &'static [&'static str] {
        &["ar"]
    }
    fn calculate(&self, stats: &TextStatistics, language: &Language) -> FormulaResult {
        let w = stats.word_count.max(1) as f64;
        let s = stats.sentence_count.max(1) as f64;
        let asl = w / s;
        let avg_letters = stats.letter_count as f64 / w;
        let hard_words_pct = (stats.polysyllable_count as f64 / w) * 100.0;
        let score = 200.0 - 2.0 * asl - 1.5 * avg_letters - 0.4 * hard_words_pct;
        FormulaResult {
            formula_name: self.name().to_string(),
            language_code: language.code.clone(),
            score: round_to(score, 1),
            grade_level: None,
            interpretation: interpret_osman(score).to_string(),
            inputs: inputs(&[
                ("asl", round_to(asl, 2)),
                ("avgLetters", round_to(avg_letters, 2)),
                ("hardWordsPct", round_to(hard_words_pct, 2)),
            ]),
        }
    }
}

fn interpret_osman(score: f64) -> &'static str {
    if score >= 90.0 {
        "Very Easy"
    } else if score >= 70.0 {
        "Easy"
    } else if score >= 50.0 {
        "Standard"
    } else if score >= 30.0 {
        "Difficult"
    } else {
        "Very Difficult"
    }
}