use governor_core::domain::version::{BreakingChange, Feature, Fix};
#[derive(Debug, Clone, serde::Serialize)]
pub struct RiskAssessment {
pub level: String,
pub score: u8,
pub factors: Vec<RiskFactor>,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct RiskFactor {
pub name: String,
pub impact: String,
pub weight: f64,
}
#[must_use]
pub fn calculate_risk_assessment(
breaking: &[BreakingChange],
features: &[Feature],
fixes: &[Fix],
) -> RiskAssessment {
let mut score: u8 = 0;
let mut factors = Vec::new();
let mut recommendations = Vec::new();
if !breaking.is_empty() {
let breaking_count = u32::try_from(breaking.len()).unwrap_or(u32::MAX);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let breaking_weight = (f64::from(breaking_count).min(5.0) * 15.0) as u8;
score = score.saturating_add(breaking_weight);
factors.push(RiskFactor {
name: "breaking_changes".to_string(),
impact: "high".to_string(),
weight: 0.4,
});
recommendations.push("Add migration guide for breaking changes".to_string());
recommendations.push("Consider beta release for community testing".to_string());
}
if features.len() > 5 {
score = score.saturating_add(10);
factors.push(RiskFactor {
name: "many_features".to_string(),
impact: "medium".to_string(),
weight: 0.2,
});
}
if fixes.len() > 10 {
score = score.saturating_add(15);
factors.push(RiskFactor {
name: "many_fixes".to_string(),
impact: "medium".to_string(),
weight: 0.3,
});
recommendations.push("Review test coverage for fixed issues".to_string());
}
let level = if score >= 70 {
"high"
} else if score >= 40 {
"medium"
} else {
"low"
};
RiskAssessment {
level: level.to_string(),
score: score.min(100),
factors,
recommendations,
}
}