Skip to main content

vwi_core/
report.rs

1use crate::types::{Finding, Severity};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Báo cáo kiểm toán văn bản toàn diện kèm điểm số chất lượng (0.0 - 100.0).
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct AuditReport {
8    pub text_len_chars: usize,
9    pub word_count: usize,
10    pub score: f32, // Thang điểm 0.0 - 100.0
11    pub findings: Vec<Finding>,
12    pub finding_counts: HashMap<String, usize>, // Severity -> Count
13    pub category_counts: HashMap<String, usize>,
14    pub execution_time_ms: f64,
15}
16
17impl AuditReport {
18    /// Thuật toán tính điểm chất lượng văn bản dựa trên mật độ lỗi phi tuyến (Non-linear Error Density)
19    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            // Điểm phạt cơ sở theo mức độ nghiêm trọng
36            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        // Chuẩn hóa theo độ dài văn bản (mật độ lỗi trên mỗi 200 từ)
46        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        // Điểm số tối thiểu là 0.0, tối đa là 100.0
51        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, // Làm tròn 1 chữ số thập phân
57            findings,
58            finding_counts,
59            category_counts,
60            execution_time_ms,
61        }
62    }
63
64    /// Định dạng xuất JSON
65    pub fn to_json(&self) -> String {
66        serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
67    }
68
69    /// Định dạng xuất Markdown
70    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}