pub const SPRT_BENIGN_HARMFUL_RATE: f64 = 0.1;
pub const SPRT_BAD_HARMFUL_RATE: f64 = 0.4;
pub const SPRT_ALPHA: f64 = 0.01;
pub const SPRT_BETA: f64 = 0.05;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SprtObservation {
Harmful,
Helpful,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SprtDecision {
Continue,
Release,
Quarantine,
}
impl SprtDecision {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Continue => "continue",
Self::Release => "release",
Self::Quarantine => "quarantine",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SprtEvaluation {
pub statistic: f64,
pub upper_bound: f64,
pub lower_bound: f64,
pub event_count: usize,
pub harmful_count: usize,
pub helpful_count: usize,
pub decision: SprtDecision,
}
#[must_use]
pub fn evaluate_sprt<I>(observations: I) -> SprtEvaluation
where
I: IntoIterator<Item = SprtObservation>,
{
let upper_bound = ((1.0 - SPRT_BETA) / SPRT_ALPHA).ln();
let lower_bound = (SPRT_BETA / (1.0 - SPRT_ALPHA)).ln();
let harmful_increment = (SPRT_BAD_HARMFUL_RATE / SPRT_BENIGN_HARMFUL_RATE).ln();
let helpful_increment = ((1.0 - SPRT_BAD_HARMFUL_RATE) / (1.0 - SPRT_BENIGN_HARMFUL_RATE)).ln();
let mut statistic = 0.0;
let mut event_count = 0;
let mut harmful_count = 0;
let mut helpful_count = 0;
for observation in observations {
event_count += 1;
match observation {
SprtObservation::Harmful => {
harmful_count += 1;
statistic += harmful_increment;
}
SprtObservation::Helpful => {
helpful_count += 1;
statistic += helpful_increment;
}
}
}
let decision = if statistic > upper_bound {
SprtDecision::Quarantine
} else if statistic < lower_bound {
SprtDecision::Release
} else {
SprtDecision::Continue
};
SprtEvaluation {
statistic,
upper_bound,
lower_bound,
event_count,
harmful_count,
helpful_count,
decision,
}
}
#[cfg(test)]
mod tests {
use super::{SprtDecision, SprtObservation, evaluate_sprt};
#[test]
fn harmful_stream_crosses_quarantine_threshold_quickly() {
let evaluation = evaluate_sprt([SprtObservation::Harmful; 4]);
assert_eq!(evaluation.event_count, 4);
assert_eq!(evaluation.harmful_count, 4);
assert_eq!(evaluation.decision, SprtDecision::Quarantine);
assert!(evaluation.statistic > evaluation.upper_bound);
}
#[test]
fn helpful_stream_crosses_release_threshold() {
let evaluation = evaluate_sprt([SprtObservation::Helpful; 8]);
assert_eq!(evaluation.helpful_count, 8);
assert_eq!(evaluation.decision, SprtDecision::Release);
assert!(evaluation.statistic < evaluation.lower_bound);
}
}