1use anyhow::Result;
2use diffx_core::format_diff_output;
3
4use crate::types::{DiffResult, OutputFormat};
5
6pub fn format_output(results: &[DiffResult], format: OutputFormat) -> Result<String> {
14 format_diff_results(results, format)
15}
16
17pub fn format_diff_results(results: &[DiffResult], format: OutputFormat) -> Result<String> {
19 match format {
20 OutputFormat::Json => {
21 Ok(serde_json::to_string_pretty(results)?)
23 }
24 OutputFormat::Yaml => {
25 Ok(serde_yaml::to_string(results)?)
27 }
28 OutputFormat::Diffai => {
29 let base_results: Vec<diffx_core::DiffResult> = results
31 .iter()
32 .filter_map(|r| match r {
33 DiffResult::Added(path, value) => {
34 Some(diffx_core::DiffResult::Added(path.clone(), value.clone()))
35 }
36 DiffResult::Removed(path, value) => {
37 Some(diffx_core::DiffResult::Removed(path.clone(), value.clone()))
38 }
39 DiffResult::Modified(path, old, new) => Some(diffx_core::DiffResult::Modified(
40 path.clone(),
41 old.clone(),
42 new.clone(),
43 )),
44 DiffResult::TypeChanged(path, old, new) => Some(
45 diffx_core::DiffResult::TypeChanged(path.clone(), old.clone(), new.clone()),
46 ),
47 _ => None,
48 })
49 .collect();
50
51 let mut output = String::new();
52
53 if !base_results.is_empty() {
55 let base_format = format.to_base_format();
56 let formatted = format_diff_output(&base_results, base_format, None)?;
57 output.push_str(&formatted);
58 }
59
60 let ml_results: Vec<&DiffResult> = results
62 .iter()
63 .filter(|r| {
64 match r {
65 DiffResult::TensorStatsChanged(path, _, _) => {
67 !path.contains("data_summary")
68 }
69 DiffResult::TensorShapeChanged(_, _, _)
70 | DiffResult::TensorDataChanged(_, _, _)
71 | DiffResult::ModelArchitectureChanged(_, _, _)
72 | DiffResult::WeightSignificantChange(_, _)
73 | DiffResult::ActivationFunctionChanged(_, _, _)
74 | DiffResult::LearningRateChanged(_, _, _)
75 | DiffResult::OptimizerChanged(_, _, _)
76 | DiffResult::LossChange(_, _, _)
77 | DiffResult::AccuracyChange(_, _, _)
78 | DiffResult::ModelVersionChanged(_, _, _) => true,
79 _ => false,
80 }
81 })
82 .collect();
83
84 if !ml_results.is_empty() {
85 if !output.is_empty() && !output.ends_with('\n') {
86 output.push('\n');
87 }
88 output.push('\n');
89
90 for result in &ml_results {
91 match result {
92 DiffResult::ModelArchitectureChanged(path, old, new) => {
93 output.push_str(&format!(" ~ {path}: {old} -> {new}\n"));
94 }
95 DiffResult::TensorShapeChanged(path, old_shape, new_shape) => {
96 output.push_str(&format!(
97 " ~ {path} shape: {old_shape:?} -> {new_shape:?}\n"
98 ));
99 }
100 DiffResult::TensorStatsChanged(path, old_stats, new_stats) => {
101 output.push_str(&format!(
102 " ~ {} stats: mean {:.3} -> {:.3}\n",
103 path, old_stats.mean, new_stats.mean
104 ));
105 }
106 _ => {
107 output.push_str(&format!(
109 " ~ ML analysis: {}\n",
110 serde_json::to_string(result)?
111 ));
112 }
113 }
114 }
115 }
116
117 Ok(output)
118 }
119 }
120}