lean_ctx_protocol/
savings.rs1use crate::{EvidenceRefV1, MoneyV1, UsageBreakdownV1};
2use serde::{Deserialize, Serialize};
3
4#[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}