mod comparison;
mod html;
mod json;
mod thresholds;
pub use comparison::{BaselineComparison, ComparisonResult, MetricChange};
pub use html::HtmlReportGenerator;
pub use json::JsonReportGenerator;
pub use thresholds::{ThresholdChecker, ThresholdResult};
use crate::coherence::CoherenceEvaluation;
use crate::ml::MLReadinessEvaluation;
use crate::quality::QualityEvaluation;
use crate::statistical::StatisticalEvaluation;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvaluationReport {
pub metadata: ReportMetadata,
pub statistical: Option<StatisticalEvaluation>,
pub coherence: Option<CoherenceEvaluation>,
pub quality: Option<QualityEvaluation>,
pub ml_readiness: Option<MLReadinessEvaluation>,
pub passes: bool,
pub all_issues: Vec<ReportIssue>,
pub overall_score: f64,
pub baseline_comparison: Option<BaselineComparison>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportMetadata {
pub generated_at: DateTime<Utc>,
pub version: String,
pub data_source: String,
pub thresholds_name: String,
pub records_evaluated: usize,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportIssue {
pub category: IssueCategory,
pub severity: IssueSeverity,
pub description: String,
pub metric: Option<String>,
pub actual_value: Option<String>,
pub threshold_value: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum IssueCategory {
Statistical,
Coherence,
Quality,
MLReadiness,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum IssueSeverity {
Critical,
Warning,
Info,
}
impl EvaluationReport {
pub fn new(
metadata: ReportMetadata,
statistical: Option<StatisticalEvaluation>,
coherence: Option<CoherenceEvaluation>,
quality: Option<QualityEvaluation>,
ml_readiness: Option<MLReadinessEvaluation>,
) -> Self {
let mut report = Self {
metadata,
statistical,
coherence,
quality,
ml_readiness,
passes: true,
all_issues: Vec::new(),
overall_score: 1.0,
baseline_comparison: None,
};
report.aggregate_results();
report
}
fn aggregate_results(&mut self) {
let mut scores = Vec::new();
if let Some(ref stat) = self.statistical {
if !stat.passes {
self.passes = false;
}
scores.push(stat.overall_score);
for issue in &stat.issues {
self.all_issues.push(ReportIssue {
category: IssueCategory::Statistical,
severity: IssueSeverity::Critical,
description: issue.clone(),
metric: None,
actual_value: None,
threshold_value: None,
});
}
}
if let Some(ref coh) = self.coherence {
if !coh.passes {
self.passes = false;
}
for failure in &coh.failures {
self.all_issues.push(ReportIssue {
category: IssueCategory::Coherence,
severity: IssueSeverity::Critical,
description: failure.clone(),
metric: None,
actual_value: None,
threshold_value: None,
});
}
}
if let Some(ref qual) = self.quality {
if !qual.passes {
self.passes = false;
}
scores.push(qual.overall_score);
for issue in &qual.issues {
self.all_issues.push(ReportIssue {
category: IssueCategory::Quality,
severity: IssueSeverity::Critical,
description: issue.clone(),
metric: None,
actual_value: None,
threshold_value: None,
});
}
}
if let Some(ref ml) = self.ml_readiness {
if !ml.passes {
self.passes = false;
}
scores.push(ml.overall_score);
for issue in &ml.issues {
self.all_issues.push(ReportIssue {
category: IssueCategory::MLReadiness,
severity: IssueSeverity::Critical,
description: issue.clone(),
metric: None,
actual_value: None,
threshold_value: None,
});
}
}
self.overall_score = if scores.is_empty() {
1.0
} else {
scores.iter().sum::<f64>() / scores.len() as f64
};
}
pub fn with_baseline_comparison(mut self, comparison: BaselineComparison) -> Self {
self.baseline_comparison = Some(comparison);
self
}
pub fn issues_by_category(&self, category: IssueCategory) -> Vec<&ReportIssue> {
self.all_issues
.iter()
.filter(|i| i.category == category)
.collect()
}
pub fn critical_issues(&self) -> Vec<&ReportIssue> {
self.all_issues
.iter()
.filter(|i| i.severity == IssueSeverity::Critical)
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReportFormat {
Json,
Html,
Both,
}
pub trait ReportGenerator {
fn generate(&self, report: &EvaluationReport) -> crate::error::EvalResult<String>;
fn generate_to_file(
&self,
report: &EvaluationReport,
path: &std::path::Path,
) -> crate::error::EvalResult<()> {
let content = self.generate(report)?;
std::fs::write(path, content)?;
Ok(())
}
}