Skip to main content

lean_ctx_protocol/
savings.rs

1use crate::{EvidenceRefV1, MoneyV1, UsageBreakdownV1};
2use serde::{Deserialize, Serialize};
3
4/// What OSS emits. NOT VerifiedSavings (that's proprietary).
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct SavingsObservationV1 {
7    pub observation_id: String,
8    pub provider_id: String,
9    pub model_id: String,
10    pub original_usage: UsageBreakdownV1,
11    pub actual_usage: UsageBreakdownV1,
12    pub local_cost_estimate: MoneyV1,
13    pub evidence_refs: Vec<EvidenceRefV1>,
14    pub observed_at: String,
15    pub runtime_version: String,
16    pub sequence_number: u64,
17}
18
19#[cfg(test)]
20mod tests {
21    use crate::{MoneyV1, SavingsObservationV1, UsageBreakdownV1};
22
23    #[test]
24    fn serialization_round_trip() {
25        let observation = SavingsObservationV1 {
26            observation_id: "obs-1".to_owned(),
27            provider_id: "provider".to_owned(),
28            model_id: "model".to_owned(),
29            original_usage: UsageBreakdownV1::default(),
30            actual_usage: UsageBreakdownV1::default(),
31            local_cost_estimate: MoneyV1 {
32                currency: "USD".to_owned(),
33                coefficient: 1,
34                scale: 4,
35            },
36            evidence_refs: Vec::new(),
37            observed_at: "2026-01-01T00:00:00Z".to_owned(),
38            runtime_version: "1.0.0".to_owned(),
39            sequence_number: 1,
40        };
41        let json = serde_json::to_string(&observation).expect("observation should serialize");
42        let decoded = serde_json::from_str(&json).expect("observation should deserialize");
43        assert_eq!(observation, decoded);
44    }
45}