use super::html_escape; use compliance_core::models::dast::DastFinding; pub(super) fn executive_summary( findings: &[DastFinding], target_name: &str, target_url: &str, tool_count: usize, tool_invocations: u32, success_rate: f64, ) -> String { let critical = findings .iter() .filter(|f| f.severity.to_string() == "critical") .count(); let high = findings .iter() .filter(|f| f.severity.to_string() == "high") .count(); let medium = findings .iter() .filter(|f| f.severity.to_string() == "medium") .count(); let low = findings .iter() .filter(|f| f.severity.to_string() == "low") .count(); let info = findings .iter() .filter(|f| f.severity.to_string() == "info") .count(); let exploitable = findings.iter().filter(|f| f.exploitable).count(); let total = findings.len(); let overall_risk = if critical > 0 { "CRITICAL" } else if high > 0 { "HIGH" } else if medium > 0 { "MEDIUM" } else if low > 0 { "LOW" } else { "INFORMATIONAL" }; let risk_color = match overall_risk { "CRITICAL" => "#991b1b", "HIGH" => "#c2410c", "MEDIUM" => "#a16207", "LOW" => "#1d4ed8", _ => "#4b5563", }; let risk_score: usize = std::cmp::min(100, critical * 25 + high * 15 + medium * 8 + low * 3 + info); let severity_bar = build_severity_bar(critical, high, medium, low, info, total); // Table of contents finding sub-entries let severity_order = ["critical", "high", "medium", "low", "info"]; let toc_findings_sub = if !findings.is_empty() { let mut sub = String::new(); let mut fnum = 0usize; for &sev_key in severity_order.iter() { let count = findings .iter() .filter(|f| f.severity.to_string() == sev_key) .count(); if count == 0 { continue; } for f in findings .iter() .filter(|f| f.severity.to_string() == sev_key) { fnum += 1; sub.push_str(&format!( r#"
F-{:03} — {}
"#, fnum, html_escape(&f.title), )); } } sub } else { String::new() }; let critical_high_str = format!("{} / {}", critical, high); let escaped_target_name = html_escape(target_name); let escaped_target_url = html_escape(target_url); format!( r##"

Table of Contents

1Executive Summary
2Scope & Methodology
3Findings ({total_findings})
{toc_findings_sub}
4Attack Chain Timeline
5Appendix

1. Executive Summary

{risk_score} / 100
Overall Risk: {overall_risk}
Based on {total_findings} finding{findings_plural} identified across the target application.
{total_findings}
Total Findings
{critical_high}
Critical / High
{exploitable_count}
Exploitable
{tool_count}
Tools Used

Severity Distribution

{severity_bar}

This report presents the results of an automated penetration test conducted against {target_name} ({target_url}) using the Compliance Scanner AI-powered testing engine. A total of {total_findings} vulnerabilities were identified, of which {exploitable_count} were confirmed exploitable with working proof-of-concept payloads. The assessment employed {tool_count} security tools across {tool_invocations} invocations ({success_rate:.0}% success rate).

"##, total_findings = total, findings_plural = if total == 1 { "" } else { "s" }, critical_high = critical_high_str, exploitable_count = exploitable, target_name = escaped_target_name, target_url = escaped_target_url, ) } fn build_severity_bar( critical: usize, high: usize, medium: usize, low: usize, info: usize, total: usize, ) -> String { if total == 0 { return String::new(); } let crit_pct = (critical as f64 / total as f64 * 100.0) as usize; let high_pct = (high as f64 / total as f64 * 100.0) as usize; let med_pct = (medium as f64 / total as f64 * 100.0) as usize; let low_pct = (low as f64 / total as f64 * 100.0) as usize; let info_pct = 100_usize.saturating_sub(crit_pct + high_pct + med_pct + low_pct); let mut bar = String::from(r#"
"#); if critical > 0 { bar.push_str(&format!( r#"
{}
"#, std::cmp::max(crit_pct, 4), critical )); } if high > 0 { bar.push_str(&format!( r#"
{}
"#, std::cmp::max(high_pct, 4), high )); } if medium > 0 { bar.push_str(&format!( r#"
{}
"#, std::cmp::max(med_pct, 4), medium )); } if low > 0 { bar.push_str(&format!( r#"
{}
"#, std::cmp::max(low_pct, 4), low )); } if info > 0 { bar.push_str(&format!( r#"
{}
"#, std::cmp::max(info_pct, 4), info )); } bar.push_str("
"); bar.push_str(r#"
"#); if critical > 0 { bar.push_str(r#" Critical"#); } if high > 0 { bar.push_str(r#" High"#); } if medium > 0 { bar.push_str(r#" Medium"#); } if low > 0 { bar.push_str(r#" Low"#); } if info > 0 { bar.push_str(r#" Info"#); } bar.push_str("
"); bar }