///|
/// A deterministic import gate built from table quality and structural checks.
pub(all) struct QualityGateReport {
passed : Bool
quality_score : Int
density_score : Int
duplicate_row_count : Int
missing_columns : Array[String]
reasons : Array[String]
} derive(Eq)
///|
fn quality_gate_threshold(value : Int) -> Int {
clamp_percent(value)
}
///|
fn quality_gate_join(values : Array[String], separator : String) -> String {
if values.length() == 0 {
"none"
} else {
let out = StringBuilder::StringBuilder()
let mut i = 0
while i < values.length() {
if i > 0 {
out.write_string(separator)
}
out.write_string(values[i])
i = i + 1
}
out.to_string()
}
}
///|
fn quality_gate_add_score_reason(
reasons : Array[String],
label : String,
actual : Int,
minimum : Int,
) -> Unit {
if actual < minimum {
let text =
$|\{label} score \{actual} is below minimum \{minimum}
reasons.push(text)
}
}
///|
fn quality_gate_add_missing_reason(
reasons : Array[String],
missing : Array[String],
) -> Unit {
if missing.length() > 0 {
let names = quality_gate_join(missing, ", ")
let text =
$|missing required columns: \{names}
reasons.push(text)
}
}
///|
fn quality_gate_add_duplicate_reason(
reasons : Array[String],
duplicate_count : Int,
reject_duplicates : Bool,
) -> Unit {
if reject_duplicates && duplicate_count > 0 {
let text =
$|duplicate rows detected: \{duplicate_count}
reasons.push(text)
}
}
///|
/// Check whether a table is safe to pass into an import or release workflow.
/// Thresholds are clamped to the inclusive range 0..100.
pub fn Table::quality_gate(
self : Table,
min_quality_score : Int,
min_density_score : Int,
required_columns : Array[String],
reject_duplicates : Bool,
) -> QualityGateReport {
let minimum_quality = quality_gate_threshold(min_quality_score)
let minimum_density = quality_gate_threshold(min_density_score)
let prof = profile(self)
let stats = table_stats(self)
let missing = self.missing_columns(required_columns)
let reasons : Array[String] = []
quality_gate_add_score_reason(
reasons,
"quality",
prof.quality_score,
minimum_quality,
)
quality_gate_add_score_reason(
reasons,
"density",
stats.density_score,
minimum_density,
)
quality_gate_add_missing_reason(reasons, missing)
quality_gate_add_duplicate_reason(
reasons,
stats.duplicate_row_count,
reject_duplicates,
)
{
passed: reasons.length() == 0,
quality_score: prof.quality_score,
density_score: stats.density_score,
duplicate_row_count: stats.duplicate_row_count,
missing_columns: missing,
reasons,
}
}
///|
pub fn QualityGateReport::summary(self : QualityGateReport) -> String {
let status = if self.passed { "passed" } else { "failed" }
let reasons = quality_gate_join(self.reasons, "; ")
let text =
$|\{status}: quality=\{self.quality_score}, density=\{self.density_score}, duplicates=\{self.duplicate_row_count}, reasons=\{reasons}
text
}
///|
fn quality_gate_markdown_row(
out : StringBuilder,
name : String,
value : String,
) -> Unit {
out.write_string("| ")
out.write_string(markdown_escape_cell(name))
out.write_string(" | ")
out.write_string(markdown_escape_cell(value))
out.write_string(" |\n")
}
///|
/// Render gate evidence as a compact Markdown table for CI or review records.
pub fn QualityGateReport::to_markdown(self : QualityGateReport) -> String {
let out = StringBuilder::StringBuilder()
quality_gate_markdown_row(out, "metric", "value")
quality_gate_markdown_row(out, "---", "---")
quality_gate_markdown_row(
out,
"status",
if self.passed {
"passed"
} else {
"failed"
},
)
quality_gate_markdown_row(
out,
"quality_score",
self.quality_score.to_string(),
)
quality_gate_markdown_row(
out,
"density_score",
self.density_score.to_string(),
)
quality_gate_markdown_row(
out,
"duplicate_row_count",
self.duplicate_row_count.to_string(),
)
quality_gate_markdown_row(
out,
"missing_columns",
quality_gate_join(self.missing_columns, ", "),
)
quality_gate_markdown_row(
out,
"reasons",
quality_gate_join(self.reasons, "; "),
)
out.to_string()
}