///|
/// One local report used to gate a model before it reaches a viewer or exporter.
pub(all) struct ModelDeliveryReport {
source_length : Int
profile : ModelProfile
quality : ModelQualityReport
manifest : AssetManifest
canonical : CanonicalStepStats
} derive(Debug)
///|
/// Build the profile, quality score, cache manifest and canonical STEP statistics together.
pub fn model_delivery_report(
source : String,
) -> ModelDeliveryReport raise ParseError {
let model = parse(source)
{
source_length: source.length(),
profile: model_profile(model),
quality: model_quality_audit(model),
manifest: asset_manifest(model),
canonical: canonical_step_stats(source),
}
}
///|
pub fn ModelDeliveryReport::is_ready(self : ModelDeliveryReport) -> Bool {
!self.quality.has_errors() &&
self.manifest.entry_count() > 0 &&
!self.canonical.has_duplicates()
}
///|
pub fn ModelDeliveryReport::has_geometry(self : ModelDeliveryReport) -> Bool {
self.profile.geometry_count > 0 && self.manifest.triangle_count > 0
}
///|
pub fn ModelDeliveryReport::score(self : ModelDeliveryReport) -> Int {
let quality = self.quality.score
let geometry = if self.has_geometry() { 10 } else { 0 }
let canonical = if self.canonical.changed { 5 } else { 10 }
let complete = if self.is_ready() { 10 } else { 0 }
let result = quality + geometry + canonical + complete
if result > 100 {
100
} else {
result
}
}
///|
pub fn ModelDeliveryReport::headline(self : ModelDeliveryReport) -> String {
"ready=" +
self.is_ready().to_string() +
" score=" +
self.score().to_string() +
" schema=" +
quality_schema_name(self.profile.schema) +
" entities=" +
self.profile.entity_count.to_string() +
" products=" +
self.profile.product_count.to_string() +
" assets=" +
self.manifest.entry_count().to_string()
}
///|
pub fn ModelDeliveryReport::to_json(self : ModelDeliveryReport) -> String {
"{\"ready\":" +
self.is_ready().to_string() +
",\"score\":" +
self.score().to_string() +
",\"source_length\":" +
self.source_length.to_string() +
",\"profile\":" +
self.profile.to_json() +
",\"quality\":" +
self.quality.to_json() +
",\"manifest\":" +
self.manifest.to_json() +
",\"canonical\":" +
self.canonical.to_json() +
"}"
}
///|
pub fn ModelDeliveryReport::to_markdown(self : ModelDeliveryReport) -> String {
let builder = StringBuilder()
builder <+ "# Model delivery report\n\n"
builder <+ "- \{self.headline()}\n"
builder <+ "- Source length: \{self.source_length}\n"
builder <+ "- Has geometry: \{self.has_geometry()}\n"
builder <+ "- Quality: \{self.quality.headline()}\n"
builder <+ "- Manifest: \{self.manifest.headline()}\n"
builder <+ "- Canonical STEP: \{self.canonical.headline()}\n\n"
builder <+ "## Delivery gates\n\n"
builder <+ "| Gate | Result |\n| --- | --- |\n"
builder <+ "| Quality errors | \{self.quality.has_errors()}\n"
builder <+ "| Cache manifest | \{self.manifest.entry_count() > 0}\n"
builder <+ "| Duplicate ids | \{self.canonical.has_duplicates()}\n"
builder <+ "| Geometry | \{self.has_geometry()}\n"
builder <+ "| Ready | \{self.is_ready()}\n"
builder.to_string()
}
///|
pub fn model_delivery_summary(
source : String,
) -> Array[String] raise ParseError {
let report = model_delivery_report(source)
[
report.headline(),
"quality_score=" + report.quality.score.to_string(),
"manifest_bytes=" + report.manifest.total_bytes().to_string(),
"canonical_ratio=" + report.canonical.compression_ratio().to_string(),
]
}
///|
pub fn model_delivery_gate(source : String) -> Bool raise ParseError {
model_delivery_report(source).is_ready()
}
///|
pub fn ModelDeliveryReport::asset_bytes(
self : ModelDeliveryReport,
kind : AssetKind,
) -> Int {
self.manifest
.entries_of_kind(kind)
.fold(init=0, (sum, entry) => sum + entry.byte_length)
}
///|
pub fn ModelDeliveryReport::quality_passed(self : ModelDeliveryReport) -> Bool {
self.quality.count_level(Error) == 0 && self.quality.count_level(Warning) == 0
}
///|
pub fn ModelDeliveryReport::canonical_passed(
self : ModelDeliveryReport,
) -> Bool {
!self.canonical.has_duplicates() && self.canonical.entity_count > 0
}
///|
pub fn ModelDeliveryReport::gate_results(
self : ModelDeliveryReport,
) -> Map[String, Bool] {
let result : Map[String, Bool] = Map([])
result["quality"] = self.quality_passed()
result["manifest"] = self.manifest.entry_count() > 0
result["canonical"] = self.canonical_passed()
result["geometry"] = self.has_geometry()
result["ready"] = self.is_ready()
result
}
///|
pub fn model_delivery_report_with_policy(
source : String,
policy : QualityPolicy,
options : AssetManifestOptions,
) -> ModelDeliveryReport raise ParseError {
let model = parse(source)
{
source_length: source.length(),
profile: model_profile(model),
quality: model_quality_audit(model, policy~),
manifest: asset_manifest(model, options~),
canonical: canonical_step_stats(source),
}
}
///|
pub fn model_delivery_gate_with_policy(
source : String,
policy : QualityPolicy,
options : AssetManifestOptions,
) -> Bool raise ParseError {
model_delivery_report_with_policy(source, policy, options).is_ready()
}
///|
pub fn model_delivery_asset_breakdown(
report : ModelDeliveryReport,
) -> Array[String] {
[
"manifest_bytes=" + report.asset_bytes(Manifest).to_string(),
"geometry_bytes=" + report.asset_bytes(Geometry).to_string(),
"properties_bytes=" + report.asset_bytes(Properties).to_string(),
"spatial_bytes=" + report.asset_bytes(Spatial).to_string(),
"product_bytes=" + report.asset_bytes(Product).to_string(),
]
}