synor/crates/synor-economics/src/pricing/mod.rs
Gulshan Yadav 17f0b4ce4b feat(economics): add Phase 12 - Economics & Billing infrastructure
Complete economics service implementation with:

- Price Oracle with TWAP (Time-Weighted Average Price)
  - Multi-source price aggregation
  - Configurable staleness thresholds
  - Exponential, SMA, and standard TWAP strategies

- Metering Service for L2 usage tracking
  - Storage: GB-months, retrieval bandwidth
  - Hosting: bandwidth, custom domains
  - Database: queries, vector searches
  - Compute: CPU core-hours, GPU hours, memory
  - Network: bandwidth, requests

- Billing Engine
  - Invoice generation with line items
  - Payment processing (crypto/fiat)
  - Credit management with expiration
  - Auto-pay from prepaid balance

- Pricing Tiers: Free, Standard, Premium, Enterprise
  - 0%, 10%, 20%, 30% usage discounts
  - SLA guarantees: 95%, 99%, 99.9%, 99.99%

- Cost Calculator & Estimator
  - Usage projections
  - Tier comparison recommendations
  - ROI analysis

- Docker deployment with PostgreSQL schema

All 61 tests passing.
2026-01-19 21:51:26 +05:30

463 lines
16 KiB
Rust

//! Pricing Engine
//!
//! Service pricing, tier management, and discount system.
mod discounts;
mod tiers;
pub use discounts::{Discount, DiscountType};
pub use tiers::PricingTier;
use crate::error::{EconomicsError, Result};
use crate::{ResourceUnit, ServiceType, SynorDecimal};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Service pricing configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServicePricing {
/// Base prices by resource unit
pub base_prices: HashMap<ResourceUnit, SynorDecimal>,
/// Minimum billable amount
pub minimum_charge: SynorDecimal,
/// Free tier included
pub free_tier: Option<FreeTier>,
}
/// Free tier allocation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FreeTier {
/// Free allocation per resource
pub allocations: HashMap<ResourceUnit, Decimal>,
/// Monthly reset
pub monthly_reset: bool,
}
/// Pricing engine managing all pricing logic
pub struct PricingEngine {
/// Pricing by service type
service_pricing: HashMap<ServiceType, ServicePricing>,
/// Available pricing tiers
tiers: Vec<PricingTier>,
/// Active discounts
discounts: Vec<Discount>,
}
impl PricingEngine {
/// Create a new pricing engine with default prices
pub fn new() -> Self {
let mut engine = Self {
service_pricing: HashMap::new(),
tiers: Vec::new(),
discounts: Vec::new(),
};
engine.initialize_default_pricing();
engine.initialize_default_tiers();
engine
}
/// Initialize default service pricing
fn initialize_default_pricing(&mut self) {
// Storage L2 pricing
let mut storage_prices = HashMap::new();
storage_prices.insert(ResourceUnit::GbMonth, Decimal::new(2, 2)); // 0.02 SYNOR/GB/month
storage_prices.insert(ResourceUnit::BandwidthGb, Decimal::new(1, 2)); // 0.01 SYNOR/GB retrieval
storage_prices.insert(ResourceUnit::Bytes, Decimal::new(2, 11)); // For small files
self.service_pricing.insert(
ServiceType::Storage,
ServicePricing {
base_prices: storage_prices,
minimum_charge: Decimal::new(1, 4), // 0.0001 SYNOR
free_tier: Some(FreeTier {
allocations: [(ResourceUnit::GbMonth, Decimal::new(5, 1))].into(), // 0.5 GB free
monthly_reset: true,
}),
},
);
// Hosting pricing
let mut hosting_prices = HashMap::new();
hosting_prices.insert(ResourceUnit::BandwidthGb, Decimal::new(5, 2)); // 0.05 SYNOR/GB
hosting_prices.insert(ResourceUnit::Domains, Decimal::new(50, 2)); // 0.50 SYNOR/domain/month
self.service_pricing.insert(
ServiceType::Hosting,
ServicePricing {
base_prices: hosting_prices,
minimum_charge: Decimal::ZERO,
free_tier: Some(FreeTier {
allocations: [
(ResourceUnit::BandwidthGb, Decimal::new(1, 0)), // 1 GB free
]
.into(),
monthly_reset: true,
}),
},
);
// Database L2 pricing
let mut db_prices = HashMap::new();
db_prices.insert(ResourceUnit::GbMonth, Decimal::new(10, 2)); // 0.10 SYNOR/GB/month
db_prices.insert(ResourceUnit::Queries, Decimal::new(1, 8)); // 0.00000001 SYNOR/query (0.01/million)
db_prices.insert(ResourceUnit::VectorSearches, Decimal::new(5, 8)); // 0.00000005 SYNOR/search (0.05/million)
self.service_pricing.insert(
ServiceType::Database,
ServicePricing {
base_prices: db_prices,
minimum_charge: Decimal::new(1, 4),
free_tier: Some(FreeTier {
allocations: [
(ResourceUnit::GbMonth, Decimal::new(5, 1)), // 0.5 GB free
(ResourceUnit::Queries, Decimal::new(1_000_000, 0)), // 1M queries free
(ResourceUnit::VectorSearches, Decimal::new(100_000, 0)), // 100K vector searches free
]
.into(),
monthly_reset: true,
}),
},
);
// Compute L2 pricing
let mut compute_prices = HashMap::new();
compute_prices.insert(ResourceUnit::CpuCoreHours, Decimal::new(2, 2)); // 0.02 SYNOR/core-hour
compute_prices.insert(ResourceUnit::GpuHours, Decimal::new(50, 2)); // 0.50 SYNOR/GPU-hour (RTX 4090)
compute_prices.insert(ResourceUnit::MemoryGbHours, Decimal::new(5, 3)); // 0.005 SYNOR/GB-hour
compute_prices.insert(ResourceUnit::Invocations, Decimal::new(2, 7)); // 0.0000002 SYNOR/invocation (0.20/million)
self.service_pricing.insert(
ServiceType::Compute,
ServicePricing {
base_prices: compute_prices,
minimum_charge: Decimal::new(1, 4),
free_tier: Some(FreeTier {
allocations: [
(ResourceUnit::CpuCoreHours, Decimal::new(100, 0)), // 100 core-hours free
(ResourceUnit::MemoryGbHours, Decimal::new(200, 0)), // 200 GB-hours free
(ResourceUnit::Invocations, Decimal::new(1_000_000, 0)), // 1M invocations free
]
.into(),
monthly_reset: true,
}),
},
);
// Network pricing
let mut network_prices = HashMap::new();
network_prices.insert(ResourceUnit::BandwidthGb, Decimal::new(1, 2)); // 0.01 SYNOR/GB
self.service_pricing.insert(
ServiceType::Network,
ServicePricing {
base_prices: network_prices,
minimum_charge: Decimal::ZERO,
free_tier: Some(FreeTier {
allocations: [(ResourceUnit::BandwidthGb, Decimal::new(10, 0))].into(), // 10 GB free
monthly_reset: true,
}),
},
);
// Contract execution (gas pricing)
let mut contract_prices = HashMap::new();
contract_prices.insert(ResourceUnit::Gas, Decimal::new(1, 9)); // 0.000000001 SYNOR/gas
self.service_pricing.insert(
ServiceType::Contract,
ServicePricing {
base_prices: contract_prices,
minimum_charge: Decimal::ZERO,
free_tier: None,
},
);
}
/// Initialize default pricing tiers
fn initialize_default_tiers(&mut self) {
self.tiers = vec![
PricingTier::free(),
PricingTier::standard(),
PricingTier::premium(),
PricingTier::enterprise(),
];
}
/// Calculate cost for resource usage
pub fn calculate_cost(
&self,
service_type: ServiceType,
resource_unit: ResourceUnit,
amount: Decimal,
) -> Result<SynorDecimal> {
let pricing = self
.service_pricing
.get(&service_type)
.ok_or_else(|| EconomicsError::ServiceNotConfigured(service_type.to_string()))?;
let unit_price = pricing
.base_prices
.get(&resource_unit)
.ok_or_else(|| {
EconomicsError::ServiceNotConfigured(format!(
"{} - {}",
service_type, resource_unit
))
})?;
let cost = amount * unit_price;
// Apply minimum charge
Ok(cost.max(pricing.minimum_charge))
}
/// Calculate cost with tier discount applied
pub fn calculate_cost_with_tier(
&self,
service_type: ServiceType,
resource_unit: ResourceUnit,
amount: Decimal,
tier_name: &str,
) -> Result<SynorDecimal> {
let base_cost = self.calculate_cost(service_type, resource_unit, amount)?;
let tier = self.tiers.iter().find(|t| t.name == tier_name);
if let Some(tier) = tier {
let discount_rate = tier.discount_percentage / Decimal::ONE_HUNDRED;
Ok(base_cost * (Decimal::ONE - discount_rate))
} else {
Ok(base_cost)
}
}
/// Calculate tier discount amount
pub fn calculate_tier_discount(
&self,
tier_name: &str,
subtotal: SynorDecimal,
) -> Result<SynorDecimal> {
let tier = self.tiers.iter().find(|t| t.name == tier_name);
if let Some(tier) = tier {
let discount_rate = tier.discount_percentage / Decimal::ONE_HUNDRED;
Ok(subtotal * discount_rate)
} else {
Ok(Decimal::ZERO)
}
}
/// Get free tier allocation for a resource
pub fn get_free_allocation(
&self,
service_type: ServiceType,
resource_unit: ResourceUnit,
) -> Decimal {
self.service_pricing
.get(&service_type)
.and_then(|p| p.free_tier.as_ref())
.and_then(|f| f.allocations.get(&resource_unit))
.copied()
.unwrap_or(Decimal::ZERO)
}
/// Get base price for a resource
pub fn get_base_price(
&self,
service_type: ServiceType,
resource_unit: ResourceUnit,
) -> Option<SynorDecimal> {
self.service_pricing
.get(&service_type)
.and_then(|p| p.base_prices.get(&resource_unit))
.copied()
}
/// Get pricing tier by name
pub fn get_tier(&self, name: &str) -> Option<&PricingTier> {
self.tiers.iter().find(|t| t.name == name)
}
/// Get all tiers
pub fn get_all_tiers(&self) -> &[PricingTier] {
&self.tiers
}
/// Add a custom discount
pub fn add_discount(&mut self, discount: Discount) {
self.discounts.push(discount);
}
/// Apply applicable discounts to an amount
pub fn apply_discounts(
&self,
amount: SynorDecimal,
account_id: &str,
service_type: Option<ServiceType>,
) -> (SynorDecimal, Vec<Discount>) {
let mut discounted = amount;
let mut applied = Vec::new();
for discount in &self.discounts {
if discount.is_applicable(account_id, service_type) {
let discount_amount = discount.calculate_discount(discounted);
discounted -= discount_amount;
applied.push(discount.clone());
}
}
(discounted, applied)
}
/// Get pricing summary for display
pub fn get_pricing_summary(&self) -> PricingSummary {
PricingSummary {
storage: ServicePricingSummary {
gb_month: self.get_base_price(ServiceType::Storage, ResourceUnit::GbMonth),
retrieval_gb: self.get_base_price(ServiceType::Storage, ResourceUnit::BandwidthGb),
free_storage_gb: self.get_free_allocation(ServiceType::Storage, ResourceUnit::GbMonth),
},
hosting: HostingPricingSummary {
bandwidth_gb: self.get_base_price(ServiceType::Hosting, ResourceUnit::BandwidthGb),
domain_month: self.get_base_price(ServiceType::Hosting, ResourceUnit::Domains),
free_bandwidth_gb: self
.get_free_allocation(ServiceType::Hosting, ResourceUnit::BandwidthGb),
},
database: DatabasePricingSummary {
storage_gb_month: self.get_base_price(ServiceType::Database, ResourceUnit::GbMonth),
queries_million: self
.get_base_price(ServiceType::Database, ResourceUnit::Queries)
.map(|p| p * Decimal::from(1_000_000)),
vector_searches_million: self
.get_base_price(ServiceType::Database, ResourceUnit::VectorSearches)
.map(|p| p * Decimal::from(1_000_000)),
free_queries: self
.get_free_allocation(ServiceType::Database, ResourceUnit::Queries),
},
compute: ComputePricingSummary {
cpu_core_hour: self.get_base_price(ServiceType::Compute, ResourceUnit::CpuCoreHours),
gpu_hour: self.get_base_price(ServiceType::Compute, ResourceUnit::GpuHours),
memory_gb_hour: self
.get_base_price(ServiceType::Compute, ResourceUnit::MemoryGbHours),
invocations_million: self
.get_base_price(ServiceType::Compute, ResourceUnit::Invocations)
.map(|p| p * Decimal::from(1_000_000)),
free_cpu_hours: self
.get_free_allocation(ServiceType::Compute, ResourceUnit::CpuCoreHours),
},
}
}
}
impl Default for PricingEngine {
fn default() -> Self {
Self::new()
}
}
/// Pricing summary for display
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricingSummary {
pub storage: ServicePricingSummary,
pub hosting: HostingPricingSummary,
pub database: DatabasePricingSummary,
pub compute: ComputePricingSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServicePricingSummary {
pub gb_month: Option<SynorDecimal>,
pub retrieval_gb: Option<SynorDecimal>,
pub free_storage_gb: SynorDecimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HostingPricingSummary {
pub bandwidth_gb: Option<SynorDecimal>,
pub domain_month: Option<SynorDecimal>,
pub free_bandwidth_gb: SynorDecimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabasePricingSummary {
pub storage_gb_month: Option<SynorDecimal>,
pub queries_million: Option<SynorDecimal>,
pub vector_searches_million: Option<SynorDecimal>,
pub free_queries: SynorDecimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComputePricingSummary {
pub cpu_core_hour: Option<SynorDecimal>,
pub gpu_hour: Option<SynorDecimal>,
pub memory_gb_hour: Option<SynorDecimal>,
pub invocations_million: Option<SynorDecimal>,
pub free_cpu_hours: SynorDecimal,
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_storage_pricing() {
let engine = PricingEngine::new();
// 10 GB storage for a month
let cost = engine
.calculate_cost(ServiceType::Storage, ResourceUnit::GbMonth, dec!(10))
.unwrap();
assert_eq!(cost, dec!(0.20)); // 10 * 0.02
}
#[test]
fn test_compute_pricing() {
let engine = PricingEngine::new();
// 5 GPU hours
let cost = engine
.calculate_cost(ServiceType::Compute, ResourceUnit::GpuHours, dec!(5))
.unwrap();
assert_eq!(cost, dec!(2.50)); // 5 * 0.50
}
#[test]
fn test_database_queries() {
let engine = PricingEngine::new();
// 10 million queries
let cost = engine
.calculate_cost(ServiceType::Database, ResourceUnit::Queries, dec!(10_000_000))
.unwrap();
assert_eq!(cost, dec!(0.10)); // 10M * 0.00000001
}
#[test]
fn test_tier_discount() {
let engine = PricingEngine::new();
// Premium tier gets 20% discount
let base_cost = dec!(100);
let discount = engine.calculate_tier_discount("premium", base_cost).unwrap();
assert_eq!(discount, dec!(20)); // 20%
}
#[test]
fn test_free_allocation() {
let engine = PricingEngine::new();
let free_storage = engine.get_free_allocation(ServiceType::Storage, ResourceUnit::GbMonth);
assert_eq!(free_storage, dec!(0.5));
let free_queries = engine.get_free_allocation(ServiceType::Database, ResourceUnit::Queries);
assert_eq!(free_queries, dec!(1_000_000));
}
}