///|
/// Render the stable fields of a batch summary.
pub fn render_batch_summary(summary : BatchSummary) -> String {
  "feature=" +
  summary.feature_id +
  " count=" +
  summary.count.to_string() +
  " pass=" +
  summary.pass_count.to_string() +
  " fail=" +
  summary.fail_count.to_string() +
  " unmeasured=" +
  summary.unmeasured_count.to_string() +
  " mean=" +
  summary.mean.to_string() +
  " range=" +
  summary.range.to_string() +
  " stddev=" +
  summary.standard_deviation.to_string() +
  " defect_rate=" +
  summary.defect_rate.to_string() +
  " cp=" +
  summary.capability.cp.to_string() +
  " cpk=" +
  summary.capability.cpk.to_string() +
  "\n"
}

///|
/// Render a complete plan/trace report with stable keys.
pub fn render_audit_report(
  trace~ : AuditTrace,
  summary~ : BatchSummary?,
) -> String {
  let mut output = "part=" + trace.part_id + "\n"
  output = output + "entries=" + trace.entries.length().to_string() + "\n"
  output = output + "coverage=" + trace.coverage.to_string() + "\n"
  output = output + "capability=" + trace.capability.to_string() + "\n"
  let counts = audit_verdict_counts(trace)
  let pass = match counts {
    (value, _, _) => value
  }
  let fail = match counts {
    (_, value, _) => value
  }
  let unmeasured = match counts {
    (_, _, value) => value
  }
  output = output + "verdict.pass=" + pass.to_string() + "\n"
  output = output + "verdict.fail=" + fail.to_string() + "\n"
  output = output + "verdict.unmeasured=" + unmeasured.to_string() + "\n"
  if trace.warnings.length() > 0 {
    output = output + "warnings=" + trace.warnings.length().to_string() + "\n"
    for warning in trace.warnings {
      output = output + "warning=" + warning + "\n"
    }
  } else {
    output = output + "warnings=0\n"
  }
  output = output + render_trace_entries(trace.entries)
  match summary {
    Some(summary) => output + render_batch_summary(summary)
    None => output
  }
}