use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnomalyFingerprint {
pub overall: AnomalyOverview,
pub profiles: Vec<AnomalyProfile>,
pub temporal_patterns: TemporalAnomalyPatterns,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub entity_patterns: Option<EntityAnomalyPatterns>,
#[serde(skip_serializing_if = "Option::is_none")]
pub clustering: Option<AnomalyClustering>,
}
impl AnomalyFingerprint {
pub fn new(overall: AnomalyOverview) -> Self {
Self {
overall,
profiles: Vec::new(),
temporal_patterns: TemporalAnomalyPatterns::default(),
entity_patterns: None,
clustering: None,
}
}
pub fn add_profile(&mut self, profile: AnomalyProfile) {
self.profiles.push(profile);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnomalyOverview {
pub total_records: u64,
pub total_anomalies: u64,
pub anomaly_rate: f64,
pub category_distribution: HashMap<String, f64>,
pub type_count: usize,
pub has_labels: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub label_field: Option<String>,
}
impl AnomalyOverview {
pub fn new(total_records: u64, total_anomalies: u64) -> Self {
let anomaly_rate = if total_records > 0 {
total_anomalies as f64 / total_records as f64
} else {
0.0
};
Self {
total_records,
total_anomalies,
anomaly_rate,
category_distribution: HashMap::new(),
type_count: 0,
has_labels: false,
label_field: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnomalyProfile {
pub anomaly_type: String,
pub name: String,
pub category: AnomalyCategory,
pub rate: f64,
pub count: u64,
pub severity: u8,
pub characteristics: AnomalyCharacteristics,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub detection_features: Vec<String>,
}
impl AnomalyProfile {
pub fn new(
anomaly_type: impl Into<String>,
name: impl Into<String>,
category: AnomalyCategory,
rate: f64,
) -> Self {
Self {
anomaly_type: anomaly_type.into(),
name: name.into(),
category,
rate,
count: 0,
severity: 3,
characteristics: AnomalyCharacteristics::default(),
detection_features: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnomalyCategory {
Fraud,
Error,
ProcessIssue,
Statistical,
Relational,
}
impl std::fmt::Display for AnomalyCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fraud => write!(f, "fraud"),
Self::Error => write!(f, "error"),
Self::ProcessIssue => write!(f, "process_issue"),
Self::Statistical => write!(f, "statistical"),
Self::Relational => write!(f, "relational"),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AnomalyCharacteristics {
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<AmountCharacteristics>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timing: Option<TimingCharacteristics>,
#[serde(skip_serializing_if = "Option::is_none")]
pub entity: Option<EntityCharacteristics>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub properties: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AmountCharacteristics {
pub threshold_adjacent: bool,
pub typical_range: Option<(f64, f64)>,
pub round_amounts: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount_distribution: Option<AmountDistribution>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AmountDistribution {
pub mean: f64,
pub std_dev: f64,
pub median: f64,
pub p95: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimingCharacteristics {
pub period_end_spike: bool,
pub off_hours: bool,
pub day_of_week_weights: [f64; 7],
pub month_weights: [f64; 12],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityCharacteristics {
pub repeat_offender_rate: f64,
pub volume_correlation: f64,
pub anomalies_per_entity: f64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TemporalAnomalyPatterns {
pub year_end_multiplier: f64,
pub quarter_end_multiplier: f64,
pub month_end_multiplier: f64,
pub monthly_rates: Vec<MonthlyRate>,
pub trend: i8,
pub seasonality_strength: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonthlyRate {
pub period: String,
pub rate: f64,
pub count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityAnomalyPatterns {
pub affected_entity_rate: f64,
pub anomalies_per_entity_distribution: Vec<(u32, f64)>,
pub high_risk_entity_types: Vec<EntityRisk>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityRisk {
pub entity_type: String,
pub relative_risk: f64,
pub entity_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnomalyClustering {
pub temporal_clustering: bool,
pub typical_cluster_size: f64,
pub cluster_window_days: f64,
pub entity_clustering: bool,
pub type_co_occurrence: HashMap<String, Vec<String>>,
}