parlov-analysis 0.3.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Analysis engine for parlov: signal detection, statistics, and oracle classification.
//!
//! This crate is pure synchronous computation — no I/O, no async, no network stack. Keeping it
//! isolated from `parlov-probe` means changing statistical thresholds or adding a new oracle
//! pattern does not recompile `reqwest` or `hyper`.
//!
//! # Trait contract
//!
//! Implementors of [`Analyzer`] incrementally evaluate a growing [`ProbeSet`] via [`Analyzer::evaluate`]
//! and signal when enough samples have been collected. The provided [`Analyzer::analyze`] method
//! wraps `evaluate` for callers that supply a complete `ProbeSet` in one shot.

#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![deny(missing_docs)]

pub mod existence;

use parlov_core::{OracleClass, OracleResult, ProbeSet};

/// Decision returned by [`Analyzer::evaluate`] after inspecting the current sample set.
pub enum SampleDecision {
    /// Enough samples collected; here is the final result.
    Complete(OracleResult),
    /// Differential detected but not yet confirmed stable — collect one more pair.
    NeedMore,
}

/// Analyzes a set of baseline and probe response surfaces and produces an oracle verdict.
///
/// Implementors must be `Send + Sync` so they can be held in shared state across async tasks.
/// All methods take `&self` — analyzers are stateless with respect to individual probe runs.
pub trait Analyzer: Send + Sync {
    /// Incrementally evaluate a growing `ProbeSet`.
    ///
    /// Called after each new pair is added. Returns `NeedMore` until enough samples are
    /// collected to determine stability, then `Complete` with the final result.
    fn evaluate(&self, data: &ProbeSet) -> SampleDecision;

    /// The oracle class this analyzer handles.
    fn oracle_class(&self) -> OracleClass;

    /// Analyze a fully-collected `ProbeSet` and return a verdict with evidence and severity.
    ///
    /// This is a provided method that delegates to [`evaluate`][Self::evaluate]. It exists for
    /// callers that supply a complete `ProbeSet` in one shot rather than driving the incremental
    /// sampling loop. Panics if `evaluate` returns `NeedMore`, which indicates the `ProbeSet`
    /// does not yet contain enough samples.
    ///
    /// # Panics
    ///
    /// Panics when `evaluate` returns `NeedMore`, meaning the supplied `ProbeSet` has fewer
    /// samples than this analyzer requires.
    fn analyze(&self, data: &ProbeSet) -> OracleResult {
        match self.evaluate(data) {
            SampleDecision::Complete(result) => result,
            SampleDecision::NeedMore => {
                panic!("analyze called with insufficient samples; use evaluate to drive sampling")
            }
        }
    }
}