usage_monitor_cli/model/
cost.rs1use chrono::NaiveDate;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct CostSnapshot {
7 pub total_cost: Option<f64>,
8 pub currency: String,
9 pub daily_costs: Vec<DailyCost>,
10 pub spend_limit: Option<SpendLimit>,
11}
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct DailyCost {
16 pub date: NaiveDate,
17 pub cost: f64,
18 pub tokens_input: Option<u64>,
19 pub tokens_output: Option<u64>,
20 pub requests: Option<u64>,
21}
22
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct SpendLimit {
26 pub limit: f64,
27 pub used: f64,
28 pub period: String,
29}
30
31impl SpendLimit {
32 pub fn ratio(&self) -> f64 {
33 if self.limit > 0.0 {
34 (self.used / self.limit).clamp(0.0, 1.0)
35 } else {
36 0.0
37 }
38 }
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn test_spend_limit_ratio() {
47 let sl = SpendLimit {
48 limit: 100.0,
49 used: 45.0,
50 period: "monthly".into(),
51 };
52 assert!((sl.ratio() - 0.45).abs() < f64::EPSILON);
53 }
54
55 #[test]
56 fn test_spend_limit_zero_limit() {
57 let sl = SpendLimit {
58 limit: 0.0,
59 used: 50.0,
60 period: "monthly".into(),
61 };
62 assert_eq!(sl.ratio(), 0.0);
63 }
64
65 #[test]
66 fn test_daily_cost_serialization() {
67 let dc = DailyCost {
68 date: NaiveDate::from_ymd_opt(2026, 6, 12).unwrap(),
69 cost: 1.20,
70 tokens_input: Some(45000),
71 tokens_output: Some(12000),
72 requests: Some(450),
73 };
74 let json = serde_json::to_string(&dc).unwrap();
75 let back: DailyCost = serde_json::from_str(&json).unwrap();
76 assert_eq!(dc.date, back.date);
77 assert_eq!(dc.cost, back.cost);
78 }
79
80 #[test]
81 fn test_cost_snapshot_new() {
82 let cs = CostSnapshot {
83 total_cost: None,
84 currency: "USD".into(),
85 daily_costs: vec![],
86 spend_limit: None,
87 };
88 assert_eq!(cs.currency, "USD");
89 assert!(cs.daily_costs.is_empty());
90 }
91}