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