#[cfg(feature = "report")]
pub mod plots;
#[cfg(feature = "report")]
pub mod plots_live;
use crate::grammar::Episode;
use crate::metrics::PerMotifMetrics;
use crate::non_claims;
#[cfg(feature = "report")]
use anyhow::Context;
use anyhow::Result;
use serde::Serialize;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
#[derive(Debug, Serialize)]
pub struct ReportHeader {
pub crate_version: &'static str,
pub generated_at: String,
pub non_claims: [&'static str; 6],
pub source: String,
}
pub fn write_episodes_csv(path: &Path, episodes: &[Episode]) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let mut wtr = csv::Writer::from_path(path)?;
wtr.write_record([
"motif",
"channel",
"t_start",
"t_end",
"peak",
"ema_at_boundary",
"trust_sum",
])?;
for e in episodes {
wtr.write_record([
e.motif.name(),
e.channel.as_deref().unwrap_or(""),
&format!("{}", e.t_start),
&format!("{}", e.t_end),
&format!("{}", e.peak),
&format!("{}", e.ema_at_boundary),
&format!("{}", e.trust_sum),
])?;
}
wtr.flush()?;
Ok(())
}
pub fn write_metrics_csv(path: &Path, metrics: &[PerMotifMetrics]) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let mut wtr = csv::Writer::from_path(path)?;
wtr.write_record([
"motif",
"tp",
"fp",
"fn",
"precision",
"recall",
"f1",
"ttd_median_s",
"ttd_p95_s",
"false_alarm_per_hour",
"compression_ratio",
])?;
for m in metrics {
wtr.write_record([
&m.motif,
&m.tp.to_string(),
&m.fp.to_string(),
&m.fn_.to_string(),
&format!("{:.4}", m.precision),
&format!("{:.4}", m.recall),
&format!("{:.4}", m.f1),
&format!("{:.2}", m.time_to_detection_median_s),
&format!("{:.2}", m.time_to_detection_p95_s),
&format!("{:.4}", m.false_alarm_rate_per_hour),
&format!("{:.2}", m.episode_compression_ratio),
])?;
}
wtr.flush()?;
Ok(())
}
#[cfg(feature = "report")]
pub fn write_json<T: Serialize + ?Sized>(path: &Path, value: &T) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let s = serde_json::to_string_pretty(value)?;
File::create(path)
.with_context(|| format!("creating {}", path.display()))?
.write_all(s.as_bytes())?;
Ok(())
}
pub fn write_provenance(path: &Path, source: &str) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let mut f = File::create(path)?;
writeln!(f, "DSFB-Database report")?;
writeln!(f, "crate_version = {}", crate::CRATE_VERSION)?;
writeln!(f, "source = {}", source)?;
writeln!(f)?;
writeln!(f, "Non-claims:")?;
writeln!(f, "{}", non_claims::as_block())?;
Ok(())
}