Skip to main content

lean_ctx_protocol/
value_share.rs

1//! Savings attribution and value-share settlement contracts.
2
3use crate::MoneyV1;
4use serde::{Deserialize, Serialize};
5
6/// Attributed, evidence-backed savings for one customer billing period.
7#[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/// Lifecycle state of a value-share settlement.
19#[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/// Result of calculating a value-share settlement.
29#[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
37/// Extension point for Enterprise billing implementations such as `VerifiedValueShare`.
38pub trait ValueShareContract {
39    /// Calculate the settlement owed for a savings attribution.
40    fn calculate_share(&self, attribution: SavingsAttribution) -> SettlementRecord;
41}
42
43/// OSS value-share calculator that never produces a billable settlement.
44#[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}