ccboard_core/
usage_estimator.rs1use crate::models::billing_block::BillingBlockManager;
7use chrono::{Datelike, Local, NaiveDate};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum SubscriptionPlan {
12 Pro,
14 Max5x,
16 Max20x,
18 Api,
20 Unknown,
22}
23
24impl SubscriptionPlan {
25 pub fn from_str(s: &str) -> Self {
27 match s.to_lowercase().as_str() {
28 "pro" => Self::Pro,
29 "max5x" | "max-5x" | "max_5x" => Self::Max5x,
30 "max20x" | "max-20x" | "max_20x" => Self::Max20x,
31 "api" => Self::Api,
32 _ => Self::Unknown,
33 }
34 }
35
36 pub fn monthly_budget_usd(self) -> Option<f64> {
42 match self {
43 Self::Pro => Some(20.0),
44 Self::Max5x => Some(50.0), Self::Max20x => Some(200.0),
46 Self::Api => None, Self::Unknown => None,
48 }
49 }
50
51 pub fn display_name(self) -> &'static str {
53 match self {
54 Self::Pro => "Claude Pro",
55 Self::Max5x => "Claude Max 5x",
56 Self::Max20x => "Claude Max 20x",
57 Self::Api => "API (Pay-as-you-go)",
58 Self::Unknown => "Unknown Plan",
59 }
60 }
61}
62
63#[derive(Debug, Clone, Default)]
65pub struct UsageEstimate {
66 pub cost_today: f64,
68 pub cost_week: f64,
70 pub cost_month: f64,
72 pub plan: SubscriptionPlan,
74 pub budget_usd: Option<f64>,
76}
77
78impl UsageEstimate {
79 pub fn percent_today(&self) -> Option<f64> {
81 self.budget_usd
82 .map(|budget| (self.cost_today / budget * 100.0).min(100.0))
83 }
84
85 pub fn percent_week(&self) -> Option<f64> {
87 self.budget_usd
88 .map(|budget| (self.cost_week / budget * 100.0).min(100.0))
89 }
90
91 pub fn percent_month(&self) -> Option<f64> {
93 self.budget_usd
94 .map(|budget| (self.cost_month / budget * 100.0).min(100.0))
95 }
96}
97
98impl Default for SubscriptionPlan {
99 fn default() -> Self {
100 Self::Unknown
101 }
102}
103
104pub fn calculate_usage_estimate(
106 billing_blocks: &BillingBlockManager,
107 plan: SubscriptionPlan,
108) -> UsageEstimate {
109 let now = Local::now();
110 let today = now.date_naive();
111 let week_start = today - chrono::Duration::days(today.weekday().num_days_from_monday() as i64);
112 let month_start = NaiveDate::from_ymd_opt(today.year(), today.month(), 1).unwrap();
113
114 let mut cost_today = 0.0;
115 let mut cost_week = 0.0;
116 let mut cost_month = 0.0;
117
118 for (block, usage) in billing_blocks.get_all_blocks() {
120 let block_date = block.date;
121 let cost = usage.total_cost;
122
123 if block_date == today {
124 cost_today += cost;
125 }
126 if block_date >= week_start {
127 cost_week += cost;
128 }
129 if block_date >= month_start {
130 cost_month += cost;
131 }
132 }
133
134 UsageEstimate {
135 cost_today,
136 cost_week,
137 cost_month,
138 plan,
139 budget_usd: plan.monthly_budget_usd(),
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_parse_plan() {
149 assert_eq!(SubscriptionPlan::from_str("pro"), SubscriptionPlan::Pro);
150 assert_eq!(SubscriptionPlan::from_str("max5x"), SubscriptionPlan::Max5x);
151 assert_eq!(
152 SubscriptionPlan::from_str("max-20x"),
153 SubscriptionPlan::Max20x
154 );
155 assert_eq!(SubscriptionPlan::from_str("api"), SubscriptionPlan::Api);
156 assert_eq!(
157 SubscriptionPlan::from_str("unknown"),
158 SubscriptionPlan::Unknown
159 );
160 }
161
162 #[test]
163 fn test_monthly_budget() {
164 assert_eq!(SubscriptionPlan::Pro.monthly_budget_usd(), Some(20.0));
165 assert_eq!(SubscriptionPlan::Max5x.monthly_budget_usd(), Some(50.0));
166 assert_eq!(SubscriptionPlan::Max20x.monthly_budget_usd(), Some(200.0));
167 assert_eq!(SubscriptionPlan::Api.monthly_budget_usd(), None);
168 assert_eq!(SubscriptionPlan::Unknown.monthly_budget_usd(), None);
169 }
170
171 #[test]
172 fn test_percent_calculation() {
173 let estimate = UsageEstimate {
174 cost_today: 5.0,
175 cost_week: 15.0,
176 cost_month: 40.0,
177 plan: SubscriptionPlan::Max5x,
178 budget_usd: Some(50.0),
179 };
180
181 assert_eq!(estimate.percent_today(), Some(10.0));
182 assert_eq!(estimate.percent_week(), Some(30.0));
183 assert_eq!(estimate.percent_month(), Some(80.0));
184 }
185
186 #[test]
187 fn test_no_budget() {
188 let estimate = UsageEstimate {
189 cost_today: 5.0,
190 cost_week: 15.0,
191 cost_month: 40.0,
192 plan: SubscriptionPlan::Api,
193 budget_usd: None,
194 };
195
196 assert_eq!(estimate.percent_today(), None);
197 assert_eq!(estimate.percent_week(), None);
198 assert_eq!(estimate.percent_month(), None);
199 }
200}