#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SignalFamily {
Range,
CacheValidator,
Auth,
Precondition,
Negotiation,
ErrorBody,
Redirect,
General,
}
impl SignalFamily {
pub(crate) const COUNT: usize = 8;
pub(crate) const fn index(self) -> usize {
match self {
Self::Range => 0,
Self::CacheValidator => 1,
Self::Auth => 2,
Self::Precondition => 3,
Self::Negotiation => 4,
Self::ErrorBody => 5,
Self::Redirect => 6,
Self::General => 7,
}
}
}
const FAMILY_CAP: u8 = 75;
pub(crate) struct SignalContribution<'a> {
pub family: SignalFamily,
pub confidence: f32,
pub impact: u8,
pub description: &'a str,
}
pub(crate) struct FamilyAdjustedScores {
pub(crate) confidence_total: f32,
pub(crate) impact_total: u8,
pub(crate) family_count: usize,
}
pub(crate) fn apply_family_adjustment(
contributions: &[SignalContribution<'_>],
) -> FamilyAdjustedScores {
let mut family_counts = [0u8; SignalFamily::COUNT];
let mut confidence_total: f32 = 0.0;
let mut impact_total: u8 = 0;
for contrib in contributions {
let idx = contrib.family.index();
let count = family_counts[idx];
let capped = family_adjusted_confidence(contrib.confidence, count);
confidence_total += capped;
impact_total = impact_total.saturating_add(contrib.impact);
family_counts[idx] += 1;
}
let family_count = family_counts.iter().filter(|&&c| c > 0).count();
FamilyAdjustedScores {
confidence_total,
impact_total,
family_count,
}
}
fn family_adjusted_confidence(raw: f32, position: u8) -> f32 {
let cap = f32::from(FAMILY_CAP);
let clamped = raw.min(cap);
match position {
0 => clamped,
1 => clamped * 0.5,
_ => 0.0,
}
}
pub(crate) fn header_family(name: &str) -> SignalFamily {
match name {
"content-range" | "accept-ranges" => SignalFamily::Range,
"etag" | "last-modified" => SignalFamily::CacheValidator,
"www-authenticate" => SignalFamily::Auth,
"location" => SignalFamily::Redirect,
_ => SignalFamily::General,
}
}
pub(crate) fn status_code_family(baseline_status: u16) -> SignalFamily {
match baseline_status {
206 | 416 => SignalFamily::Range,
304 => SignalFamily::CacheValidator,
401 | 403 => SignalFamily::Auth,
412 => SignalFamily::Precondition,
406 => SignalFamily::Negotiation,
300 | 301 | 302 | 303 | 307 | 308 => SignalFamily::Redirect,
_ => SignalFamily::General,
}
}
pub(crate) fn corroboration_bonus(family_count: usize) -> u8 {
match family_count {
0 | 1 => 0,
2 => 3,
3 => 6,
_ => 8,
}
}
#[cfg(test)]
#[path = "families_tests.rs"]
mod tests;