use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use super::metrics::SlaMetricType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ViolationSeverity {
Warning,
Critical,
}
impl std::fmt::Display for ViolationSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Warning => write!(f, "warning"),
Self::Critical => write!(f, "critical"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlaViolation {
pub metric: SlaMetricType,
pub actual: Decimal,
pub required: Decimal,
pub severity: ViolationSeverity,
pub penalty_amount: Decimal,
}
#[must_use]
pub fn determine_severity(
metric: SlaMetricType,
actual: Decimal,
required: Decimal,
) -> ViolationSeverity {
if required.is_zero() {
return ViolationSeverity::Critical;
}
let ratio = match metric {
SlaMetricType::ResponseTimeMs => {
if actual.is_zero() {
return ViolationSeverity::Warning;
}
required / actual
}
_ => actual / required,
};
if ratio > dec!(0.8) { ViolationSeverity::Warning } else { ViolationSeverity::Critical }
}
#[must_use]
pub fn compute_penalty_amount(avg_transaction_value: Decimal, penalty_percent: Decimal) -> Decimal {
(avg_transaction_value * penalty_percent / dec!(100)).round_dp(2)
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn severity_display() {
assert_eq!(ViolationSeverity::Warning.to_string(), "warning");
assert_eq!(ViolationSeverity::Critical.to_string(), "critical");
}
#[test]
fn response_time_slight_violation_is_warning() {
let severity = determine_severity(SlaMetricType::ResponseTimeMs, dec!(600), dec!(500));
assert_eq!(severity, ViolationSeverity::Warning);
}
#[test]
fn response_time_severe_violation_is_critical() {
let severity = determine_severity(SlaMetricType::ResponseTimeMs, dec!(1000), dec!(500));
assert_eq!(severity, ViolationSeverity::Critical);
}
#[test]
fn response_time_zero_actual_is_warning() {
let severity = determine_severity(SlaMetricType::ResponseTimeMs, Decimal::ZERO, dec!(500));
assert_eq!(severity, ViolationSeverity::Warning);
}
#[test]
fn uptime_slight_violation_is_warning() {
let severity = determine_severity(SlaMetricType::UptimePercent, dec!(95), dec!(99));
assert_eq!(severity, ViolationSeverity::Warning);
}
#[test]
fn uptime_severe_violation_is_critical() {
let severity = determine_severity(SlaMetricType::UptimePercent, dec!(50), dec!(99));
assert_eq!(severity, ViolationSeverity::Critical);
}
#[test]
fn quality_slight_violation_is_warning() {
let severity = determine_severity(SlaMetricType::QualityMinScore, dec!(3.5), dec!(4.0));
assert_eq!(severity, ViolationSeverity::Warning);
}
#[test]
fn quality_severe_violation_is_critical() {
let severity = determine_severity(SlaMetricType::QualityMinScore, dec!(2.0), dec!(4.0));
assert_eq!(severity, ViolationSeverity::Critical);
}
#[test]
fn throughput_violation_warning() {
let severity = determine_severity(SlaMetricType::ThroughputRps, dec!(85), dec!(100));
assert_eq!(severity, ViolationSeverity::Warning);
}
#[test]
fn throughput_violation_critical() {
let severity = determine_severity(SlaMetricType::ThroughputRps, dec!(60), dec!(100));
assert_eq!(severity, ViolationSeverity::Critical);
}
#[test]
fn zero_required_is_critical() {
let severity = determine_severity(SlaMetricType::UptimePercent, dec!(50), Decimal::ZERO);
assert_eq!(severity, ViolationSeverity::Critical);
}
#[test]
fn penalty_5_percent_of_100() {
assert_eq!(compute_penalty_amount(dec!(100), dec!(5)), dec!(5));
}
#[test]
fn penalty_10_percent_of_250() {
assert_eq!(compute_penalty_amount(dec!(250), dec!(10)), dec!(25));
}
#[test]
fn penalty_rounds_to_2dp() {
assert_eq!(compute_penalty_amount(dec!(33.33), dec!(7)), dec!(2.33));
}
#[test]
fn penalty_zero_value() {
assert_eq!(compute_penalty_amount(Decimal::ZERO, dec!(5)), Decimal::ZERO);
}
#[test]
fn penalty_zero_percent() {
assert_eq!(compute_penalty_amount(dec!(100), Decimal::ZERO), Decimal::ZERO);
}
}