1use crate::types::{Finding, Severity};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct AuditReport {
8 pub text_len_chars: usize,
9 pub word_count: usize,
10 pub score: f32, pub findings: Vec<Finding>,
12 pub finding_counts: HashMap<String, usize>, pub category_counts: HashMap<String, usize>,
14 pub execution_time_ms: f64,
15}
16
17impl AuditReport {
18 pub fn calculate(
20 text: &str,
21 findings: Vec<Finding>,
22 word_count: usize,
23 execution_time_ms: f64,
24 ) -> Self {
25 let mut finding_counts = HashMap::new();
26 let mut category_counts = HashMap::new();
27
28 let mut total_penalty = 0.0f32;
29
30 for f in &findings {
31 let sev = Severity::parse(&f.severity);
32 *finding_counts.entry(sev.as_str().to_string()).or_insert(0) += 1;
33 *category_counts.entry(f.category.clone()).or_insert(0) += 1;
34
35 let base_penalty = match sev {
37 Severity::Critical => 12.0,
38 Severity::Major => 8.0,
39 Severity::Minor => 3.0,
40 Severity::Suggestion => 1.0,
41 };
42 total_penalty += base_penalty;
43 }
44
45 let effective_words = (word_count as f32).max(50.0);
47 let density_factor = 200.0 / effective_words;
48 let scaled_penalty = total_penalty * density_factor;
49
50 let score = (100.0 - scaled_penalty).clamp(0.0, 100.0);
52
53 AuditReport {
54 text_len_chars: text.chars().count(),
55 word_count,
56 score: (score * 10.0).round() / 10.0, findings,
58 finding_counts,
59 category_counts,
60 execution_time_ms,
61 }
62 }
63
64 pub fn to_json(&self) -> String {
66 serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
67 }
68
69 pub fn to_markdown(&self) -> String {
71 let mut md = String::new();
72 md.push_str(&format!(
73 "# BÁO CÁO KIỂM TOÁN VĂN BẢN (Điểm: {}/100)\n\n",
74 self.score
75 ));
76 md.push_str(&format!(
77 "- **Số từ**: {}\n- **Số lỗi phát hiện**: {}\n- **Thời gian xử lý**: {:.2} ms\n\n",
78 self.word_count,
79 self.findings.len(),
80 self.execution_time_ms
81 ));
82
83 if self.findings.is_empty() {
84 md.push_str("✅ **Văn bản đạt chuẩn thuần Việt! Không phát hiện lỗi cấu trúc.**\n");
85 return md;
86 }
87
88 md.push_str("| Vị trí | Mức độ | Nhóm | Đoạn văn vi phạm | Chi tiết lỗi | Gợi ý khắc phục |\n");
89 md.push_str("| :--- | :--- | :--- | :--- | :--- | :--- |\n");
90 for f in &self.findings {
91 let sev = Severity::parse(&f.severity);
92 let sev_badge = match sev {
93 Severity::Critical => "🚨 Critical",
94 Severity::Major => "🔴 Major",
95 Severity::Minor => "🟡 Minor",
96 Severity::Suggestion => "💡 Suggestion",
97 };
98 let sugg = f.suggestion.as_deref().unwrap_or("—");
99 md.push_str(&format!(
100 "| {}..{} | {} | {} | `{}` | {} | {} |\n",
101 f.start, f.end, sev_badge, f.category, f.matched_span, f.rationale, sugg
102 ));
103 }
104 md
105 }
106}