Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/devkit/benches/congestion_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
33 changes: 27 additions & 6 deletions packages/devkit/src/simulation/congestion_predictor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
}

Expand Down
29 changes: 17 additions & 12 deletions packages/devkit/tests/fee_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]");
Expand All @@ -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!(
Expand All @@ -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),
Expand All @@ -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}"
);
}

Expand Down
Loading