///|
/// Core data model for explainable text audits.
pub(all) enum Severity {
Info
Warning
Error
} derive(Eq, @debug.Debug)
///|
/// Stable lowercase label used by reports and external tools.
pub fn Severity::label(self : Severity) -> String {
match self {
Info => "info"
Warning => "warning"
Error => "error"
}
}
///|
/// A small ordinal used when callers need to sort or gate findings.
pub fn Severity::rank(self : Severity) -> Int {
match self {
Info => 1
Warning => 2
Error => 3
}
}
///|
/// Token category emitted by `tokenize_lines`.
pub(all) enum LineKind {
Blank
Heading
Bullet
CodeFence
Paragraph
} derive(Eq, @debug.Debug)
///|
/// A line-level token that keeps the source line number.
pub(all) struct LineToken {
line : Int
kind : LineKind
text : String
} derive(Eq, @debug.Debug)
///|
/// Rule strategies supported by the engine.
pub(all) enum RuleKind {
MustContainAny(Array[String])
MustNotContainAny(Array[String])
MinOccurrences(String, Int)
MaxLineLength(Int)
RequiresPair(String, String)
SectionOrder(Array[String])
} derive(@debug.Debug)
///|
/// A weighted rule with an explanation-oriented hint.
pub(all) struct Rule {
id : String
title : String
category : String
severity : Severity
weight : Int
kind : RuleKind
hint : String
} derive(@debug.Debug)
///|
/// Evidence points at a concrete line and matching term.
pub(all) struct Evidence {
line : Int
snippet : String
matched : String
} derive(Eq, @debug.Debug)
///|
/// Result of evaluating a single rule.
pub(all) struct Finding {
rule_id : String
title : String
category : String
severity : Severity
passed : Bool
weight : Int
earned : Int
message : String
hint : String
evidence : Array[Evidence]
} derive(@debug.Debug)
///|
/// Shape summary produced while tokenizing a document.
pub(all) struct DocumentShape {
lines : Int
headings : Int
bullets : Int
code_fences : Int
paragraphs : Int
blanks : Int
} derive(Eq, @debug.Debug)
///|
/// Aggregated counters for an audit run.
pub(all) struct AuditStats {
line_count : Int
char_count : Int
matched_rules : Int
failed_rules : Int
error_count : Int
warning_count : Int
heading_count : Int
bullet_count : Int
} derive(Eq, @debug.Debug)
///|
/// Complete audit result.
pub(all) struct Audit {
title : String
score : Int
max_score : Int
earned_score : Int
grade : String
findings : Array[Finding]
stats : AuditStats
} derive(@debug.Debug)
///|
/// Construct a presence rule that passes when any term is found.
pub fn Rule::must_contain_any(
id : StringView,
title : StringView,
category : StringView,
severity : Severity,
weight : Int,
terms : ArrayView[String],
hint : StringView,
) -> Rule {
{
id: id.to_owned(),
title: title.to_owned(),
category: category.to_owned(),
severity,
weight,
kind: MustContainAny(terms.to_owned()),
hint: hint.to_owned(),
}
}
///|
/// Construct a rule that fails when any forbidden term is found.
pub fn Rule::must_not_contain_any(
id : StringView,
title : StringView,
category : StringView,
severity : Severity,
weight : Int,
terms : ArrayView[String],
hint : StringView,
) -> Rule {
{
id: id.to_owned(),
title: title.to_owned(),
category: category.to_owned(),
severity,
weight,
kind: MustNotContainAny(terms.to_owned()),
hint: hint.to_owned(),
}
}
///|
/// Construct a minimum occurrence rule.
pub fn Rule::min_occurrences(
id : StringView,
title : StringView,
category : StringView,
severity : Severity,
weight : Int,
needle : StringView,
min : Int,
hint : StringView,
) -> Rule {
{
id: id.to_owned(),
title: title.to_owned(),
category: category.to_owned(),
severity,
weight,
kind: MinOccurrences(needle.to_owned(), min),
hint: hint.to_owned(),
}
}
///|
/// Construct a maximum line length rule.
pub fn Rule::max_line_length(
id : StringView,
title : StringView,
category : StringView,
severity : Severity,
weight : Int,
max : Int,
hint : StringView,
) -> Rule {
{
id: id.to_owned(),
title: title.to_owned(),
category: category.to_owned(),
severity,
weight,
kind: MaxLineLength(max),
hint: hint.to_owned(),
}
}
///|
/// Construct a rule where the right term is required when the left term exists.
pub fn Rule::requires_pair(
id : StringView,
title : StringView,
category : StringView,
severity : Severity,
weight : Int,
left : StringView,
right : StringView,
hint : StringView,
) -> Rule {
{
id: id.to_owned(),
title: title.to_owned(),
category: category.to_owned(),
severity,
weight,
kind: RequiresPair(left.to_owned(), right.to_owned()),
hint: hint.to_owned(),
}
}
///|
/// Construct an ordered-section rule.
pub fn Rule::section_order(
id : StringView,
title : StringView,
category : StringView,
severity : Severity,
weight : Int,
sections : ArrayView[String],
hint : StringView,
) -> Rule {
{
id: id.to_owned(),
title: title.to_owned(),
category: category.to_owned(),
severity,
weight,
kind: SectionOrder(sections.to_owned()),
hint: hint.to_owned(),
}
}
///|
/// True when a failed finding should block release.
pub fn Finding::is_blocking(self : Finding) -> Bool {
!self.passed && self.severity is Error
}
///|
/// True when the audit has no failing error-level findings.
pub fn Audit::is_success(self : Audit) -> Bool {
self.stats.error_count == 0
}
///|
/// Return only failed findings.
pub fn Audit::failed(self : Audit) -> Array[Finding] {
self.findings.filter(finding => !finding.passed)
}
///|
/// Return only passed findings.
pub fn Audit::passed(self : Audit) -> Array[Finding] {
self.findings.filter(finding => finding.passed)
}