///|
/// A compact, deterministic quality view for CI logs and acceptance reports.
pub fn model_complexity_score(model : Model) -> Int {
let metrics = analyze_model(model.to_step(), model)
let mut score = 0
if metrics.entity_count > 0 {
score += 20
}
if metrics.product_count > 0 {
score += 20
}
if metrics.geometry_count > 0 {
score += 20
}
if metrics.property_count > 0 {
score += 20
}
if metrics.spatial_depth > 0 {
score += 20
}
score
}
///|
pub fn model_geometry_coverage(model : Model) -> Double {
let products = model.products()
if products.length() == 0 {
0.0
} else {
products
.fold(init=0, (count, product) => {
if product.representation is Some(_) {
count + 1
} else {
count
}
})
.to_double() /
products.length().to_double()
}
}
///|
pub fn model_property_coverage(model : Model) -> Double {
let products = model.products()
if products.length() == 0 {
0.0
} else {
products
.fold(init=0, (count, product) => {
if product.property_sets.length() > 0 {
count + 1
} else {
count
}
})
.to_double() /
products.length().to_double()
}
}
///|
pub fn model_spatial_coverage(model : Model) -> Bool {
spatial_roots(model.index()).length() > 0
}
///|
pub fn model_quality_grade(model : Model) -> String {
let report = validate(model)
let score = model_complexity_score(model)
if !report.is_valid() {
"invalid"
} else if score >= 80 {
"complete"
} else if score >= 60 {
"usable"
} else {
"partial"
}
}
///|
pub fn model_quality_lines(model : Model) -> Array[String] {
let metrics = analyze_model(model.to_step(), model)
let validation = validate(model)
let health = source_health(model.to_step())
let schema = validate_schema(model)
[
"grade=" + model_quality_grade(model),
"score=" + model_complexity_score(model).to_string() + "/100",
"schema=" + model.schema(),
"entities=" + metrics.entity_count.to_string(),
"products=" + metrics.product_count.to_string(),
"geometry=" + metrics.geometry_count.to_string(),
"properties=" + metrics.property_count.to_string(),
"relationships=" + metrics.relationship_count.to_string(),
"references=" + metrics.reference_count.to_string(),
"spatial_depth=" + metrics.spatial_depth.to_string(),
"geometry_coverage=" + model_geometry_coverage(model).to_string(),
"property_coverage=" + model_property_coverage(model).to_string(),
"spatial_root=" + model_spatial_coverage(model).to_string(),
"parse_health=" + health.summary(),
"semantic_errors=" + validation.error_count.to_string(),
"semantic_warnings=" + validation.warning_count.to_string(),
"schema_errors=" + schema.error_count.to_string(),
"schema_warnings=" + schema.warning_count.to_string(),
]
}
///|
pub fn model_quality_report(model : Model) -> String {
model_quality_lines(model).join("\n")
}
///|
pub fn model_quality_json(model : Model) -> String {
let metrics = analyze_model(model.to_step(), model)
let validation = validate(model)
"{\"grade\":" +
json_string(model_quality_grade(model)) +
",\"score\":" +
model_complexity_score(model).to_string() +
",\"schema\":" +
json_string(model.schema()) +
",\"entities\":" +
metrics.entity_count.to_string() +
",\"products\":" +
metrics.product_count.to_string() +
",\"geometry\":" +
metrics.geometry_count.to_string() +
",\"properties\":" +
metrics.property_count.to_string() +
",\"errors\":" +
validation.error_count.to_string() +
",\"warnings\":" +
validation.warning_count.to_string() +
"}"
}
///|
pub fn acceptance_checklist(model : Model) -> Array[String] {
let checklist : Array[String] = []
let validation = validate(model)
let parser_status = if model.entities.length() > 0 { "pass" } else { "empty" }
let semantic_status = if validation.is_valid() { "pass" } else { "fail" }
checklist.push("parser: " + parser_status)
checklist.push("semantic validation: " + semantic_status)
checklist.push("schema profile: " + validate_schema(model).headline())
let spatial_status = if model_spatial_coverage(model) {
"pass"
} else {
"not present"
}
let geometry_status = if geometry_entities(model.index()).length() > 0 {
"pass"
} else {
"not present"
}
let property_status = if model.index().ids_named("IFCPROPERTYSET").length() >
0 {
"pass"
} else {
"not present"
}
checklist.push("spatial extraction: " + spatial_status)
checklist.push("geometry extraction: " + geometry_status)
checklist.push("property extraction: " + property_status)
checklist
}
///|
pub fn acceptance_checklist_markdown(model : Model) -> String {
acceptance_checklist(model)
.map(item => {
let marker = if item.contains("fail") { " " } else { "x" }
"- [" + marker + "] " + item
})
.join("\n")
}
///|
pub fn model_summary_json(model : Model) -> String raise ParseError {
let catalog = ModelCatalog::new(model)
let query = CatalogQuery::new().page(0, 100000)
let page = catalog.query(query)
"{\"quality\":" +
model_quality_json(model) +
",\"catalog_products\":" +
page.total_matches.to_string() +
",\"types\":[" +
catalog
.type_histogram()
.to_array()
.map(item => {
let (name, count) = item
"{\"type\":" + json_string(name) + ",\"count\":" + count.to_string() + "}"
})
.join(",") +
"]}"
}