use crate::analysis::{ComparisonMetrics, ModelMetrics};
use crate::dataset::DatasetProcessingSummary;
use crate::errors::VizError;
use crate::models::{ModelCatalogReport, ModelDownloadReport};
use crate::validation::report::ModelValidationSummary;
use crate::viz::manifest::{ArtifactKind, OutputArtifactManifest};
use crate::viz::report::{
build_compare_overview, CompareOverview, DriftReport, InspectReport, NeighborsReport,
PairwiseMatrix, PairwiseMetricSupport, ProfileReport, SimilarityReport,
};
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VisualAsset {
pub title: String,
pub path: String,
pub alt: String,
pub description: String,
}
#[derive(Debug, Clone, Default)]
pub struct CompareHtmlAssets {
pub source_images: Vec<VisualAsset>,
pub pca_images: Vec<VisualAsset>,
pub heatmaps: Vec<VisualAsset>,
}
impl CompareHtmlAssets {
fn is_empty(&self) -> bool {
self.source_images.is_empty() && self.pca_images.is_empty() && self.heatmaps.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct GalleryAssets {
pub visuals: Vec<VisualAsset>,
}
impl GalleryAssets {
fn is_empty(&self) -> bool {
self.visuals.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct InspectHtmlAssets {
pub source_image: Option<VisualAsset>,
pub pca_image: Option<VisualAsset>,
pub variance_image: Option<VisualAsset>,
pub attention_image: Option<VisualAsset>,
pub similarity_heatmap: Option<VisualAsset>,
}
impl InspectHtmlAssets {
fn is_empty(&self) -> bool {
self.source_image.is_none()
&& self.pca_image.is_none()
&& self.variance_image.is_none()
&& self.attention_image.is_none()
&& self.similarity_heatmap.is_none()
}
}
pub fn write_report(
image_name: &str,
metrics: &[ModelMetrics],
comparisons: &[ComparisonMetrics],
output_path: &Path,
) -> Result<(), VizError> {
write_report_with_validation(image_name, metrics, comparisons, &[], output_path)
}
pub fn write_report_with_validation(
image_name: &str,
metrics: &[ModelMetrics],
comparisons: &[ComparisonMetrics],
validation: &[ModelValidationSummary],
output_path: &Path,
) -> Result<(), VizError> {
write_report_with_validation_and_assets(
image_name,
metrics,
comparisons,
validation,
&CompareHtmlAssets::default(),
output_path,
)
}
pub fn write_report_with_validation_and_assets(
image_name: &str,
metrics: &[ModelMetrics],
comparisons: &[ComparisonMetrics],
validation: &[ModelValidationSummary],
assets: &CompareHtmlAssets,
output_path: &Path,
) -> Result<(), VizError> {
write_report_with_validation_assets_and_bundle(
image_name,
metrics,
comparisons,
validation,
assets,
None,
output_path,
)
}
pub fn write_report_with_validation_assets_and_bundle(
image_name: &str,
metrics: &[ModelMetrics],
comparisons: &[ComparisonMetrics],
validation: &[ModelValidationSummary],
assets: &CompareHtmlAssets,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html =
render_html_with_bundle(image_name, metrics, comparisons, validation, assets, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_validation_report(
validation: &[ModelValidationSummary],
output_path: &Path,
) -> Result<(), VizError> {
write_validation_report_with_bundle(validation, None, output_path)
}
pub fn write_validation_report_with_bundle(
validation: &[ModelValidationSummary],
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_validation_html_with_bundle(validation, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_neighbors_report(
report: &NeighborsReport,
output_path: &Path,
) -> Result<(), VizError> {
write_neighbors_report_with_assets(report, &GalleryAssets::default(), output_path)
}
pub fn write_neighbors_report_with_assets(
report: &NeighborsReport,
assets: &GalleryAssets,
output_path: &Path,
) -> Result<(), VizError> {
write_neighbors_report_with_assets_and_bundle(report, assets, None, output_path)
}
pub fn write_neighbors_report_with_assets_and_bundle(
report: &NeighborsReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_neighbors_html_with_bundle(report, assets, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_similarity_report(
report: &SimilarityReport,
output_path: &Path,
) -> Result<(), VizError> {
write_similarity_report_with_assets(report, &GalleryAssets::default(), output_path)
}
pub fn write_similarity_report_with_assets(
report: &SimilarityReport,
assets: &GalleryAssets,
output_path: &Path,
) -> Result<(), VizError> {
write_similarity_report_with_assets_and_bundle(report, assets, None, output_path)
}
pub fn write_similarity_report_with_assets_and_bundle(
report: &SimilarityReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_similarity_html_with_bundle(report, assets, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_drift_report(report: &DriftReport, output_path: &Path) -> Result<(), VizError> {
write_drift_report_with_assets(report, &GalleryAssets::default(), output_path)
}
pub fn write_drift_report_with_assets(
report: &DriftReport,
assets: &GalleryAssets,
output_path: &Path,
) -> Result<(), VizError> {
write_drift_report_with_assets_and_bundle(report, assets, None, output_path)
}
pub fn write_drift_report_with_assets_and_bundle(
report: &DriftReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_drift_html_with_bundle(report, assets, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_profile_report(report: &ProfileReport, output_path: &Path) -> Result<(), VizError> {
write_profile_report_with_assets(report, &GalleryAssets::default(), output_path)
}
pub fn write_profile_report_with_assets(
report: &ProfileReport,
assets: &GalleryAssets,
output_path: &Path,
) -> Result<(), VizError> {
write_profile_report_with_assets_and_bundle(report, assets, None, output_path)
}
pub fn write_profile_report_with_assets_and_bundle(
report: &ProfileReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_profile_html_with_bundle(report, assets, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_model_catalog_report(
report: &ModelCatalogReport,
output_path: &Path,
) -> Result<(), VizError> {
write_model_catalog_report_with_bundle(report, None, output_path)
}
pub fn write_model_catalog_report_with_bundle(
report: &ModelCatalogReport,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_model_catalog_html_with_bundle(report, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_model_download_report(
report: &ModelDownloadReport,
output_path: &Path,
) -> Result<(), VizError> {
write_model_download_report_with_bundle(report, None, output_path)
}
pub fn write_model_download_report_with_bundle(
report: &ModelDownloadReport,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_model_download_html_with_bundle(report, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
pub fn write_inspect_report(report: &InspectReport, output_path: &Path) -> Result<(), VizError> {
write_inspect_report_with_assets(report, &InspectHtmlAssets::default(), output_path)
}
pub fn write_inspect_report_with_assets(
report: &InspectReport,
assets: &InspectHtmlAssets,
output_path: &Path,
) -> Result<(), VizError> {
write_inspect_report_with_assets_and_bundle(report, assets, None, output_path)
}
pub fn write_inspect_report_with_assets_and_bundle(
report: &InspectReport,
assets: &InspectHtmlAssets,
bundle: Option<&OutputArtifactManifest>,
output_path: &Path,
) -> Result<(), VizError> {
let html = render_inspect_html_with_bundle(report, assets, bundle);
std::fs::write(output_path, &html)
.map_err(|e| VizError::Html(format!("Failed to write {}: {e}", output_path.display())))?;
Ok(())
}
#[cfg(test)]
fn render_html(
image_name: &str,
metrics: &[ModelMetrics],
comparisons: &[ComparisonMetrics],
validation: &[ModelValidationSummary],
assets: &CompareHtmlAssets,
) -> String {
render_html_with_bundle(image_name, metrics, comparisons, validation, assets, None)
}
fn render_html_with_bundle(
image_name_raw: &str,
metrics: &[ModelMetrics],
comparisons: &[ComparisonMetrics],
validation: &[ModelValidationSummary],
assets: &CompareHtmlAssets,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let image_name = escape_html(image_name_raw);
let overview = build_compare_overview(metrics, comparisons);
let comparison_rows = comparisons
.iter()
.map(|comparison| {
format!(
"<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{:.3}</td><td>{:.3}</td><td>{}</td><td>{}</td></tr>",
escape_html(&comparison.model_a),
escape_html(&comparison.model_b),
escape_html(&comparison.alignment.summary()),
comparison
.cls_cosine_sim
.map(|value| format!("{value:.3}"))
.unwrap_or_else(|| "N/A".to_string()),
comparison.linear_cka,
comparison.knn_overlap_k10,
comparison
.mean_patch_correspondence
.map(|value| format!("{value:.3}"))
.unwrap_or_else(|| "N/A".to_string()),
render_metric_caveats(comparison),
)
})
.collect::<Vec<_>>()
.join("\n");
let metrics_rows = metrics
.iter()
.map(|metric| {
format!(
"<tr><td>{}</td><td>{}/{}</td><td>{}</td><td>{:.2}</td><td>{}</td><td>{}</td><td>{:.1}%</td><td>{}</td><td>{:.3}</td><td>{:.2}</td></tr>",
escape_html(&metric.model_name),
metric.effective_rank,
metric.embed_dim,
metric.dead_dimensions,
metric.patch_entropy,
metric
.attention_gini
.map(|value| format!("{value:.2}"))
.unwrap_or_else(|| "N/A".to_string()),
metric
.cls_l2_norm
.map(|value| format!("{value:.1}"))
.unwrap_or_else(|| "N/A".to_string()),
metric.top10_variance_pct,
metric.components_90pct,
metric.patch_isotropy,
metric.patch_uniformity,
)
})
.collect::<Vec<_>>()
.join("\n");
let validated_count = validation
.iter()
.filter(|summary| summary.status.label() == "validated")
.count();
let highlights = render_overview_cards(&overview);
let comparison_table = render_comparison_table(&comparison_rows);
let matrix_sections = render_matrix_sections(&overview);
let visual_section = if assets.is_empty() {
String::new()
} else {
format!(
r#"<div class="panel"><h2>Visual Artefacts</h2>{}</div>"#,
render_compare_asset_gallery(assets)
)
};
let bundle_section = bundle
.map(|bundle| {
format!(
r#"<div class="panel"><h2>Export Bundle</h2>{}</div>"#,
render_bundle_section(bundle)
)
})
.unwrap_or_default();
let validation_section = render_validation_section_body(
validation,
"No validation evidence was attached to this report.",
);
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>latent-inspector: {image_name}</title>
<style>
:root {{
color-scheme: dark;
--bg: #0d1117;
--panel: #161b22;
--panel-2: #11161d;
--text: #e6edf3;
--muted: #8b949e;
--accent: #79c0ff;
--ok: #3fb950;
--warn: #d29922;
--bad: #f85149;
--border: #30363d;
--chip: #1b2230;
}}
body {{ font-family: 'Segoe UI', system-ui, sans-serif; margin: 2rem; background: radial-gradient(circle at top, #182032 0%, var(--bg) 45%); color: var(--text); }}
h1, h2, h3 {{ color: var(--accent); }}
h3 {{ margin-top: 0; }}
.panel {{ background: linear-gradient(180deg, var(--panel), var(--panel-2)); border: 1px solid var(--border); border-radius: 16px; padding: 1rem 1.25rem; margin: 1rem 0 1.5rem; }}
table {{ border-collapse: collapse; width: 100%; margin: 1rem 0; }}
th {{ background: var(--panel); padding: 0.5rem 1rem; text-align: left; color: var(--accent); border-bottom: 2px solid var(--border); }}
td {{ padding: 0.4rem 1rem; border-bottom: 1px solid #21262d; vertical-align: top; }}
tr:hover td {{ background: #161b22; }}
.badge {{ font-size: 0.8em; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); background: #1f2937; text-transform: uppercase; letter-spacing: 0.04em; }}
.badge.validated {{ color: var(--ok); border-color: rgba(63,185,80,0.35); }}
.badge.partial, .badge.stale {{ color: var(--warn); border-color: rgba(210,153,34,0.35); }}
.badge.failed, .badge.unverified {{ color: var(--bad); border-color: rgba(248,81,73,0.35); }}
.caveat {{ color: var(--muted); margin: 0.3rem 0 0; }}
.stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0.9rem; margin-top: 1rem; }}
.stat-card {{ border: 1px solid var(--border); border-radius: 14px; padding: 0.9rem 1rem; background: rgba(255,255,255,0.03); }}
.stat-card strong {{ display: block; font-size: 1.4rem; margin-top: 0.25rem; }}
.chip-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0.8rem; }}
.chip-card {{ border: 1px solid var(--border); border-radius: 12px; padding: 0.9rem 1rem; background: var(--chip); }}
.chip-card p {{ margin: 0.3rem 0 0; color: var(--muted); }}
.matrix-grid {{ display: grid; gap: 1rem; }}
.matrix-card {{ border: 1px solid var(--border); border-radius: 12px; padding: 0.9rem 1rem; background: rgba(255,255,255,0.02); }}
.validation-grid {{ display: grid; gap: 1rem; }}
.validation-card {{ border: 1px solid var(--border); border-radius: 14px; padding: 1rem; background: rgba(255,255,255,0.02); }}
.bundle-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }}
.bundle-card {{ border: 1px solid var(--border); border-radius: 14px; padding: 1rem; background: rgba(255,255,255,0.02); }}
.json-block {{ margin: 0; padding: 0.85rem 1rem; border-radius: 12px; border: 1px solid var(--border); background: rgba(0,0,0,0.25); overflow-x: auto; white-space: pre-wrap; word-break: break-word; }}
.delta-list {{ margin: 0.5rem 0 0; padding-left: 1.1rem; color: var(--muted); }}
.delta-list li {{ margin: 0.25rem 0; }}
.empty-state {{ color: var(--muted); margin: 0; }}
code {{ color: #c9d1d9; }}
</style>
</head>
<body>
<h1>latent-inspector</h1>
<div class="panel">
<p>Image: <code>{image_name}</code></p>
<div class="stats-grid">
<div class="stat-card"><span>Models analysed</span><strong>{}</strong></div>
<div class="stat-card"><span>Pairwise comparisons</span><strong>{}</strong></div>
<div class="stat-card"><span>Validated reports</span><strong>{}</strong></div>
</div>
</div>
<div class="panel">
<h2>Highlights</h2>
{}
</div>
<div class="panel">
<h2>Per-model metrics</h2>
<table>
<thead>
<tr>
<th>Model</th>
<th>Repr. rank</th>
<th>Dead dims</th>
<th>Patch entropy</th>
<th>Attention Gini</th>
<th>CLS L2 norm</th>
<th>Top-10 var%</th>
<th>Components@90%</th>
<th>Patch isotropy</th>
<th>Patch uniformity</th>
</tr>
</thead>
<tbody>
{metrics_rows}
</tbody>
</table>
</div>
<div class="panel">
<h2>Cross-model comparison</h2>
{}
</div>
<div class="panel">
<h2>Pairwise matrices</h2>
{}
</div>
{}
{}
<div class="panel">
<h2>Validation Summary</h2>
{}
</div>
<footer style="margin-top:3rem;color:#8b949e;font-size:0.8em">
Generated by <a href="https://github.com/AbdelStark/latent-inspector" style="color:#79c0ff">latent-inspector</a>
</footer>
</body>
</html>"#,
metrics.len(),
comparisons.len(),
validated_count,
highlights,
comparison_table,
matrix_sections,
visual_section,
bundle_section,
validation_section,
)
}
fn render_validation_html_with_bundle(
validation: &[ModelValidationSummary],
bundle: Option<&OutputArtifactManifest>,
) -> String {
render_html_with_bundle(
"validation-run",
&[],
&[],
validation,
&CompareHtmlAssets::default(),
bundle,
)
}
fn render_neighbors_html_with_bundle(
report: &NeighborsReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let rows = report
.neighbors
.iter()
.map(|neighbor| {
format!(
"<tr><td>{}</td><td><code>{}</code></td><td>{:.4}</td></tr>",
neighbor.rank,
escape_html(&neighbor.image),
neighbor.similarity,
)
})
.collect::<Vec<_>>()
.join("\n");
let table = if rows.is_empty() {
"<p class=\"empty-state\">No neighbors were returned for this query.</p>".to_string()
} else {
format!(
"<table><thead><tr><th>Rank</th><th>Image</th><th>Cosine similarity</th></tr></thead><tbody>{rows}</tbody></table>"
)
};
let mut sections = vec![("Top Matches", table)];
if !assets.is_empty() {
sections.push((
"Visual Artefacts",
render_gallery_assets(assets, "No neighbor chart was generated for this report."),
));
}
sections.push((
"Dataset Processing",
render_dataset_summary_html(&report.dataset_summary),
));
sections.push((
"Validation Summary",
render_validation_section_body(
std::slice::from_ref(&report.validation),
"No validation evidence was attached to this report.",
),
));
render_secondary_html(
"Nearest Neighbors",
&format!(
"Query <code>{}</code> searched with model <code>{}</code> using a <strong>{}</strong> global embedding.",
escape_html(&report.query_image),
escape_html(&report.model),
escape_html(report.embedding_basis.label()),
),
&[
(
"Embedding basis",
report.embedding_basis.label().to_string(),
),
("Requested k", report.requested_k.to_string()),
("Neighbors returned", report.neighbors.len().to_string()),
("Loaded images", report.dataset_summary.loaded.to_string()),
],
§ions,
bundle,
)
}
fn render_similarity_html_with_bundle(
report: &SimilarityReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let metrics = if report.metrics.is_empty() {
"<p class=\"empty-state\">No similarity metric was available for the selected mode.</p>"
.to_string()
} else {
let rows = report
.metrics
.iter()
.map(|metric| {
format!(
"<tr><td>{}</td><td>{:.4}</td></tr>",
escape_html(&metric.label),
metric.value
)
})
.collect::<Vec<_>>()
.join("\n");
format!("<table><thead><tr><th>Metric</th><th>Value</th></tr></thead><tbody>{rows}</tbody></table>")
};
let note = report
.note
.as_ref()
.map(|note| format!("<p class=\"caveat\">{}</p>", escape_html(note)))
.unwrap_or_default();
let mut sections = vec![("Similarity Metrics", format!("{metrics}{note}"))];
if !assets.is_empty() {
sections.push((
"Visual Artefacts",
render_gallery_assets(assets, "No similarity chart was generated for this report."),
));
}
sections.push((
"Dataset Processing",
render_dataset_summary_html(&report.dataset_summary),
));
sections.push((
"Validation Summary",
render_validation_section_body(
&report.validation,
"No validation evidence was attached to this report.",
),
));
render_secondary_html(
"Representation Similarity",
&format!(
"<code>{}</code> vs <code>{}</code> across <code>{}</code>. Dataset-level similarity metrics use <strong>{}</strong> embeddings.",
escape_html(&report.model_a),
escape_html(&report.model_b),
escape_html(&report.dataset),
escape_html(report.dataset_embedding_basis.label()),
),
&[
(
"Dataset embedding basis",
report.dataset_embedding_basis.label().to_string(),
),
("Requested mode", report.requested_metric.clone()),
("Loaded samples", report.sample_count.to_string()),
("Metrics reported", report.metrics.len().to_string()),
],
§ions,
bundle,
)
}
fn render_drift_html_with_bundle(
report: &DriftReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let rows = if report.drift.is_empty() {
"<p class=\"empty-state\">Need at least two checkpoints to compute consecutive drift.</p>"
.to_string()
} else {
let body = report
.drift
.iter()
.map(|step| {
format!(
"<tr><td><code>{}</code></td><td><code>{}</code></td><td>{:.4}</td></tr>",
escape_html(&step.from_checkpoint),
escape_html(&step.to_checkpoint),
step.linear_cka
)
})
.collect::<Vec<_>>()
.join("\n");
format!(
"<table><thead><tr><th>From</th><th>To</th><th>Linear CKA</th></tr></thead><tbody>{body}</tbody></table>"
)
};
let largest_shift = report
.largest_shift
.as_ref()
.map(|step| {
format!(
"<p><strong>Largest shift:</strong> <code>{}</code> → <code>{}</code> ({:.4})</p>",
escape_html(&step.from_checkpoint),
escape_html(&step.to_checkpoint),
step.linear_cka,
)
})
.unwrap_or_else(|| {
"<p class=\"empty-state\">No drift highlight available yet.</p>".to_string()
});
let summary_section = report
.dataset_summary
.as_ref()
.map(render_dataset_summary_html)
.unwrap_or_else(|| {
"<p class=\"empty-state\">Dataset processing did not run because no checkpoints were available.</p>"
.to_string()
});
let mut sections = vec![("Consecutive Drift", rows)];
if !assets.is_empty() {
sections.push((
"Visual Artefacts",
render_gallery_assets(assets, "No drift chart was generated for this report."),
));
}
sections.push(("Highlights", largest_shift));
sections.push(("Dataset Processing", summary_section));
sections.push((
"Validation Summary",
render_validation_section_body(
&report.validation,
"No validation evidence was attached to this report.",
),
));
render_secondary_html(
"Representation Drift",
&format!(
"Model <code>{}</code> across checkpoints in <code>{}</code>. Consecutive drift uses <strong>{}</strong> embeddings.",
escape_html(&report.model),
escape_html(&report.checkpoints),
escape_html(report.dataset_embedding_basis.label()),
),
&[
(
"Dataset embedding basis",
report.dataset_embedding_basis.label().to_string(),
),
("Checkpoints", report.checkpoint_names.len().to_string()),
("Consecutive comparisons", report.drift.len().to_string()),
(
"Mean CKA",
report
.mean_consecutive_cka
.map(|value| format!("{value:.4}"))
.unwrap_or_else(|| "N/A".to_string()),
),
],
§ions,
bundle,
)
}
#[cfg(test)]
fn render_model_catalog_html(report: &ModelCatalogReport) -> String {
render_model_catalog_html_with_bundle(report, None)
}
fn render_model_catalog_html_with_bundle(
report: &ModelCatalogReport,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let fixture_status = match (
&report.fixture_set,
&report.evidence_timestamp,
&report.fixture_error,
) {
(_, _, Some(error)) => format!("Unavailable: {}", escape_html(error)),
(Some(fixture_set), Some(timestamp), None) => format!(
"<code>{}</code> @ <code>{}</code>",
escape_html(fixture_set),
escape_html(timestamp),
),
(Some(fixture_set), None, None) => {
format!("<code>{}</code>", escape_html(fixture_set))
}
_ => "Unavailable".to_string(),
};
let sections = vec![
(
"Fixture Provenance",
format!(
"<p><strong>Validation fixtures:</strong> {}</p><p><strong>Evidence summary:</strong> {} approved, {} stale, {} missing, {} unverified</p><p><strong>Readiness summary:</strong> {} ready, {} need download, {} need evidence refresh, {} need validation, {} planned, {} blocked</p>",
fixture_status,
report.summary.evidence.approved,
report.summary.evidence.stale,
report.summary.evidence.missing,
report.summary.evidence.unverified,
report.summary.readiness.ready,
report.summary.readiness.needs_download,
report.summary.readiness.needs_evidence_refresh,
report.summary.readiness.needs_validation,
report.summary.readiness.planned,
report.summary.readiness.blocked,
),
),
("Model Inventory", render_model_catalog_table(report)),
];
render_secondary_html(
"Model inventory",
"Registry availability, cache state, and validation evidence for each known integration.",
&[
("Registered models", report.summary.total_models.to_string()),
("Ready in registry", report.summary.ready_models.to_string()),
("Ready to run", report.summary.readiness.ready.to_string()),
(
"Need download",
report.summary.readiness.needs_download.to_string(),
),
(
"Need evidence refresh",
report.summary.readiness.needs_evidence_refresh.to_string(),
),
(
"Need validation",
report.summary.readiness.needs_validation.to_string(),
),
("Planned", report.summary.readiness.planned.to_string()),
],
§ions,
bundle,
)
}
#[cfg(test)]
fn render_inspect_html(report: &InspectReport, assets: &InspectHtmlAssets) -> String {
render_inspect_html_with_bundle(report, assets, None)
}
fn render_inspect_html_with_bundle(
report: &InspectReport,
assets: &InspectHtmlAssets,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let metrics = &report.metrics;
let variance = &report.variance_spectrum;
let variance_rows = variance
.ratios
.iter()
.zip(variance.cumulative.iter())
.take(12)
.enumerate()
.map(|(index, (ratio, cumulative))| {
let width = (ratio.clamp(0.0, 1.0) * 100.0).round();
format!(
"<tr><td>PC{:02}</td><td>{:.2}%</td><td>{:.2}%</td><td><div class=\"spectrum-bar-track\"><div class=\"spectrum-bar-fill\" style=\"width:{width:.0}%\"></div></div></td></tr>",
index + 1,
ratio * 100.0,
cumulative * 100.0,
)
})
.collect::<Vec<_>>()
.join("\n");
let variance_table = if variance_rows.is_empty() {
"<p class=\"empty-state\">Variance spectrum was unavailable for this report.</p>"
.to_string()
} else {
format!(
"<table><thead><tr><th>Component</th><th>Variance</th><th>Cumulative</th><th>Profile</th></tr></thead><tbody>{variance_rows}</tbody></table>"
)
};
let mut sections = vec![
(
"Representation Metrics",
format!(
"<table><thead><tr><th>Metric</th><th>Value</th></tr></thead><tbody>\
<tr><td>Patch tokens</td><td>{}</td></tr>\
<tr><td>Embedding dimension</td><td>{}</td></tr>\
<tr><td>Effective rank</td><td>{}/{}</td></tr>\
<tr><td>Dead dimensions</td><td>{}</td></tr>\
<tr><td>Patch entropy</td><td>{:.3}</td></tr>\
<tr><td>Attention concentration (Gini)</td><td>{}</td></tr>\
<tr><td>CLS L2 norm</td><td>{}</td></tr>\
<tr><td>Patch norm mean ± std</td><td>{:.2} ± {:.2}</td></tr>\
<tr><td>Top-10 variance concentration</td><td>{:.1}%</td></tr>\
<tr><td>Patch isotropy</td><td>{:.3}</td></tr>\
<tr><td>Patch uniformity</td><td>{:.2}</td></tr>\
</tbody></table>",
metrics.n_patches,
metrics.embed_dim,
metrics.effective_rank,
metrics.embed_dim,
metrics.dead_dimensions,
metrics.patch_entropy,
metrics
.attention_gini
.map(|value| format!("{value:.3}"))
.unwrap_or_else(|| "N/A".to_string()),
metrics
.cls_l2_norm
.map(|value| format!("{value:.2}"))
.unwrap_or_else(|| "N/A".to_string()),
metrics.patch_norm_mean,
metrics.patch_norm_std,
metrics.top10_variance_pct,
metrics.patch_isotropy,
metrics.patch_uniformity,
),
),
(
"Variance Spectrum",
format!(
"<p>Top principal components of the patch embedding space for <code>{}</code>.</p>\
<div class=\"stats-grid\">\
<div class=\"stat-card\"><span>Components @ 90%</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Components @ 99%</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Top-10 concentration</span><strong>{:.1}%</strong></div>\
<div class=\"stat-card\"><span>Components shown</span><strong>{}</strong></div>\
</div>{}",
escape_html(&report.model),
variance.components_90pct,
variance.components_99pct,
variance.top10_concentration * 100.0,
variance.ratios.len(),
variance_table,
),
),
];
if let Some(attention) = &report.attention {
sections.push((
"Attention Summary",
format!(
"<p>{}</p>\
<div class=\"stats-grid\">\
<div class=\"stat-card\"><span>Mean Gini</span><strong>{:.3}</strong></div>\
<div class=\"stat-card\"><span>Layers</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Heads</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Tokens</span><strong>{}</strong></div>\
</div>",
escape_html(attention.map_basis.description()),
attention.mean_gini,
attention.layers,
attention.heads,
attention.token_count,
),
));
}
if !assets.is_empty() {
sections.push(("Visual Artefacts", render_inspect_asset_gallery(assets)));
}
sections.push((
"Validation Summary",
render_validation_section_body(
std::slice::from_ref(&report.validation),
"No validation evidence was attached to this report.",
),
));
render_secondary_html(
"Representation Inspect",
&format!(
"Image <code>{}</code> analysed with model <code>{}</code>.",
escape_html(&report.image),
escape_html(&report.model),
),
&[
("Model", report.model.clone()),
("Patch tokens", metrics.n_patches.to_string()),
("Embed dim", metrics.embed_dim.to_string()),
("Effective rank", metrics.effective_rank.to_string()),
("Patch isotropy", format!("{:.3}", metrics.patch_isotropy)),
],
§ions,
bundle,
)
}
fn render_inspect_asset_gallery(assets: &InspectHtmlAssets) -> String {
let mut visuals = Vec::new();
if let Some(asset) = &assets.source_image {
visuals.push(asset.clone());
}
if let Some(asset) = &assets.pca_image {
visuals.push(asset.clone());
}
if let Some(asset) = &assets.variance_image {
visuals.push(asset.clone());
}
if let Some(asset) = &assets.attention_image {
visuals.push(asset.clone());
}
if let Some(asset) = &assets.similarity_heatmap {
visuals.push(asset.clone());
}
render_visual_asset_cards(
&visuals,
"No inspect artefacts were generated for this report.",
)
}
fn render_compare_asset_gallery(assets: &CompareHtmlAssets) -> String {
let mut sections = Vec::new();
if !assets.source_images.is_empty() {
sections.push(format!(
"<div><h3>Source image</h3>{}</div>",
render_visual_asset_cards(
&assets.source_images,
"No source image preview was generated for this report.",
),
));
}
if !assets.pca_images.is_empty() {
sections.push(format!(
"<div><h3>Per-model PCA projections</h3>{}</div>",
render_visual_asset_cards(
&assets.pca_images,
"No per-model PCA projections were generated for this report.",
),
));
}
if !assets.heatmaps.is_empty() {
sections.push(format!(
"<div><h3>Pairwise metric heatmaps</h3>{}</div>",
render_visual_asset_cards(
&assets.heatmaps,
"No pairwise heatmaps were generated for this report.",
),
));
}
if sections.is_empty() {
"<p class=\"empty-state\">No compare artefacts were generated for this report.</p>"
.to_string()
} else {
sections.join("\n")
}
}
fn render_gallery_assets(assets: &GalleryAssets, empty_message: &str) -> String {
render_visual_asset_cards(&assets.visuals, empty_message)
}
fn render_visual_asset_cards(visuals: &[VisualAsset], empty_message: &str) -> String {
if visuals.is_empty() {
return format!(
"<p class=\"empty-state\">{}</p>",
escape_html(empty_message)
);
}
let cards = visuals
.iter()
.map(|asset| {
format!(
"<article class=\"inspect-asset-card\"><h3>{}</h3><img src=\"{}\" alt=\"{}\" /><p class=\"caveat\">{}</p></article>",
escape_html(&asset.title),
escape_html(&asset.path),
escape_html(&asset.alt),
escape_html(&asset.description),
)
})
.collect::<Vec<_>>()
.join("\n");
format!("<div class=\"inspect-asset-grid\">{cards}</div>")
}
fn render_validation_section_body(
validation: &[ModelValidationSummary],
empty_message: &str,
) -> String {
if validation.is_empty() {
format!(
"<p class=\"empty-state\">{}</p>",
escape_html(empty_message)
)
} else {
let validation_rows = validation
.iter()
.map(render_validation_row)
.collect::<Vec<_>>()
.join("\n");
format!("<div class=\"validation-grid\">{validation_rows}</div>")
}
}
fn render_validation_row(summary: &ModelValidationSummary) -> String {
let tensor_summary = render_validation_tensor_summary(summary);
let provenance = render_validation_provenance(summary);
let fixture_drifts = render_validation_fixture_drifts(summary);
let parity_deltas = render_validation_delta_table(summary);
let caveats = if summary.caveats.is_empty() {
"<p class=\"caveat\">No open caveats.</p>".to_string()
} else {
summary
.caveats
.iter()
.map(|caveat| format!("<p class=\"caveat\">{}</p>", escape_html(caveat)))
.collect::<Vec<_>>()
.join("")
};
format!(
"<article class=\"validation-card\"><div style=\"display:flex;justify-content:space-between;align-items:center;gap:1rem\"><strong>{}</strong><span class=\"badge {}\">{}</span></div><p>{}</p>{}<p><strong>Backend:</strong> {} <span class=\"badge {}\">{}</span></p><p class=\"caveat\">{}</p><p><strong>Preprocess:</strong> <span class=\"badge {}\">{}</span> {}</p><div><strong>Tensor semantics:</strong>{}</div><p><strong>Parity:</strong> <span class=\"badge {}\">{}</span> {}</p>{}{}{}</article>",
escape_html(&summary.model),
summary.status.label(),
summary.status.label(),
escape_html(&summary.recommendation),
provenance,
escape_html(summary.backend.kind.display_name()),
summary.backend.status.label(),
summary.backend.status.label(),
escape_html(&summary.backend.summary),
summary.preprocess.status.label(),
summary.preprocess.status.label(),
escape_html(&summary.preprocess.summary),
tensor_summary,
summary.parity.status.label(),
summary.parity.status.label(),
escape_html(&summary.parity.summary),
fixture_drifts,
caveats,
parity_deltas,
)
}
fn render_validation_tensor_summary(summary: &ModelValidationSummary) -> String {
if summary.tensors.is_empty() {
return "<p class=\"empty-state\">No tensor semantics were recorded for this report.</p>"
.to_string();
}
let items = summary
.tensors
.iter()
.map(|tensor| {
format!(
"<li><span class=\"badge {}\">{}</span> <code>{}</code> ({}) {} </li>",
tensor.status.label(),
tensor.status.label(),
escape_html(&tensor.name),
escape_html(&tensor.role),
escape_html(&tensor.summary),
)
})
.collect::<Vec<_>>()
.join("");
format!("<ul class=\"delta-list\">{items}</ul>")
}
fn render_validation_provenance(summary: &ModelValidationSummary) -> String {
format!(
"<div class=\"stats-grid\">\
<div class=\"stat-card\"><span>Evidence timestamp</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Fixture set</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Signals checked</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Signals drifted</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Drifted fixtures</span><strong>{}</strong></div>\
</div>\
<p><strong>Approved artifact:</strong> <code>{}</code></p>",
escape_html(&summary.evidence_timestamp),
escape_html(summary.parity.fixture_set.as_deref().unwrap_or("n/a")),
summary.parity.checked_signals,
summary.parity.drifted_signals,
summary.parity.drifted_fixtures.len(),
escape_html(summary.parity.artifact_id.as_deref().unwrap_or("n/a")),
)
}
fn render_validation_fixture_drifts(summary: &ModelValidationSummary) -> String {
if summary.parity.checked_signals == 0 {
return String::new();
}
if summary.parity.drifted_fixtures.is_empty() {
return "<p class=\"caveat\"><strong>Fixture drift:</strong> none across the checked fixture set.</p>"
.to_string();
}
let items = summary
.parity
.drifted_fixtures
.iter()
.map(|fixture| {
format!(
"<li><code>{}</code>: {} signal(s) drifted ({})</li>",
escape_html(&fixture.fixture_id),
fixture.signal_count,
escape_html(&fixture.signals.join(", ")),
)
})
.collect::<Vec<_>>()
.join("");
format!("<div><strong>Fixture drift:</strong><ul class=\"delta-list\">{items}</ul></div>")
}
fn render_validation_delta_table(summary: &ModelValidationSummary) -> String {
if summary.parity.deltas.is_empty() {
return String::new();
}
let rows = summary
.parity
.deltas
.iter()
.take(8)
.map(|delta| {
format!(
"<tr><td><code>{}</code></td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
escape_html(&delta.name),
escape_html(&delta.observed),
escape_html(&delta.expected),
delta
.abs_diff
.map(|value| format!("{value:.6}"))
.unwrap_or_else(|| "N/A".to_string()),
delta
.tolerance
.map(|value| format!("{value:.6}"))
.unwrap_or_else(|| "N/A".to_string()),
)
})
.collect::<Vec<_>>()
.join("\n");
let omitted = summary.parity.deltas.len().saturating_sub(8);
let footer = if omitted == 0 {
String::new()
} else {
format!(
"<p class=\"caveat\">{} additional parity deltas were omitted from this preview.</p>",
omitted
)
};
format!(
"<div><strong>Parity deltas</strong><table><thead><tr><th>Signal</th><th>Observed</th><th>Expected</th><th>Abs diff</th><th>Tolerance</th></tr></thead><tbody>{}</tbody></table>{}</div>",
rows,
footer,
)
}
fn render_overview_cards(overview: &CompareOverview) -> String {
let mut cards = overview
.model_highlights
.iter()
.map(|highlight| {
format!(
"<article class=\"chip-card\"><strong>{}</strong><p>{}: {}</p></article>",
escape_html(&highlight.label),
escape_html(&highlight.model),
escape_html(&highlight.value),
)
})
.collect::<Vec<_>>();
cards.extend(overview.comparison_highlights.iter().map(|highlight| {
format!(
"<article class=\"chip-card\"><strong>{}</strong><p>{} ↔ {}: {:.3}</p></article>",
escape_html(&highlight.label),
escape_html(&highlight.model_a),
escape_html(&highlight.model_b),
highlight.value,
)
}));
if cards.is_empty() {
"<p class=\"empty-state\">No summary highlights were generated for this report.</p>"
.to_string()
} else {
format!("<div class=\"chip-grid\">{}</div>", cards.join("\n"))
}
}
fn render_matrix_sections(overview: &CompareOverview) -> String {
let matrices = [
(
"CLS cosine similarity",
&overview.cls_cosine_matrix,
&overview.cls_cosine_support,
),
(
"Linear CKA",
&overview.linear_cka_matrix,
&overview.linear_cka_support,
),
(
"k-NN overlap (k=10)",
&overview.knn_overlap_matrix,
&overview.knn_overlap_support,
),
(
"Mean patch correspondence",
&overview.correspondence_matrix,
&overview.correspondence_support,
),
];
let cards = matrices
.into_iter()
.filter(|(_, matrix, _)| matrix.len() >= 2)
.map(|(title, matrix, support)| {
format!(
"<article class=\"matrix-card\"><h3>{}</h3>{}{}</article>",
escape_html(title),
render_matrix_support_summary(support),
render_matrix_table(matrix),
)
})
.collect::<Vec<_>>();
if cards.is_empty() {
"<p class=\"empty-state\">Pairwise matrices require at least two models.</p>".to_string()
} else {
format!("<div class=\"matrix-grid\">{}</div>", cards.join("\n"))
}
}
fn render_matrix_support_summary(support: &PairwiseMetricSupport) -> String {
let mut lines = vec![format!(
"<p class=\"caveat\"><strong>Comparable model pairs:</strong> {}/{}</p>",
support.supported_pairs, support.total_pairs
)];
if support.unavailable_pairs > 0 {
lines.push(format!(
"<p class=\"caveat\"><strong>Unavailable pairs:</strong> {}</p>",
support.unavailable_pairs
));
lines.extend(support.unavailable_reasons.iter().map(|reason| {
format!(
"<p class=\"caveat\">x{} {}</p>",
reason.count,
escape_html(&reason.reason),
)
}));
}
lines.join("")
}
fn render_comparison_table(rows: &str) -> String {
if rows.is_empty() {
"<p class=\"empty-state\">No pairwise comparisons were generated.</p>".to_string()
} else {
format!(
"<table>
<thead>
<tr>
<th>Model A</th>
<th>Model B</th>
<th>Patch alignment</th>
<th>CLS cosine sim</th>
<th>Linear CKA</th>
<th>k-NN overlap (k=10)</th>
<th>Mean patch corr.</th>
<th>Metric caveats</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>"
)
}
}
fn render_model_catalog_table(report: &ModelCatalogReport) -> String {
if report.entries.is_empty() {
return "<p class=\"empty-state\">No models are registered.</p>".to_string();
}
let rows = report
.entries
.iter()
.map(|entry| {
format!(
"<tr><td><code>{}</code></td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
escape_html(&entry.name),
escape_html(&entry.phase),
escape_html(&entry.availability_status.to_string()),
escape_html(entry.readiness_status.label()),
escape_html(entry.runtime_support.label()),
escape_html(entry.evidence_status.label()),
escape_html(entry.cache_status.label()),
escape_html(&entry.verification_label),
escape_html(&entry.method.to_string()),
entry.params_m,
render_model_catalog_details(entry),
)
})
.collect::<Vec<_>>()
.join("\n");
format!(
"<table><thead><tr><th>Name</th><th>Phase</th><th>Status</th><th>Readiness</th><th>Runtime</th><th>Evidence</th><th>Cache</th><th>Verify</th><th>Method</th><th>Params (M)</th><th>Details</th></tr></thead><tbody>{rows}</tbody></table>"
)
}
fn render_model_catalog_details(entry: &crate::models::ModelInventoryEntry) -> String {
let mut parts = vec![
format!(
"<p><strong>Availability:</strong> {}</p>",
escape_html(&entry.availability_note),
),
format!(
"<p><strong>Architecture:</strong> {} | {}x{} | dim {} | {} layers / {} heads</p>",
escape_html(&entry.architecture),
entry.input_size,
entry.input_size,
entry.embed_dim,
entry.num_layers,
entry.num_heads,
),
format!(
"<p><strong>Readiness:</strong> {} ({})</p>",
escape_html(&entry.readiness_summary),
escape_html(entry.readiness_status.label()),
),
format!(
"<p><strong>Runtime:</strong> {} ({})</p>",
escape_html(&entry.runtime_summary),
escape_html(entry.runtime_support.label()),
),
format!(
"<p><strong>Evidence:</strong> {} [{} @ {}]</p>",
escape_html(&entry.evidence_summary),
escape_html(&entry.approved_fixture_set),
escape_html(&entry.approved_evidence_timestamp),
),
format!(
"<p><strong>Cache:</strong> {}</p>",
escape_html(&entry.cache_summary),
),
format!(
"<p><strong>Artifacts:</strong> {} total, {} usable, {} verified, {} pending verification, {} missing, {} invalid, {} unusable, {} unknown</p>",
entry.artifact_summary.total,
entry.artifact_summary.usable,
entry.artifact_summary.verified,
entry.artifact_summary.pending_verification,
entry.artifact_summary.missing,
entry.artifact_summary.invalid,
entry.artifact_summary.unusable,
entry.artifact_summary.unknown,
),
];
if let Some(note) = &entry.verification_note {
parts.push(format!(
"<p><strong>Verification note:</strong> {}</p>",
escape_html(note),
));
}
if !entry.evidence_details.is_empty() {
let items = entry
.evidence_details
.iter()
.map(|detail| format!("<li>{}</li>", escape_html(detail)))
.collect::<Vec<_>>()
.join("");
parts.push(format!(
"<p><strong>Evidence details:</strong></p><ul>{items}</ul>"
));
}
if !entry.next_steps.is_empty() {
let items = entry
.next_steps
.iter()
.map(|step| format!("<li>{}</li>", escape_html(step)))
.collect::<Vec<_>>()
.join("");
parts.push(format!(
"<p><strong>Next steps:</strong></p><ul>{items}</ul>"
));
}
if !entry.artifacts.is_empty() {
let rows = entry
.artifacts
.iter()
.map(|artifact| {
let byte_size = artifact
.byte_size
.map(|value| value.to_string())
.unwrap_or_else(|| "N/A".to_string());
let verification = artifact
.verification_note
.as_ref()
.map(|note| {
format!(
"{}<br/><span class=\"caveat\">{}</span>",
escape_html(&artifact.verification_label),
escape_html(note),
)
})
.unwrap_or_else(|| escape_html(&artifact.verification_label));
format!(
"<tr><td><code>{}</code></td><td>{}</td><td>{}</td><td>{}</td><td><code>{}</code></td><td><a href=\"{}\">{}</a><br/><span class=\"caveat\">{}</span></td></tr>",
escape_html(&artifact.relative_path),
escape_html(artifact.cache_status.label()),
byte_size,
verification,
escape_html(&artifact.absolute_path),
sanitize_href(&artifact.url),
escape_html(&artifact.url),
escape_html(&artifact.cache_summary),
)
})
.collect::<Vec<_>>()
.join("");
parts.push(format!(
"<p><strong>Artifact details:</strong></p><table><thead><tr><th>Relative path</th><th>Cache status</th><th>Bytes</th><th>Verify</th><th>Expected path</th><th>Source / cache detail</th></tr></thead><tbody>{rows}</tbody></table>"
));
}
parts.join("")
}
fn render_model_download_html_with_bundle(
report: &ModelDownloadReport,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let outcome = format!(
"<p><strong>Action:</strong> {}</p><p>{}</p>",
escape_html(report.action.label()),
escape_html(&report.summary),
);
let readiness = format!(
"<p><strong>Readiness:</strong> {} ({})</p>{}",
escape_html(&report.entry.readiness_summary),
escape_html(report.entry.readiness_status.label()),
render_string_list(
&report.entry.next_steps,
"No follow-up steps are required for this model.",
),
);
let sections = vec![
("Outcome", outcome),
("Readiness", readiness),
("Artifact Changes", render_model_download_changes(report)),
("Model Details", render_model_catalog_details(&report.entry)),
];
render_secondary_html(
"Model download",
"Download outcome and readiness for one model integration.",
&[
("Model", report.model.clone()),
("Action", report.action.label().to_string()),
(
"Downloaded artifacts",
report.downloaded_artifact_count().to_string(),
),
(
"Repaired artifacts",
report.repaired_artifact_count().to_string(),
),
(
"Readiness",
report.entry.readiness_status.label().to_string(),
),
],
§ions,
bundle,
)
}
fn render_model_download_changes(report: &ModelDownloadReport) -> String {
if report.artifact_changes.is_empty() {
return "<p class=\"empty-state\">No artifact transitions were recorded.</p>".to_string();
}
let rows = report
.artifact_changes
.iter()
.map(|artifact| {
let byte_size = artifact
.byte_size
.map(|value| value.to_string())
.unwrap_or_else(|| "N/A".to_string());
format!(
"<tr><td><code>{}</code></td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
escape_html(&artifact.relative_path),
escape_html(artifact.previous_status.label()),
escape_html(artifact.current_status.label()),
if artifact.downloaded { "yes" } else { "no" },
byte_size,
escape_html(&artifact.cache_summary),
)
})
.collect::<Vec<_>>()
.join("");
format!(
"<table><thead><tr><th>Artifact</th><th>Before</th><th>After</th><th>Downloaded</th><th>Bytes</th><th>Detail</th></tr></thead><tbody>{rows}</tbody></table>"
)
}
fn render_string_list(items: &[String], empty: &str) -> String {
if items.is_empty() {
return format!("<p class=\"empty-state\">{}</p>", escape_html(empty));
}
let rows = items
.iter()
.map(|item| format!("<li>{}</li>", escape_html(item)))
.collect::<Vec<_>>()
.join("");
format!("<ul>{rows}</ul>")
}
fn render_metric_caveats(comparison: &ComparisonMetrics) -> String {
let mut caveats = Vec::new();
if let Some(note) = &comparison.alignment.note {
caveats.push(format!("Patch alignment: {}", escape_html(note)));
}
caveats.extend(comparison.metric_caveats.iter().map(|caveat| {
format!(
"{}: {}",
escape_html(&caveat.label),
escape_html(&caveat.reason)
)
}));
if caveats.is_empty() {
"None".to_string()
} else {
caveats.join("<br/>")
}
}
fn render_matrix_table(matrix: &PairwiseMatrix) -> String {
let header = matrix
.labels
.iter()
.map(|label| format!("<th>{}</th>", escape_html(label)))
.collect::<Vec<_>>()
.join("");
let rows = matrix
.rows
.iter()
.enumerate()
.map(|(row_idx, row)| {
let cells = row
.iter()
.map(|value| {
format!(
"<td>{}</td>",
value
.map(|value| format!("{value:.3}"))
.unwrap_or_else(|| "N/A".to_string())
)
})
.collect::<Vec<_>>()
.join("");
format!(
"<tr><th>{}</th>{}</tr>",
escape_html(&matrix.labels[row_idx]),
cells
)
})
.collect::<Vec<_>>()
.join("");
format!(
"<table><thead><tr><th></th>{}</tr></thead><tbody>{}</tbody></table>",
header, rows
)
}
fn render_profile_html_with_bundle(
report: &ProfileReport,
assets: &GalleryAssets,
bundle: Option<&OutputArtifactManifest>,
) -> String {
let space_rows = format!(
concat!(
"<tr><td>Isotropy (cosine)</td><td>{:.4}</td><td>1 - avg pairwise cosine similarity. Higher = more uniform spread.</td></tr>\n",
"<tr><td>Isotropy (partition)</td><td>{:.4}</td><td>Singular-value uniformity. Higher = less dominated by top components.</td></tr>\n",
"<tr><td>Uniformity</td><td>{:.4}</td><td>Wang & Isola (2020). More negative = better spread on hypersphere.</td></tr>\n",
"<tr><td>Intrinsic dimensionality</td><td>{:.1}</td><td>MLE estimate (Levina & Bickel 2004) of manifold dimension.</td></tr>",
),
report.space_metrics.isotropy_cosine,
report.space_metrics.isotropy_partition,
report.space_metrics.uniformity,
report.space_metrics.intrinsic_dimensionality,
);
let space_table = format!(
"<table><thead><tr><th>Metric</th><th>Value</th><th>Interpretation</th></tr></thead><tbody>{space_rows}</tbody></table>"
);
let aggregate_table = if report.aggregate_metrics.is_empty() {
"<p class=\"empty-state\">No per-image metrics were collected.</p>".to_string()
} else {
let rows = report
.aggregate_metrics
.iter()
.map(|agg| {
format!(
"<tr><td>{}</td><td>{:.3}</td><td>{:.3}</td><td>{:.3}</td><td>{:.3}</td></tr>",
escape_html(&agg.label),
agg.mean,
agg.std,
agg.min,
agg.max,
)
})
.collect::<Vec<_>>()
.join("\n");
format!(
"<table><thead><tr><th>Metric</th><th>Mean</th><th>Std</th><th>Min</th><th>Max</th></tr></thead><tbody>{rows}</tbody></table>"
)
};
let mut sections = vec![
("Space-Level Metrics", space_table),
("Per-Image Metric Aggregates", aggregate_table),
];
if !assets.is_empty() {
sections.push((
"Visual Artefacts",
render_gallery_assets(assets, "No charts were generated for this report."),
));
}
sections.push((
"Dataset Processing",
render_dataset_summary_html(&report.dataset_summary),
));
sections.push((
"Validation Summary",
render_validation_section_body(
std::slice::from_ref(&report.validation),
"No validation evidence was attached to this report.",
),
));
render_secondary_html(
"Representation Profile",
&format!(
"Model <code>{}</code> profiled across {} images from <code>{}</code>. Global embeddings use <strong>{}</strong>.",
escape_html(&report.model),
report.sample_count,
escape_html(&report.dataset),
escape_html(report.embedding_basis.label()),
),
&[
("Model", report.model.clone()),
("Images", report.sample_count.to_string()),
("Embedding dim", report.embed_dim.to_string()),
(
"Embedding basis",
report.embedding_basis.label().to_string(),
),
(
"Intrinsic dim",
format!("{:.1}", report.space_metrics.intrinsic_dimensionality),
),
],
§ions,
bundle,
)
}
fn render_secondary_html(
title: &str,
subtitle: &str,
stats: &[(&str, String)],
sections: &[(&str, String)],
bundle: Option<&OutputArtifactManifest>,
) -> String {
let escaped_title = escape_html(title);
let stats_html = render_secondary_stats(stats);
let bundle_section = bundle
.map(|bundle| {
format!(
"<div class=\"panel\"><h2>Export Bundle</h2>{}</div>",
render_bundle_section(bundle)
)
})
.unwrap_or_default();
let sections_html = sections
.iter()
.map(|(heading, body)| {
format!(
"<div class=\"panel\"><h2>{}</h2>{}</div>",
escape_html(heading),
body
)
})
.collect::<Vec<_>>()
.join("\n");
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>latent-inspector: {escaped_title}</title>
<style>
:root {{
color-scheme: dark;
--bg: #0d1117;
--panel: #161b22;
--panel-2: #11161d;
--text: #e6edf3;
--muted: #8b949e;
--accent: #79c0ff;
--ok: #3fb950;
--warn: #d29922;
--bad: #f85149;
--border: #30363d;
}}
body {{ font-family: 'Segoe UI', system-ui, sans-serif; margin: 2rem; background: radial-gradient(circle at top, #182032 0%, var(--bg) 45%); color: var(--text); }}
h1, h2 {{ color: var(--accent); }}
.panel {{ background: linear-gradient(180deg, var(--panel), var(--panel-2)); border: 1px solid var(--border); border-radius: 16px; padding: 1rem 1.25rem; margin: 1rem 0 1.5rem; }}
.stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0.9rem; margin-top: 1rem; }}
.stat-card {{ border: 1px solid var(--border); border-radius: 14px; padding: 0.9rem 1rem; background: rgba(255,255,255,0.03); }}
.stat-card strong {{ display: block; font-size: 1.4rem; margin-top: 0.25rem; }}
table {{ border-collapse: collapse; width: 100%; margin: 1rem 0; }}
th {{ background: var(--panel); padding: 0.5rem 1rem; text-align: left; color: var(--accent); border-bottom: 2px solid var(--border); }}
td {{ padding: 0.4rem 1rem; border-bottom: 1px solid #21262d; vertical-align: top; }}
tr:hover td {{ background: #161b22; }}
.badge {{ font-size: 0.8em; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); background: #1f2937; text-transform: uppercase; letter-spacing: 0.04em; }}
.badge.validated {{ color: var(--ok); border-color: rgba(63,185,80,0.35); }}
.badge.partial, .badge.stale {{ color: var(--warn); border-color: rgba(210,153,34,0.35); }}
.badge.failed, .badge.unverified {{ color: var(--bad); border-color: rgba(248,81,73,0.35); }}
.validation-grid {{ display: grid; gap: 1rem; }}
.validation-card {{ border: 1px solid var(--border); border-radius: 14px; padding: 1rem; background: rgba(255,255,255,0.02); }}
.bundle-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }}
.bundle-card {{ border: 1px solid var(--border); border-radius: 14px; padding: 1rem; background: rgba(255,255,255,0.02); }}
.json-block {{ margin: 0; padding: 0.85rem 1rem; border-radius: 12px; border: 1px solid var(--border); background: rgba(0,0,0,0.25); overflow-x: auto; white-space: pre-wrap; word-break: break-word; }}
.inspect-asset-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; }}
.inspect-asset-card {{ border: 1px solid var(--border); border-radius: 14px; padding: 1rem; background: rgba(255,255,255,0.02); }}
.inspect-asset-card h3 {{ margin-bottom: 0.75rem; }}
.inspect-asset-card img {{ width: 100%; display: block; border-radius: 10px; border: 1px solid var(--border); background: rgba(255,255,255,0.02); }}
.delta-list {{ margin: 0.5rem 0 0; padding-left: 1.1rem; color: var(--muted); }}
.delta-list li {{ margin: 0.25rem 0; }}
.spectrum-bar-track {{ width: 100%; min-width: 140px; height: 0.75rem; border-radius: 999px; background: rgba(255,255,255,0.08); overflow: hidden; }}
.spectrum-bar-fill {{ height: 100%; border-radius: inherit; background: linear-gradient(90deg, #58a6ff, #3fb950); }}
.empty-state, .caveat, li {{ color: var(--muted); }}
ul {{ margin: 0.6rem 0 0; padding-left: 1.2rem; }}
code {{ color: #c9d1d9; }}
</style>
</head>
<body>
<h1>latent-inspector</h1>
<div class="panel">
<h2>{}</h2>
<p>{}</p>
{}
</div>
{}
{}
</body>
</html>"#,
&escaped_title, subtitle, stats_html, bundle_section, sections_html
)
}
fn render_secondary_stats(stats: &[(&str, String)]) -> String {
if stats.is_empty() {
return String::new();
}
let cards = stats
.iter()
.map(|(label, value)| {
format!(
"<div class=\"stat-card\"><span>{}</span><strong>{}</strong></div>",
escape_html(label),
escape_html(value),
)
})
.collect::<Vec<_>>()
.join("\n");
format!("<div class=\"stats-grid\">{cards}</div>")
}
fn render_bundle_section(bundle: &OutputArtifactManifest) -> String {
let primary_artifact = bundle
.primary_artifact
.as_deref()
.unwrap_or("N/A")
.to_string();
let validation_status = bundle
.validation_summary
.as_ref()
.map(|summary| summary.overall_status.label().to_string())
.unwrap_or_else(|| "not attached".to_string());
let artifact_count = bundle.artifacts.len() + 1;
let validation_details = bundle
.validation_summary
.as_ref()
.map(render_bundle_validation_overview)
.unwrap_or_else(|| {
"<p class=\"empty-state\">No validation overview was attached to this bundle.</p>"
.to_string()
});
format!(
"<div class=\"stats-grid\">\
<div class=\"stat-card\"><span>Command</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Format</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Primary artifact</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Bundle files</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Validation overview</span><strong>{}</strong></div>\
</div>\
<div class=\"bundle-grid\" style=\"margin-top:1rem\">\
<article class=\"bundle-card\"><h3>Bundle Files</h3>{}</article>\
<article class=\"bundle-card\"><h3>Run Context</h3>{}</article>\
<article class=\"bundle-card\"><h3>Bundle Summary</h3>{}</article>\
<article class=\"bundle-card\"><h3>Validation Overview</h3>{}</article>\
</div>",
escape_html(&bundle.command),
escape_html(&bundle.format.to_string()),
escape_html(&primary_artifact),
artifact_count,
escape_html(&validation_status),
render_bundle_artifact_table(bundle),
render_json_block(
bundle.context.as_ref(),
"This bundle did not record command context.",
),
render_json_block(
bundle.summary.as_ref(),
"This bundle did not record a top-line summary.",
),
validation_details,
)
}
fn render_bundle_artifact_table(bundle: &OutputArtifactManifest) -> String {
let mut rows = bundle
.artifacts
.iter()
.map(|artifact| render_bundle_artifact_row(artifact, bundle.primary_artifact.as_deref()))
.collect::<Vec<_>>();
rows.push(format!(
"<tr><td><a href=\"artifacts.json\"><code>artifacts.json</code></a></td><td>{}</td><td>Artifact manifest</td><td>supporting</td><td>—</td><td>—</td></tr>",
render_artifact_kind_label(ArtifactKind::Json),
));
format!(
"<table><thead><tr><th>Path</th><th>Kind</th><th>Label</th><th>Role</th><th>Size</th><th>SHA-256</th></tr></thead><tbody>{}</tbody></table>",
rows.join("")
)
}
fn render_bundle_artifact_row(
artifact: &crate::viz::manifest::OutputArtifact,
primary_artifact: Option<&str>,
) -> String {
let role = if primary_artifact == Some(artifact.path.as_str()) {
"primary"
} else {
"supporting"
};
format!(
"<tr><td><a href=\"{}\"><code>{}</code></a></td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
escape_html(&artifact.path),
escape_html(&artifact.path),
render_artifact_kind_label(artifact.kind),
escape_html(&artifact.label),
role,
render_bundle_artifact_size(artifact.byte_size),
render_bundle_artifact_digest(artifact.sha256.as_deref()),
)
}
fn render_artifact_kind_label(kind: ArtifactKind) -> &'static str {
match kind {
ArtifactKind::Json => "json",
ArtifactKind::Html => "html",
ArtifactKind::Png => "png",
}
}
fn render_bundle_artifact_size(byte_size: Option<u64>) -> String {
let Some(byte_size) = byte_size else {
return "—".to_string();
};
const KIB: f64 = 1024.0;
const MIB: f64 = 1024.0 * 1024.0;
if byte_size < 1024 {
format!("{byte_size} B")
} else if (byte_size as f64) < MIB {
format!("{:.1} KiB", byte_size as f64 / KIB)
} else {
format!("{:.1} MiB", byte_size as f64 / MIB)
}
}
fn render_bundle_artifact_digest(sha256: Option<&str>) -> String {
let Some(sha256) = sha256 else {
return "—".to_string();
};
let preview = if sha256.len() > 16 {
format!("{}…", &sha256[..16])
} else {
sha256.to_string()
};
format!(
"<code title=\"{}\">{}</code>",
escape_html(sha256),
escape_html(&preview),
)
}
fn render_bundle_validation_overview(
overview: &crate::viz::manifest::ArtifactValidationOverview,
) -> String {
format!(
"<div class=\"stats-grid\">\
<div class=\"stat-card\"><span>Overall status</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Validated</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Partial</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Stale</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Unverified</span><strong>{}</strong></div>\
<div class=\"stat-card\"><span>Failed</span><strong>{}</strong></div>\
</div>",
escape_html(overview.overall_status.label()),
overview.validated,
overview.partial,
overview.stale,
overview.unverified,
overview.failed,
)
}
fn render_json_block(value: Option<&serde_json::Value>, empty_message: &str) -> String {
match value {
Some(value) => {
let pretty = serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string());
format!("<pre class=\"json-block\">{}</pre>", escape_html(&pretty))
}
None => format!(
"<p class=\"empty-state\">{}</p>",
escape_html(empty_message)
),
}
}
fn render_dataset_summary_html(summary: &DatasetProcessingSummary) -> String {
let skipped = if summary.skipped_examples.is_empty() {
"<p class=\"empty-state\">No skipped images.</p>".to_string()
} else {
let items = summary
.skipped_examples
.iter()
.map(|item| {
format!(
"<li><code>{}</code>: {}</li>",
escape_html(&item.path),
escape_html(&item.reason),
)
})
.collect::<Vec<_>>()
.join("");
format!("<ul>{items}</ul>")
};
format!(
"<p><strong>Supported files:</strong> {}</p><p><strong>Loaded images:</strong> {}</p><p><strong>Skipped images:</strong> {}</p>{}",
summary.discovered,
summary.loaded,
summary.skipped,
skipped,
)
}
fn escape_html(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
fn sanitize_href(url: &str) -> String {
let lower = url.trim().to_lowercase();
if lower.starts_with("javascript:")
|| lower.starts_with("data:")
|| lower.starts_with("vbscript:")
{
"#blocked".to_string()
} else {
escape_html(url)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dataset::{DatasetProcessingSummary, SkippedImage};
use crate::models::{build_model_catalog, EvidenceStatus};
use crate::validation::report::{
CheckSummary, ModelValidationSummary, ParitySignalDelta, ParityValidationSummary,
TensorValidationSummary, ValidationStatus,
};
use crate::viz::manifest::{ArtifactKind, OutputArtifactManifest};
use crate::viz::report::{
DriftReport, DriftStep, InspectReport, NeighborMatch, NeighborsReport,
SimilarityMetricValue, SimilarityReport, VarianceSpectrumReport,
};
use crate::viz::OutputFormat;
use crate::TEST_PROCESS_ENV_LOCK;
use std::ffi::OsString;
use tempfile::tempdir;
struct CacheDirEnvGuard {
previous: Option<OsString>,
}
impl CacheDirEnvGuard {
fn set(path: &std::path::Path) -> Self {
let previous = std::env::var_os("LATENT_INSPECTOR_CACHE_DIR");
std::env::set_var("LATENT_INSPECTOR_CACHE_DIR", path);
Self { previous }
}
}
impl Drop for CacheDirEnvGuard {
fn drop(&mut self) {
match &self.previous {
Some(path) => std::env::set_var("LATENT_INSPECTOR_CACHE_DIR", path),
None => std::env::remove_var("LATENT_INSPECTOR_CACHE_DIR"),
}
}
}
fn validation_summary(model: &str) -> ModelValidationSummary {
ModelValidationSummary::from_checks(
model,
"2026-03-28T00:00:00Z",
CheckSummary::validated("Preprocess matches contract."),
vec![TensorValidationSummary {
name: "last_hidden_state".into(),
role: "patch embeddings".into(),
status: ValidationStatus::Validated,
summary: "Tensor semantics match the registry contract.".into(),
}],
ParityValidationSummary::new(
ValidationStatus::Validated,
"Reference parity matches approved evidence.",
),
)
}
fn failed_validation_summary(model: &str) -> ModelValidationSummary {
let parity = ParityValidationSummary {
status: ValidationStatus::Failed,
summary: "Reference parity drift detected in 2 checked signals.".into(),
artifact_id: Some("dinov2-vit-l14:standard:2026-03-27T12:00:00Z".into()),
fixture_set: Some("standard".into()),
checked_signals: 0,
drifted_signals: 0,
deltas: vec![
ParitySignalDelta {
name: "fixtures.gradient-224.patch_signature[0]".into(),
observed: "9.900000".into(),
expected: "0.100000".into(),
abs_diff: Some(9.8),
tolerance: Some(0.001),
},
ParitySignalDelta {
name: "fixtures.gradient-224.cls_signature[1]".into(),
observed: "8.800000".into(),
expected: "0.200000".into(),
abs_diff: Some(8.6),
tolerance: Some(0.001),
},
],
drifted_fixtures: Vec::new(),
}
.with_diagnostics(73);
ModelValidationSummary::from_checks(
model,
"2026-03-27T12:00:00Z",
CheckSummary::validated("Preprocess matches contract."),
vec![TensorValidationSummary {
name: "last_hidden_state".into(),
role: "patch embeddings".into(),
status: ValidationStatus::Validated,
summary: "Tensor semantics match the registry contract.".into(),
}],
parity,
)
}
fn inspect_report(model: &str) -> InspectReport {
InspectReport {
image: "fixture.png".into(),
model: model.into(),
metrics: ModelMetrics {
model_name: model.into(),
n_patches: 256,
embed_dim: 1024,
effective_rank: 212,
dead_dimensions: 6,
patch_entropy: 5.47,
attention_gini: Some(0.58),
cls_l2_norm: Some(14.3),
patch_norm_mean: 6.1,
patch_norm_std: 0.8,
top10_variance_pct: 28.5,
components_90pct: 41,
patch_isotropy: 0.65,
patch_uniformity: -2.1,
},
validation: validation_summary(model),
variance_spectrum: VarianceSpectrumReport {
ratios: vec![0.28, 0.19, 0.13, 0.09],
cumulative: vec![0.28, 0.47, 0.60, 0.69],
components_90pct: 41,
components_99pct: 88,
top10_concentration: 0.62,
},
attention: Some(crate::viz::report::InspectAttentionSummary {
mean_gini: 0.58,
layers: 2,
heads: 4,
token_count: 257,
map_basis: crate::extract::AttentionMapBasis::ClsToPatch,
}),
}
}
fn model_catalog_report() -> ModelCatalogReport {
let _lock = TEST_PROCESS_ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let cache_dir = tempdir().unwrap();
let _guard = CacheDirEnvGuard::set(cache_dir.path());
build_model_catalog(None)
}
fn bundle_manifest(command: &str) -> OutputArtifactManifest {
OutputArtifactManifest::new(command, OutputFormat::Html)
.with_primary_artifact("report.html")
.with_context(serde_json::json!({
"model": "dinov2-vit-l14",
"image": "fixture.png",
}))
.with_summary(serde_json::json!({
"models": 1,
"artifacts": ["report.html", "compare.json"],
}))
.add_artifact("report.html", ArtifactKind::Html, "Primary report")
.add_artifact("compare.json", ArtifactKind::Json, "Structured report data")
.with_validation(&[validation_summary("dinov2-vit-l14")])
}
#[test]
fn test_html_contains_title() {
let html = render_html("test.jpg", &[], &[], &[], &CompareHtmlAssets::default());
assert!(html.contains("test.jpg"));
}
#[test]
fn test_html_contains_pairwise_matrices_when_comparisons_exist() {
let metrics = vec![
ModelMetrics {
model_name: "dinov2".into(),
n_patches: 256,
embed_dim: 1024,
effective_rank: 256,
dead_dimensions: 4,
patch_entropy: 5.4,
attention_gini: Some(0.62),
cls_l2_norm: Some(12.0),
patch_norm_mean: 6.0,
patch_norm_std: 1.0,
top10_variance_pct: 20.0,
components_90pct: 48,
patch_isotropy: 0.65,
patch_uniformity: -2.1,
},
ModelMetrics {
model_name: "clip".into(),
n_patches: 256,
embed_dim: 1024,
effective_rank: 192,
dead_dimensions: 8,
patch_entropy: 4.8,
attention_gini: Some(0.47),
cls_l2_norm: Some(10.0),
patch_norm_mean: 5.0,
patch_norm_std: 1.1,
top10_variance_pct: 35.0,
components_90pct: 36,
patch_isotropy: 0.65,
patch_uniformity: -2.1,
},
];
let comparisons = vec![ComparisonMetrics {
model_a: "dinov2".into(),
model_b: "clip".into(),
alignment: crate::analysis::ComparisonAlignment {
patch_count_a: 256,
patch_count_b: 256,
compared_patch_count: 256,
note: None,
},
cls_cosine_sim: Some(0.55),
linear_cka: 0.71,
knn_overlap_k10: 0.32,
mean_patch_correspondence: Some(0.48),
metric_caveats: Vec::new(),
}];
let html = render_html(
"test.jpg",
&metrics,
&comparisons,
&[],
&CompareHtmlAssets::default(),
);
assert!(html.contains("Pairwise matrices"));
assert!(html.contains("Linear CKA"));
assert!(html.contains("Strongest CKA alignment"));
}
#[test]
fn test_html_renders_comparison_alignment_and_caveats() {
let metrics = vec![
ModelMetrics {
model_name: "dinov2".into(),
n_patches: 256,
embed_dim: 1024,
effective_rank: 256,
dead_dimensions: 4,
patch_entropy: 5.4,
attention_gini: Some(0.62),
cls_l2_norm: Some(12.0),
patch_norm_mean: 6.0,
patch_norm_std: 1.0,
top10_variance_pct: 20.0,
components_90pct: 48,
patch_isotropy: 0.65,
patch_uniformity: -2.1,
},
ModelMetrics {
model_name: "mae".into(),
n_patches: 196,
embed_dim: 1024,
effective_rank: 192,
dead_dimensions: 8,
patch_entropy: 4.8,
attention_gini: None,
cls_l2_norm: None,
patch_norm_mean: 5.0,
patch_norm_std: 1.1,
top10_variance_pct: 35.0,
components_90pct: 36,
patch_isotropy: 0.65,
patch_uniformity: -2.1,
},
];
let comparisons = vec![ComparisonMetrics {
model_a: "dinov2".into(),
model_b: "mae".into(),
alignment: crate::analysis::ComparisonAlignment {
patch_count_a: 256,
patch_count_b: 196,
compared_patch_count: 196,
note: Some(
"Compared the first 196 shared patches because the models expose different patch grids (256 vs 196)."
.into(),
),
},
cls_cosine_sim: None,
linear_cka: 0.71,
knn_overlap_k10: 0.32,
mean_patch_correspondence: Some(0.48),
metric_caveats: vec![crate::analysis::MetricCaveat {
key: "cls_cosine_sim".into(),
label: "CLS cosine similarity".into(),
reason: "Unavailable because only one model exposes a CLS token.".into(),
}],
}];
let html = render_html(
"test.jpg",
&metrics,
&comparisons,
&[],
&CompareHtmlAssets::default(),
);
assert!(html.contains("Patch alignment"));
assert!(html.contains("196 shared patches (from 256 vs 196)"));
assert!(html.contains("Metric caveats"));
assert!(html.contains("only one model exposes a CLS token"));
}
#[test]
fn test_html_renders_compare_visual_assets() {
let assets = CompareHtmlAssets {
source_images: vec![VisualAsset {
title: "Input image".into(),
path: "input_image.png".into(),
alt: "Input image".into(),
description: "Source image used for comparison.".into(),
}],
pca_images: vec![VisualAsset {
title: "dinov2 PCA projection".into(),
path: "dinov2_pca.png".into(),
alt: "dinov2 PCA projection".into(),
description: "Top-three PCA components.".into(),
}],
heatmaps: vec![VisualAsset {
title: "Linear CKA heatmap".into(),
path: "linear_cka.png".into(),
alt: "Linear CKA heatmap".into(),
description: "Pairwise linear CKA matrix.".into(),
}],
};
let html = render_html("test.jpg", &[], &[], &[], &assets);
assert!(html.contains("Visual Artefacts"));
assert!(html.contains("Source image"));
assert!(html.contains("input_image.png"));
assert!(html.contains("Per-model PCA projections"));
assert!(html.contains("Pairwise metric heatmaps"));
assert!(html.contains("dinov2_pca.png"));
assert!(html.contains("linear_cka.png"));
}
#[test]
fn test_html_renders_export_bundle_section() {
let manifest = bundle_manifest("compare");
let html = render_html_with_bundle(
"test.jpg",
&[],
&[],
&[validation_summary("dinov2-vit-l14")],
&CompareHtmlAssets::default(),
Some(&manifest),
);
assert!(html.contains("Export Bundle"));
assert!(html.contains("artifacts.json"));
assert!(html.contains("Bundle Files"));
assert!(html.contains("Run Context"));
assert!(html.contains("compare.json"));
assert!(html.contains("dinov2-vit-l14"));
}
#[test]
fn test_validation_html_renders_fixture_drift_details() {
let html = render_validation_html_with_bundle(
&[failed_validation_summary("dinov2-vit-l14")],
None,
);
assert!(html.contains("Approved artifact"));
assert!(html.contains("Signals checked"));
assert!(html.contains("Fixture drift"));
assert!(html.contains("gradient-224"));
assert!(html.contains("Parity deltas"));
assert!(html.contains("patch_signature[0]"));
assert!(html.contains("Abs diff"));
}
#[test]
fn test_render_inspect_html_includes_variance_spectrum_and_validation() {
let report = inspect_report("dinov2-vit-l14");
let assets = InspectHtmlAssets {
source_image: Some(VisualAsset {
title: "Input image".into(),
path: "input_image.png".into(),
alt: "Input image".into(),
description: "Source image used for inspect.".into(),
}),
pca_image: Some(VisualAsset {
title: "PCA Projection".into(),
path: "dinov2-vit-l14_pca.png".into(),
alt: "Inspect PCA projection".into(),
description: "Top-three PCA projection.".into(),
}),
variance_image: Some(VisualAsset {
title: "Variance Chart".into(),
path: "dinov2-vit-l14_variance.png".into(),
alt: "Inspect variance spectrum chart".into(),
description: "Variance profile.".into(),
}),
attention_image: Some(VisualAsset {
title: "Attention Overlay".into(),
path: "dinov2-vit-l14_attention.png".into(),
alt: "Inspect attention overlay".into(),
description: "Attention projected back onto the input image.".into(),
}),
similarity_heatmap: Some(VisualAsset {
title: "Patch Self-Similarity".into(),
path: "dinov2-vit-l14_similarity.png".into(),
alt: "Patch self-similarity heatmap".into(),
description: "Cosine similarity between all patch pairs.".into(),
}),
};
let html = render_inspect_html(&report, &assets);
assert!(html.contains("Representation Inspect"));
assert!(html.contains("Variance Spectrum"));
assert!(html.contains("Attention Summary"));
assert!(html.contains("Components @ 99%"));
assert!(html.contains("PC01"));
assert!(html.contains("Visual Artefacts"));
assert!(html.contains("input_image.png"));
assert!(html.contains("dinov2-vit-l14_pca.png"));
assert!(html.contains("dinov2-vit-l14_attention.png"));
assert!(html.contains("dinov2-vit-l14_similarity.png"));
assert!(html.contains("Validation Summary"));
assert!(html.contains("dinov2-vit-l14"));
}
#[test]
fn test_secondary_html_renders_export_bundle_section() {
let manifest = bundle_manifest("inspect");
let html = render_inspect_html_with_bundle(
&inspect_report("dinov2-vit-l14"),
&InspectHtmlAssets::default(),
Some(&manifest),
);
assert!(html.contains("Export Bundle"));
assert!(html.contains("Validation Overview"));
assert!(html.contains("report.html"));
assert!(html.contains("compare.json"));
}
#[test]
fn test_escape_html() {
assert_eq!(escape_html("<script>"), "<script>");
}
#[test]
fn test_write_report() {
let dir = tempdir().unwrap();
let path = dir.path().join("report.html");
write_report("img.jpg", &[], &[], &path).unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("<!DOCTYPE html>"));
}
#[test]
fn test_write_inspect_report() {
let dir = tempdir().unwrap();
let path = dir.path().join("inspect.html");
let report = inspect_report("dinov2-vit-l14");
write_inspect_report(&report, &path).unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("Variance Spectrum"));
assert!(content.contains("fixture.png"));
}
#[test]
fn test_write_inspect_report_with_assets() {
let dir = tempdir().unwrap();
let path = dir.path().join("inspect-assets.html");
let report = inspect_report("dinov2-vit-l14");
let assets = InspectHtmlAssets {
source_image: Some(VisualAsset {
title: "Input image".into(),
path: "input_image.png".into(),
alt: "Input image".into(),
description: "Source image used for inspect.".into(),
}),
pca_image: Some(VisualAsset {
title: "PCA Projection".into(),
path: "dinov2-vit-l14_pca.png".into(),
alt: "Inspect PCA projection".into(),
description: "Top-three PCA projection.".into(),
}),
variance_image: Some(VisualAsset {
title: "Variance Chart".into(),
path: "dinov2-vit-l14_variance.png".into(),
alt: "Inspect variance spectrum chart".into(),
description: "Variance profile.".into(),
}),
attention_image: Some(VisualAsset {
title: "Attention Overlay".into(),
path: "dinov2-vit-l14_attention.png".into(),
alt: "Inspect attention overlay".into(),
description: "Attention projected back onto the input image.".into(),
}),
similarity_heatmap: Some(VisualAsset {
title: "Patch Self-Similarity".into(),
path: "dinov2-vit-l14_similarity.png".into(),
alt: "Patch self-similarity heatmap".into(),
description: "Cosine similarity between all patch pairs.".into(),
}),
};
write_inspect_report_with_assets(&report, &assets, &path).unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("Visual Artefacts"));
assert!(content.contains("input_image.png"));
assert!(content.contains("dinov2-vit-l14_variance.png"));
assert!(content.contains("dinov2-vit-l14_attention.png"));
}
#[test]
fn test_write_neighbors_report() {
let dir = tempdir().unwrap();
let path = dir.path().join("neighbors.html");
let report = NeighborsReport {
query_image: "query.png".into(),
dataset: "dataset".into(),
model: "dinov2".into(),
embedding_basis: crate::extract::EmbeddingBasis::ClsToken,
requested_k: 2,
dataset_summary: DatasetProcessingSummary {
discovered: 3,
loaded: 2,
skipped: 1,
skipped_examples: vec![SkippedImage {
path: "broken.png".into(),
reason: "decode failed".into(),
}],
},
neighbors: vec![NeighborMatch {
rank: 1,
image: "class-a/leaf".into(),
similarity: 0.91,
}],
validation: validation_summary("dinov2"),
};
write_neighbors_report(&report, &path).unwrap();
let html = std::fs::read_to_string(&path).unwrap();
assert!(html.contains("Nearest Neighbors"));
assert!(html.contains("class-a/leaf"));
assert!(html.contains("Validation Summary"));
assert!(html.contains("CLS token"));
assert!(html.contains("Reference parity matches approved evidence."));
}
#[test]
fn test_write_similarity_report() {
let dir = tempdir().unwrap();
let path = dir.path().join("similarity.html");
let report = SimilarityReport {
model_a: "dinov2".into(),
model_b: "clip".into(),
dataset: "dataset".into(),
dataset_embedding_basis: crate::extract::EmbeddingBasis::MeanPatch,
requested_metric: "all".into(),
sample_count: 2,
dataset_summary: DatasetProcessingSummary {
discovered: 2,
loaded: 2,
skipped: 0,
skipped_examples: Vec::new(),
},
metrics: vec![SimilarityMetricValue {
key: "linear_cka".into(),
label: "Linear CKA".into(),
value: 0.77,
}],
note: Some("N/A (CLS tokens unavailable)".into()),
validation: vec![validation_summary("dinov2"), validation_summary("clip")],
};
write_similarity_report(&report, &path).unwrap();
let html = std::fs::read_to_string(&path).unwrap();
assert!(html.contains("Representation Similarity"));
assert!(html.contains("Linear CKA"));
assert!(html.contains("CLS tokens unavailable"));
assert!(html.contains("Validation Summary"));
assert!(html.contains("Mean patch"));
assert!(html.contains("dinov2"));
assert!(html.contains("clip"));
}
#[test]
fn test_write_drift_report() {
let dir = tempdir().unwrap();
let path = dir.path().join("drift.html");
let report = DriftReport::new(
"dinov2",
"checkpoints",
"dataset",
crate::extract::EmbeddingBasis::MeanPatch,
vec!["step-1".into(), "step-2".into()],
Some(DatasetProcessingSummary {
discovered: 2,
loaded: 2,
skipped: 0,
skipped_examples: Vec::new(),
}),
vec![DriftStep {
from_checkpoint: "step-1".into(),
to_checkpoint: "step-2".into(),
linear_cka: 0.88,
}],
vec![validation_summary("step-1"), validation_summary("step-2")],
);
write_drift_report(&report, &path).unwrap();
let html = std::fs::read_to_string(&path).unwrap();
assert!(html.contains("Representation Drift"));
assert!(html.contains("step-1"));
assert!(html.contains("Largest shift"));
assert!(html.contains("Validation Summary"));
assert!(html.contains("Mean patch"));
}
#[test]
fn test_write_model_catalog_report() {
let dir = tempdir().unwrap();
let path = dir.path().join("models.html");
let report = model_catalog_report();
write_model_catalog_report(&report, &path).unwrap();
let html = std::fs::read_to_string(&path).unwrap();
assert!(html.contains("Model inventory"));
assert!(html.contains("Fixture Provenance"));
assert!(html.contains("dinov2-vit-l14"));
assert!(html.contains("approved"));
}
#[test]
fn test_model_catalog_report_includes_summary_counts() {
let report = model_catalog_report();
let html = render_model_catalog_html(&report);
assert_eq!(report.summary.evidence.approved, 4);
assert_eq!(report.summary.evidence.unverified, 4);
assert!(html.contains("Registered models"));
assert!(html.contains("Ready to run"));
assert!(html.contains("Readiness summary:"));
assert!(html.contains(EvidenceStatus::Approved.label()));
}
}