mod coverage;
mod production_coverage;
mod scores;
mod targets;
mod trends;
mod vital_signs;
pub use coverage::*;
pub use production_coverage::*;
pub use scores::*;
pub use targets::*;
pub use trends::*;
pub use vital_signs::*;
#[derive(Debug, Clone, serde::Serialize)]
pub struct HealthTimings {
pub config_ms: f64,
pub discover_ms: f64,
pub parse_ms: f64,
pub complexity_ms: f64,
pub file_scores_ms: f64,
pub git_churn_ms: f64,
pub git_churn_cache_hit: bool,
pub hotspots_ms: f64,
pub duplication_ms: f64,
pub targets_ms: f64,
pub total_ms: f64,
}
#[derive(Debug, serde::Serialize)]
pub struct HealthReport {
pub findings: Vec<HealthFinding>,
pub summary: HealthSummary,
#[serde(skip_serializing_if = "Option::is_none")]
pub vital_signs: Option<VitalSigns>,
#[serde(skip_serializing_if = "Option::is_none")]
pub health_score: Option<HealthScore>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub file_scores: Vec<FileHealthScore>,
#[serde(skip_serializing_if = "Option::is_none")]
pub coverage_gaps: Option<CoverageGaps>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub hotspots: Vec<HotspotEntry>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hotspot_summary: Option<HotspotSummary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub production_coverage: Option<ProductionCoverageReport>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub large_functions: Vec<LargeFunctionEntry>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub targets: Vec<RefactoringTarget>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_thresholds: Option<TargetThresholds>,
#[serde(skip_serializing_if = "Option::is_none")]
pub health_trend: Option<HealthTrend>,
}
#[cfg(test)]
#[expect(
clippy::derivable_impls,
reason = "test-only Default with custom HealthSummary thresholds (20/15)"
)]
impl Default for HealthReport {
fn default() -> Self {
Self {
findings: vec![],
summary: HealthSummary::default(),
vital_signs: None,
health_score: None,
file_scores: vec![],
coverage_gaps: None,
hotspots: vec![],
hotspot_summary: None,
production_coverage: None,
large_functions: vec![],
targets: vec![],
target_thresholds: None,
health_trend: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn health_report_skips_empty_collections() {
let report = HealthReport::default();
let json = serde_json::to_string(&report).unwrap();
assert!(!json.contains("file_scores"));
assert!(!json.contains("hotspots"));
assert!(!json.contains("hotspot_summary"));
assert!(!json.contains("production_coverage"));
assert!(!json.contains("large_functions"));
assert!(!json.contains("targets"));
assert!(!json.contains("vital_signs"));
assert!(!json.contains("health_score"));
}
#[test]
fn health_score_none_skipped_in_report() {
let report = HealthReport::default();
let json = serde_json::to_string(&report).unwrap();
assert!(!json.contains("health_score"));
}
}