#[must_use]
pub fn indicator_score(indicators: &[(bool, u8)]) -> u8 {
let sum: u32 = indicators
.iter()
.filter(|(hit, _)| *hit)
.map(|(_, w)| u32::from(*w))
.sum();
sum.min(100) as u8
}
#[must_use]
pub fn combine_confidence(c1: u8, c2: u8) -> u8 {
let p1 = f32::from(c1) / 100.0;
let p2 = f32::from(c2) / 100.0;
let combined = 1.0 - (1.0 - p1) * (1.0 - p2);
(combined * 100.0).round() as u8
}
#[must_use]
pub fn exceeds_threshold(score: u8, threshold: u8) -> bool {
score >= threshold
}
#[must_use]
pub fn combine_all_confidence(confidences: &[u8]) -> u8 {
match confidences {
[] => 0,
[single] => *single,
[first, rest @ ..] => rest
.iter()
.fold(*first, |acc, &c| combine_confidence(acc, c)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_indicators_scores_zero() {
assert_eq!(indicator_score(&[]), 0);
}
#[test]
fn single_true_indicator_scores_its_weight() {
assert_eq!(indicator_score(&[(true, 40)]), 40);
}
#[test]
fn single_false_indicator_scores_zero() {
assert_eq!(indicator_score(&[(false, 40)]), 0);
}
#[test]
fn multiple_true_indicators_sum_clamped_to_100() {
let indicators = [(true, 50u8), (true, 50), (true, 50)];
assert_eq!(indicator_score(&indicators), 100);
}
#[test]
fn combine_confidence_independent_events() {
assert_eq!(combine_confidence(50, 50), 75);
}
#[test]
fn combine_confidence_zero_with_any() {
assert_eq!(combine_confidence(0, 80), 80);
}
#[test]
fn combine_confidence_full_certainty() {
assert_eq!(combine_confidence(100, 50), 100);
}
#[test]
fn exceeds_threshold_equal_returns_true() {
assert!(exceeds_threshold(75, 75));
}
#[test]
fn exceeds_threshold_below_returns_false() {
assert!(!exceeds_threshold(74, 75));
}
#[test]
fn combine_all_empty_returns_zero() {
assert_eq!(combine_all_confidence(&[]), 0);
}
#[test]
fn combine_all_single_returns_value() {
assert_eq!(combine_all_confidence(&[60]), 60);
}
#[test]
fn combine_all_multiple_probabilistic() {
let result = combine_all_confidence(&[50, 50, 50]);
assert!(
result == 87 || result == 88,
"expected 87 or 88, got {result}"
);
}
}