use crate::audit::occurrence_analysis::{
build_location_hints, build_pattern_clusters, build_representative_occurrences,
};
use crate::audit::prioritization::{derive_execution_priority, severity_to_priority};
use crate::i18n::I18n;
use crate::output::explanations::get_explanation;
use crate::output::report_model::{classify_criticality_tier, Effort, FindingGroup, Role};
use super::super::actions::{build_narrative_arc, derive_business_impact, localized_finding_text};
pub(super) fn finding_group_from_normalized(
i18n: &I18n,
f: &crate::audit::normalized::NormalizedFinding,
) -> FindingGroup {
let locale = i18n.locale();
let explanation = get_explanation(&f.rule_id).or_else(|| get_explanation(&f.wcag_criterion));
let (
title,
customer_desc,
user_impact_text,
business_impact,
typical_cause,
recommendation,
technical_note,
role,
effort,
execution_priority,
) = if let Some(expl) = explanation {
(
expl.customer_title_for(locale).to_string(),
expl.customer_description_for(locale).to_string(),
expl.user_impact_for(locale).to_string(),
derive_business_impact(
i18n,
expl.user_impact_for(locale),
f.dimension.as_str(),
f.severity,
Some(f.subcategory_kind.label(false)),
f.occurrence_count,
),
expl.typical_cause_for(locale).to_string(),
expl.recommendation_for(locale).to_string(),
expl.technical_note_for(locale).to_string(),
expl.responsible_role,
expl.effort_estimate,
derive_execution_priority(f.severity, expl.effort_estimate, f.dimension.as_str()),
)
} else {
let (title_loc, user_impact_loc, _technical_loc) = localized_finding_text(locale, f);
(
title_loc,
f.description.clone(),
user_impact_loc.clone(),
derive_business_impact(
i18n,
&user_impact_loc,
f.dimension.as_str(),
f.severity,
Some(f.subcategory_kind.label(false)),
f.occurrence_count,
),
String::new(),
f.occurrences
.first()
.and_then(|o| o.fix_suggestion.clone())
.unwrap_or_default(),
String::new(),
Role::Development,
Effort::Medium,
derive_execution_priority(f.severity, Effort::Medium, f.dimension.as_str()),
)
};
let examples = explanation.map(|e| e.examples()).unwrap_or_default();
let location_hints = build_location_hints(&f.occurrences);
let representative_occurrences = build_representative_occurrences(&f.occurrences);
let pattern_clusters = build_pattern_clusters(&f.occurrences);
let additional_occurrences = f
.occurrence_count
.saturating_sub(representative_occurrences.len());
let narrative = build_narrative_arc(
i18n,
f.occurrence_count,
f.severity,
f.dimension.as_str(),
&customer_desc,
&user_impact_text,
&business_impact,
&typical_cause,
&recommendation,
effort,
role,
);
FindingGroup {
title,
rule_id: f.rule_id.clone(),
wcag_criterion: f.wcag_criterion.clone(),
wcag_level: f.wcag_level.clone(),
help_url: f.help_url.clone(),
dimension: Some(f.dimension.clone()),
subcategory: Some(f.subcategory.clone()),
issue_class: Some(f.issue_class.clone()),
severity: f.severity,
priority: severity_to_priority(f.severity),
customer_description: customer_desc,
user_impact: user_impact_text,
business_impact,
typical_cause,
recommendation,
technical_note,
confidence: f.confidence.clone(),
false_positive_risk: f.false_positive_risk.clone(),
verification: f.verification.clone(),
complexity: f.complexity.clone(),
complexity_reason: f.complexity_reason.clone(),
expected_impact: f.expected_impact.clone(),
bfsg_relevance: f.bfsg_relevance.clone(),
remediation_priority: f.remediation_priority.clone(),
occurrence_count: f.occurrence_count,
affected_urls: Vec::new(),
affected_elements: f.occurrence_count,
additional_occurrences,
pattern_clusters,
location_hints,
representative_occurrences,
responsible_role: role,
effort,
execution_priority,
examples,
structural_cause: if f.occurrence_count >= 10 {
Some(i18n.t_args(
"finding-structural-cause-component",
&[("count", f.occurrence_count.to_string())],
))
} else if f.occurrence_count >= 5 {
Some(i18n.t_args(
"finding-structural-cause-shared",
&[("count", f.occurrence_count.to_string())],
))
} else {
None
},
is_component_issue: f.occurrence_count >= 10,
criticality_tier: classify_criticality_tier(&f.category, &f.wcag_level),
narrative,
}
}