extern crate std;
use crate::pipeline::{EvaluationResult, PaperLockExpected};
pub struct PaperLockConfig {
pub oracle: PaperLockExpected,
pub radioml_reference: PaperLockExpected,
}
impl PaperLockConfig {
pub fn from_paper() -> Self {
Self {
oracle: PaperLockExpected {
episode_count: 52,
precision: 0.712,
recall_min: 96,
},
radioml_reference: PaperLockExpected {
episode_count: 87,
precision: 0.736,
recall_min: 97,
},
}
}
}
pub fn verify(
oracle_result: &EvaluationResult,
) -> Result<(), std::vec::Vec<std::string::String>> {
let config = PaperLockConfig::from_paper();
let mut errors: std::vec::Vec<std::string::String> = std::vec::Vec::new();
if let Err(e) = oracle_result.check_paper_lock(&config.oracle) {
errors.push(e);
}
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
pub fn advisory_check_radioml(result: &EvaluationResult) {
advisory_check_radioml_aggregate(
result.dsfb_episode_count,
result.episode_precision,
result.recall_numerator,
result.recall_denominator,
);
}
pub fn advisory_check_radioml_aggregate(
episodes: usize,
precision: f32,
recall_num: usize,
recall_den: usize,
) {
let config = PaperLockConfig::from_paper();
let ref_cfg = &config.radioml_reference;
std::println!("┌──────────────────────────────────────────────────────────────────┐");
std::println!("│ DSFB Augmentation Summary (NOT a detection benchmark) │");
std::println!("└──────────────────────────────────────────────────────────────────┘");
std::println!(" DSFB augments an existing receiver chain — it does not replace it.");
std::println!(" The amplitude-template Wasserstein residual is the \"usually");
std::println!(" discarded residual\" from an amplitude-domain receiver. DSFB");
std::println!(" compresses raw threshold alarms into structured episodes,");
std::println!(" providing human-readable regime characterisation.");
std::println!();
std::println!(" Structural information extracted:");
std::println!(" DSFB episodes : {:<6} (compressed from raw boundary alarms)", episodes);
std::println!(" Episode precision : {:>5.1}% (fraction covering a GT bin boundary)",
precision * 100.0);
std::println!(" Recall : {}/{} (GT bin boundaries covered)",
recall_num, recall_den);
std::println!();
std::println!(" Paper Table IV reference (different residual source — NOT comparable):");
std::println!(" Episodes: {} Precision: {:.1}% Recall: ≥{}/102",
ref_cfg.episode_count, ref_cfg.precision * 100.0, ref_cfg.recall_min);
std::println!(" (uses carrier-synchronised decoder residual, 128-sample variant)");
std::println!(" See src/hdf5_loader.rs module documentation.");
}
pub fn print_report(
oracle: &EvaluationResult,
) {
std::println!("╔══════════════════════════════════════════════════════╗");
std::println!("║ DSFB-RF Paper-Lock Verification (ORACLE) ║");
std::println!("╚══════════════════════════════════════════════════════╝");
std::println!();
std::println!(" Paper: de Beer (2026) DSFB Structural Semiotics Engine");
std::println!(" for RF Signal Monitoring, Table IV");
std::println!(" Note: RadioML paper-lock removed — see src/paper_lock.rs §Scope.");
std::println!();
let config = PaperLockConfig::from_paper();
print_dataset_check("ORACLE (USRP B200)", oracle, &config.oracle);
std::println!();
match verify(oracle) {
Ok(()) => {
std::println!("✓ ORACLE METRICS MATCH — paper-lock PASSED");
std::println!(" Crate evolution has not changed published results.");
}
Err(errs) => {
std::println!("✗ PAPER-LOCK FAILED — {} violation(s):", errs.len());
for e in &errs {
std::println!(" • {}", e);
}
}
}
}
fn print_dataset_check(label: &str, result: &EvaluationResult, expected: &PaperLockExpected) {
let ep_ok = (result.episode_precision - expected.precision).abs() <= 0.005;
let rc_ok = result.recall_numerator >= expected.recall_min;
let cnt_ok = result.dsfb_episode_count == expected.episode_count;
std::println!(" Dataset: {}", label);
std::println!(" Episodes: {} (expected {}) {}",
result.dsfb_episode_count, expected.episode_count,
if cnt_ok { "✓" } else { "✗" });
std::println!(" Precision: {:.1}% (expected {:.1}% ±0.5%) {}",
result.episode_precision * 100.0, expected.precision * 100.0,
if ep_ok { "✓" } else { "✗" });
std::println!(" Recall: {}/{} (min {}) {}",
result.recall_numerator, result.recall_denominator,
expected.recall_min,
if rc_ok { "✓" } else { "✗" });
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::{synthetic_radioml_stream, run_stage_iii, EvaluationResult};
fn make_synthetic_result(label: &'static str, n: usize, events_at: &[usize])
-> EvaluationResult
{
let (obs, events) = synthetic_radioml_stream(n, events_at, 15.0);
run_stage_iii(label, &obs, &events)
}
#[test]
fn paper_lock_config_matches_paper_table_iv() {
let config = PaperLockConfig::from_paper();
assert_eq!(config.oracle.episode_count, 52);
assert!((config.oracle.precision - 0.712).abs() < 1e-4);
assert_eq!(config.oracle.recall_min, 96);
assert_eq!(config.radioml_reference.episode_count, 87);
assert!((config.radioml_reference.precision - 0.736).abs() < 1e-4);
assert_eq!(config.radioml_reference.recall_min, 97);
}
#[test]
fn verify_oracle_error_messages_are_informative() {
let r = make_synthetic_result("fake_oracle", 400, &[150, 250, 350]);
match verify(&r) {
Ok(()) => { }
Err(errs) => {
for e in &errs {
assert!(!e.is_empty(), "error messages must not be empty");
assert!(e.contains("expected") || e.contains("below"),
"error message should be informative: {}", e);
}
}
}
}
#[test]
fn print_report_does_not_panic() {
let r = make_synthetic_result("oracle_print", 400, &[180, 300]);
print_report(&r);
}
#[test]
fn advisory_check_does_not_panic() {
let r = make_synthetic_result("radioml_advisory", 500, &[200, 350]);
advisory_check_radioml(&r);
}
}