pub mod gnsslogger;
pub mod iqif;
pub mod jammertest;
pub mod lola_dem;
pub mod raim;
pub mod rinex;
pub mod satgrid;
pub mod sqm;
pub mod ubx;
pub mod yunnan;
use crate::impairment_study::ProbeRecord;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Orient {
Raw,
Negate,
}
impl Orient {
pub fn apply(self, raw: f64) -> f64 {
match self {
Orient::Raw => raw,
Orient::Negate => -raw,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Observation {
pub detector: String,
pub raw: f64,
pub score: f64,
}
impl Observation {
pub fn new(detector: impl Into<String>, raw: f64, orient: Orient) -> Self {
Self {
detector: detector.into(),
raw,
score: orient.apply(raw),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct FileLabel<'a> {
pub class: &'a str,
pub shift_bin: &'a str,
pub is_nominal: bool,
}
pub fn to_records(obs: &[Observation], label: &FileLabel) -> Vec<ProbeRecord> {
obs.iter()
.map(|o| {
ProbeRecord::new(
o.detector.clone(),
label.class,
label.shift_bin,
o.score,
label.is_nominal,
)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn negate_orientation_flips_sign_so_lower_cn0_scores_higher() {
let jammed = Observation::new("cn0", 30.0, Orient::Negate);
let clean = Observation::new("cn0", 45.0, Orient::Negate);
assert_eq!(jammed.raw, 30.0);
assert_eq!(jammed.score, -30.0);
assert!(jammed.score > clean.score, "lower C/N0 must score higher");
}
#[test]
fn raw_orientation_passes_value_through() {
let o = Observation::new("raim", 12.5, Orient::Raw);
assert_eq!(o.raw, 12.5);
assert_eq!(o.score, 12.5);
}
#[test]
fn to_records_stamps_the_file_label_on_every_observation() {
let obs = vec![
Observation::new("cn0", 40.0, Orient::Negate),
Observation::new("cn0", 35.0, Orient::Negate),
];
let label = FileLabel {
class: "jamming",
shift_bin: "jsr20",
is_nominal: false,
};
let recs = to_records(&obs, &label);
assert_eq!(recs.len(), 2);
for (r, o) in recs.iter().zip(&obs) {
assert_eq!(r.detector, "cn0");
assert_eq!(r.class, "jamming");
assert_eq!(r.shift_bin, "jsr20");
assert!(!r.is_nominal);
assert_eq!(r.score, o.score);
}
}
#[test]
fn nominal_label_marks_records_as_negatives_and_ignores_class() {
let obs = vec![Observation::new("cn0", 46.0, Orient::Negate)];
let label = FileLabel {
class: "nominal",
shift_bin: "id",
is_nominal: true,
};
let recs = to_records(&obs, &label);
assert!(recs[0].is_nominal);
}
}