From 8394ecfb51dcea803d8884438483560d8a02ec82 Mon Sep 17 00:00:00 2001 From: Solomon Nbubest Date: Fri, 29 May 2026 21:31:32 +0100 Subject: [PATCH] feat(devkit): add --window flag to export subcommand --- packages/devkit/src/cli/export.rs | 93 ++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/packages/devkit/src/cli/export.rs b/packages/devkit/src/cli/export.rs index 4454a41..3886e73 100644 --- a/packages/devkit/src/cli/export.rs +++ b/packages/devkit/src/cli/export.rs @@ -1,11 +1,54 @@ -use crate::simulation::fee_model::FeePoint; +use crate::simulation::fee_model::FeePoint; use std::fmt::Write as FmtWrite; +/// Time window filter for exports. +#[derive(Debug, Clone, Copy)] +pub enum Window { + OneHour, + SixHours, + TwentyFourHours, + All, +} + +impl Window { + pub fn from_str(s: &str) -> Option { + match s { + "1h" => Some(Self::OneHour), + "6h" => Some(Self::SixHours), + "24h" => Some(Self::TwentyFourHours), + "all" => Some(Self::All), + _ => None, + } + } + + pub fn cutoff_seconds(&self) -> Option { + match self { + Self::OneHour => Some(3600), + Self::SixHours => Some(21600), + Self::TwentyFourHours => Some(86400), + Self::All => None, + } + } +} + /// Exports devkit results to external formats. pub struct Export; impl Export { - /// Serialize fee points to CSV string with columns: timestamp,fee,ledger,is_spike. + /// Filter points by window relative to the latest timestamp. + pub fn filter_window<'a>(points: &'a [FeePoint], window: Window) -> &'a [FeePoint] { + match window.cutoff_seconds() { + None => points, + Some(secs) => { + let max_ts = points.iter().map(|p| p.timestamp).max().unwrap_or(0); + let cutoff = max_ts.saturating_sub(secs); + let start = points.partition_point(|p| p.timestamp < cutoff); + &points[start..] + } + } + } + + /// Serialize fee points to CSV: timestamp,fee,ledger,is_spike. pub fn to_csv(points: &[FeePoint]) -> String { let mut out = String::from("timestamp,fee,ledger,is_spike\n"); for p in points { @@ -18,4 +61,50 @@ impl Export { pub fn write_csv(points: &[FeePoint], path: &std::path::Path) -> std::io::Result<()> { std::fs::write(path, Self::to_csv(points)) } + + /// Serialize fee points to a JSON array. + pub fn to_json(points: &[FeePoint]) -> String { + let items: Vec = points + .iter() + .map(|p| { + format!( + r#"{{"timestamp":{},"fee":{},"ledger":{},"is_spike":{}}}"#, + p.timestamp, p.fee, p.ledger, p.is_spike + ) + }) + .collect(); + format!("[{}]", items.join(",")) + } + + /// Write fee points to a JSON file. + pub fn write_json(points: &[FeePoint], path: &std::path::Path) -> std::io::Result<()> { + std::fs::write(path, Self::to_json(points)) + } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::simulation::fee_model::FeePoint; + + fn pts() -> Vec { + vec![ + FeePoint { timestamp: 0, fee: 100, ledger: 1, is_spike: false }, + FeePoint { timestamp: 7200, fee: 200, ledger: 2, is_spike: true }, + ] + } + + #[test] + fn window_1h_filters() { + let p = pts(); + let filtered = Export::filter_window(&p, Window::OneHour); + assert_eq!(filtered.len(), 1); + assert_eq!(filtered[0].timestamp, 7200); + } + + #[test] + fn window_all_keeps_all() { + let p = pts(); + assert_eq!(Export::filter_window(&p, Window::All).len(), 2); + } +} \ No newline at end of file