Skip to main content

usage_monitor_cli/model/
cost.rs

1use chrono::NaiveDate;
2use serde::{Deserialize, Serialize};
3
4/// Cost snapshot for a provider.
5#[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/// Daily cost.
14#[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/// Spend limit.
24#[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}