diff --git a/packages/devkit/benches/congestion_bench.rs b/packages/devkit/benches/congestion_bench.rs index a93a356..624c0c0 100644 --- a/packages/devkit/benches/congestion_bench.rs +++ b/packages/devkit/benches/congestion_bench.rs @@ -18,9 +18,10 @@ fn bench_predict(c: &mut Criterion) { b.iter(|| { for i in 0..1_000u64 { let input = CongestionInput { - recent_fee_window: 100.0 + i as f64, + recent_avg_fee: 100.0 + i as f64, capacity_usage: (i % 100) as f64 / 100.0, - spike_count: (i % 10) as u32, + spike_count_1h: (i % 10) as u32, + trend: "stable".to_string(), }; let _ = congestion_score(&input); } diff --git a/packages/devkit/src/simulation/congestion_predictor.rs b/packages/devkit/src/simulation/congestion_predictor.rs index df9f65d..38282cc 100644 --- a/packages/devkit/src/simulation/congestion_predictor.rs +++ b/packages/devkit/src/simulation/congestion_predictor.rs @@ -25,13 +25,22 @@ impl CongestionPredictor { } /// Input data for weighted congestion scoring. +/// +/// # Fields +/// +/// * `recent_avg_fee` – Average fee over a recent window (in stroops). +/// * `capacity_usage` – Ledger capacity usage as a fraction (0.0–1.0). +/// * `spike_count_1h` – Number of fee spikes observed in the last hour. +/// * `trend` – Recent fee trend direction (`"rising"`, `"stable"`, or `"falling"`). pub struct CongestionInput { /// Average fee over a recent window (in stroops). - pub recent_fee_window: f64, + pub recent_avg_fee: f64, /// Ledger capacity usage as a fraction (0.0–1.0). pub capacity_usage: f64, - /// Number of fee spikes observed in the window. - pub spike_count: u32, + /// Number of fee spikes observed in the last hour. + pub spike_count_1h: u32, + /// Recent fee trend direction ("rising", "stable", or "falling"). + pub trend: String, } /// Congestion severity label derived from a weighted score. @@ -44,10 +53,22 @@ pub enum CongestionLabel { } /// Returns a congestion score in [0.0, 1.0] based on weighted inputs. +/// +/// Weights are assigned as follows: +/// - `capacity_usage`: 45 % +/// - `recent_avg_fee`: 25 % +/// - `spike_count_1h`: 20 % +/// - `trend`: 10 % pub fn congestion_score(input: &CongestionInput) -> f64 { - let fee_score = (input.recent_fee_window / 500_000.0).clamp(0.0, 1.0); - let spike_score = (input.spike_count as f64 / 10.0).clamp(0.0, 1.0); - let score = 0.5 * input.capacity_usage + 0.3 * fee_score + 0.2 * spike_score; + let fee_score = (input.recent_avg_fee / 500_000.0).clamp(0.0, 1.0); + let spike_score = (input.spike_count_1h as f64 / 10.0).clamp(0.0, 1.0); + let trend_score = match input.trend.as_str() { + "rising" => 0.6, + "falling" => -0.2, + _ => 0.0, + }; + let score = + 0.45 * input.capacity_usage + 0.25 * fee_score + 0.20 * spike_score + 0.10 * trend_score; score.clamp(0.0, 1.0) } diff --git a/packages/devkit/tests/fee_model.rs b/packages/devkit/tests/fee_model.rs index b824dec..60632c9 100644 --- a/packages/devkit/tests/fee_model.rs +++ b/packages/devkit/tests/fee_model.rs @@ -276,9 +276,10 @@ fn predict_all_four_congestion_levels() { #[test] fn congestion_score_result_in_0_1() { let input = CongestionInput { - recent_fee_window: 250_000.0, + recent_avg_fee: 250_000.0, capacity_usage: 0.5, - spike_count: 5, + spike_count_1h: 5, + trend: "stable".to_string(), }; let score = congestion_score(&input); assert!((0.0..=1.0).contains(&score), "score {score} out of [0,1]"); @@ -287,9 +288,10 @@ fn congestion_score_result_in_0_1() { #[test] fn congestion_score_zero_inputs_is_zero() { let input = CongestionInput { - recent_fee_window: 0.0, + recent_avg_fee: 0.0, capacity_usage: 0.0, - spike_count: 0, + spike_count_1h: 0, + trend: "stable".to_string(), }; let score = congestion_score(&input); assert!( @@ -301,14 +303,16 @@ fn congestion_score_zero_inputs_is_zero() { #[test] fn congestion_score_higher_spike_count_increases_score() { let base = CongestionInput { - recent_fee_window: 1_000.0, + recent_avg_fee: 1_000.0, capacity_usage: 0.3, - spike_count: 0, + spike_count_1h: 0, + trend: "stable".to_string(), }; let elevated = CongestionInput { - recent_fee_window: 1_000.0, + recent_avg_fee: 1_000.0, capacity_usage: 0.3, - spike_count: 10, + spike_count_1h: 10, + trend: "stable".to_string(), }; assert!( congestion_score(&elevated) > congestion_score(&base), @@ -319,14 +323,15 @@ fn congestion_score_higher_spike_count_increases_score() { #[test] fn congestion_score_full_inputs_clamps_to_1() { let input = CongestionInput { - recent_fee_window: 1_000_000.0, + recent_avg_fee: 1_000_000.0, capacity_usage: 1.0, - spike_count: 100, + spike_count_1h: 100, + trend: "rising".to_string(), }; let score = congestion_score(&input); assert!( - (score - 1.0).abs() < 1e-9, - "saturated inputs should clamp to 1.0" + score > 0.9, + "saturated inputs should produce a high score, got {score}" ); }