lean_ctx_protocol/
value_share.rs1use crate::MoneyV1;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct SavingsAttribution {
10 pub customer_id: String,
11 pub period: String,
12 pub baseline_cost: MoneyV1,
13 pub treatment_cost: MoneyV1,
14 pub proven_savings: MoneyV1,
15 pub share_percentage: u16,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum SettlementStatus {
22 NoSettlement,
23 Pending,
24 Settled,
25 Disputed,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(deny_unknown_fields)]
31pub struct SettlementRecord {
32 pub attribution: SavingsAttribution,
33 pub invoice_amount: MoneyV1,
34 pub settlement_status: SettlementStatus,
35}
36
37pub trait ValueShareContract {
39 fn calculate_share(&self, attribution: SavingsAttribution) -> SettlementRecord;
41}
42
43#[derive(Debug, Default, Clone, Copy)]
45pub struct LocalValueShare;
46
47impl ValueShareContract for LocalValueShare {
48 fn calculate_share(&self, attribution: SavingsAttribution) -> SettlementRecord {
49 let invoice_amount = MoneyV1 {
50 currency: attribution.proven_savings.currency.clone(),
51 coefficient: 0,
52 scale: attribution.proven_savings.scale,
53 };
54 SettlementRecord {
55 attribution,
56 invoice_amount,
57 settlement_status: SettlementStatus::NoSettlement,
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 fn attribution() -> SavingsAttribution {
67 SavingsAttribution {
68 customer_id: "customer-1".to_owned(),
69 period: "2026-08".to_owned(),
70 baseline_cost: MoneyV1 {
71 currency: "USD".to_owned(),
72 coefficient: 10_000,
73 scale: 4,
74 },
75 treatment_cost: MoneyV1 {
76 currency: "USD".to_owned(),
77 coefficient: 5_000,
78 scale: 4,
79 },
80 proven_savings: MoneyV1 {
81 currency: "USD".to_owned(),
82 coefficient: 5_000,
83 scale: 4,
84 },
85 share_percentage: 200,
86 }
87 }
88
89 #[test]
90 fn local_value_share_returns_zero_settlement() {
91 let settlement = LocalValueShare.calculate_share(attribution());
92 assert_eq!(settlement.invoice_amount.coefficient, 0);
93 assert_eq!(settlement.settlement_status, SettlementStatus::NoSettlement);
94 }
95
96 #[test]
97 fn contract_is_object_safe() {
98 let value_share: &dyn ValueShareContract = &LocalValueShare;
99 assert_eq!(
100 value_share
101 .calculate_share(attribution())
102 .invoice_amount
103 .coefficient,
104 0
105 );
106 }
107}