1use serde::{Deserialize, Serialize};
8
9pub const ETPAO_FORMULA_VERSION: &str = "1.0.0";
12
13pub type EtpaoResult = f64;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TokenPricing {
19 pub fresh_input: f64,
20 pub cached_input: f64,
21 pub output: f64,
22 pub reasoning: f64,
23}
24
25impl Default for TokenPricing {
26 fn default() -> Self {
27 Self {
28 fresh_input: 3.00,
29 cached_input: 0.30,
30 output: 15.00,
31 reasoning: 15.00,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38pub struct TokenUsage {
39 pub fresh_input: u64,
40 pub cached_input: u64,
41 pub output: u64,
42 pub reasoning: u64,
43}
44
45impl TokenUsage {
46 pub fn weighted_tokens(&self, pricing: &TokenPricing) -> f64 {
48 let normalize = pricing.fresh_input;
49 if normalize == 0.0 {
50 return 0.0;
51 }
52 self.fresh_input as f64
53 + (self.cached_input as f64 * pricing.cached_input / normalize)
54 + (self.output as f64 * pricing.output / normalize)
55 + (self.reasoning as f64 * pricing.reasoning / normalize)
56 }
57
58 pub fn total_raw(&self) -> u64 {
60 self.fresh_input + self.cached_input + self.output + self.reasoning
61 }
62
63 pub fn cost_usd(&self, pricing: &TokenPricing) -> f64 {
65 (self.fresh_input as f64 * pricing.fresh_input
66 + self.cached_input as f64 * pricing.cached_input
67 + self.output as f64 * pricing.output
68 + self.reasoning as f64 * pricing.reasoning)
69 / 1_000_000.0
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct EtpaoReport {
76 pub leanctx_usage: TokenUsage,
77 pub baseline_usage: TokenUsage,
78 pub leanctx_weighted: f64,
79 pub baseline_weighted: f64,
80 pub delta_pct: f64,
81 pub leanctx_cost_usd: f64,
82 pub baseline_cost_usd: f64,
83 pub cost_delta_pct: f64,
84}
85
86impl EtpaoReport {
87 pub fn compute(leanctx: TokenUsage, baseline: TokenUsage, pricing: &TokenPricing) -> Self {
89 let lw = leanctx.weighted_tokens(pricing);
90 let bw = baseline.weighted_tokens(pricing);
91 let delta_pct = if bw > 0.0 {
92 ((lw - bw) / bw) * 100.0
93 } else {
94 0.0
95 };
96
97 let lc = leanctx.cost_usd(pricing);
98 let bc = baseline.cost_usd(pricing);
99 let cost_delta = if bc > 0.0 {
100 ((lc - bc) / bc) * 100.0
101 } else {
102 0.0
103 };
104
105 Self {
106 leanctx_usage: leanctx,
107 baseline_usage: baseline,
108 leanctx_weighted: lw,
109 baseline_weighted: bw,
110 delta_pct,
111 leanctx_cost_usd: lc,
112 baseline_cost_usd: bc,
113 cost_delta_pct: cost_delta,
114 }
115 }
116
117 pub fn is_cost_efficient(&self) -> bool {
119 self.cost_delta_pct < 0.0
120 }
121}
122
123#[cfg(test)]
124pub mod tests {
125 use super::*;
126
127 #[test]
128 fn weighted_tokens_accounts_for_pricing() {
129 let usage = TokenUsage {
130 fresh_input: 1_000_000,
131 cached_input: 1_000_000,
132 output: 100_000,
133 reasoning: 0,
134 };
135 let pricing = TokenPricing::default();
136 let weighted = usage.weighted_tokens(&pricing);
137 assert!((weighted - 1_600_000.0).abs() < 1.0);
139 }
140
141 #[test]
142 fn cost_usd_calculation() {
143 let usage = TokenUsage {
144 fresh_input: 1_000_000,
145 cached_input: 0,
146 output: 0,
147 reasoning: 0,
148 };
149 let cost = usage.cost_usd(&TokenPricing::default());
150 assert!((cost - 3.00).abs() < 0.01);
151 }
152
153 #[test]
154 fn etpao_report_negative_delta_means_savings() {
155 let leanctx = TokenUsage {
156 fresh_input: 500_000,
157 cached_input: 500_000,
158 output: 50_000,
159 reasoning: 0,
160 };
161 let baseline = TokenUsage {
162 fresh_input: 1_000_000,
163 cached_input: 0,
164 output: 50_000,
165 reasoning: 0,
166 };
167 let report = EtpaoReport::compute(leanctx, baseline, &TokenPricing::default());
168 assert!(report.is_cost_efficient());
169 assert!(report.delta_pct < 0.0);
170 }
171
172 #[test]
173 fn etpao_report_positive_delta_means_overhead() {
174 let leanctx = TokenUsage {
175 fresh_input: 1_200_000,
176 cached_input: 0,
177 output: 50_000,
178 reasoning: 0,
179 };
180 let baseline = TokenUsage {
181 fresh_input: 1_000_000,
182 cached_input: 0,
183 output: 50_000,
184 reasoning: 0,
185 };
186 let report = EtpaoReport::compute(leanctx, baseline, &TokenPricing::default());
187 assert!(!report.is_cost_efficient());
188 assert!(report.delta_pct > 0.0);
189 }
190}