use crate::audit::normalized::NormalizedFinding;
use crate::taxonomy::Severity;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
Low,
Medium,
High,
Critical,
}
impl Priority {
pub fn label(&self, en: bool) -> &'static str {
match self {
Priority::Critical => {
if en {
"Critical"
} else {
"Kritisch"
}
}
Priority::High => {
if en {
"High"
} else {
"Hoch"
}
}
Priority::Medium => {
if en {
"Medium"
} else {
"Mittel"
}
}
Priority::Low => {
if en {
"Low"
} else {
"Niedrig"
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Effort {
Quick,
Medium,
Structural,
}
impl Effort {
pub fn at_least_for_complexity(self, complexity: &str) -> Effort {
let floor = match complexity {
"high" => Effort::Structural,
"medium" => Effort::Medium,
_ => Effort::Quick,
};
self.max(floor)
}
pub fn label(&self, en: bool) -> &'static str {
match self {
Effort::Quick => {
if en {
"Low complexity"
} else {
"Geringe Komplexität"
}
}
Effort::Medium => {
if en {
"Medium effort"
} else {
"Mittlerer Aufwand"
}
}
Effort::Structural => {
if en {
"High complexity"
} else {
"Hohe Komplexität"
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ExecutionPriority {
Optional,
Important,
Immediate,
}
impl ExecutionPriority {
pub fn label(&self, en: bool) -> &'static str {
match self {
ExecutionPriority::Immediate => {
if en {
"Urgent"
} else {
"Dringend"
}
}
ExecutionPriority::Important => {
if en {
"High"
} else {
"Hoch"
}
}
ExecutionPriority::Optional => "Standard",
}
}
}
pub fn severity_to_priority(severity: Severity) -> Priority {
match severity {
Severity::Critical => Priority::Critical,
Severity::High => Priority::High,
Severity::Medium => Priority::Medium,
Severity::Low => Priority::Low,
}
}
pub fn score_to_priority(score: f32) -> Priority {
if score < 50.0 {
Priority::Critical
} else if score < 70.0 {
Priority::High
} else if score < 85.0 {
Priority::Medium
} else {
Priority::Low
}
}
pub fn derive_execution_priority(
severity: Severity,
effort: Effort,
dimension: &str,
) -> ExecutionPriority {
match (severity, effort, dimension) {
(Severity::Critical, _, _) => ExecutionPriority::Immediate,
(Severity::High, _, "Accessibility") => ExecutionPriority::Immediate,
(Severity::High, Effort::Quick, _) => ExecutionPriority::Important,
(Severity::High, _, _) => ExecutionPriority::Important,
(Severity::Medium, Effort::Quick, _) => ExecutionPriority::Important,
_ => ExecutionPriority::Optional,
}
}
pub fn is_mandatory(category: &str, wcag_level: &str) -> bool {
category == "wcag" && matches!(wcag_level, "A" | "AA")
}
pub fn action_order(a: &NormalizedFinding, b: &NormalizedFinding) -> std::cmp::Ordering {
b.severity
.cmp(&a.severity)
.then_with(|| {
is_mandatory(&b.category, &b.wcag_level).cmp(&is_mandatory(&a.category, &a.wcag_level))
})
.then_with(|| b.priority_score.total_cmp(&a.priority_score))
.then_with(|| b.occurrence_count.cmp(&a.occurrence_count))
.then_with(|| a.rule_id.cmp(&b.rule_id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn score_bands_map_to_priority() {
assert_eq!(score_to_priority(40.0), Priority::Critical);
assert_eq!(score_to_priority(60.0), Priority::High);
assert_eq!(score_to_priority(80.0), Priority::Medium);
assert_eq!(score_to_priority(95.0), Priority::Low);
}
#[test]
fn severity_maps_to_priority() {
assert_eq!(severity_to_priority(Severity::Critical), Priority::Critical);
assert_eq!(severity_to_priority(Severity::Low), Priority::Low);
}
#[test]
fn accessibility_high_is_immediate_even_when_structural() {
assert_eq!(
derive_execution_priority(Severity::High, Effort::Structural, "Accessibility"),
ExecutionPriority::Immediate
);
assert_eq!(
derive_execution_priority(Severity::High, Effort::Structural, "SEO"),
ExecutionPriority::Important
);
assert_eq!(
derive_execution_priority(Severity::Low, Effort::Medium, "SEO"),
ExecutionPriority::Optional
);
}
}