use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use std::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct PlatformMetrics {
pub metrics_id: Uuid,
pub total_users: i64,
pub active_users_30d: i64,
pub new_users_7d: i64,
pub total_tokens: i64,
pub active_tokens: i64,
pub total_orders: i64,
pub total_trades: i64,
pub total_volume_btc: Decimal,
pub volume_24h_btc: Decimal,
pub volume_7d_btc: Decimal,
pub total_fees_btc: Decimal,
pub kyc_verified_users: i64,
pub kyc_pending: i64,
pub avg_reputation_score: Option<Decimal>,
pub total_commitments: i64,
pub fulfilled_commitments: i64,
pub total_liquidity_pools: i64,
pub total_value_locked_btc: Decimal,
pub snapshot_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}
impl fmt::Display for PlatformMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PlatformMetrics(users={}, tokens={}, volume_24h={} BTC)",
self.total_users, self.total_tokens, self.volume_24h_btc
)
}
}
impl PlatformMetrics {
pub fn user_growth_rate_percent(&self) -> Decimal {
if self.total_users == 0 {
return dec!(0);
}
(Decimal::from(self.new_users_7d) / Decimal::from(self.total_users)) * dec!(100)
}
pub fn active_user_ratio_percent(&self) -> Decimal {
if self.total_users == 0 {
return dec!(0);
}
(Decimal::from(self.active_users_30d) / Decimal::from(self.total_users)) * dec!(100)
}
pub fn commitment_fulfillment_rate_percent(&self) -> Decimal {
if self.total_commitments == 0 {
return dec!(0);
}
(Decimal::from(self.fulfilled_commitments) / Decimal::from(self.total_commitments))
* dec!(100)
}
pub fn avg_order_size_btc(&self) -> Decimal {
if self.total_orders == 0 {
return dec!(0);
}
self.total_volume_btc / Decimal::from(self.total_orders)
}
pub fn kyc_completion_rate_percent(&self) -> Decimal {
if self.total_users == 0 {
return dec!(0);
}
(Decimal::from(self.kyc_verified_users) / Decimal::from(self.total_users)) * dec!(100)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct TimeSeriesMetrics {
pub id: Uuid,
pub metric_name: String,
pub value: Decimal,
pub period_start: DateTime<Utc>,
pub period_end: DateTime<Utc>,
pub granularity: MetricGranularity,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, sqlx::Type, PartialEq, Eq)]
#[sqlx(type_name = "varchar", rename_all = "lowercase")]
#[derive(Default)]
pub enum MetricGranularity {
Hourly,
#[default]
Daily,
Weekly,
Monthly,
}
impl fmt::Display for MetricGranularity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MetricGranularity::Hourly => write!(f, "hourly"),
MetricGranularity::Daily => write!(f, "daily"),
MetricGranularity::Weekly => write!(f, "weekly"),
MetricGranularity::Monthly => write!(f, "monthly"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenMetrics {
pub token_id: Uuid,
pub symbol: String,
pub holder_count: i64,
pub trade_count: i64,
pub volume_btc: Decimal,
pub market_cap_btc: Decimal,
pub price_change_24h_percent: Decimal,
pub price_change_7d_percent: Decimal,
pub ath_price_btc: Decimal,
pub ath_date: DateTime<Utc>,
pub current_price_btc: Decimal,
pub liquidity_score: Decimal,
}
impl TokenMetrics {
pub fn velocity(&self) -> Decimal {
if self.holder_count == 0 {
return dec!(0);
}
Decimal::from(self.trade_count) / Decimal::from(self.holder_count)
}
pub fn distance_from_ath_percent(&self) -> Decimal {
if self.ath_price_btc == dec!(0) {
return dec!(0);
}
((self.ath_price_btc - self.current_price_btc) / self.ath_price_btc) * dec!(100)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserActivityMetrics {
pub user_id: Uuid,
pub tokens_created: i32,
pub orders_placed: i32,
pub trades_executed: i32,
pub total_volume_btc: Decimal,
pub total_fees_paid_btc: Decimal,
pub tokens_traded_count: i32,
pub days_active: i32,
pub last_activity_at: DateTime<Utc>,
pub account_age_days: i32,
}
impl UserActivityMetrics {
pub fn avg_daily_volume_btc(&self) -> Decimal {
if self.days_active == 0 {
return dec!(0);
}
self.total_volume_btc / Decimal::from(self.days_active)
}
pub fn is_active(&self) -> bool {
let days_since_activity = (Utc::now() - self.last_activity_at).num_days();
days_since_activity <= 30
}
pub fn engagement_score(&self) -> Decimal {
let mut score = dec!(0);
if self.trades_executed > 0 {
score += dec!(10);
}
if self.trades_executed >= 10 {
score += dec!(10);
}
if self.trades_executed >= 50 {
score += dec!(10);
}
if self.trades_executed >= 100 {
score += dec!(10);
}
if self.tokens_created > 0 {
score += dec!(10);
}
if self.tokens_created >= 3 {
score += dec!(10);
}
if self.total_volume_btc >= dec!(0.01) {
score += dec!(5);
}
if self.total_volume_btc >= dec!(0.1) {
score += dec!(5);
}
if self.total_volume_btc >= dec!(1) {
score += dec!(5);
}
if self.total_volume_btc >= dec!(10) {
score += dec!(5);
}
if self.is_active() {
score += dec!(10);
}
if self.days_active >= 30 {
score += dec!(5);
}
if self.days_active >= 90 {
score += dec!(5);
}
score.min(dec!(100))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemHealthMetrics {
pub uptime_24h_percent: Decimal,
pub avg_response_time_ms: f64,
pub error_rate_percent: Decimal,
pub db_connections_active: i32,
pub db_pool_utilization_percent: Decimal,
pub btc_node_synced: bool,
pub btc_node_block_height: i64,
pub pending_payments: i64,
pub pending_kyc_reviews: i64,
pub checked_at: DateTime<Utc>,
}
impl SystemHealthMetrics {
pub fn is_healthy(&self) -> bool {
self.uptime_24h_percent >= dec!(99)
&& self.error_rate_percent < dec!(1)
&& self.btc_node_synced
&& self.db_pool_utilization_percent < dec!(90)
}
pub fn health_status(&self) -> HealthStatus {
if !self.btc_node_synced {
return HealthStatus::Critical;
}
if self.uptime_24h_percent < dec!(95) || self.error_rate_percent > dec!(5) {
return HealthStatus::Critical;
}
if self.uptime_24h_percent < dec!(99) || self.error_rate_percent > dec!(1) {
return HealthStatus::Degraded;
}
if self.db_pool_utilization_percent > dec!(80) {
return HealthStatus::Warning;
}
HealthStatus::Healthy
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum HealthStatus {
Healthy,
Warning,
Degraded,
Critical,
}
impl fmt::Display for HealthStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HealthStatus::Healthy => write!(f, "healthy"),
HealthStatus::Warning => write!(f, "warning"),
HealthStatus::Degraded => write!(f, "degraded"),
HealthStatus::Critical => write!(f, "critical"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlatformLeaderboardEntry {
pub rank: i32,
pub user_id: Uuid,
pub username: String,
pub display_name: Option<String>,
pub score: Decimal,
pub secondary_score: Option<Decimal>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum LeaderboardType {
TopTraders,
TopIssuers,
TopReputation,
MostActive,
}
impl fmt::Display for LeaderboardType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LeaderboardType::TopTraders => write!(f, "Top Traders"),
LeaderboardType::TopIssuers => write!(f, "Top Issuers"),
LeaderboardType::TopReputation => write!(f, "Top Reputation"),
LeaderboardType::MostActive => write!(f, "Most Active"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_platform_metrics_calculations() {
let metrics = PlatformMetrics {
metrics_id: Uuid::new_v4(),
total_users: 100,
active_users_30d: 60,
new_users_7d: 10,
total_tokens: 50,
active_tokens: 45,
total_orders: 200,
total_trades: 180,
total_volume_btc: dec!(10),
volume_24h_btc: dec!(1),
volume_7d_btc: dec!(5),
total_fees_btc: dec!(0.1),
kyc_verified_users: 30,
kyc_pending: 5,
avg_reputation_score: Some(dec!(65)),
total_commitments: 20,
fulfilled_commitments: 15,
total_liquidity_pools: 10,
total_value_locked_btc: dec!(50),
snapshot_at: Utc::now(),
created_at: Utc::now(),
};
assert_eq!(metrics.user_growth_rate_percent(), dec!(10));
assert_eq!(metrics.active_user_ratio_percent(), dec!(60));
assert_eq!(metrics.commitment_fulfillment_rate_percent(), dec!(75));
assert_eq!(metrics.avg_order_size_btc(), dec!(0.05));
assert_eq!(metrics.kyc_completion_rate_percent(), dec!(30));
}
#[test]
fn test_token_metrics() {
let metrics = TokenMetrics {
token_id: Uuid::new_v4(),
symbol: "$TEST".to_string(),
holder_count: 50,
trade_count: 200,
volume_btc: dec!(5),
market_cap_btc: dec!(10),
price_change_24h_percent: dec!(5),
price_change_7d_percent: dec!(15),
ath_price_btc: dec!(0.002),
ath_date: Utc::now(),
current_price_btc: dec!(0.0015),
liquidity_score: dec!(75),
};
assert_eq!(metrics.velocity(), dec!(4));
assert_eq!(metrics.distance_from_ath_percent(), dec!(25));
}
#[test]
fn test_user_activity_metrics() {
let metrics = UserActivityMetrics {
user_id: Uuid::new_v4(),
tokens_created: 2,
orders_placed: 50,
trades_executed: 45,
total_volume_btc: dec!(1),
total_fees_paid_btc: dec!(0.01),
tokens_traded_count: 10,
days_active: 30,
last_activity_at: Utc::now() - chrono::Duration::days(5),
account_age_days: 60,
};
assert!(metrics.is_active());
assert_eq!(metrics.avg_daily_volume_btc(), dec!(1) / dec!(30));
let score = metrics.engagement_score();
assert!(score > dec!(0) && score <= dec!(100));
}
#[test]
fn test_system_health() {
let healthy_metrics = SystemHealthMetrics {
uptime_24h_percent: dec!(99.9),
avg_response_time_ms: 50.0,
error_rate_percent: dec!(0.1),
db_connections_active: 10,
db_pool_utilization_percent: dec!(50),
btc_node_synced: true,
btc_node_block_height: 800000,
pending_payments: 5,
pending_kyc_reviews: 2,
checked_at: Utc::now(),
};
assert!(healthy_metrics.is_healthy());
assert_eq!(healthy_metrics.health_status(), HealthStatus::Healthy);
let critical_metrics = SystemHealthMetrics {
uptime_24h_percent: dec!(90),
avg_response_time_ms: 500.0,
error_rate_percent: dec!(10),
db_connections_active: 50,
db_pool_utilization_percent: dec!(95),
btc_node_synced: false,
btc_node_block_height: 800000,
pending_payments: 100,
pending_kyc_reviews: 50,
checked_at: Utc::now(),
};
assert!(!critical_metrics.is_healthy());
assert_eq!(critical_metrics.health_status(), HealthStatus::Critical);
}
}