use std::collections::BTreeMap;
use serde::Serialize;
use super::{LegRecord, Prices, SwapMode, fmt::commas_u};
pub fn bucket(outcome: &str, category: Option<&str>) -> Option<String> {
match outcome {
"filled" => None,
"reverted" => Some(format!("reverted_{}", category.unwrap_or_default())),
other => Some(other.to_string()),
}
}
#[derive(Default, Clone, Serialize)]
pub struct TemplateProbes {
pub outcomes: BTreeMap<String, u64>,
pub scored_vs_historical: u64,
pub bps_vs_historical_total: i64,
pub pairs_vs_same_state: u64,
pub bps_vs_same_state_total: f64,
pub notional_usd: f64,
pub weighted_bps_usd: f64,
}
impl TemplateProbes {
fn probes(&self) -> u64 {
self.outcomes.values().sum()
}
fn filled(&self) -> u64 {
self.outcomes.get("filled").copied().unwrap_or(0)
}
fn mean(total: f64, count: u64, unit: &str) -> String {
match count {
0 => "—".to_string(),
n => format!("{:+.2} ({} {unit})", total / n as f64, commas_u(n)),
}
}
fn row(&self, template: &str) -> String {
let probes = self.probes();
let filled = self.filled();
let pct = filled as f64 / probes as f64 * 100.0;
let vs_historical = Self::mean(
self.bps_vs_historical_total as f64,
self.scored_vs_historical,
"scored",
);
let vs_same_state = Self::mean(
self.bps_vs_same_state_total,
self.pairs_vs_same_state,
"pairs",
);
let per_dollar = match self.notional_usd {
notional if notional > 0.0 => format!(
"{:+.2} (${})",
self.weighted_bps_usd / notional,
commas_u(notional as u64)
),
_ => "—".to_string(),
};
format!(
"| {template} | {} | {} ({pct:.1}%) | {vs_historical} | {per_dollar} | {vs_same_state} |",
commas_u(probes),
commas_u(filled),
)
}
fn outcomes_line(&self, template: &str) -> String {
let parts: Vec<String> = self
.outcomes
.iter()
.map(|(outcome, n)| format!("{outcome} {}", commas_u(*n)))
.collect();
format!("{template} outcomes: {}", parts.join(", "))
}
}
#[derive(Default, Clone, Serialize)]
pub struct ProbeReport {
pub by_template: BTreeMap<String, TemplateProbes>,
}
impl ProbeReport {
pub fn from_records(records: &[LegRecord], prices: Option<&Prices>) -> Self {
let mut report = Self::default();
for record in records {
let template = record
.template
.clone()
.unwrap_or_else(|| "(unnamed)".to_string());
let entry = report.by_template.entry(template).or_default();
let outcome = record
.error_category
.clone()
.unwrap_or_else(|| "filled".to_string());
*entry.outcomes.entry(outcome).or_default() += 1;
if let Some(bps) = record.output_bps {
entry.scored_vs_historical += 1;
entry.bps_vs_historical_total += bps;
let priced_mint = match record.swap_mode {
SwapMode::ExactIn => &record.input_mint,
SwapMode::ExactOut => &record.output_mint,
};
if let Some(usd) = prices.and_then(|p| p.usd(priced_mint, record.amount as f64)) {
entry.notional_usd += usd;
entry.weighted_bps_usd += usd * bps as f64;
}
}
if let (Some(new), Some(original)) =
(record.new_output_amount, record.quoted_output_amount)
&& original > 0
{
entry.pairs_vs_same_state += 1;
let change = (new as f64 / original as f64 - 1.0) * 1e4;
entry.bps_vs_same_state_total += match record.swap_mode {
SwapMode::ExactIn => change,
SwapMode::ExactOut => -change,
};
}
}
report
}
pub fn merge(&mut self, other: &Self) {
for (template, probes) in &other.by_template {
let entry = self.by_template.entry(template.clone()).or_default();
entry.notional_usd += probes.notional_usd;
entry.weighted_bps_usd += probes.weighted_bps_usd;
for (outcome, n) in &probes.outcomes {
*entry.outcomes.entry(outcome.clone()).or_default() += n;
}
entry.scored_vs_historical += probes.scored_vs_historical;
entry.bps_vs_historical_total += probes.bps_vs_historical_total;
entry.pairs_vs_same_state += probes.pairs_vs_same_state;
entry.bps_vs_same_state_total += probes.bps_vs_same_state_total;
}
}
pub fn is_empty(&self) -> bool {
self.by_template.is_empty()
}
pub fn render(&self, name: &str) -> String {
let mut lines = vec![
format!("### {name}: direct-fill probes"),
"| Template | probes | filled | per hop (bps) | per dollar (bps) | vs same-state (bps) |"
.to_string(),
"|---|---|---|---|---|---|".to_string(),
];
for (template, probes) in &self.by_template {
lines.push(probes.row(template));
}
for (template, probes) in &self.by_template {
lines.push(probes.outcomes_line(template));
}
lines.push(String::new());
lines.join("\n")
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::{
mints,
reroute_report::{Samples, testing::record_of},
};
fn prices() -> Prices {
let mut samples = Samples::default();
for n in 1..=3u64 {
samples.push(&LegRecord {
original_realized_output_amount: Some(n * 100_000_000),
..record_of(mints::SOL, mints::USDC, n * 1_000_000_000, None, None, None)
});
}
samples.prices().expect("anchored")
}
#[test]
fn the_per_dollar_mean_weighs_hops_by_notional() {
let hop = |lamports: u64, bps: i64| LegRecord {
template: Some("Tempest".to_string()),
output_bps: Some(bps),
..record_of(mints::SOL, mints::USDC, lamports, None, None, None)
};
let records = [
hop(10_000_000, -100),
hop(10_000_000, -100),
hop(1_000_000_000, 10),
];
let template = |prices: Option<&Prices>| {
ProbeReport::from_records(&records, prices).by_template["Tempest"].clone()
};
let weighted = template(Some(&prices()));
let per_hop: i64 = (-100 - 100 + 10) / 3;
let per_dollar = (0.01 * -100.0 + 0.01 * -100.0 + 1.0 * 10.0) / 1.02;
assert_eq!(weighted.bps_vs_historical_total / 3, per_hop, "per hop");
assert!(
(weighted.weighted_bps_usd / weighted.notional_usd - per_dollar).abs() < 0.01,
"per dollar follows the large hop, not the count"
);
assert_eq!(template(None).notional_usd, 0.0);
}
fn probe(template: &str) -> LegRecord {
LegRecord {
template: Some(template.to_string()),
..record_of(mints::SOL, mints::USDC, 1_000_000, None, None, None)
}
}
#[rstest]
#[case::success_falls_back_to_filled(None, "filled")]
#[case::reverted_keeps_its_category(Some("reverted_slippage".to_string()), "reverted_slippage")]
#[case::unattributed_keeps_its_category(Some("unattributed".to_string()), "unattributed")]
fn from_records_buckets_outcomes_by_error_category_else_filled(
#[case] error_category: Option<String>,
#[case] expected: &str,
) {
let record = LegRecord {
error_category,
..probe("Tempest")
};
let report = ProbeReport::from_records(&[record], None);
assert_eq!(
report.by_template["Tempest"].outcomes.get(expected),
Some(&1)
);
}
#[test]
fn scored_vs_historical_counts_only_rows_carrying_output_bps() {
let scored = LegRecord {
output_bps: Some(12),
..probe("Tempest")
};
let unscored = probe("Tempest");
let report = ProbeReport::from_records(&[scored, unscored], None);
let template = &report.by_template["Tempest"];
assert_eq!(template.scored_vs_historical, 1);
assert_eq!(template.bps_vs_historical_total, 12);
}
#[rstest]
#[case::both_present_and_positive(Some(1_010_000), Some(1_000_000), 1)]
#[case::new_amount_missing(None, Some(1_000_000), 0)]
#[case::original_amount_missing(Some(1_010_000), None, 0)]
#[case::original_amount_zero(Some(1_010_000), Some(0), 0)]
fn same_state_pairs_require_both_amounts_and_a_positive_original(
#[case] new_output_amount: Option<u64>,
#[case] quoted_output_amount: Option<u64>,
#[case] expected_pairs: u64,
) {
let record = LegRecord {
new_output_amount,
quoted_output_amount,
..probe("Tempest")
};
let report = ProbeReport::from_records(&[record], None);
assert_eq!(
report.by_template["Tempest"].pairs_vs_same_state,
expected_pairs
);
}
#[test]
fn same_state_bps_is_the_relative_change_from_quoted_to_new() {
let record = LegRecord {
new_output_amount: Some(1_010_000),
quoted_output_amount: Some(1_000_000),
..probe("Tempest")
};
let report = ProbeReport::from_records(&[record], None);
let template = &report.by_template["Tempest"];
assert_eq!(template.pairs_vs_same_state, 1);
assert!((template.bps_vs_same_state_total - 100.0).abs() < 1e-9);
}
#[test]
fn merge_carries_every_field() {
let a = LegRecord {
output_bps: Some(10),
new_output_amount: Some(1_010_000),
quoted_output_amount: Some(1_000_000),
..probe("Tempest")
};
let b = LegRecord {
error_category: Some("reverted_slippage".to_string()),
output_bps: Some(20),
new_output_amount: Some(1_020_000),
quoted_output_amount: Some(1_000_000),
..probe("Tempest")
};
let mut report = ProbeReport::from_records(&[a], None);
report.merge(&ProbeReport::from_records(&[b], None));
let template = &report.by_template["Tempest"];
assert_eq!(template.outcomes.get("filled"), Some(&1));
assert_eq!(template.outcomes.get("reverted_slippage"), Some(&1));
assert_eq!(template.scored_vs_historical, 2);
assert_eq!(template.bps_vs_historical_total, 30);
assert_eq!(template.pairs_vs_same_state, 2);
assert!((template.bps_vs_same_state_total - 300.0).abs() < 1e-9);
}
#[test]
fn a_record_with_no_template_lands_under_unnamed() {
let record = record_of(mints::SOL, mints::USDC, 1_000_000, None, None, None);
let report = ProbeReport::from_records(&[record], None);
assert!(report.by_template.contains_key("(unnamed)"));
}
#[test]
fn render_contains_the_template_row_and_outcomes_line() {
let filled_a = LegRecord {
output_bps: Some(100),
new_output_amount: Some(1_010_000),
quoted_output_amount: Some(1_000_000),
..probe("Tempest")
};
let filled_b = LegRecord {
output_bps: Some(200),
new_output_amount: Some(1_020_000),
quoted_output_amount: Some(1_000_000),
..probe("Tempest")
};
let reverted = LegRecord {
error_category: Some("reverted_slippage".to_string()),
..probe("Tempest")
};
let report = ProbeReport::from_records(&[filled_a, filled_b, reverted], None);
let rendered = report.render("R1");
assert!(rendered.contains("### R1: direct-fill probes"));
assert!(rendered.contains("| Template | probes | filled |"));
assert!(
rendered.contains(
"| Tempest | 3 | 2 (66.7%) | +150.00 (2 scored) | — | +150.00 (2 pairs) |"
)
);
assert!(rendered.contains("Tempest outcomes: filled 2, reverted_slippage 1"));
}
#[test]
fn render_shows_an_em_dash_when_a_mean_has_no_observations() {
let reverted = LegRecord {
error_category: Some("reverted_slippage".to_string()),
..probe("Vertigo")
};
let report = ProbeReport::from_records(&[reverted], None);
let rendered = report.render("R2");
assert!(rendered.contains("| Vertigo | 1 | 0 (0.0%) | — | — |"));
assert!(rendered.contains("Vertigo outcomes: reverted_slippage 1"));
}
#[test]
fn is_empty_reflects_an_empty_template_map() {
assert!(ProbeReport::default().is_empty());
let report = ProbeReport::from_records(&[probe("Tempest")], None);
assert!(!report.is_empty());
}
#[test]
fn an_exact_out_notional_is_priced_in_the_output_mint() {
let record = LegRecord {
swap_mode: SwapMode::ExactOut,
output_bps: Some(10),
..probe("Tempest")
};
let report = ProbeReport::from_records(&[record], Some(&prices()));
let template = &report.by_template["Tempest"];
assert!(
(template.notional_usd - 1.0).abs() < 1e-9,
"{}",
template.notional_usd
);
}
#[rstest]
#[case::cheaper(900, 1_000.0)]
#[case::dearer(1_100, -1_000.0)]
fn an_exact_out_comparison_scores_input_saved(#[case] charged: u64, #[case] expected: f64) {
let record = LegRecord {
swap_mode: SwapMode::ExactOut,
new_output_amount: Some(charged),
quoted_output_amount: Some(1_000),
..probe("t")
};
let report = ProbeReport::from_records(&[record], None);
assert!((report.by_template["t"].bps_vs_same_state_total - expected).abs() < 1e-9);
}
}