use crate::utils::math::clamp_score;
pub fn apply_weighted_score(
score: f32,
weight: f32,
total_score: &mut f32,
total_weight: &mut f32,
) {
if weight > 0.0 && score.is_finite() {
let clamped_score = clamp_score(score);
*total_score += clamped_score * weight;
*total_weight += weight;
}
}
pub fn finalize_weighted_score(total_score: f32, total_weight: f32, default_score: f32) -> f32 {
if total_weight > 0.0 {
clamp_score(total_score / total_weight)
} else {
clamp_score(default_score)
}
}
pub fn score_by_range(
value: usize,
optimal_range: Option<(usize, usize)>,
default_ranges: &[(usize, usize, f32)],
) -> f32 {
if let Some((min_len, max_len)) = optimal_range {
match value {
0 => 0.0,
v if v < min_len => (v as f32 / min_len as f32) * 60.0,
v if v >= min_len && v <= max_len => 100.0,
v if v <= max_len + (max_len - min_len) / 2 => 80.0,
_ => 50.0,
}
} else {
for (min, max, score) in default_ranges {
if value >= *min && value <= *max {
return *score;
}
}
30.0
}
}
pub fn score_by_percentage(
percentage: f32,
excellent_threshold: f32,
good_threshold: f32,
fair_threshold: f32,
) -> f32 {
match percentage {
p if p >= excellent_threshold => 100.0,
p if p >= good_threshold => {
let range = excellent_threshold - good_threshold;
if range > 0.0 {
80.0 + ((p - good_threshold) / range) * 20.0
} else {
80.0
}
}
p if p >= fair_threshold => {
let range = good_threshold - fair_threshold;
if range > 0.0 {
60.0 + ((p - fair_threshold) / range) * 20.0
} else {
60.0
}
}
p => (p / fair_threshold) * 60.0,
}
}
pub fn calculate_length_penalty(value: usize, optimal_min: usize, optimal_max: usize) -> f32 {
match value {
0 => 0.0,
v if v < optimal_min => (v as f32 / optimal_min as f32) * 60.0,
v if v >= optimal_min && v <= optimal_max => 100.0,
v if v <= optimal_max + (optimal_max - optimal_min) / 2 => 80.0,
_ => 50.0,
}
}
pub fn calculate_readability_penalty(fk_score: Option<f32>) -> f32 {
match fk_score {
Some(score) if score > 18.0 => (score - 18.0) * 2.0, Some(score) if score < 2.0 => (2.0 - score) * 3.0, _ => 0.0, }
}
pub fn readability_fk_to_score(fk_score: f32) -> f32 {
let readability_percentage = if fk_score <= 6.0 {
100.0 } else if fk_score <= 9.0 {
90.0 - ((fk_score - 6.0) / 3.0) * 10.0 } else if fk_score <= 12.0 {
80.0 - ((fk_score - 9.0) / 3.0) * 20.0 } else {
(60.0 - ((fk_score - 12.0) * 2.0)).max(20.0) };
score_by_percentage(readability_percentage, 100.0, 80.0, 60.0)
}
pub struct ScoringPatterns;
impl ScoringPatterns {
pub fn word_count_score(word_count: usize, min_words: Option<usize>) -> f32 {
if let Some(min_words) = min_words {
let percentage_met = (word_count as f32 / min_words as f32) * 100.0;
match word_count {
0 => 0.0,
count if count < min_words / 4 => {
(percentage_met / 25.0) * 5.0 }
count if count < min_words / 2 => {
5.0 + ((percentage_met - 25.0) / 25.0) * 10.0 }
count if count < (min_words * 3) / 4 => {
15.0 + ((percentage_met - 50.0) / 25.0) * 20.0 }
count if count < min_words => {
35.0 + ((percentage_met - 75.0) / 25.0) * 25.0 }
count if count >= min_words && count <= min_words * 5 => 100.0,
count if count <= min_words * 10 => 85.0,
_ => 65.0,
}
} else {
match word_count {
0 => 0.0,
1..=50 => 20.0,
51..=150 => 50.0,
151..=300 => 75.0,
301..=2000 => 100.0,
2001..=5000 => 85.0,
_ => 65.0,
}
}
}
pub fn image_count_score(image_count: usize) -> f32 {
match image_count {
0 => 30.0, 1..=3 => 80.0, 4..=10 => 100.0, 11..=20 => 90.0, _ => 70.0, }
}
pub fn heading_count_score(heading_count: usize) -> f32 {
match heading_count {
0 => 10.0, 1 => 40.0, 2..=8 => 100.0, 9..=15 => 75.0, 16..=30 => 50.0, _ => 20.0, }
}
pub fn paragraph_count_score(paragraph_count: usize) -> f32 {
match paragraph_count {
0 => 0.0, 1 => 30.0, 2 => 70.0, 3..=20 => 100.0, _ => 85.0, }
}
}
pub fn score_by_simple_range(value: usize, min: usize, max: usize) -> f32 {
if value <= min {
0.0
} else if value >= max {
100.0
} else {
((value - min) as f32 / (max - min) as f32) * 100.0
}
}
pub fn score_by_inverse_range(value: usize, min: usize, max: usize) -> f32 {
if value <= min {
100.0
} else if value >= max {
0.0
} else {
100.0 - ((value - min) as f32 / (max - min) as f32) * 100.0
}
}
pub fn score_by_target(value: usize, target: usize) -> f32 {
if value == target {
100.0
} else {
let diff = if value > target {
value - target
} else {
target - value
};
let max_diff = target.max(100 - target); let score = 100.0 - (diff as f32 / max_diff as f32 * 100.0);
score.max(0.0)
}
}
pub fn calculate_broken_link_penalty(broken_ratio: f32, total_links: usize) -> f32 {
if total_links == 0 || broken_ratio == 0.0 {
0.0
} else {
let base_penalty = broken_ratio * 50.0; let link_factor = (total_links as f32).ln().max(1.0); (base_penalty * link_factor).min(50.0) }
}
pub fn score_by_target_range(value: usize, lower: usize, upper: usize) -> f32 {
if value >= lower && value <= upper {
100.0 } else if value < lower {
let diff = lower - value;
let max_diff = lower; let penalty = (diff as f32 / max_diff as f32) * 12.5; (100.0 - penalty).max(0.0)
} else {
let diff = value - upper;
let max_diff = upper; let penalty = (diff as f32 / max_diff as f32) * 10.0; (100.0 - penalty).max(0.0)
}
}
pub fn calculate_image_alt_penalty(alt_coverage: f32, image_count: usize) -> f32 {
if image_count == 0 {
0.0
} else {
let missing_ratio = 1.0 - alt_coverage;
missing_ratio * (image_count as f32 * 5.0) }
}