///|
/// Deterministic production audit primitives for generated artwork.
///
/// The audit layer is intentionally independent from the export backends. It
/// turns a composition, raster preview, or SVG document into measurable
/// findings that can be consumed by batch jobs, CI checks, and design tools.
pub(all) enum AuditSeverity {
AuditInfo
AuditNotice
AuditWarning
AuditError
} derive(Eq, Debug, ToJson)
///|
pub(all) enum AuditDomain {
AuditCanvas
AuditGeometry
AuditColor
AuditLayer
AuditRaster
AuditExport
AuditPerformance
} derive(Eq, Debug, ToJson)
///|
pub(all) struct AuditFinding {
code : String
domain : AuditDomain
severity : AuditSeverity
message : String
value : Double
target : Double
passed : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct AuditMetric {
name : String
domain : AuditDomain
mut value : Double
mut minimum : Double
mut maximum : Double
mut weight : Double
mut passed : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct RenderAuditConfig {
minimum_canvas : Double
maximum_canvas : Double
minimum_layers : Int
maximum_layers : Int
minimum_motifs : Int
maximum_motifs : Int
minimum_coverage : Double
maximum_coverage : Double
minimum_contrast : Double
minimum_color_count : Int
maximum_color_count : Int
minimum_raster_occupancy : Double
maximum_svg_bytes : Int
maximum_path_points : Int
strict : Bool
} derive(Eq, Debug, ToJson)
///|
pub(all) struct RenderAuditReport {
mut name : String
mut seed : UInt
findings : Array[AuditFinding]
metrics : Array[AuditMetric]
mut score : Double
mut passed : Bool
mut elapsed_hint : Int
} derive(Eq, Debug, ToJson)
///|
pub(all) struct AuditBatchSummary {
total : Int
passed : Int
failed : Int
score : Double
warnings : Int
errors : Int
reports : Array[RenderAuditReport]
} derive(Eq, Debug, ToJson)
///|
fn ra_clamp(value : Double) -> Double {
value.max(0.0).min(1.0)
}
///|
fn ra_positive(value : Double) -> Double {
if value < 0.0 {
0.0
} else {
value
}
}
///|
fn ra_severity_weight(severity : AuditSeverity) -> Double {
match severity {
AuditInfo => 0.0
AuditNotice => 0.05
AuditWarning => 0.2
AuditError => 1.0
}
}
///|
fn ra_domain_name(domain : AuditDomain) -> String {
match domain {
AuditCanvas => "canvas"
AuditGeometry => "geometry"
AuditColor => "color"
AuditLayer => "layer"
AuditRaster => "raster"
AuditExport => "export"
AuditPerformance => "performance"
}
}
///|
fn ra_severity_name(severity : AuditSeverity) -> String {
match severity {
AuditInfo => "info"
AuditNotice => "notice"
AuditWarning => "warning"
AuditError => "error"
}
}
///|
pub fn RenderAuditConfig::default() -> RenderAuditConfig {
{
minimum_canvas: 64.0,
maximum_canvas: 16384.0 * 16384.0,
minimum_layers: 1,
maximum_layers: 128,
minimum_motifs: 1,
maximum_motifs: 100000,
minimum_coverage: 0.01,
maximum_coverage: 0.98,
minimum_contrast: 1.2,
minimum_color_count: 1,
maximum_color_count: 64,
minimum_raster_occupancy: 0.001,
maximum_svg_bytes: 16 * 1024 * 1024,
maximum_path_points: 200000,
strict: false,
}
}
///|
pub fn RenderAuditConfig::strict() -> RenderAuditConfig {
{
..RenderAuditConfig::default(),
minimum_canvas: 128.0,
minimum_layers: 1,
minimum_motifs: 2,
minimum_coverage: 0.03,
maximum_coverage: 0.92,
minimum_contrast: 2.0,
minimum_color_count: 2,
maximum_path_points: 100000,
strict: true,
}
}
///|
pub fn RenderAuditReport::new(name : String, seed : UInt) -> RenderAuditReport {
{
name,
seed,
findings: [],
metrics: [],
score: 1.0,
passed: true,
elapsed_hint: 0,
}
}
///|
pub fn RenderAuditReport::add_metric(
self : RenderAuditReport,
name : String,
domain : AuditDomain,
value : Double,
minimum : Double,
maximum : Double,
weight : Double,
) -> Unit {
let passed = value >= minimum && value <= maximum
self.metrics.push({
name,
domain,
value,
minimum,
maximum,
weight: ra_positive(weight),
passed,
})
if !passed {
self.score = (self.score - ra_positive(weight) * 0.25).max(0.0)
}
}
///|
pub fn RenderAuditReport::add_finding(
self : RenderAuditReport,
code : String,
domain : AuditDomain,
severity : AuditSeverity,
message : String,
value : Double,
target : Double,
) -> Unit {
let passed = match severity {
AuditInfo | AuditNotice => true
AuditWarning => !self.strict_failure()
AuditError => false
}
self.findings.push({ code, domain, severity, message, value, target, passed })
if severity == AuditError {
self.passed = false
self.score = (self.score - 0.25).max(0.0)
} else if severity == AuditWarning {
self.score = (self.score - 0.05).max(0.0)
}
}
///|
fn RenderAuditReport::strict_failure(self : RenderAuditReport) -> Bool {
self.passed && false
}
///|
pub fn RenderAuditReport::finalize(
self : RenderAuditReport,
strict : Bool,
) -> Unit {
let mut errors = 0
let mut warnings = 0
for finding in self.findings {
match finding.severity {
AuditError => errors += 1
AuditWarning => warnings += 1
AuditInfo | AuditNotice => ()
}
}
self.passed = errors == 0 && (!strict || warnings == 0)
self.score = ra_clamp(self.score)
}
///|
pub fn RenderAuditReport::error_count(self : RenderAuditReport) -> Int {
self.findings.fold(init=0, fn(total, finding) {
if finding.severity == AuditError {
total + 1
} else {
total
}
})
}
///|
pub fn RenderAuditReport::warning_count(self : RenderAuditReport) -> Int {
self.findings.fold(init=0, fn(total, finding) {
if finding.severity == AuditWarning {
total + 1
} else {
total
}
})
}
///|
pub fn RenderAuditReport::info_count(self : RenderAuditReport) -> Int {
self.findings.fold(init=0, fn(total, finding) {
if finding.severity == AuditInfo {
total + 1
} else {
total
}
})
}
///|
pub fn RenderAuditReport::metric_value(
self : RenderAuditReport,
name : String,
) -> Double? {
for metric in self.metrics {
if metric.name == name {
return Some(metric.value)
}
}
None
}
///|
pub fn RenderAuditReport::to_json_string(
self : RenderAuditReport,
indent? : Int,
) -> String {
ToJson::to_json(self).stringify(indent=indent.unwrap_or(0))
}
///|
pub fn RenderAuditReport::to_text(self : RenderAuditReport) -> String {
let builder = StringBuilder()
builder.write_string("render-audit: \{self.name}\n")
builder.write_string(
"seed=\{self.seed} score=\{self.score} passed=\{self.passed}\n",
)
builder.write_string(
"metrics=\{self.metrics.length()} findings=\{self.findings.length()}\n",
)
for metric in self.metrics {
builder.write_string(
"metric \{ra_domain_name(metric.domain)}.\{metric.name}=\{metric.value} range=[\{metric.minimum},\{metric.maximum}] passed=\{metric.passed}\n",
)
}
for finding in self.findings {
builder.write_string(
"finding [\{ra_severity_name(finding.severity)}] \{finding.code}: \{finding.message}\n",
)
}
builder.to_string()
}
///|
fn ra_canvas_area(composition : Composition) -> Double {
composition.width.max(0.0) * composition.height.max(0.0)
}
///|
fn ra_motif_count(composition : Composition) -> Int {
composition.layers.fold(init=0, fn(total, layer) {
total + layer.motifs.length()
})
}
///|
fn ra_visible_layer_count(composition : Composition) -> Int {
composition.layers.fold(init=0, fn(total, layer) {
if layer.visible {
total + 1
} else {
total
}
})
}
///|
fn ra_unique_fills(composition : Composition) -> Array[String] {
let colors = []
for layer in composition.layers {
for motif in layer.motifs {
if !colors.contains(motif.style.fill) {
colors.push(motif.style.fill)
}
}
}
colors
}
///|
fn ra_geometry_bounds(composition : Composition) -> Rect {
let points = []
for layer in composition.layers {
for motif in layer.motifs {
for point in motif.transformed_path().flatten(subdivisions=6) {
points.push(point)
}
}
}
if points.length() == 0 {
Rect::new(0.0, 0.0, 0.0, 0.0)
} else {
bounds_of_points(points)
}
}
///|
fn ra_geometry_point_count(composition : Composition) -> Int {
composition.layers.fold(init=0, fn(total, layer) {
total +
layer.motifs.fold(init=0, fn(layer_total, motif) {
layer_total + motif.transformed_path().flatten(subdivisions=4).length()
})
})
}
///|
fn ra_occupied_area(composition : Composition) -> Double {
let mut area = 0.0
for layer in composition.layers {
if layer.visible {
for motif in layer.motifs {
area += path_quality_area(motif.transformed_path(), 6)
}
}
}
area
}
///|
fn ra_total_perimeter(composition : Composition) -> Double {
let mut perimeter = 0.0
for layer in composition.layers {
for motif in layer.motifs {
perimeter += path_quality_perimeter(motif.transformed_path(), 6, true)
}
}
perimeter
}
///|
fn ra_average_opacity(composition : Composition) -> Double {
let mut total = 0.0
let mut count = 0
for layer in composition.layers {
for motif in layer.motifs {
total += motif.style.opacity * layer.opacity
count += 1
}
}
if count == 0 {
0.0
} else {
total / count.to_double()
}
}
///|
fn ra_center_offset(composition : Composition) -> Double {
let bounds = ra_geometry_bounds(composition)
let center = Vec2::new(composition.width / 2.0, composition.height / 2.0)
let actual = Vec2::new(
bounds.x + bounds.width / 2.0,
bounds.y + bounds.height / 2.0,
)
actual.distance(center) / composition.width.max(composition.height).max(1.0)
}
///|
fn ra_add_canvas_findings(
report : RenderAuditReport,
composition : Composition,
config : RenderAuditConfig,
) -> Unit {
let area = ra_canvas_area(composition)
report.add_metric(
"canvas_area",
AuditCanvas,
area,
config.minimum_canvas,
config.maximum_canvas,
0.2,
)
if area < config.minimum_canvas {
report.add_finding(
"CANVAS_SMALL",
AuditCanvas,
AuditError,
"canvas area is below the production minimum",
area,
config.minimum_canvas,
)
} else if area > config.maximum_canvas {
report.add_finding(
"CANVAS_LARGE",
AuditCanvas,
AuditWarning,
"canvas area may exceed the target memory budget",
area,
config.maximum_canvas,
)
} else {
report.add_finding(
"CANVAS_OK",
AuditCanvas,
AuditInfo,
"canvas dimensions are within the configured range",
area,
config.minimum_canvas,
)
}
if composition.width <= 0.0 || composition.height <= 0.0 {
report.add_finding(
"CANVAS_DIMENSIONS",
AuditCanvas,
AuditError,
"canvas width and height must be positive",
area,
1.0,
)
}
if composition.width.is_nan() || composition.height.is_nan() {
report.add_finding(
"CANVAS_NAN",
AuditCanvas,
AuditError,
"canvas dimensions contain NaN",
area,
0.0,
)
}
}
///|
fn ra_add_layer_findings(
report : RenderAuditReport,
composition : Composition,
config : RenderAuditConfig,
) -> Unit {
let layers = composition.layers.length()
let visible = ra_visible_layer_count(composition)
let motifs = ra_motif_count(composition)
report.add_metric(
"layers",
AuditLayer,
layers.to_double(),
config.minimum_layers.to_double(),
config.maximum_layers.to_double(),
0.15,
)
report.add_metric(
"motifs",
AuditLayer,
motifs.to_double(),
config.minimum_motifs.to_double(),
config.maximum_motifs.to_double(),
0.2,
)
report.add_metric(
"visible_layers",
AuditLayer,
visible.to_double(),
0.0,
layers.to_double().max(1.0),
0.05,
)
if layers < config.minimum_layers {
report.add_finding(
"LAYER_EMPTY",
AuditLayer,
AuditError,
"composition has too few layers",
layers.to_double(),
config.minimum_layers.to_double(),
)
}
if layers > config.maximum_layers {
report.add_finding(
"LAYER_MANY",
AuditLayer,
audit_layer_severity(config),
"composition has more layers than the configured budget",
layers.to_double(),
config.maximum_layers.to_double(),
)
}
if visible == 0 && layers > 0 {
report.add_finding(
"LAYER_HIDDEN",
AuditLayer,
AuditWarning,
"all layers are hidden and the export will be visually empty",
0.0,
1.0,
)
}
if motifs < config.minimum_motifs {
report.add_finding(
"MOTIF_EMPTY",
AuditGeometry,
AuditError,
"composition contains no production motif",
motifs.to_double(),
config.minimum_motifs.to_double(),
)
}
if motifs > config.maximum_motifs {
report.add_finding(
"MOTIF_MANY",
AuditPerformance,
AuditWarning,
"motif count may increase render cost",
motifs.to_double(),
config.maximum_motifs.to_double(),
)
}
}
///|
fn audit_layer_severity(config : RenderAuditConfig) -> AuditSeverity {
if config.strict {
AuditError
} else {
AuditWarning
}
}
///|
fn ra_add_geometry_findings(
report : RenderAuditReport,
composition : Composition,
config : RenderAuditConfig,
) -> Unit {
let area = ra_canvas_area(composition).max(1.0)
let occupied = ra_occupied_area(composition)
let coverage = (occupied / area).min(1.0)
let points = ra_geometry_point_count(composition)
let perimeter = ra_total_perimeter(composition)
let bounds = ra_geometry_bounds(composition)
let offset = ra_center_offset(composition)
report.add_metric(
"coverage",
AuditGeometry,
coverage,
config.minimum_coverage,
config.maximum_coverage,
0.25,
)
report.add_metric(
"point_count",
AuditGeometry,
points.to_double(),
1.0,
config.maximum_path_points.to_double(),
0.15,
)
report.add_metric(
"perimeter",
AuditGeometry,
perimeter,
0.0,
area.sqrt() * 10000.0,
0.05,
)
report.add_metric("center_offset", AuditGeometry, offset, 0.0, 0.5, 0.1)
if coverage < config.minimum_coverage {
report.add_finding(
"GEOMETRY_SPARSE",
AuditGeometry,
AuditWarning,
"visible geometry covers too little of the canvas",
coverage,
config.minimum_coverage,
)
}
if coverage > config.maximum_coverage {
report.add_finding(
"GEOMETRY_DENSE",
AuditGeometry,
AuditWarning,
"geometry coverage may clip or flatten visual hierarchy",
coverage,
config.maximum_coverage,
)
}
if points > config.maximum_path_points {
report.add_finding(
"GEOMETRY_COMPLEX",
AuditPerformance,
AuditError,
"flattened path point count exceeds the configured limit",
points.to_double(),
config.maximum_path_points.to_double(),
)
}
if bounds.width <= 0.0 || bounds.height <= 0.0 {
report.add_finding(
"GEOMETRY_BOUNDS",
AuditGeometry,
AuditError,
"all visible paths have empty bounds",
0.0,
1.0,
)
}
if offset > 0.5 {
report.add_finding(
"GEOMETRY_OFFSET",
AuditGeometry,
audit_geometry_severity(config),
"composition center is far outside the canvas center",
offset,
0.5,
)
}
}
///|
fn audit_geometry_severity(config : RenderAuditConfig) -> AuditSeverity {
if config.strict {
AuditWarning
} else {
AuditNotice
}
}
///|
fn ra_add_color_findings(
report : RenderAuditReport,
composition : Composition,
config : RenderAuditConfig,
) -> Unit {
let colors = ra_unique_fills(composition)
let count = colors.length()
let diversity = if ra_motif_count(composition) == 0 {
0.0
} else {
count.to_double() / ra_motif_count(composition).to_double()
}
report.add_metric(
"color_count",
AuditColor,
count.to_double(),
config.minimum_color_count.to_double(),
config.maximum_color_count.to_double(),
0.1,
)
report.add_metric("color_diversity", AuditColor, diversity, 0.0, 1.0, 0.05)
if count < config.minimum_color_count {
report.add_finding(
"COLOR_FEW",
AuditColor,
AuditWarning,
"palette usage has fewer colors than expected",
count.to_double(),
config.minimum_color_count.to_double(),
)
}
if count > config.maximum_color_count {
report.add_finding(
"COLOR_MANY",
AuditColor,
audit_color_severity(config),
"palette usage has too many distinct fills",
count.to_double(),
config.maximum_color_count.to_double(),
)
}
if composition.background.a <= 0.0 {
report.add_finding(
"COLOR_BACKGROUND_ALPHA",
AuditColor,
AuditWarning,
"background alpha is zero",
composition.background.a,
1.0,
)
}
let contrast = Color::rgba(0.5, 0.5, 0.5).contrast_ratio(
composition.background,
)
report.add_metric(
"background_contrast",
AuditColor,
contrast,
config.minimum_contrast,
21.0,
0.1,
)
if contrast < config.minimum_contrast {
report.add_finding(
"COLOR_CONTRAST",
AuditColor,
AuditWarning,
"representative foreground contrast is below the configured minimum",
contrast,
config.minimum_contrast,
)
}
}
///|
fn audit_color_severity(config : RenderAuditConfig) -> AuditSeverity {
if config.strict {
AuditError
} else {
AuditWarning
}
}
///|
fn ra_add_opacity_findings(
report : RenderAuditReport,
composition : Composition,
) -> Unit {
let opacity = ra_average_opacity(composition)
report.add_metric("average_opacity", AuditLayer, opacity, 0.05, 1.0, 0.05)
if opacity <= 0.0 {
report.add_finding(
"LAYER_OPACITY",
AuditLayer,
AuditError,
"all motifs have zero effective opacity",
opacity,
0.05,
)
} else if opacity < 0.05 {
report.add_finding(
"LAYER_FAINT",
AuditLayer,
audit_layer_severity(RenderAuditConfig::default()),
"effective opacity is too low for reliable export",
opacity,
0.05,
)
}
for layer in composition.layers {
if layer.opacity < 0.0 || layer.opacity > 1.0 {
report.add_finding(
"LAYER_ALPHA_RANGE",
AuditLayer,
AuditError,
"layer opacity is outside [0,1]",
layer.opacity,
1.0,
)
}
for motif in layer.motifs {
if motif.style.opacity < 0.0 || motif.style.opacity > 1.0 {
report.add_finding(
"MOTIF_ALPHA_RANGE",
AuditLayer,
AuditError,
"motif opacity is outside [0,1]",
motif.style.opacity,
1.0,
)
}
}
}
}
///|
pub fn audit_composition(
name : String,
seed : UInt,
composition : Composition,
config? : RenderAuditConfig,
) -> RenderAuditReport {
let config = config.unwrap_or(RenderAuditConfig::default())
let report = RenderAuditReport::new(name, seed)
ra_add_canvas_findings(report, composition, config)
ra_add_layer_findings(report, composition, config)
ra_add_geometry_findings(report, composition, config)
ra_add_color_findings(report, composition, config)
ra_add_opacity_findings(report, composition)
report.elapsed_hint = ra_motif_count(composition) +
ra_geometry_point_count(composition) / 100
report.finalize(config.strict)
report
}
///|
fn ra_raster_non_empty(raster : Raster) -> Int {
let mut count = 0
for color in raster.pixels {
if color.a > 0.001 {
count += 1
}
}
count
}
///|
fn ra_raster_mean(raster : Raster) -> Double {
if raster.pixels.length() == 0 {
0.0
} else {
raster.pixels.fold(init=0.0, fn(total, color) { total + color.luminance() }) /
raster.pixels.length().to_double()
}
}
///|
fn ra_raster_span(raster : Raster) -> Double {
if raster.pixels.length() == 0 {
0.0
} else {
let mut minimum = 1.0
let mut maximum = 0.0
for color in raster.pixels {
let value = color.luminance()
minimum = minimum.min(value)
maximum = maximum.max(value)
}
maximum - minimum
}
}
///|
fn ra_raster_edge_count(raster : Raster) -> Int {
let mut edges = 0
if raster.width > 1 && raster.height > 1 {
for y in 0..<(raster.height - 1) {
for x in 0..<(raster.width - 1) {
match (raster.get(x, y), raster.get(x + 1, y + 1)) {
(Some(left), Some(right)) =>
if (left.luminance() - right.luminance()).abs() > 0.08 {
edges += 1
}
_ => ()
}
}
}
}
edges
}
///|
fn ra_raster_color_count(raster : Raster) -> Int {
let colors = []
for color in raster.pixels {
let key = color.to_hex()
if !colors.contains(key) {
colors.push(key)
}
}
colors.length()
}
///|
fn ra_raster_border_occupancy(raster : Raster) -> Double {
if raster.width <= 0 || raster.height <= 0 {
0.0
} else {
let mut total = 0
let mut occupied = 0
for x in 0.. {
total += 1
if color.a > 0.001 {
occupied += 1
}
}
None => ()
}
}
}
if raster.height > 2 {
for y in 1..<(raster.height - 1) {
for x in [0, raster.width - 1] {
match raster.get(x, y) {
Some(color) => {
total += 1
if color.a > 0.001 {
occupied += 1
}
}
None => ()
}
}
}
}
if total == 0 {
0.0
} else {
occupied.to_double() / total.to_double()
}
}
}
///|
fn ra_raster_add_findings(
report : RenderAuditReport,
raster : Raster,
config : RenderAuditConfig,
) -> Unit {
let pixels = raster.width.max(0) * raster.height.max(0)
let non_empty = ra_raster_non_empty(raster)
let occupancy = if pixels == 0 {
0.0
} else {
non_empty.to_double() / pixels.to_double()
}
let mean = ra_raster_mean(raster)
let span = ra_raster_span(raster)
let edges = ra_raster_edge_count(raster)
let edge_density = if pixels == 0 {
0.0
} else {
edges.to_double() / pixels.to_double()
}
let colors = ra_raster_color_count(raster)
let border = ra_raster_border_occupancy(raster)
report.add_metric(
"raster_pixels",
AuditRaster,
pixels.to_double(),
1.0,
config.maximum_svg_bytes.to_double() * 16.0,
0.1,
)
report.add_metric(
"raster_occupancy",
AuditRaster,
occupancy,
config.minimum_raster_occupancy,
1.0,
0.2,
)
report.add_metric("raster_luminance", AuditColor, mean, 0.0, 1.0, 0.05)
report.add_metric("raster_span", AuditColor, span, 0.0, 1.0, 0.1)
report.add_metric(
"raster_edge_density",
AuditRaster,
edge_density,
0.0,
0.5,
0.05,
)
report.add_metric(
"raster_border_occupancy",
AuditRaster,
border,
0.0,
1.0,
0.05,
)
report.add_metric(
"raster_color_count",
AuditColor,
colors.to_double(),
1.0,
config.maximum_color_count.to_double(),
0.05,
)
if raster.width <= 0 || raster.height <= 0 {
report.add_finding(
"RASTER_EMPTY_SIZE",
AuditRaster,
AuditError,
"raster dimensions must be positive",
pixels.to_double(),
1.0,
)
}
if occupancy < config.minimum_raster_occupancy {
report.add_finding(
"RASTER_EMPTY",
AuditRaster,
AuditError,
"raster contains too few visible pixels",
occupancy,
config.minimum_raster_occupancy,
)
}
if span < 0.02 && non_empty > 0 {
report.add_finding(
"RASTER_FLAT",
AuditColor,
AuditWarning,
"raster luminance span is too small for a useful preview",
span,
0.02,
)
}
if border > 0.98 {
report.add_finding(
"RASTER_CLIP",
AuditRaster,
AuditWarning,
"nearly every border pixel is occupied; inspect clipping",
border,
0.98,
)
}
if pixels > config.maximum_svg_bytes * 16 {
report.add_finding(
"RASTER_MEMORY",
AuditPerformance,
AuditWarning,
"raster pixel count may exceed the export memory budget",
pixels.to_double(),
(config.maximum_svg_bytes * 16).to_double(),
)
}
}
///|
pub fn audit_raster(
name : String,
seed : UInt,
raster : Raster,
config? : RenderAuditConfig,
) -> RenderAuditReport {
let config = config.unwrap_or(RenderAuditConfig::default())
let report = RenderAuditReport::new(name, seed)
ra_raster_add_findings(report, raster, config)
report.elapsed_hint = raster.width.max(0) * raster.height.max(0)
report.finalize(config.strict)
report
}
///|
fn ra_svg_path_count(elements : Array[SvgElement]) -> Int {
let mut count = 0
for element in elements {
match element {
SvgPath(_, _) => count += 1
SvgGroup(children, ..) => count += ra_svg_path_count(children)
}
}
count
}
///|
fn ra_svg_group_count(elements : Array[SvgElement]) -> Int {
let mut count = 0
for element in elements {
match element {
SvgPath(_, _) => ()
SvgGroup(children, ..) => count += 1 + ra_svg_group_count(children)
}
}
count
}
///|
fn ra_svg_point_count(elements : Array[SvgElement]) -> Int {
let mut count = 0
for element in elements {
match element {
SvgPath(path, _) => count += path.flatten(subdivisions=4).length()
SvgGroup(children, ..) => count += ra_svg_point_count(children)
}
}
count
}
///|
fn ra_svg_effective_opacity(
elements : Array[SvgElement],
parent : Double,
) -> Double {
let mut total = 0.0
let mut count = 0
for element in elements {
match element {
SvgPath(_, style) => {
total += parent * style.opacity
count += 1
}
SvgGroup(children, ..) => {
let value = ra_svg_effective_opacity(children, parent)
if children.length() > 0 {
total += value * children.length().to_double()
count += children.length()
}
}
}
}
if count == 0 {
0.0
} else {
total / count.to_double()
}
}
///|
fn ra_svg_add_findings(
report : RenderAuditReport,
document : SvgDocument,
config : RenderAuditConfig,
) -> Unit {
let xml = document.to_svg_string()
let bytes = xml.length()
let paths = ra_svg_path_count(document.elements)
let groups = ra_svg_group_count(document.elements)
let points = ra_svg_point_count(document.elements)
let opacity = ra_svg_effective_opacity(document.elements, 1.0)
report.add_metric(
"svg_bytes",
AuditExport,
bytes.to_double(),
32.0,
config.maximum_svg_bytes.to_double(),
0.25,
)
report.add_metric(
"svg_paths",
AuditExport,
paths.to_double(),
1.0,
config.maximum_motifs.to_double(),
0.15,
)
report.add_metric(
"svg_groups",
AuditLayer,
groups.to_double(),
0.0,
config.maximum_layers.to_double(),
0.05,
)
report.add_metric(
"svg_points",
AuditPerformance,
points.to_double(),
1.0,
config.maximum_path_points.to_double(),
0.15,
)
report.add_metric("svg_opacity", AuditColor, opacity, 0.01, 1.0, 0.05)
if document.width <= 0.0 || document.height <= 0.0 {
report.add_finding(
"SVG_VIEWBOX",
AuditExport,
AuditError,
"SVG viewport dimensions must be positive",
document.width * document.height,
1.0,
)
}
if bytes < 32 {
report.add_finding(
"SVG_EMPTY",
AuditExport,
AuditError,
"SVG output is unexpectedly short",
bytes.to_double(),
32.0,
)
}
if bytes > config.maximum_svg_bytes {
report.add_finding(
"SVG_LARGE",
AuditPerformance,
AuditError,
"SVG output exceeds the configured byte budget",
bytes.to_double(),
config.maximum_svg_bytes.to_double(),
)
}
if paths == 0 {
report.add_finding(
"SVG_NO_PATHS",
AuditExport,
AuditError,
"SVG contains no drawable paths",
0.0,
1.0,
)
}
if points > config.maximum_path_points {
report.add_finding(
"SVG_COMPLEX",
AuditPerformance,
AuditWarning,
"SVG flattened point count is high",
points.to_double(),
config.maximum_path_points.to_double(),
)
}
if opacity <= 0.0 && paths > 0 {
report.add_finding(
"SVG_INVISIBLE",
AuditColor,
AuditWarning,
"all SVG paths have zero effective opacity",
opacity,
0.01,
)
}
}
///|
pub fn audit_svg(
name : String,
seed : UInt,
document : SvgDocument,
config? : RenderAuditConfig,
) -> RenderAuditReport {
let config = config.unwrap_or(RenderAuditConfig::default())
let report = RenderAuditReport::new(name, seed)
ra_svg_add_findings(report, document, config)
report.elapsed_hint = document.elements.length()
report.finalize(config.strict)
report
}
///|
pub fn audit_render_pair(
name : String,
seed : UInt,
composition : Composition,
document : SvgDocument,
raster : Raster,
config? : RenderAuditConfig,
) -> RenderAuditReport {
let config = config.unwrap_or(RenderAuditConfig::default())
let report = audit_composition(name, seed, composition, config~)
let raster_report = audit_raster("preview", seed, raster, config~)
let svg_report = audit_svg("export", seed, document, config~)
for metric in raster_report.metrics {
report.metrics.push({ ..metric, name: "raster_" + metric.name })
}
for metric in svg_report.metrics {
report.metrics.push({ ..metric, name: "svg_" + metric.name })
}
for finding in raster_report.findings {
report.findings.push(finding)
}
for finding in svg_report.findings {
report.findings.push(finding)
}
report.score = (report.score + raster_report.score + svg_report.score) / 3.0
report.passed = report.passed && raster_report.passed && svg_report.passed
report.elapsed_hint += raster_report.elapsed_hint + svg_report.elapsed_hint
report
}
///|
pub fn summarize_audits(
reports : Array[RenderAuditReport],
) -> AuditBatchSummary {
let mut passed = 0
let mut warnings = 0
let mut errors = 0
let mut score = 0.0
for report in reports {
if report.passed {
passed += 1
}
warnings += report.warning_count()
errors += report.error_count()
score += report.score
}
let total = reports.length()
{
total,
passed,
failed: total - passed,
score: if total == 0 {
0.0
} else {
score / total.to_double()
},
warnings,
errors,
reports,
}
}
///|
pub fn AuditBatchSummary::to_text(self : AuditBatchSummary) -> String {
let builder = StringBuilder()
builder.write_string(
"audits=\{self.total} passed=\{self.passed} failed=\{self.failed} score=\{self.score} warnings=\{self.warnings} errors=\{self.errors}\n",
)
for report in self.reports {
builder.write_string(
"- \{report.name}: passed=\{report.passed} score=\{report.score} findings=\{report.findings.length()}\n",
)
}
builder.to_string()
}
///|
pub fn AuditBatchSummary::to_json_string(
self : AuditBatchSummary,
indent? : Int,
) -> String {
ToJson::to_json(self).stringify(indent=indent.unwrap_or(0))
}
///|
fn ra_quality_band(score : Double) -> String {
if score >= 0.9 {
"excellent"
} else if score >= 0.75 {
"good"
} else if score >= 0.5 {
"review"
} else {
"reject"
}
}
///|
pub fn render_audit_badge(report : RenderAuditReport) -> String {
"\{ra_quality_band(report.score)}:\{report.score.to_string()}:\{report.passed}"
}
///|
pub fn render_audit_manifest(report : RenderAuditReport) -> String {
let builder = StringBuilder()
builder.write_string("name=\{report.name}\n")
builder.write_string("seed=\{report.seed}\n")
builder.write_string("score=\{report.score}\n")
builder.write_string("quality=\{ra_quality_band(report.score)}\n")
builder.write_string("passed=\{report.passed}\n")
builder.write_string("errors=\{report.error_count()}\n")
builder.write_string("warnings=\{report.warning_count()}\n")
for metric in report.metrics {
builder.write_string("metric.\{metric.name}=\{metric.value}\n")
}
for finding in report.findings {
builder.write_string("finding.\{finding.code}=\{finding.message}\n")
}
builder.to_string()
}
///|
pub fn audit_is_exportable(report : RenderAuditReport) -> Bool {
report.passed && report.error_count() == 0
}
///|
pub fn audit_has_domain_error(
report : RenderAuditReport,
domain : AuditDomain,
) -> Bool {
for finding in report.findings {
if finding.domain == domain && finding.severity == AuditError {
return true
}
}
false
}
///|
pub fn audit_metric_within(
report : RenderAuditReport,
name : String,
minimum : Double,
maximum : Double,
) -> Bool {
match report.metric_value(name) {
Some(value) => value >= minimum && value <= maximum
None => false
}
}
///|
pub fn audit_score_delta(
before : RenderAuditReport,
after : RenderAuditReport,
) -> Double {
after.score - before.score
}
///|
pub fn audit_report_merge(
primary : RenderAuditReport,
secondary : RenderAuditReport,
) -> RenderAuditReport {
let merged = RenderAuditReport::new(primary.name, primary.seed)
for metric in primary.metrics {
merged.metrics.push(metric)
}
for metric in secondary.metrics {
merged.metrics.push(metric)
}
for finding in primary.findings {
merged.findings.push(finding)
}
for finding in secondary.findings {
merged.findings.push(finding)
}
merged.score = (primary.score + secondary.score) / 2.0
merged.passed = primary.passed && secondary.passed
merged.elapsed_hint = primary.elapsed_hint + secondary.elapsed_hint
merged
}
///|
pub fn audit_recommendation(report : RenderAuditReport) -> String {
if report.error_count() > 0 {
"fix blocking errors before export"
} else if report.warning_count() > 0 {
"review warnings and export with an audit manifest"
} else if report.score >= 0.9 {
"export is ready for production use"
} else {
"run the audit again after adjusting the composition"
}
}
///|
pub fn audit_domain_score(
report : RenderAuditReport,
domain : AuditDomain,
) -> Double {
let mut total = 0.0
let mut count = 0
for metric in report.metrics {
if metric.domain == domain {
total += if metric.passed { 1.0 } else { 0.0 }
count += 1
}
}
if count == 0 {
1.0
} else {
total / count.to_double()
}
}
///|
pub fn audit_domain_scores(
report : RenderAuditReport,
) -> Array[(AuditDomain, Double)] {
[
(AuditCanvas, audit_domain_score(report, AuditCanvas)),
(AuditGeometry, audit_domain_score(report, AuditGeometry)),
(AuditColor, audit_domain_score(report, AuditColor)),
(AuditLayer, audit_domain_score(report, AuditLayer)),
(AuditRaster, audit_domain_score(report, AuditRaster)),
(AuditExport, audit_domain_score(report, AuditExport)),
(AuditPerformance, audit_domain_score(report, AuditPerformance)),
]
}
///|
pub fn audit_report_line_count(report : RenderAuditReport) -> Int {
4 + report.metrics.length() + report.findings.length()
}
///|
pub fn audit_report_digest(report : RenderAuditReport) -> UInt {
let text = report.to_text()
let mut hash = 2166136261U
for byte in @utf8.encode(text) {
hash = (hash ^ byte.to_uint()) * 16777619U
}
hash
}
///|
pub fn audit_batch_digest(summary : AuditBatchSummary) -> UInt {
let mut hash = 2166136261U
for report in summary.reports {
hash = (hash ^ audit_report_digest(report)) * 16777619U
}
hash
}
///|
pub fn audit_findings_by_domain(
report : RenderAuditReport,
domain : AuditDomain,
) -> Array[AuditFinding] {
let result = []
for finding in report.findings {
if finding.domain == domain {
result.push(finding)
}
}
result
}
///|
pub fn audit_findings_by_severity(
report : RenderAuditReport,
severity : AuditSeverity,
) -> Array[AuditFinding] {
let result = []
for finding in report.findings {
if finding.severity == severity {
result.push(finding)
}
}
result
}
///|
pub fn audit_metric_names(report : RenderAuditReport) -> Array[String] {
report.metrics.map(fn(metric) { metric.name })
}
///|
pub fn audit_metric_pass_rate(report : RenderAuditReport) -> Double {
if report.metrics.length() == 0 {
1.0
} else {
let passed = report.metrics.fold(init=0, fn(total, metric) {
if metric.passed {
total + 1
} else {
total
}
})
passed.to_double() / report.metrics.length().to_double()
}
}
///|
pub fn audit_report_is_deterministic(
first : RenderAuditReport,
second : RenderAuditReport,
) -> Bool {
audit_report_digest(first) == audit_report_digest(second)
}
///|
pub fn audit_config_for_canvas(
width : Double,
height : Double,
) -> RenderAuditConfig {
let area = width.max(1.0) * height.max(1.0)
{
..RenderAuditConfig::default(),
minimum_canvas: area * 0.95,
maximum_canvas: area * 1.05,
minimum_raster_occupancy: if area > 1000000.0 {
0.0001
} else {
0.001
},
maximum_svg_bytes: (area.sqrt().to_int().max(256) * 4096).min(
64 * 1024 * 1024,
),
}
}
///|
pub fn audit_config_for_thumbnail() -> RenderAuditConfig {
{
..RenderAuditConfig::default(),
minimum_canvas: 16.0,
maximum_canvas: 4096.0 * 4096.0,
minimum_motifs: 0,
minimum_coverage: 0.001,
minimum_raster_occupancy: 0.0001,
maximum_svg_bytes: 2 * 1024 * 1024,
}
}
///|
pub fn audit_config_for_batch() -> RenderAuditConfig {
{
..RenderAuditConfig::default(),
maximum_layers: 256,
maximum_motifs: 250000,
maximum_path_points: 500000,
maximum_svg_bytes: 64 * 1024 * 1024,
}
}
///|
pub fn audit_config_for_ci() -> RenderAuditConfig {
{
..RenderAuditConfig::strict(),
maximum_layers: 64,
maximum_motifs: 50000,
maximum_path_points: 50000,
maximum_svg_bytes: 8 * 1024 * 1024,
}
}
///|
pub fn audit_config_with_limits(
base : RenderAuditConfig,
maximum_motifs : Int,
maximum_points : Int,
maximum_bytes : Int,
) -> RenderAuditConfig {
{
..base,
maximum_motifs: maximum_motifs.max(1),
maximum_path_points: maximum_points.max(1),
maximum_svg_bytes: maximum_bytes.max(1024),
}
}
///|
pub fn audit_findings_text(report : RenderAuditReport) -> String {
report.findings
.map(fn(finding) {
"\{finding.code}:\{ra_severity_name(finding.severity)}:\{finding.message}"
})
.join("\n")
}
///|
pub fn audit_metric_text(report : RenderAuditReport) -> String {
report.metrics.map(fn(metric) { "\{metric.name}=\{metric.value}" }).join(" ")
}
///|
pub fn audit_summary_text(reports : Array[RenderAuditReport]) -> String {
summarize_audits(reports).to_text()
}
///|
pub fn audit_report_header(report : RenderAuditReport) -> String {
"\{report.name} / seed \{report.seed} / \{ra_quality_band(report.score)}"
}
///|
pub fn audit_report_status(report : RenderAuditReport) -> String {
if report.passed {
"passed"
} else {
"failed"
}
}
///|
pub fn audit_report_has_warning(report : RenderAuditReport) -> Bool {
report.warning_count() > 0
}
///|
pub fn audit_report_has_error(report : RenderAuditReport) -> Bool {
report.error_count() > 0
}
///|
pub fn audit_report_is_empty(report : RenderAuditReport) -> Bool {
report.metrics.length() == 0 && report.findings.length() == 0
}
///|
pub fn audit_report_copy(report : RenderAuditReport) -> RenderAuditReport {
let copy = RenderAuditReport::new(report.name, report.seed)
for metric in report.metrics {
copy.metrics.push(metric)
}
for finding in report.findings {
copy.findings.push(finding)
}
copy.score = report.score
copy.passed = report.passed
copy.elapsed_hint = report.elapsed_hint
copy
}
///|
pub fn audit_report_with_name(
report : RenderAuditReport,
name : String,
) -> RenderAuditReport {
let copy = audit_report_copy(report)
copy.name = name
copy
}
///|
pub fn audit_report_with_seed(
report : RenderAuditReport,
seed : UInt,
) -> RenderAuditReport {
let copy = audit_report_copy(report)
copy.seed = seed
copy
}
///|
pub fn audit_report_with_elapsed(
report : RenderAuditReport,
elapsed_hint : Int,
) -> RenderAuditReport {
let copy = audit_report_copy(report)
copy.elapsed_hint = elapsed_hint.max(0)
copy
}
///|
pub fn audit_report_recompute(
report : RenderAuditReport,
strict : Bool,
) -> RenderAuditReport {
let copy = audit_report_copy(report)
copy.score = 1.0
copy.passed = true
for finding in copy.findings {
if finding.severity == AuditError {
copy.passed = false
copy.score = (copy.score - 0.25).max(0.0)
} else if finding.severity == AuditWarning {
copy.score = (copy.score - 0.05).max(0.0)
}
}
for metric in copy.metrics {
if !metric.passed {
copy.score = (copy.score - metric.weight * 0.25).max(0.0)
}
}
if strict && copy.warning_count() > 0 {
copy.passed = false
}
copy.score = ra_clamp(copy.score)
copy
}
///|
pub fn audit_report_filter_passed_metrics(
report : RenderAuditReport,
) -> Array[AuditMetric] {
report.metrics.filter(fn(metric) { metric.passed })
}
///|
pub fn audit_report_filter_failed_metrics(
report : RenderAuditReport,
) -> Array[AuditMetric] {
report.metrics.filter(fn(metric) { !metric.passed })
}
///|
pub fn audit_report_filter_actionable(
report : RenderAuditReport,
) -> Array[AuditFinding] {
report.findings.filter(fn(finding) {
finding.severity == AuditWarning || finding.severity == AuditError
})
}
///|
pub fn audit_report_weighted_score(report : RenderAuditReport) -> Double {
let mut total = 0.0
let mut weight = 0.0
for metric in report.metrics {
let metric_weight = metric.weight.max(0.001)
total += if metric.passed { metric_weight } else { 0.0 }
weight += metric_weight
}
if weight == 0.0 {
report.score
} else {
total / weight
}
}
///|
pub fn audit_report_quality_label(report : RenderAuditReport) -> String {
ra_quality_band(audit_report_weighted_score(report))
}
///|
pub fn audit_report_compact(report : RenderAuditReport) -> String {
"\{audit_report_status(report)} \{audit_report_quality_label(report)} score=\{report.score} errors=\{report.error_count()} warnings=\{report.warning_count()}"
}
///|
pub fn audit_reports_all_passed(reports : Array[RenderAuditReport]) -> Bool {
for report in reports {
if !report.passed {
return false
}
}
true
}
///|
pub fn audit_reports_best(
reports : Array[RenderAuditReport],
) -> RenderAuditReport? {
if reports.length() == 0 {
None
} else {
let mut best = reports[0]
for report in reports[1:] {
if report.score > best.score {
best = report
}
}
Some(best)
}
}
///|
pub fn audit_reports_worst(
reports : Array[RenderAuditReport],
) -> RenderAuditReport? {
if reports.length() == 0 {
None
} else {
let mut worst = reports[0]
for report in reports[1:] {
if report.score < worst.score {
worst = report
}
}
Some(worst)
}
}
///|
pub fn audit_report_findings_digest(report : RenderAuditReport) -> String {
report.findings.map(fn(finding) { finding.code }).join(",")
}
///|
pub fn audit_report_metrics_digest(report : RenderAuditReport) -> String {
report.metrics.map(fn(metric) { metric.name }).join(",")
}
///|
pub fn audit_report_to_lines(report : RenderAuditReport) -> Array[String] {
let lines = [audit_report_header(report), audit_report_compact(report)]
for metric in report.metrics {
lines.push("metric \{metric.name} \{metric.value} \{metric.passed}")
}
for finding in report.findings {
lines.push("finding \{finding.code} \{ra_severity_name(finding.severity)}")
}
lines
}
///|
pub fn audit_report_from_composition(
composition : Composition,
seed : UInt,
) -> RenderAuditReport {
audit_composition("composition", seed, composition)
}
///|
pub fn audit_report_from_raster(
raster : Raster,
seed : UInt,
) -> RenderAuditReport {
audit_raster("raster", seed, raster)
}
///|
pub fn audit_report_from_svg(
document : SvgDocument,
seed : UInt,
) -> RenderAuditReport {
audit_svg("svg", seed, document)
}
///|
pub fn audit_export_gate(report : RenderAuditReport, strict : Bool) -> Bool {
if strict {
report.passed && report.warning_count() == 0
} else {
report.error_count() == 0
}
}
///|
pub fn audit_batch_export_gate(
summary : AuditBatchSummary,
strict : Bool,
) -> Bool {
if summary.total == 0 || summary.errors > 0 {
false
} else if strict {
summary.failed == 0 && summary.warnings == 0
} else {
summary.failed == 0
}
}
///|
pub fn audit_report_warning_codes(report : RenderAuditReport) -> Array[String] {
report.findings
.filter(fn(finding) { finding.severity == AuditWarning })
.map(fn(finding) { finding.code })
}
///|
pub fn audit_report_error_codes(report : RenderAuditReport) -> Array[String] {
report.findings
.filter(fn(finding) { finding.severity == AuditError })
.map(fn(finding) { finding.code })
}
///|
pub fn audit_report_info_codes(report : RenderAuditReport) -> Array[String] {
report.findings
.filter(fn(finding) { finding.severity == AuditInfo })
.map(fn(finding) { finding.code })
}
///|
pub fn audit_report_domain_errors(
report : RenderAuditReport,
) -> Array[AuditDomain] {
let domains = []
for finding in report.findings {
if finding.severity == AuditError && !domains.contains(finding.domain) {
domains.push(finding.domain)
}
}
domains
}
///|
pub fn audit_report_domain_warnings(
report : RenderAuditReport,
) -> Array[AuditDomain] {
let domains = []
for finding in report.findings {
if finding.severity == AuditWarning && !domains.contains(finding.domain) {
domains.push(finding.domain)
}
}
domains
}
///|
pub fn audit_report_max_metric(
report : RenderAuditReport,
domain : AuditDomain,
) -> Double {
let mut maximum = 0.0
for metric in report.metrics {
if metric.domain == domain {
maximum = maximum.max(metric.value)
}
}
maximum
}
///|
pub fn audit_report_min_metric(
report : RenderAuditReport,
domain : AuditDomain,
) -> Double {
let mut minimum = 0.0
let mut initialized = false
for metric in report.metrics {
if metric.domain == domain {
if !initialized || metric.value < minimum {
minimum = metric.value
initialized = true
}
}
}
minimum
}
///|
pub fn audit_report_metric_average(
report : RenderAuditReport,
domain : AuditDomain,
) -> Double {
let mut total = 0.0
let mut count = 0
for metric in report.metrics {
if metric.domain == domain {
total += metric.value
count += 1
}
}
if count == 0 {
0.0
} else {
total / count.to_double()
}
}
///|
pub fn audit_report_severity_penalty(report : RenderAuditReport) -> Double {
report.findings.fold(init=0.0, fn(total, finding) {
total + ra_severity_weight(finding.severity)
})
}
///|
pub fn audit_report_expected_score(report : RenderAuditReport) -> Double {
ra_clamp(1.0 - audit_report_severity_penalty(report) / 10.0)
}
///|
pub fn audit_report_needs_review(report : RenderAuditReport) -> Bool {
report.warning_count() > 0 || report.score < 0.75
}
///|
pub fn audit_report_is_high_risk(report : RenderAuditReport) -> Bool {
report.error_count() > 0 || report.score < 0.5
}
///|
pub fn audit_report_risk_level(report : RenderAuditReport) -> String {
if audit_report_is_high_risk(report) {
"high"
} else if audit_report_needs_review(report) {
"medium"
} else {
"low"
}
}
///|
pub fn audit_report_risk_summary(report : RenderAuditReport) -> String {
"risk=\{audit_report_risk_level(report)} errors=\{report.error_count()} warnings=\{report.warning_count()} recommendation=\{audit_recommendation(report)}"
}
///|
pub fn audit_report_export_name(report : RenderAuditReport) -> String {
"\{report.name}-\{report.seed}-\{ra_quality_band(report.score)}"
}
///|
pub fn audit_report_cache_key(report : RenderAuditReport) -> String {
"\{audit_report_export_name(report)}-\{audit_report_digest(report)}"
}
///|
pub fn audit_report_is_cache_compatible(
first : RenderAuditReport,
second : RenderAuditReport,
) -> Bool {
first.seed == second.seed &&
first.name == second.name &&
audit_report_metrics_digest(first) == audit_report_metrics_digest(second)
}
///|
pub fn audit_report_compare_scores(
first : RenderAuditReport,
second : RenderAuditReport,
) -> Int {
if first.score < second.score {
-1
} else if first.score > second.score {
1
} else {
0
}
}
///|
pub fn audit_report_compare_risk(
first : RenderAuditReport,
second : RenderAuditReport,
) -> Int {
let left = if audit_report_is_high_risk(first) {
2
} else if audit_report_needs_review(first) {
1
} else {
0
}
let right = if audit_report_is_high_risk(second) {
2
} else if audit_report_needs_review(second) {
1
} else {
0
}
if left < right {
-1
} else if left > right {
1
} else {
0
}
}
///|
pub fn audit_report_append_note(
report : RenderAuditReport,
message : String,
) -> Unit {
if message.length() > 0 {
report.findings.push({
code: "NOTE",
domain: AuditExport,
severity: AuditNotice,
message,
value: 0.0,
target: 0.0,
passed: true,
})
}
}
///|
pub fn audit_report_mark_failed(
report : RenderAuditReport,
code : String,
message : String,
) -> Unit {
report.add_finding(code, AuditExport, AuditError, message, 0.0, 1.0)
}
///|
pub fn audit_report_mark_warning(
report : RenderAuditReport,
code : String,
message : String,
) -> Unit {
report.add_finding(code, AuditExport, AuditWarning, message, 0.0, 1.0)
}
///|
pub fn audit_report_mark_info(
report : RenderAuditReport,
code : String,
message : String,
) -> Unit {
report.add_finding(code, AuditExport, AuditInfo, message, 0.0, 1.0)
}
///|
pub fn audit_report_set_score(
report : RenderAuditReport,
score : Double,
) -> Unit {
report.score = ra_clamp(score)
}
///|
pub fn audit_report_set_passed(
report : RenderAuditReport,
passed : Bool,
) -> Unit {
report.passed = passed
}
///|
pub fn audit_report_set_elapsed(
report : RenderAuditReport,
elapsed : Int,
) -> Unit {
report.elapsed_hint = elapsed.max(0)
}
///|
pub fn audit_report_metric_count(report : RenderAuditReport) -> Int {
report.metrics.length()
}
///|
pub fn audit_report_finding_count(report : RenderAuditReport) -> Int {
report.findings.length()
}
///|
pub fn audit_report_quality_percent(report : RenderAuditReport) -> Int {
(report.score * 100.0).to_int().max(0).min(100)
}
///|
pub fn audit_report_progress(report : RenderAuditReport) -> String {
"\{audit_report_quality_percent(report)}% / \{audit_report_status(report)}"
}
///|
pub fn audit_report_footer(report : RenderAuditReport) -> String {
"audit \{audit_report_digest(report)} elapsed_hint=\{report.elapsed_hint} recommendation=\{audit_recommendation(report)}"
}
///|
pub fn audit_report_to_markdown(report : RenderAuditReport) -> String {
let builder = StringBuilder()
builder.write_string("## Render audit: \{report.name}\n\n")
builder.write_string("- Seed: \{report.seed}\n")
builder.write_string("- Status: \{audit_report_status(report)}\n")
builder.write_string(
"- Quality: \{audit_report_quality_label(report)} (\{report.score})\n",
)
builder.write_string("- Risk: \{audit_report_risk_level(report)}\n\n")
builder.write_string("### Metrics\n\n")
builder.write_string(
"| Name | Value | Range | Passed |\n| --- | ---: | --- | --- |\n",
)
for metric in report.metrics {
builder.write_string(
"| \{metric.name} | \{metric.value} | \{metric.minimum}..\{metric.maximum} | \{metric.passed} |\n",
)
}
builder.write_string("\n### Findings\n\n")
for finding in report.findings {
builder.write_string(
"- **\{ra_severity_name(finding.severity)}** `\{finding.code}` \{finding.message}\n",
)
}
builder.write_string("\n\{audit_report_footer(report)}\n")
builder.to_string()
}
///|
pub fn audit_batch_to_markdown(summary : AuditBatchSummary) -> String {
let builder = StringBuilder()
builder.write_string("# Render audit batch\n\n")
builder.write_string("- Total: \{summary.total}\n")
builder.write_string("- Passed: \{summary.passed}\n")
builder.write_string("- Failed: \{summary.failed}\n")
builder.write_string("- Score: \{summary.score}\n")
builder.write_string("- Warnings: \{summary.warnings}\n")
builder.write_string("- Errors: \{summary.errors}\n\n")
for report in summary.reports {
builder.write_string("- \{audit_report_compact(report)}\n")
}
builder.to_string()
}
///|
pub fn audit_batch_report(summary : AuditBatchSummary) -> String {
"\{summary.to_text()}digest=\{audit_batch_digest(summary)}\n"
}
///|
pub fn audit_report_debug(report : RenderAuditReport) -> String {
"\{audit_report_header(report)} | \{audit_report_risk_summary(report)} | digest=\{audit_report_digest(report)}"
}
///|
pub fn audit_report_sort_key(
report : RenderAuditReport,
) -> (Int, Double, String) {
(if report.passed { 0 } else { 1 }, -report.score, report.name)
}
///|
pub fn audit_reports_sort_key(
reports : Array[RenderAuditReport],
) -> Array[String] {
reports.map(fn(report) { audit_report_cache_key(report) })
}
///|
pub fn audit_report_has_metric(
report : RenderAuditReport,
name : String,
) -> Bool {
report.metric_value(name) is Some(_)
}
///|
pub fn audit_report_requires_manual_review(report : RenderAuditReport) -> Bool {
report.warning_count() >= 3 || report.error_count() > 0 || report.score < 0.75
}
///|
pub fn audit_report_manual_review_reason(report : RenderAuditReport) -> String {
if report.error_count() > 0 {
"blocking findings are present"
} else if report.warning_count() >= 3 {
"multiple warnings require visual inspection"
} else if report.score < 0.75 {
"quality score is below the review threshold"
} else {
"no manual review required"
}
}
///|
pub fn audit_report_acceptance_line(report : RenderAuditReport) -> String {
"\{audit_report_status(report)} | score \{report.score} | \{audit_report_manual_review_reason(report)}"
}
///|
pub fn audit_report_has_duplicate_finding_codes(
report : RenderAuditReport,
) -> Bool {
let codes = []
for finding in report.findings {
if codes.contains(finding.code) {
return true
}
codes.push(finding.code)
}
false
}
///|
pub fn audit_report_unique_finding_codes(
report : RenderAuditReport,
) -> Array[String] {
let codes = []
for finding in report.findings {
if !codes.contains(finding.code) {
codes.push(finding.code)
}
}
codes
}
///|
pub fn audit_report_normalize(report : RenderAuditReport) -> RenderAuditReport {
let normalized = audit_report_copy(report)
normalized.score = ra_clamp(normalized.score)
for metric in normalized.metrics {
metric.weight = metric.weight.max(0.0)
metric.minimum = metric.minimum.min(metric.maximum)
metric.maximum = metric.maximum.max(metric.minimum)
}
normalized
}
///|
pub fn audit_report_validate(report : RenderAuditReport) -> Array[String] {
let errors = []
if report.name.length() == 0 {
errors.push("report name is empty")
}
if report.score < 0.0 || report.score > 1.0 {
errors.push("score is outside [0,1]")
}
if report.elapsed_hint < 0 {
errors.push("elapsed hint is negative")
}
for metric in report.metrics {
if metric.name.length() == 0 {
errors.push("metric name is empty")
}
if metric.minimum > metric.maximum {
errors.push("metric range is inverted")
}
}
for finding in report.findings {
if finding.code.length() == 0 {
errors.push("finding code is empty")
}
if finding.message.length() == 0 {
errors.push("finding message is empty")
}
}
errors
}
///|
pub fn audit_batch_validate(summary : AuditBatchSummary) -> Array[String] {
let errors = []
if summary.total != summary.reports.length() {
errors.push("batch total does not match reports")
}
if summary.passed + summary.failed != summary.total {
errors.push("batch pass counts do not add up")
}
if summary.score < 0.0 || summary.score > 1.0 {
errors.push("batch score is outside [0,1]")
}
for report in summary.reports {
for error in audit_report_validate(report) {
errors.push(error)
}
}
errors
}
///|
pub fn audit_report_is_valid(report : RenderAuditReport) -> Bool {
audit_report_validate(report).length() == 0
}
///|
pub fn audit_batch_is_valid(summary : AuditBatchSummary) -> Bool {
audit_batch_validate(summary).length() == 0
}
///|
pub fn audit_report_clone_findings(
report : RenderAuditReport,
) -> Array[AuditFinding] {
report.findings.map(fn(finding) { finding })
}
///|
pub fn audit_report_clone_metrics(
report : RenderAuditReport,
) -> Array[AuditMetric] {
report.metrics.map(fn(metric) { metric })
}
///|
pub fn audit_report_findings_for_export(
report : RenderAuditReport,
) -> Array[AuditFinding] {
report.findings.filter(fn(finding) {
finding.domain == AuditExport || finding.domain == AuditPerformance
})
}
///|
pub fn audit_report_visual_findings(
report : RenderAuditReport,
) -> Array[AuditFinding] {
report.findings.filter(fn(finding) {
finding.domain == AuditGeometry ||
finding.domain == AuditColor ||
finding.domain == AuditLayer
})
}
///|
pub fn audit_report_runtime_findings(
report : RenderAuditReport,
) -> Array[AuditFinding] {
report.findings.filter(fn(finding) {
finding.domain == AuditRaster || finding.domain == AuditPerformance
})
}
///|
pub fn audit_report_metric_summary(report : RenderAuditReport) -> String {
let passed = audit_report_filter_passed_metrics(report).length()
let failed = audit_report_filter_failed_metrics(report).length()
"metrics passed=\{passed} failed=\{failed} rate=\{audit_metric_pass_rate(report)}"
}
///|
pub fn audit_report_finding_summary(report : RenderAuditReport) -> String {
"findings info=\{report.info_count()} warnings=\{report.warning_count()} errors=\{report.error_count()}"
}
///|
pub fn audit_report_complete_summary(report : RenderAuditReport) -> String {
"\{audit_report_header(report)}; \{audit_report_metric_summary(report)}; \{audit_report_finding_summary(report)}"
}
///|
pub fn audit_report_for_log(report : RenderAuditReport) -> String {
"render_audit name=\{report.name} seed=\{report.seed} status=\{audit_report_status(report)} score=\{report.score} digest=\{audit_report_digest(report)}"
}
///|
pub fn audit_batch_for_log(summary : AuditBatchSummary) -> String {
"render_audit_batch total=\{summary.total} passed=\{summary.passed} failed=\{summary.failed} score=\{summary.score} digest=\{audit_batch_digest(summary)}"
}
///|
pub fn audit_report_version() -> String {
"render-audit/1"
}
///|
pub fn audit_report_schema() -> String {
"name,seed,findings,metrics,score,passed,elapsed_hint"
}
///|
pub fn audit_report_domain_schema() -> String {
"canvas,geometry,color,layer,raster,export,performance"
}
///|
pub fn audit_report_severity_schema() -> String {
"info,notice,warning,error"
}
///|
pub fn audit_report_contract(report : RenderAuditReport) -> Bool {
audit_report_is_valid(report) && audit_report_digest(report) != 0U
}
///|
pub fn audit_batch_contract(summary : AuditBatchSummary) -> Bool {
audit_batch_is_valid(summary) && audit_batch_digest(summary) != 0U
}
///|
pub fn audit_report_reject_reason(report : RenderAuditReport) -> String? {
if report.error_count() > 0 {
Some(audit_report_error_codes(report).join(","))
} else if report.score < 0.5 {
Some("quality score below 0.5")
} else {
None
}
}
///|
pub fn audit_report_publishable(report : RenderAuditReport) -> Bool {
audit_export_gate(report, false) && report.score >= 0.5
}
///|
pub fn audit_report_publish_blockers(
report : RenderAuditReport,
) -> Array[String] {
let blockers = audit_report_error_codes(report)
if report.score < 0.5 {
blockers.push("score below publish threshold")
}
blockers
}
///|
pub fn audit_report_publish_status(report : RenderAuditReport) -> String {
if audit_report_publishable(report) {
"publishable"
} else {
"blocked"
}
}
///|
pub fn audit_report_publish_line(report : RenderAuditReport) -> String {
let blockers = audit_report_publish_blockers(report).join(",")
"\{audit_report_publish_status(report)} \{audit_report_export_name(report)} blockers=\{blockers}"
}
///|
pub fn audit_batch_publishable(summary : AuditBatchSummary) -> Bool {
summary.total > 0 &&
summary.failed == 0 &&
summary.errors == 0 &&
summary.score >= 0.5
}
///|
pub fn audit_batch_publish_status(summary : AuditBatchSummary) -> String {
if audit_batch_publishable(summary) {
"publishable"
} else {
"blocked"
}
}
///|
pub fn audit_batch_publish_line(summary : AuditBatchSummary) -> String {
"\{audit_batch_publish_status(summary)} total=\{summary.total} score=\{summary.score} errors=\{summary.errors}"
}
///|
pub fn audit_report_metric_contract(report : RenderAuditReport) -> Bool {
for metric in report.metrics {
if metric.minimum > metric.maximum || metric.weight < 0.0 {
return false
}
}
true
}
///|
pub fn audit_report_finding_contract(report : RenderAuditReport) -> Bool {
for finding in report.findings {
if finding.code.length() == 0 || finding.message.length() == 0 {
return false
}
}
true
}
///|
pub fn audit_report_full_contract(report : RenderAuditReport) -> Bool {
audit_report_contract(report) &&
audit_report_metric_contract(report) &&
audit_report_finding_contract(report)
}
///|
pub fn audit_report_rebuild_score(
report : RenderAuditReport,
) -> RenderAuditReport {
audit_report_recompute(report, false)
}
///|
pub fn audit_report_rebuild_strict_score(
report : RenderAuditReport,
) -> RenderAuditReport {
audit_report_recompute(report, true)
}
///|
pub fn audit_report_strict_pass(report : RenderAuditReport) -> Bool {
audit_export_gate(audit_report_rebuild_strict_score(report), true)
}
///|
pub fn audit_report_relaxed_pass(report : RenderAuditReport) -> Bool {
audit_export_gate(audit_report_rebuild_score(report), false)
}
///|
pub fn audit_report_gate_message(report : RenderAuditReport) -> String {
if audit_report_strict_pass(report) {
"strict gate passed"
} else if audit_report_relaxed_pass(report) {
"relaxed gate passed; review warnings"
} else {
"export gate failed"
}
}
///|
pub fn audit_report_gate_line(report : RenderAuditReport) -> String {
"\{audit_report_gate_message(report)}: \{audit_report_publish_line(report)}"
}
///|
pub fn audit_report_measure(
report : RenderAuditReport,
name : String,
) -> Double {
report.metric_value(name).unwrap_or(0.0)
}
///|
pub fn audit_report_measure_or(
report : RenderAuditReport,
name : String,
fallback : Double,
) -> Double {
report.metric_value(name).unwrap_or(fallback)
}
///|
pub fn audit_report_measure_positive(
report : RenderAuditReport,
name : String,
) -> Double {
audit_report_measure(report, name).max(0.0)
}
///|
pub fn audit_report_measure_percent(
report : RenderAuditReport,
name : String,
) -> Int {
(audit_report_measure(report, name) * 100.0).to_int()
}
///|
pub fn audit_report_measure_line(
report : RenderAuditReport,
name : String,
) -> String {
"\{name}=\{audit_report_measure(report, name)} (\{audit_report_measure_percent(report, name)}%)"
}
///|
pub fn audit_report_domain_line(
report : RenderAuditReport,
domain : AuditDomain,
) -> String {
"\{ra_domain_name(domain)} score=\{audit_domain_score(report, domain)} errors=\{audit_findings_by_domain(report, domain).filter(fn(finding) { finding.severity == AuditError }).length()}"
}
///|
pub fn audit_report_domain_lines(report : RenderAuditReport) -> Array[String] {
audit_domain_scores(report).map(fn(pair) {
let (domain, _) = pair
audit_report_domain_line(report, domain)
})
}
///|
pub fn audit_report_dashboard(report : RenderAuditReport) -> String {
let builder = StringBuilder()
builder.write_string("\{audit_report_header(report)}\n")
builder.write_string("\{audit_report_progress(report)}\n")
builder.write_string("\{audit_report_risk_summary(report)}\n")
for line in audit_report_domain_lines(report) {
builder.write_string("\{line}\n")
}
builder.to_string()
}
///|
pub fn audit_batch_dashboard(summary : AuditBatchSummary) -> String {
let builder = StringBuilder()
builder.write_string("\{audit_batch_for_log(summary)}\n")
for report in summary.reports {
builder.write_string("\{audit_report_compact(report)}\n")
}
builder.to_string()
}
///|
pub fn audit_report_is_reproducible(report : RenderAuditReport) -> Bool {
report.seed != 0U && audit_report_digest(report) != 0U
}
///|
pub fn audit_report_reproducibility_key(report : RenderAuditReport) -> String {
"seed=\{report.seed};digest=\{audit_report_digest(report)};schema=\{audit_report_version()}"
}
///|
pub fn audit_batch_reproducibility_key(summary : AuditBatchSummary) -> String {
"count=\{summary.total};digest=\{audit_batch_digest(summary)};schema=\{audit_report_version()}"
}
///|
pub fn audit_report_matches_seed(
report : RenderAuditReport,
seed : UInt,
) -> Bool {
report.seed == seed
}
///|
pub fn audit_report_matches_name(
report : RenderAuditReport,
name : String,
) -> Bool {
report.name == name
}
///|
pub fn audit_report_matches_identity(
report : RenderAuditReport,
name : String,
seed : UInt,
) -> Bool {
audit_report_matches_name(report, name) &&
audit_report_matches_seed(report, seed)
}
///|
pub fn audit_report_identity(report : RenderAuditReport) -> (String, UInt) {
(report.name, report.seed)
}
///|
pub fn audit_report_identity_text(report : RenderAuditReport) -> String {
"\{report.name}:\{report.seed}"
}
///|
pub fn audit_report_add_elapsed(
report : RenderAuditReport,
delta : Int,
) -> Unit {
report.elapsed_hint += delta.max(0)
}
///|
pub fn audit_report_elapsed(report : RenderAuditReport) -> Int {
report.elapsed_hint.max(0)
}
///|
pub fn audit_report_is_fast(report : RenderAuditReport, budget : Int) -> Bool {
report.elapsed_hint <= budget.max(0)
}
///|
pub fn audit_report_runtime_status(
report : RenderAuditReport,
budget : Int,
) -> String {
if report.elapsed_hint <= budget.max(0) {
"within-budget"
} else {
"over-budget"
}
}
///|
pub fn audit_report_runtime_line(
report : RenderAuditReport,
budget : Int,
) -> String {
"runtime \{audit_report_runtime_status(report, budget)} hint=\{report.elapsed_hint} budget=\{budget.max(0)}"
}
///|
pub fn audit_report_add_runtime_finding(
report : RenderAuditReport,
budget : Int,
) -> Unit {
if report.elapsed_hint > budget.max(0) {
report.add_finding(
"RUNTIME_BUDGET",
AuditPerformance,
AuditWarning,
"estimated work exceeds the configured runtime budget",
report.elapsed_hint.to_double(),
budget.max(0).to_double(),
)
}
}
///|
pub fn audit_report_with_runtime_gate(
report : RenderAuditReport,
budget : Int,
) -> RenderAuditReport {
let copy = audit_report_copy(report)
audit_report_add_runtime_finding(copy, budget)
copy.finalize(false)
copy
}
///|
pub fn audit_report_runtime_gate(
report : RenderAuditReport,
budget : Int,
) -> Bool {
audit_report_with_runtime_gate(report, budget).error_count() == 0
}
///|
pub fn audit_report_budget_delta(
report : RenderAuditReport,
budget : Int,
) -> Int {
budget.max(0) - report.elapsed_hint
}
///|
pub fn audit_report_budget_label(
report : RenderAuditReport,
budget : Int,
) -> String {
"\{audit_report_runtime_status(report, budget)} delta=\{audit_report_budget_delta(report, budget)}"
}
///|
pub fn audit_report_with_note_if_failed(report : RenderAuditReport) -> Unit {
if !report.passed {
audit_report_append_note(report, audit_recommendation(report))
}
}
///|
pub fn audit_report_finalize_with_note(
report : RenderAuditReport,
strict : Bool,
) -> Unit {
report.finalize(strict)
audit_report_with_note_if_failed(report)
}
///|
pub fn audit_report_ready_line(report : RenderAuditReport) -> String {
"ready=\{audit_is_exportable(report)} quality=\{audit_report_quality_label(report)} risk=\{audit_report_risk_level(report)}"
}
///|
pub fn audit_report_ready_for_gallery(report : RenderAuditReport) -> Bool {
report.passed && report.score >= 0.75 && report.warning_count() <= 2
}
///|
pub fn audit_report_ready_for_archive(report : RenderAuditReport) -> Bool {
report.passed && audit_report_full_contract(report)
}
///|
pub fn audit_report_ready_for_preview(report : RenderAuditReport) -> Bool {
report.error_count() == 0 && report.score >= 0.25
}
///|
pub fn audit_report_ready_for_ci(report : RenderAuditReport) -> Bool {
audit_export_gate(report, true) && audit_report_full_contract(report)
}
///|
pub fn audit_report_ready_for_batch(report : RenderAuditReport) -> Bool {
audit_export_gate(report, false) && report.elapsed_hint >= 0
}
///|
pub fn audit_report_target_label(report : RenderAuditReport) -> String {
if audit_report_ready_for_ci(report) {
"ci"
} else if audit_report_ready_for_gallery(report) {
"gallery"
} else if audit_report_ready_for_preview(report) {
"preview"
} else {
"draft"
}
}
///|
pub fn audit_report_target_line(report : RenderAuditReport) -> String {
"target=\{audit_report_target_label(report)}; \{audit_report_ready_line(report)}"
}
///|
pub fn audit_report_serialize(report : RenderAuditReport) -> String {
report.to_json_string(indent=2)
}
///|
pub fn audit_batch_serialize(summary : AuditBatchSummary) -> String {
summary.to_json_string(indent=2)
}
///|
pub fn audit_report_roundtrip_digest(report : RenderAuditReport) -> UInt {
audit_report_digest(audit_report_copy(report))
}
///|
pub fn audit_report_copy_preserves_digest(report : RenderAuditReport) -> Bool {
audit_report_digest(report) == audit_report_roundtrip_digest(report)
}
///|
pub fn audit_report_copy_preserves_identity(report : RenderAuditReport) -> Bool {
audit_report_identity(report) ==
audit_report_identity(audit_report_copy(report))
}
///|
pub fn audit_report_copy_preserves_metrics(report : RenderAuditReport) -> Bool {
audit_report_metrics_digest(report) ==
audit_report_metrics_digest(audit_report_copy(report))
}
///|
pub fn audit_report_copy_preserves_findings(report : RenderAuditReport) -> Bool {
audit_report_findings_digest(report) ==
audit_report_findings_digest(audit_report_copy(report))
}
///|
pub fn audit_report_integrity(report : RenderAuditReport) -> Bool {
audit_report_copy_preserves_identity(report) &&
audit_report_copy_preserves_metrics(report) &&
audit_report_copy_preserves_findings(report)
}
///|
pub fn audit_report_integrity_line(report : RenderAuditReport) -> String {
"integrity=\{audit_report_integrity(report)} digest=\{audit_report_digest(report)}"
}
///|
pub fn audit_report_pipeline_line(
report : RenderAuditReport,
budget : Int,
) -> String {
"\{audit_report_target_line(report)}; \{audit_report_runtime_line(report, budget)}; \{audit_report_integrity_line(report)}"
}
///|
pub fn audit_batch_pipeline_line(
summary : AuditBatchSummary,
budget : Int,
) -> String {
"\{audit_batch_publish_line(summary)}; runtime_budget=\{budget.max(0)}; digest=\{audit_batch_digest(summary)}"
}