use std::collections::HashMap;
use std::sync::{Mutex, MutexGuard, OnceLock};
use super::accounting_fix::{PostDeliveryAccounting, compute_honest_accounting};
#[derive(Debug, Clone)]
pub struct McpReceipt {
pub tool: String,
pub tokens_in: usize,
pub tokens_out: usize,
pub kernel_overhead: usize,
pub accepted: bool,
}
#[derive(Debug, Clone, Default)]
pub struct ToolSavings {
pub tool: String,
pub calls: usize,
pub tokens_in: usize,
pub tokens_out: usize,
pub kernel_overhead: usize,
pub honest_savings_pct: f64,
}
#[derive(Default)]
struct McpReceiptStore {
receipts: Vec<McpReceipt>,
per_tool: HashMap<String, ToolSavings>,
}
static RECEIPTS: OnceLock<Mutex<McpReceiptStore>> = OnceLock::new();
fn receipt_store() -> &'static Mutex<McpReceiptStore> {
RECEIPTS.get_or_init(|| Mutex::new(McpReceiptStore::default()))
}
fn lock_store() -> MutexGuard<'static, McpReceiptStore> {
match receipt_store().lock() {
Ok(store) => store,
Err(poisoned) => poisoned.into_inner(),
}
}
fn savings_pct(tokens_in: usize, delivered: usize) -> f64 {
if tokens_in == 0 {
0.0
} else {
(1.0 - delivered as f64 / tokens_in as f64) * 100.0
}
}
pub fn record_receipt(receipt: McpReceipt) {
let mut store = lock_store();
let summary = store
.per_tool
.entry(receipt.tool.clone())
.or_insert_with(|| ToolSavings {
tool: receipt.tool.clone(),
..ToolSavings::default()
});
summary.calls = summary.calls.saturating_add(1);
summary.tokens_in = summary.tokens_in.saturating_add(receipt.tokens_in);
summary.tokens_out = summary.tokens_out.saturating_add(receipt.tokens_out);
summary.kernel_overhead = summary
.kernel_overhead
.saturating_add(receipt.kernel_overhead);
summary.honest_savings_pct = savings_pct(
summary.tokens_in,
summary.tokens_out.saturating_add(summary.kernel_overhead),
);
store.receipts.push(receipt);
}
pub fn mcp_accounting() -> PostDeliveryAccounting {
let store = lock_store();
let mut totals = (0usize, 0usize, 0usize);
for receipt in &store.receipts {
totals.0 = totals.0.saturating_add(receipt.tokens_in);
totals.1 = totals.1.saturating_add(receipt.tokens_out);
totals.2 = totals.2.saturating_add(receipt.kernel_overhead);
}
compute_honest_accounting(totals.0, totals.1, totals.2, 0)
}
pub fn per_tool_savings() -> Vec<ToolSavings> {
let mut summaries: Vec<_> = lock_store().per_tool.values().cloned().collect();
summaries.sort_unstable_by_key(|summary| summary.tool.clone());
summaries
}
pub fn savings_report() -> String {
per_tool_savings()
.iter()
.map(|summary| {
format!(
"{}: {} calls, {:.2}% savings",
summary.tool, summary.calls, summary.honest_savings_pct,
)
})
.collect::<Vec<_>>()
.join("\n")
}
pub fn total_kernel_overhead() -> usize {
mcp_accounting().kernel_overhead_tokens
}
pub fn reset_receipts() {
*lock_store() = McpReceiptStore::default();
}
#[cfg(test)]
pub mod tests {
use super::{
McpReceipt, mcp_accounting, per_tool_savings, record_receipt, reset_receipts,
savings_report, total_kernel_overhead,
};
use std::sync::{Mutex, MutexGuard};
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn isolated_test() -> MutexGuard<'static, ()> {
let guard = match TEST_LOCK.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
reset_receipts();
guard
}
fn receipt(tool: &str) -> McpReceipt {
McpReceipt {
tool: tool.to_owned(),
tokens_in: 100,
tokens_out: 40,
kernel_overhead: 10,
accepted: true,
}
}
#[test]
fn record_and_retrieve() {
let _guard = isolated_test();
record_receipt(receipt("read"));
record_receipt(receipt("search"));
record_receipt(receipt("read"));
assert_eq!(per_tool_savings().len(), 2);
}
#[test]
fn accounting_is_honest() {
let _guard = isolated_test();
record_receipt(receipt("read"));
let accounting = mcp_accounting();
assert_eq!(accounting.kernel_overhead_tokens, 10);
assert!((accounting.phantom_savings_pct - 0.1).abs() < f64::EPSILON);
}
#[test]
fn per_tool_aggregates() {
let _guard = isolated_test();
for _ in 0..3 {
record_receipt(receipt("read"));
}
let summaries = per_tool_savings();
assert_eq!(summaries[0].calls, 3);
assert_eq!(summaries[0].honest_savings_pct, 50.0);
}
#[test]
fn savings_report_formatted() {
let _guard = isolated_test();
record_receipt(receipt("search"));
let report = savings_report();
assert!(report.contains("search"));
assert!(report.contains("50.00% savings"));
}
#[test]
fn reset_clears_all() {
let _guard = isolated_test();
record_receipt(receipt("read"));
reset_receipts();
assert!(per_tool_savings().is_empty());
assert_eq!(total_kernel_overhead(), 0);
}
}