use std::collections::HashMap;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RulesFingerprint {
pub balance_rules: Vec<BalanceRule>,
pub approval_thresholds: Vec<ApprovalThreshold>,
pub temporal_rules: Vec<TemporalRule>,
pub range_constraints: Vec<RangeConstraint>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub custom_rules: Vec<CustomRule>,
pub compliance_stats: HashMap<String, RuleComplianceStats>,
}
impl RulesFingerprint {
pub fn new() -> Self {
Self {
balance_rules: Vec::new(),
approval_thresholds: Vec::new(),
temporal_rules: Vec::new(),
range_constraints: Vec::new(),
custom_rules: Vec::new(),
compliance_stats: HashMap::new(),
}
}
pub fn add_balance_rule(&mut self, rule: BalanceRule) {
self.balance_rules.push(rule);
}
pub fn add_approval_threshold(&mut self, threshold: ApprovalThreshold) {
self.approval_thresholds.push(threshold);
}
pub fn add_compliance(&mut self, rule_name: impl Into<String>, stats: RuleComplianceStats) {
self.compliance_stats.insert(rule_name.into(), stats);
}
}
impl Default for RulesFingerprint {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceRule {
pub name: String,
pub description: String,
pub table: String,
pub group_by: Vec<String>,
pub left_side: BalanceExpression,
pub right_side: BalanceExpression,
pub tolerance: BalanceTolerance,
pub compliance_rate: f64,
}
impl BalanceRule {
pub fn new(
name: impl Into<String>,
table: impl Into<String>,
left_side: BalanceExpression,
right_side: BalanceExpression,
) -> Self {
Self {
name: name.into(),
description: String::new(),
table: table.into(),
group_by: Vec::new(),
left_side,
right_side,
tolerance: BalanceTolerance::Absolute(Decimal::ZERO),
compliance_rate: 1.0,
}
}
pub fn with_group_by(mut self, columns: Vec<String>) -> Self {
self.group_by = columns;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BalanceExpression {
Sum { column: String },
SumWhere {
column: String,
filter: FilterCondition,
},
Count,
CountWhere { filter: FilterCondition },
Constant { value: Decimal },
Difference {
left: Box<BalanceExpression>,
right: Box<BalanceExpression>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterCondition {
pub column: String,
pub operator: FilterOperator,
pub value: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilterOperator {
Equals,
NotEquals,
GreaterThan,
LessThan,
GreaterOrEqual,
LessOrEqual,
In,
NotIn,
IsNull,
IsNotNull,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BalanceTolerance {
Absolute(Decimal),
Relative(f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApprovalThreshold {
pub name: String,
pub description: String,
pub thresholds: Vec<ThresholdLevel>,
pub compliance_rate: f64,
pub level_distribution: Vec<f64>,
}
impl ApprovalThreshold {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
thresholds: Vec::new(),
compliance_rate: 1.0,
level_distribution: Vec::new(),
}
}
pub fn add_level(&mut self, level: ThresholdLevel) {
self.thresholds.push(level);
self.thresholds.sort_by_key(|a| a.amount);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThresholdLevel {
pub amount: Decimal,
pub approval_level: String,
pub proportion: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalRule {
pub name: String,
pub description: String,
pub before_column: String,
pub after_column: String,
pub tables: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub join_condition: Option<String>,
pub compliance_rate: f64,
pub gap_stats: Option<GapStatistics>,
}
impl TemporalRule {
pub fn new(
name: impl Into<String>,
before_column: impl Into<String>,
after_column: impl Into<String>,
) -> Self {
Self {
name: name.into(),
description: String::new(),
before_column: before_column.into(),
after_column: after_column.into(),
tables: Vec::new(),
join_condition: None,
compliance_rate: 1.0,
gap_stats: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GapStatistics {
pub min_days: f64,
pub max_days: f64,
pub mean_days: f64,
pub median_days: f64,
pub std_dev_days: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RangeConstraint {
pub name: String,
pub table: String,
pub column: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_value: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_value: Option<f64>,
pub compliance_rate: f64,
}
impl RangeConstraint {
pub fn new(
name: impl Into<String>,
table: impl Into<String>,
column: impl Into<String>,
) -> Self {
Self {
name: name.into(),
table: table.into(),
column: column.into(),
min_value: None,
max_value: None,
compliance_rate: 1.0,
}
}
pub fn with_min(mut self, min: f64) -> Self {
self.min_value = Some(min);
self
}
pub fn with_max(mut self, max: f64) -> Self {
self.max_value = Some(max);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomRule {
pub name: String,
pub description: String,
pub category: String,
pub tables: Vec<String>,
pub columns: Vec<String>,
pub expression: String,
pub compliance_rate: f64,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub parameters: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleComplianceStats {
pub total_checked: u64,
pub passed: u64,
pub failed: u64,
pub compliance_rate: f64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub failure_patterns: Vec<FailurePattern>,
}
impl RuleComplianceStats {
pub fn from_counts(total: u64, passed: u64) -> Self {
let failed = total.saturating_sub(passed);
let compliance_rate = if total > 0 {
passed as f64 / total as f64
} else {
1.0
};
Self {
total_checked: total,
passed,
failed,
compliance_rate,
failure_patterns: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FailurePattern {
pub description: String,
pub count: u64,
pub proportion: f64,
}