Skip to main content

simular/cli/
output.rs

1//! CLI output formatting.
2//!
3//! This module contains all output formatting functions for the CLI.
4//! Extracted to enable testing of output generation.
5
6use crate::edd::{EmcComplianceReport, ExperimentResult};
7
8/// Print version information.
9pub fn print_version() {
10    println!("simular {}", env!("CARGO_PKG_VERSION"));
11}
12
13/// Print help message.
14pub fn print_help() {
15    println!(
16        r"simular - Unified Simulation Engine for the Sovereign AI Stack
17
18USAGE:
19    simular <COMMAND> [OPTIONS]
20
21COMMANDS:
22    run <experiment.yaml>       Run an experiment
23        --seed <N>              Override the experiment seed
24        -v, --verbose           Enable verbose output
25
26    verify <experiment.yaml>    Verify reproducibility across multiple runs
27        --runs <N>              Number of verification runs (default: 3)
28
29    emc-check <experiment.yaml> Check EMC compliance and generate report
30
31    emc-validate <file.emc.yaml> Validate an EMC file against the schema
32
33    list-emc                    List available EMCs in the library
34
35    help                        Show this help message
36    version                     Show version information
37
38EXAMPLES:
39    simular run experiments/harmonic_oscillator.yaml
40    simular run experiments/harmonic_oscillator.yaml --seed 12345
41    simular verify experiments/harmonic_oscillator.yaml --runs 5
42    simular emc-check experiments/littles_law.yaml
43
44EDD COMPLIANCE:
45    All experiments are validated against the four pillars of EDD:
46    1. Prove It  - Every simulation has an EMC reference
47    2. Fail It   - Verification tests are executed
48    3. Seed It   - Explicit seed is required
49    4. Falsify It - Falsification criteria are checked
50
51For more information, see: https://github.com/paiml/simular
52"
53    );
54}
55
56/// Print experiment result.
57///
58/// # Arguments
59///
60/// * `result` - The experiment result to display
61/// * `verbose` - Whether to show verbose output
62pub fn print_experiment_result(result: &ExperimentResult, verbose: bool) {
63    let status = if result.passed { "PASSED" } else { "FAILED" };
64    let status_symbol = if result.passed { "✓" } else { "✗" };
65
66    print_header(&result.name, &result.experiment_id, result.seed);
67    print_verification(&result.verification, verbose);
68    print_falsification(&result.falsification, verbose);
69    print_reproducibility(result.reproducibility.as_ref());
70    print_execution(&result.execution);
71    print_warnings(&result.warnings);
72    print_footer(status_symbol, status);
73}
74
75fn print_header(name: &str, id: &str, seed: u64) {
76    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
77    println!("Experiment: {name}");
78    println!("ID: {id}");
79    println!("Seed: {seed}");
80    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
81}
82
83fn print_verification(verification: &crate::edd::VerificationSummary, verbose: bool) {
84    println!("Verification Tests:");
85    println!("  Total:  {}", verification.total);
86    println!("  Passed: {}", verification.passed);
87    println!("  Failed: {}", verification.failed);
88
89    if verbose && !verification.tests.is_empty() {
90        println!();
91        for test in &verification.tests {
92            let sym = if test.passed { "✓" } else { "✗" };
93            println!("  {} {}: {}", sym, test.id, test.name);
94            if let Some(ref err) = test.error {
95                if !test.passed {
96                    println!("      Error: {err}");
97                }
98            }
99        }
100    }
101}
102
103fn print_falsification(falsification: &crate::edd::FalsificationSummary, verbose: bool) {
104    println!("\nFalsification Criteria:");
105    println!("  Total:     {}", falsification.total);
106    println!("  Passed:    {}", falsification.passed);
107    println!("  Triggered: {}", falsification.triggered);
108
109    if falsification.jidoka_triggered {
110        println!("  Jidoka:    TRIGGERED (stop-on-error)");
111    }
112
113    if verbose && !falsification.criteria.is_empty() {
114        println!();
115        for crit in &falsification.criteria {
116            let sym = if crit.triggered { "✗" } else { "✓" };
117            println!("  {} {}: {}", sym, crit.id, crit.name);
118            if crit.triggered {
119                println!("      Condition: {}", crit.condition);
120            }
121        }
122    }
123}
124
125fn print_reproducibility(_reproducibility: Option<&crate::edd::ReproducibilitySummary>) {
126    // Reserved for future reproducibility output
127}
128
129fn print_execution(execution: &crate::edd::ExecutionMetrics) {
130    println!("\nExecution:");
131    println!("  Duration:     {} ms", execution.duration_ms);
132    println!("  Replications: {}", execution.replications);
133}
134
135fn print_warnings(warnings: &[String]) {
136    if !warnings.is_empty() {
137        println!("\nWarnings:");
138        for warning in warnings {
139            println!("  ! {warning}");
140        }
141    }
142}
143
144fn print_footer(status_symbol: &str, status: &str) {
145    println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
146    println!("{status_symbol} Result: {status}");
147    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
148}
149
150/// Print EMC compliance report.
151///
152/// # Arguments
153///
154/// * `report` - The EMC compliance report to display
155pub fn print_emc_report(report: &EmcComplianceReport) {
156    let status = if report.passed {
157        "COMPLIANT"
158    } else {
159        "NON-COMPLIANT"
160    };
161    let sym = if report.passed { "✓" } else { "✗" };
162
163    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
164    println!("EMC Compliance Report: {}", report.experiment_name);
165    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
166
167    // EDD Compliance Checklist
168    println!("EDD Compliance Checklist:");
169    let check = |passed: bool| if passed { "✓" } else { "✗" };
170
171    println!(
172        "  {} EDD-01: EMC Reference",
173        check(report.edd_compliance.edd_01_emc_reference)
174    );
175    println!(
176        "  {} EDD-02: Verification Tests",
177        check(report.edd_compliance.edd_02_verification_tests)
178    );
179    println!(
180        "  {} EDD-03: Seed Specified",
181        check(report.edd_compliance.edd_03_seed_specified)
182    );
183    println!(
184        "  {} EDD-04: Falsification Criteria",
185        check(report.edd_compliance.edd_04_falsification_criteria)
186    );
187    println!(
188        "  {} EDD-05: Hypothesis (Optional)",
189        check(report.edd_compliance.edd_05_hypothesis)
190    );
191
192    // Schema errors
193    if !report.schema_errors.is_empty() {
194        println!("\nSchema Errors:");
195        for err in &report.schema_errors {
196            println!("  ✗ {err}");
197        }
198    }
199
200    // EMC errors
201    if !report.emc_errors.is_empty() {
202        println!("\nEMC Errors:");
203        for err in &report.emc_errors {
204            println!("  ✗ {err}");
205        }
206    }
207
208    // Warnings
209    if !report.warnings.is_empty() {
210        println!("\nWarnings:");
211        for warning in &report.warnings {
212            println!("  ! {warning}");
213        }
214    }
215
216    println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
217    println!("{sym} Result: {status}");
218    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
219}
220
221/// Print EMC validation results.
222///
223/// # Arguments
224///
225/// * `yaml` - The parsed YAML value
226/// * `errors` - List of validation errors
227/// * `warnings` - List of validation warnings
228pub fn print_emc_validation_results(
229    yaml: &serde_yaml::Value,
230    errors: &[String],
231    warnings: &[String],
232) {
233    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
234    if let Some(name) = yaml
235        .get("identity")
236        .and_then(|i| i.get("name"))
237        .and_then(|n| n.as_str())
238    {
239        println!("EMC: {name}");
240    }
241    if let Some(id) = yaml.get("emc_id").and_then(|id| id.as_str()) {
242        println!("ID: {id}");
243    }
244    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
245
246    let check = |b: bool| if b { "✓" } else { "✗" };
247    println!("Schema Validation:");
248    println!("  {} emc_version", check(yaml.get("emc_version").is_some()));
249    println!("  {} emc_id", check(yaml.get("emc_id").is_some()));
250    println!("  {} identity", check(yaml.get("identity").is_some()));
251    println!(
252        "  {} governing_equation",
253        check(yaml.get("governing_equation").is_some())
254    );
255    println!(
256        "  {} analytical_derivation",
257        check(yaml.get("analytical_derivation").is_some())
258    );
259    println!(
260        "  {} domain_of_validity",
261        check(yaml.get("domain_of_validity").is_some())
262    );
263    println!(
264        "  {} verification_tests",
265        check(yaml.get("verification_tests").is_some())
266    );
267    println!(
268        "  {} falsification_criteria",
269        check(yaml.get("falsification_criteria").is_some())
270    );
271
272    if !errors.is_empty() {
273        println!("\nErrors:");
274        for err in errors {
275            println!("  ✗ {err}");
276        }
277    }
278    if !warnings.is_empty() {
279        println!("\nWarnings:");
280        for w in warnings {
281            println!("  ! {w}");
282        }
283    }
284
285    let (status, sym) = if errors.is_empty() {
286        ("VALID", "✓")
287    } else {
288        ("INVALID", "✗")
289    };
290    println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
291    println!("{sym} Result: {status}");
292    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
293}