use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivacyAudit {
pub epsilon_budget: f64,
pub total_epsilon_spent: f64,
pub k_anonymity: u32,
pub actions: Vec<PrivacyAction>,
pub summary: PrivacySummary,
pub created_at: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<PrivacyWarning>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub composition_method: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rdp_alpha_effective: Option<f64>,
}
impl PrivacyAudit {
pub fn new(epsilon_budget: f64, k_anonymity: u32) -> Self {
Self {
epsilon_budget,
total_epsilon_spent: 0.0,
k_anonymity,
actions: Vec::new(),
summary: PrivacySummary::default(),
created_at: Utc::now(),
warnings: Vec::new(),
composition_method: None,
rdp_alpha_effective: None,
}
}
pub fn record_action(&mut self, action: PrivacyAction) {
if let Some(epsilon) = action.epsilon_spent {
self.total_epsilon_spent += epsilon;
}
match action.action_type {
PrivacyActionType::LaplaceNoise => self.summary.noise_additions += 1,
PrivacyActionType::Suppression => self.summary.suppressions += 1,
PrivacyActionType::Generalization => self.summary.generalizations += 1,
PrivacyActionType::Winsorization => self.summary.winsorizations += 1,
PrivacyActionType::Binning => self.summary.binnings += 1,
PrivacyActionType::Rounding => self.summary.roundings += 1,
}
self.actions.push(action);
}
pub fn is_budget_exhausted(&self) -> bool {
self.total_epsilon_spent >= self.epsilon_budget
}
pub fn remaining_budget(&self) -> f64 {
(self.epsilon_budget - self.total_epsilon_spent).max(0.0)
}
pub fn add_warning(&mut self, warning: PrivacyWarning) {
self.warnings.push(warning);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivacyAction {
pub action_type: PrivacyActionType,
pub target: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub epsilon_spent: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub original_value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resulting_value: Option<String>,
pub reason: String,
pub timestamp: DateTime<Utc>,
}
impl PrivacyAction {
pub fn new(
action_type: PrivacyActionType,
target: impl Into<String>,
description: impl Into<String>,
reason: impl Into<String>,
) -> Self {
Self {
action_type,
target: target.into(),
description: description.into(),
epsilon_spent: None,
original_value: None,
resulting_value: None,
reason: reason.into(),
timestamp: Utc::now(),
}
}
pub fn with_epsilon(mut self, epsilon: f64) -> Self {
self.epsilon_spent = Some(epsilon);
self
}
pub fn with_values(
mut self,
original: impl Into<String>,
resulting: impl Into<String>,
) -> Self {
self.original_value = Some(original.into());
self.resulting_value = Some(resulting.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PrivacyActionType {
LaplaceNoise,
Suppression,
Generalization,
Winsorization,
Binning,
Rounding,
}
impl std::fmt::Display for PrivacyActionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LaplaceNoise => write!(f, "Laplace Noise"),
Self::Suppression => write!(f, "Suppression"),
Self::Generalization => write!(f, "Generalization"),
Self::Winsorization => write!(f, "Winsorization"),
Self::Binning => write!(f, "Binning"),
Self::Rounding => write!(f, "Rounding"),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PrivacySummary {
pub noise_additions: u64,
pub suppressions: u64,
pub generalizations: u64,
pub winsorizations: u64,
pub binnings: u64,
pub roundings: u64,
pub columns_processed: u64,
pub columns_modified: u64,
pub categorical_values_suppressed: u64,
pub rows_affected: u64,
}
impl PrivacySummary {
pub fn total_actions(&self) -> u64 {
self.noise_additions
+ self.suppressions
+ self.generalizations
+ self.winsorizations
+ self.binnings
+ self.roundings
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivacyWarning {
pub level: WarningLevel,
pub message: String,
pub target: Option<String>,
pub recommendation: Option<String>,
pub timestamp: DateTime<Utc>,
}
impl PrivacyWarning {
pub fn new(level: WarningLevel, message: impl Into<String>) -> Self {
Self {
level,
message: message.into(),
target: None,
recommendation: None,
timestamp: Utc::now(),
}
}
pub fn with_target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}
pub fn with_recommendation(mut self, recommendation: impl Into<String>) -> Self {
self.recommendation = Some(recommendation.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WarningLevel {
Info,
Warning,
Serious,
Critical,
}