///|
/// Domain-level validation findings for customer event streams.
pub enum QualityIssue {
EmptyCustomerId
NegativeDay
NegativeAmount
FutureEvent
DuplicateEvent
} derive(Debug, Eq)
///|
pub struct QualityFinding {
index : Int
issue : QualityIssue
customer_id : String
message : String
}
///|
pub struct QualityReport {
accepted : Array[Event]
findings : Array[QualityFinding]
unique_customers : Int
total_value : Int
}
///|
fn issue_message(issue : QualityIssue) -> String {
match issue {
EmptyCustomerId => "customer id is empty"
NegativeDay => "day cannot be negative"
NegativeAmount => "amount cannot be negative"
FutureEvent => "event is after the analysis date"
DuplicateEvent => "duplicate event was ignored"
}
}
///|
/// Validate events, preserving valid order and reporting every rejected row.
pub fn validate_events(events : Array[Event], as_of : Int) -> QualityReport {
let accepted : Array[Event] = []
let findings : Array[QualityFinding] = []
let seen : Array[(String, Int, Int)] = []
let customers : Array[String] = []
let mut total_value = 0
for index, event in events {
let issue = if event.customer_id.trim().is_empty() {
Some(EmptyCustomerId)
} else if event.day < 0 {
Some(NegativeDay)
} else if event.amount < 0 {
Some(NegativeAmount)
} else if event.day > as_of {
Some(FutureEvent)
} else if seen.contains((event.customer_id, event.day, event.amount)) {
Some(DuplicateEvent)
} else {
None
}
match issue {
Some(value) =>
findings.push({
index,
issue: value,
customer_id: event.customer_id,
message: issue_message(value),
})
None => {
accepted.push(event)
seen.push((event.customer_id, event.day, event.amount))
if !customers.contains(event.customer_id) {
customers.push(event.customer_id)
}
total_value += event.amount
}
}
}
{ accepted, findings, unique_customers: customers.length(), total_value }
}
///|
pub fn QualityReport::issue_count(
report : QualityReport,
issue : QualityIssue,
) -> Int {
let mut count = 0
for finding in report.findings {
if finding.issue == issue {
count += 1
}
}
count
}
///|
pub fn QualityReport::is_clean(report : QualityReport) -> Bool {
report.findings.is_empty()
}
///|
/// Convenience counters keep black-box consumers independent of enum constructors.
pub fn QualityReport::duplicate_count(report : QualityReport) -> Int {
report.issue_count(DuplicateEvent)
}
///|
pub fn QualityReport::empty_customer_count(report : QualityReport) -> Int {
report.issue_count(EmptyCustomerId)
}
///|
pub fn QualityReport::future_event_count(report : QualityReport) -> Int {
report.issue_count(FutureEvent)
}