parlov_analysis/lib.rs
1//! Analysis engine for parlov: signal detection, statistics, and oracle classification.
2//!
3//! This crate is pure synchronous computation — no I/O, no async, no network stack. Keeping it
4//! isolated from `parlov-probe` means changing statistical thresholds or adding a new oracle
5//! pattern does not recompile `reqwest` or `hyper`.
6//!
7//! # Trait contract
8//!
9//! Implementors of [`Analyzer`] incrementally evaluate a growing [`DifferentialSet`] via
10//! [`Analyzer::evaluate`] and signal when enough samples have been collected. The provided
11//! [`Analyzer::analyze`] method wraps `evaluate` for callers that supply a complete set in one
12//! shot.
13
14#![deny(clippy::all)]
15#![warn(clippy::pedantic)]
16#![deny(missing_docs)]
17
18pub mod existence;
19pub mod signals;
20
21use parlov_core::{DifferentialSet, OracleClass, OracleResult};
22
23/// Decision returned by [`Analyzer::evaluate`] after inspecting the current sample set.
24pub enum SampleDecision {
25 /// Enough samples collected; here is the final result.
26 Complete(Box<OracleResult>),
27 /// Differential detected but not yet confirmed stable — collect one more pair.
28 NeedMore,
29}
30
31/// Analyzes paired baseline/probe exchanges and produces an oracle verdict.
32///
33/// Implementors must be `Send + Sync` so they can be held in shared state across async tasks.
34/// All methods take `&self` — analyzers are stateless with respect to individual probe runs.
35pub trait Analyzer: Send + Sync {
36 /// Incrementally evaluate a growing `DifferentialSet`.
37 ///
38 /// Called after each new exchange pair is added. Returns `NeedMore` until enough samples
39 /// are collected to determine stability, then `Complete` with the final result.
40 fn evaluate(&self, data: &DifferentialSet) -> SampleDecision;
41
42 /// The oracle class this analyzer handles.
43 fn oracle_class(&self) -> OracleClass;
44
45 /// Analyze a fully-collected `DifferentialSet` and return a verdict.
46 ///
47 /// Provided method that delegates to [`evaluate`][Self::evaluate]. Exists for callers that
48 /// supply a complete set in one shot rather than driving the incremental sampling loop.
49 ///
50 /// # Panics
51 ///
52 /// Panics when `evaluate` returns `NeedMore`, meaning the supplied set has fewer samples
53 /// than this analyzer requires.
54 fn analyze(&self, data: &DifferentialSet) -> OracleResult {
55 match self.evaluate(data) {
56 SampleDecision::Complete(result) => *result,
57 SampleDecision::NeedMore => {
58 panic!("analyze called with insufficient samples; use evaluate to drive sampling")
59 }
60 }
61 }
62}