use crate::common::risk::RiskLevel;
use crate::error::Result;
#[derive(Debug, Clone)]
pub struct ParetoResult {
pub dataset_name: String,
pub numbers_analyzed: usize,
pub pareto_ratio: f64, pub concentration_index: f64, pub top_20_percent_share: f64, pub cumulative_distribution: Vec<(f64, f64)>, pub custom_percentiles: Option<Vec<(f64, f64)>>, pub risk_level: RiskLevel,
}
impl ParetoResult {
pub fn new(dataset_name: String, numbers: &[f64]) -> Result<Self> {
if numbers.is_empty() {
return Err(crate::error::BenfError::NoNumbersFound);
}
if numbers.len() < 5 {
return Err(crate::error::BenfError::InsufficientData(numbers.len()));
}
let mut sorted_numbers: Vec<f64> = numbers.to_vec();
sorted_numbers.sort_by(|a, b| b.partial_cmp(a).unwrap());
let total_sum: f64 = sorted_numbers.iter().sum();
let top_20_percent_count = ((sorted_numbers.len() as f64) * 0.2).ceil() as usize;
let top_20_percent_sum: f64 = sorted_numbers.iter().take(top_20_percent_count).sum();
let top_20_percent_share = (top_20_percent_sum / total_sum) * 100.0;
let concentration_index = calculate_gini_coefficient(&sorted_numbers);
let cumulative_distribution = generate_lorenz_curve(&sorted_numbers);
let pareto_ratio = top_20_percent_share / 80.0;
let risk_level = evaluate_pareto_risk(pareto_ratio, concentration_index);
Ok(ParetoResult {
dataset_name,
numbers_analyzed: numbers.len(),
pareto_ratio,
concentration_index,
top_20_percent_share,
cumulative_distribution,
custom_percentiles: None,
risk_level,
})
}
pub fn with_custom_percentiles(mut self, percentiles: &[f64], numbers: &[f64]) -> Self {
let mut sorted_numbers: Vec<f64> = numbers.to_vec();
sorted_numbers.sort_by(|a, b| b.partial_cmp(a).unwrap());
let total_sum: f64 = sorted_numbers.iter().sum();
let custom_percentiles = percentiles
.iter()
.map(|&p| {
let top_percent_count =
((sorted_numbers.len() as f64) * (p / 100.0)).ceil() as usize;
let top_percent_sum: f64 = sorted_numbers.iter().take(top_percent_count).sum();
let top_percent_share = (top_percent_sum / total_sum) * 100.0;
(p, top_percent_share)
})
.collect();
self.custom_percentiles = Some(custom_percentiles);
self
}
}
fn calculate_gini_coefficient(sorted_numbers: &[f64]) -> f64 {
let n = sorted_numbers.len() as f64;
let sum: f64 = sorted_numbers.iter().sum();
if sum == 0.0 {
return 0.0;
}
let mut gini_sum = 0.0;
for (i, &value) in sorted_numbers.iter().enumerate() {
gini_sum += (2.0 * (i as f64 + 1.0) - n - 1.0) * value;
}
gini_sum / (n * sum)
}
fn generate_lorenz_curve(sorted_numbers: &[f64]) -> Vec<(f64, f64)> {
let total_sum: f64 = sorted_numbers.iter().sum();
#[allow(unused_assignments)]
let mut cumulative_population = 0.0;
let mut cumulative_wealth = 0.0;
let mut curve_points = Vec::new();
curve_points.push((0.0, 0.0));
for (i, &value) in sorted_numbers.iter().enumerate() {
cumulative_population = ((i + 1) as f64) / (sorted_numbers.len() as f64);
cumulative_wealth += value / total_sum;
curve_points.push((cumulative_population, cumulative_wealth));
}
curve_points
}
fn evaluate_pareto_risk(pareto_ratio: f64, gini_coefficient: f64) -> RiskLevel {
let pareto_deviation = (pareto_ratio - 1.0).abs();
match (pareto_deviation, gini_coefficient) {
(dev, gini) if dev <= 0.1 && gini <= 0.4 => RiskLevel::Low, (dev, gini) if dev <= 0.2 && gini <= 0.6 => RiskLevel::Medium, (dev, gini) if dev <= 0.4 && gini <= 0.8 => RiskLevel::High, _ => RiskLevel::Critical, }
}