///|
pub(all) enum GateStatus {
Pass
Review
Fail
} derive(Debug, Eq, ToJson)
///|
pub(all) struct QualityGate {
name : String
status : GateStatus
score : Int
evidence : String
action : String
} derive(Debug, Eq, ToJson)
///|
pub(all) struct QualityGateReport {
status : GateStatus
gates : Array[QualityGate]
score : Int
} derive(Debug, Eq, ToJson)
///|
pub fn gate_status_name(status : GateStatus) -> String {
match status {
Pass => "pass"
Review => "review"
Fail => "fail"
}
}
///|
pub fn gate_for_summary(summary : AnalysisSummary) -> QualityGate {
let status = if summary.errors > 0 {
Fail
} else if summary.warnings > 0 {
Review
} else {
Pass
}
{
name: "analysis",
status,
score: summary.score,
evidence: "\{summary.errors} errors, \{summary.warnings} warnings",
action: if status == Pass {
"continue composing"
} else {
"inspect the findings"
},
}
}
///|
pub fn gate_for_sequence(report : SequenceReport, name : String) -> QualityGate {
let status = if report.is_valid() { Pass } else { Fail }
{
name,
status,
score: if report.is_valid() {
100
} else {
0
},
evidence: "\{report.error_count()} sequence issues",
action: if report.is_valid() {
"keep the timeline"
} else {
"repair onset and duration data"
},
}
}
///|
pub fn gate_for_motion(summary : VoiceLeadingSummary) -> QualityGate {
let status = if summary.rejected == 0 {
Pass
} else if summary.accepted > summary.rejected {
Review
} else {
Fail
}
{
name: "voice-leading",
status,
score: motion_acceptance_rate(summary),
evidence: "\{summary.accepted}/\{summary.transitions} transitions accepted",
action: if status == Pass {
"retain the motion"
} else {
"try contrary or oblique motion"
},
}
}
///|
pub fn quality_gate_report(
validation : PairValidation,
motion : VoiceLeadingSummary,
) -> QualityGateReport {
let gates = [
gate_for_sequence(validation.cantus, "cantus"),
gate_for_sequence(validation.counterpoint, "counterpoint"),
gate_for_summary(validation.summary),
gate_for_motion(motion),
]
let mut total = 0
let mut failed = 0
let mut reviewed = 0
for gate in gates {
total += gate.score
if gate.status == Fail {
failed += 1
}
if gate.status == Review {
reviewed += 1
}
}
{
status: if failed > 0 {
Fail
} else if reviewed > 0 {
Review
} else {
Pass
},
gates,
score: if gates.length() == 0 {
0
} else {
total / gates.length()
},
}
}
///|
pub fn quality_gate_passed(report : QualityGateReport) -> Bool {
report.status == Pass
}
///|
pub fn gate_count(report : QualityGateReport, status : GateStatus) -> Int {
let mut result = 0
for gate in report.gates {
if gate.status == status {
result += 1
}
}
result
}
///|
pub fn quality_gate_markdown(report : QualityGateReport) -> String {
let lines : Array[String] = [
"## Quality gates",
"",
"- Overall: \{gate_status_name(report.status)}",
"- Score: \{report.score}",
"",
]
for gate in report.gates {
lines.push(
"- \{gate.name}: \{gate_status_name(gate.status)} — \{gate.evidence}; \{gate.action}",
)
}
lines.join("\n")
}
///|
pub fn quality_gate_actions(report : QualityGateReport) -> Array[String] {
let result : Array[String] = []
for gate in report.gates {
if gate.status != Pass {
result.push(gate.action)
}
}
result
}
///|
pub fn quality_gate_score(report : QualityGateReport) -> Int {
report.score
}