mod analysis;
mod io;
mod messages;
mod scoring;
pub mod types;
pub(crate) use types::parse_debtmap_json;
pub use types::{
AnalysisSummary, Comparability, ComparabilityStatus, CompareConfig, DebtmapJsonInput,
GapDetail, ValidationResult,
};
use analysis::{create_summary, identify_all_changes};
use anyhow::Result;
use io::{load_both_debtmaps, print_summary, read_automation_mode, write_validation_result};
use messages::{build_all_gaps, build_all_improvement_messages, build_all_issue_messages};
use scoring::{calculate_improvement_score, determine_status};
use types::DebtmapJsonInput as Input;
pub fn compare_debtmaps(config: CompareConfig) -> Result<()> {
let is_automation = read_automation_mode();
if !is_automation {
println!("Loading debtmap data from before and after states...");
}
let (before, after) = load_both_debtmaps(&config)?;
let result = perform_validation(&before, &after)?;
write_validation_result(&config.output_path, &result)?;
if !is_automation {
print_summary(&result);
}
Ok(())
}
fn perform_validation(before: &Input, after: &Input) -> Result<ValidationResult> {
let before_summary = create_summary(before);
let after_summary = create_summary(after);
let comparability = assess_comparability(before, after);
if comparability.status == ComparabilityStatus::Incompatible {
return Ok(non_comparable_result(
before_summary,
after_summary,
comparability,
));
}
let changes = identify_all_changes(before, after);
let improvements = build_all_improvement_messages(&changes.resolved, &changes.improved);
let remaining_issues =
build_all_issue_messages(&changes.unchanged_critical, &changes.new_items);
let gaps = build_all_gaps(&changes.unchanged_critical, &changes.new_items);
let completion = calculate_improvement_score(
&changes.resolved,
&changes.improved,
&changes.new_items,
&changes.unchanged_critical,
&before_summary,
&after_summary,
);
let status = determine_status(
completion,
&changes.new_items,
&before_summary,
&after_summary,
);
Ok(ValidationResult {
comparability,
completion_percentage: completion,
status,
improvements,
remaining_issues,
gaps,
before_summary,
after_summary,
})
}
fn assess_comparability(before: &Input, after: &Input) -> Comparability {
let (Some(before), Some(after)) = (&before.receipt, &after.receipt) else {
return Comparability {
status: ComparabilityStatus::Unknown,
reasons: vec!["One or both reports do not contain an analysis receipt".to_string()],
};
};
let reasons = incompatible_receipt_reasons(before, after);
if !reasons.is_empty() {
return Comparability {
status: ComparabilityStatus::Incompatible,
reasons,
};
}
assess_scope_comparability(before, after)
}
fn incompatible_receipt_reasons(
before: &crate::output::unified::AnalysisReceipt,
after: &crate::output::unified::AnalysisReceipt,
) -> Vec<String> {
[
(
before.policy_fingerprint != after.policy_fingerprint,
"Analysis policies differ",
),
(
before.evidence != after.evidence,
"Loaded or requested evidence differs",
),
(
before.selection != after.selection,
"Output selection policies differ",
),
(
before.analysis_target != after.analysis_target,
"Analysis targets differ",
),
]
.into_iter()
.filter(|(differs, _)| *differs)
.map(|(_, reason)| reason.to_string())
.collect()
}
fn assess_scope_comparability(
before: &crate::output::unified::AnalysisReceipt,
after: &crate::output::unified::AnalysisReceipt,
) -> Comparability {
use crate::output::unified::ScopeStatus;
let complete =
before.scope.status == ScopeStatus::Complete && after.scope.status == ScopeStatus::Complete;
Comparability {
status: if complete {
ComparabilityStatus::Comparable
} else {
ComparabilityStatus::Unknown
},
reasons: (!complete)
.then(|| "One or both reports have incomplete or unknown scope".to_string())
.into_iter()
.collect(),
}
}
fn non_comparable_result(
before_summary: AnalysisSummary,
after_summary: AnalysisSummary,
comparability: Comparability,
) -> ValidationResult {
ValidationResult {
completion_percentage: 0.0,
status: "non_comparable".to_string(),
improvements: Vec::new(),
remaining_issues: comparability.reasons.clone(),
gaps: Default::default(),
before_summary,
after_summary,
comparability,
}
}
#[cfg(test)]
mod tests;