use std::path::Path;
use crate::party::Results;
use crate::perf::{Measurement, PerformanceCase};
use crate::pipeline::{Error, RenderedFile, load_clean_root, load_party_json, read_json};
use crate::schema::results_schema;
use crate::stress::StressReport;
#[derive(Debug, serde::Deserialize)]
pub struct VerdictEvidence {
pub capabilities: Vec<(String, crate::verdict::Evidence)>,
}
pub fn conformance_assets(
root: &Path,
results_path: &Path,
verdicts_path: &Path,
suffix: &str,
) -> Result<Vec<RenderedFile>, Error> {
let loaded = crate::artifacts::load_root(root).map_err(Error::Catalogue)?;
let Some((_, matrix)) = &loaded.set.matrix else {
return Err(Error::Missing(
"artifact set has no capability matrix".to_owned(),
));
};
let results: Results = load_party_json(results_path, &results_schema(), "results.schema.json")?;
let verdicts: VerdictEvidence = read_json(verdicts_path, "verdicts")?;
let sut_label = format!("{} {}", results.sut.name, results.sut.version);
let chapters = crate::conf_assets::chapter_counts(&results)
.map_err(|e| Error::Instrument(e.to_string()))?;
Ok(vec![
RenderedFile {
name: format!("conformance-heat-grid{suffix}.svg"),
body: crate::conf_assets::heat_grid_svg(&sut_label, matrix, &verdicts.capabilities),
},
RenderedFile {
name: format!("conformance-chapter-bars{suffix}.svg"),
body: crate::conf_assets::chapter_bars_svg(&sut_label, &chapters),
},
])
}
#[derive(Debug)]
pub struct PerformanceAssets {
pub files: Vec<RenderedFile>,
cases: Vec<PerformanceCase>,
measurements: Vec<Measurement>,
}
impl PerformanceAssets {
pub fn summary_markdown(&self) -> Result<String, Error> {
crate::perf_assets::summary_markdown(&self.cases, &self.measurements)
.map_err(|e| Error::Instrument(format!("summary: {e}")))
}
}
pub fn performance_assets(
root: &Path,
results_path: &Path,
stress_path: Option<&Path>,
) -> Result<PerformanceAssets, Error> {
let loaded = load_clean_root(root)?;
let results: Results = load_party_json(results_path, &results_schema(), "results.schema.json")?;
let cases: Vec<PerformanceCase> = loaded
.set
.performance
.iter()
.map(|(_, c)| c.clone())
.collect();
let mut files = vec![RenderedFile {
name: "perf-class-ladder.svg".to_owned(),
body: crate::perf_assets::class_ladder_svg(&cases, &results.measurements),
}];
if let Some(path) = stress_path {
let report: StressReport = read_json(path, &path.display().to_string())?;
files.push(RenderedFile {
name: "perf-stress-curve.svg".to_owned(),
body: crate::perf_assets::stress_curve_svg(&report)
.map_err(|e| Error::Instrument(format!("stress curve: {e}")))?,
});
}
for measurement in &results.measurements {
files.push(RenderedFile {
name: format!("perf-latency-class-{}.svg", measurement.class.token()),
body: crate::perf_assets::latency_percentiles_svg(measurement)
.map_err(|e| Error::Instrument(format!("{}: {e}", measurement.case)))?,
});
if let Some(body) = crate::perf_assets::resources_timeseries_svg(measurement) {
files.push(RenderedFile {
name: format!("perf-resources-class-{}.svg", measurement.class.token()),
body,
});
}
}
if let Some(body) = crate::perf_assets::disk_growth_svg(&results.measurements) {
files.push(RenderedFile {
name: "perf-disk-growth.svg".to_owned(),
body,
});
}
Ok(PerformanceAssets {
files,
cases,
measurements: results.measurements,
})
}
pub fn stress_overlay(left: (&str, &Path), right: (&str, &Path)) -> Result<String, Error> {
let (left_label, left_path) = left;
let (right_label, right_path) = right;
let left_report: StressReport = read_json(left_path, &left_path.display().to_string())?;
let right_report: StressReport = read_json(right_path, &right_path.display().to_string())?;
crate::perf_assets::stress_compare_svg((left_label, &left_report), (right_label, &right_report))
.map_err(|e| Error::Instrument(format!("stress compare: {e}")))
}
#[must_use]
pub fn schema_files() -> Vec<RenderedFile> {
crate::schema::emit_all()
.into_iter()
.map(|(name, schema)| RenderedFile {
name: name.to_owned(),
body: crate::schema::render(&schema),
})
.collect()
}