1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5pub enum Severity {
6 Error,
7 Warning,
8 Info,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub enum Category {
14 Accessibility,
15 SEO,
16 Performance,
17 BestPractices,
18 Environment,
19 Safety,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Issue {
25 pub issue_type: String,
27 pub element_snippet: Option<String>,
29 pub suggested_fix: Option<String>,
31 pub severity: Severity,
33 pub fix_example: Option<String>,
35 pub category: Category,
37}
38
39impl Issue {
40 pub fn new(
41 issue_type: &str,
42 element_snippet: Option<&str>,
43 suggested_fix: Option<&str>,
44 severity: Severity,
45 fix_example: Option<&str>,
46 category: Category,
47 ) -> Self {
48 Self {
49 issue_type: issue_type.to_string(),
50 element_snippet: element_snippet.map(|s| s.to_string()),
51 suggested_fix: suggested_fix.map(|s| s.to_string()),
52 severity,
53 fix_example: fix_example.map(|s| s.to_string()),
54 category,
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct PageLoadResult {
62 pub document: scraper::Html,
64 pub load_time: f64,
66 pub request_count: u32,
68 pub page_size: u64,
70 pub is_compressed: bool,
72 pub has_caching_headers: bool,
74 pub website_url: Option<String>,
76}
77
78impl PageLoadResult {
79 pub fn new(
80 document: scraper::Html,
81 load_time: f64,
82 request_count: u32,
83 page_size: u64,
84 is_compressed: bool,
85 has_caching_headers: bool,
86 website_url: Option<&str>,
87 ) -> Self {
88 Self {
89 document,
90 load_time,
91 request_count,
92 page_size,
93 is_compressed,
94 has_caching_headers,
95 website_url: website_url.map(|s| s.to_string()),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct Report {
103 pub issues: Vec<Issue>,
105 pub total_issues: usize,
107 pub error_count: usize,
109 pub warning_count: usize,
111 pub info_count: usize,
113 pub accessibility_score: u32,
115 pub seo_score: u32,
117 pub performance_score: u32,
119 pub best_practices_score: u32,
121 pub environment_score: u32,
123 pub safety_score: u32,
125 pub compliance_status: String,
127 pub website_url: Option<String>,
129 pub page_load_time: f64,
131 pub request_count: u32,
133 pub page_size: u64,
135 pub energy_consumption_kwh: f64,
137 pub co2_emissions_grams: f64,
139 pub environmental_rating: String,
141 pub uses_cdn: bool,
143}
144
145impl Report {
146 pub fn new(website_url: Option<&str>) -> Self {
147 Self {
148 issues: Vec::new(),
149 total_issues: 0,
150 error_count: 0,
151 warning_count: 0,
152 info_count: 0,
153 accessibility_score: 0,
154 seo_score: 0,
155 performance_score: 0,
156 best_practices_score: 0,
157 environment_score: 0,
158 safety_score: 0,
159 compliance_status: "Unknown".to_string(),
160 website_url: website_url.map(|s| s.to_string()),
161 page_load_time: 0.0,
162 request_count: 0,
163 page_size: 0,
164 energy_consumption_kwh: 0.0,
165 co2_emissions_grams: 0.0,
166 environmental_rating: "Unknown".to_string(),
167 uses_cdn: false,
168 }
169 }
170
171 pub fn recalculate(&mut self) {
173 self.total_issues = self.issues.len();
174 self.error_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Error)).count();
175 self.warning_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Warning)).count();
176 self.info_count = self.issues.iter().filter(|i| matches!(i.severity, Severity::Info)).count();
177
178 let accessibility_issues: Vec<_> = self.issues.iter()
180 .filter(|i| matches!(i.category, Category::Accessibility))
181 .collect();
182
183 let total_acc_issues = accessibility_issues.len() as f64;
184 let critical_issues = accessibility_issues.iter()
185 .filter(|i| matches!(i.severity, Severity::Error))
186 .count() as f64;
187 let warning_issues = accessibility_issues.iter()
188 .filter(|i| matches!(i.severity, Severity::Warning))
189 .count() as f64;
190
191 let base_score = 100.0;
192 let critical_penalty = critical_issues * 3.0;
193 let warning_penalty = warning_issues * 1.0;
194 let volume_penalty = if total_acc_issues > 10.0 { (total_acc_issues - 10.0) * 0.5 } else { 0.0 };
195
196 self.accessibility_score = ((base_score - critical_penalty - warning_penalty - volume_penalty)
197 .max(0.0).min(100.0)) as u32;
198
199 self.compliance_status = if self.accessibility_score >= 95 {
201 "Fully Compliant"
202 } else if self.accessibility_score >= 80 {
203 "Mostly Compliant"
204 } else if self.accessibility_score >= 60 {
205 "Partially Compliant"
206 } else {
207 "Not Compliant"
208 }.to_string();
209 }
210}