dsfb_database/report/
mod.rs1#[cfg(feature = "report")]
10pub mod plots;
11#[cfg(feature = "report")]
12pub mod plots_live;
13
14use crate::grammar::Episode;
15use crate::metrics::PerMotifMetrics;
16use crate::non_claims;
17#[cfg(feature = "report")]
18use anyhow::Context;
19use anyhow::Result;
20use serde::Serialize;
21use std::fs::{self, File};
22use std::io::Write;
23use std::path::Path;
24
25#[derive(Debug, Serialize)]
26pub struct ReportHeader {
27 pub crate_version: &'static str,
28 pub generated_at: String,
29 pub non_claims: [&'static str; 6],
30 pub source: String,
31}
32
33pub fn write_episodes_csv(path: &Path, episodes: &[Episode]) -> Result<()> {
34 if let Some(parent) = path.parent() {
35 fs::create_dir_all(parent)?;
36 }
37 let mut wtr = csv::Writer::from_path(path)?;
38 wtr.write_record([
39 "motif",
40 "channel",
41 "t_start",
42 "t_end",
43 "peak",
44 "ema_at_boundary",
45 "trust_sum",
46 ])?;
47 for e in episodes {
48 wtr.write_record([
49 e.motif.name(),
50 e.channel.as_deref().unwrap_or(""),
51 &format!("{}", e.t_start),
52 &format!("{}", e.t_end),
53 &format!("{}", e.peak),
54 &format!("{}", e.ema_at_boundary),
55 &format!("{}", e.trust_sum),
56 ])?;
57 }
58 wtr.flush()?;
59 Ok(())
60}
61
62pub fn write_metrics_csv(path: &Path, metrics: &[PerMotifMetrics]) -> Result<()> {
63 if let Some(parent) = path.parent() {
64 fs::create_dir_all(parent)?;
65 }
66 let mut wtr = csv::Writer::from_path(path)?;
67 wtr.write_record([
68 "motif",
69 "tp",
70 "fp",
71 "fn",
72 "precision",
73 "recall",
74 "f1",
75 "ttd_median_s",
76 "ttd_p95_s",
77 "false_alarm_per_hour",
78 "compression_ratio",
79 ])?;
80 for m in metrics {
81 wtr.write_record([
82 &m.motif,
83 &m.tp.to_string(),
84 &m.fp.to_string(),
85 &m.fn_.to_string(),
86 &format!("{:.4}", m.precision),
87 &format!("{:.4}", m.recall),
88 &format!("{:.4}", m.f1),
89 &format!("{:.2}", m.time_to_detection_median_s),
90 &format!("{:.2}", m.time_to_detection_p95_s),
91 &format!("{:.4}", m.false_alarm_rate_per_hour),
92 &format!("{:.2}", m.episode_compression_ratio),
93 ])?;
94 }
95 wtr.flush()?;
96 Ok(())
97}
98
99#[cfg(feature = "report")]
104pub fn write_json<T: Serialize + ?Sized>(path: &Path, value: &T) -> Result<()> {
105 if let Some(parent) = path.parent() {
106 fs::create_dir_all(parent)?;
107 }
108 let s = serde_json::to_string_pretty(value)?;
109 File::create(path)
110 .with_context(|| format!("creating {}", path.display()))?
111 .write_all(s.as_bytes())?;
112 Ok(())
113}
114
115pub fn write_provenance(path: &Path, source: &str) -> Result<()> {
117 if let Some(parent) = path.parent() {
118 fs::create_dir_all(parent)?;
119 }
120 let mut f = File::create(path)?;
121 writeln!(f, "DSFB-Database report")?;
122 writeln!(f, "crate_version = {}", crate::CRATE_VERSION)?;
123 writeln!(f, "source = {}", source)?;
124 writeln!(f)?;
125 writeln!(f, "Non-claims:")?;
126 writeln!(f, "{}", non_claims::as_block())?;
127 Ok(())
128}