///|
/// Reproducible audit record for analytics decisions.
pub struct AuditEntry {
sequence : Int
operation : String
input_count : Int
output_count : Int
score : Double
parameters : Array[String]
}
///|
pub struct AuditTrail {
name : String
entries : Array[AuditEntry]
mut checksum : Double
}
///|
pub fn audit_trail(name : String) -> AuditTrail {
{ name, entries: [], checksum: 0.0 }
}
///|
pub fn audit_append(
trail : AuditTrail,
operation : String,
input_count : Int,
output_count : Int,
score : Double,
parameters : Array[String],
) -> Unit {
let sequence = trail.entries.length()
trail.entries.push({
sequence,
operation,
input_count,
output_count,
score,
parameters,
})
trail.checksum += score +
input_count.to_double() * 0.001 +
output_count.to_double() * 0.0001
}
///|
pub fn audit_from_pipeline(
result : DataPipelineResult,
name : String,
) -> AuditTrail {
let trail = audit_trail(name)
for stage in result.stages {
audit_append(
trail,
stage.name,
stage.input_count,
stage.output_count,
stage.score,
stage.notes,
)
}
trail
}
///|
pub fn audit_entry_count(trail : AuditTrail) -> Int {
trail.entries.length()
}
///|
pub fn audit_operations(trail : AuditTrail) -> Array[String] {
let result = []
for entry in trail.entries {
result.push(entry.operation)
}
result
}
///|
pub fn audit_scores(trail : AuditTrail) -> Array[Double] {
let result = []
for entry in trail.entries {
result.push(entry.score)
}
result
}
///|
pub fn audit_total_input(trail : AuditTrail) -> Int {
let mut total = 0
for entry in trail.entries {
total += entry.input_count
}
total
}
///|
pub fn audit_total_output(trail : AuditTrail) -> Int {
let mut total = 0
for entry in trail.entries {
total += entry.output_count
}
total
}
///|
pub fn audit_quality(trail : AuditTrail) -> Double {
if trail.entries.length() == 0 {
1.0
} else {
mean(audit_scores(trail))
}
}
///|
pub fn audit_reproducible(
left : AuditTrail,
right : AuditTrail,
tolerance : Double,
) -> Bool {
if left.entries.length() != right.entries.length() {
return false
}
if audit_operations(left) != audit_operations(right) {
return false
}
abs_double(left.checksum - right.checksum) <= tolerance
}
///|
pub fn audit_lines(trail : AuditTrail) -> Array[String] {
let lines = [
"name=" + trail.name,
"entries=" + trail.entries.length().to_string(),
"checksum=" + trail.checksum.to_string(),
]
for entry in trail.entries {
lines.push(
entry.sequence.to_string() +
"|" +
entry.operation +
"|" +
entry.input_count.to_string() +
"|" +
entry.output_count.to_string() +
"|" +
entry.score.to_string(),
)
}
lines
}
///|
pub fn audit_string(trail : AuditTrail) -> String {
audit_lines(trail).join("\n")
}
///|
pub fn audit_digest(trail : AuditTrail) -> Array[Double] {
[
trail.entries.length().to_double(),
trail.checksum,
audit_total_input(trail).to_double(),
audit_total_output(trail).to_double(),
audit_quality(trail),
]
}
///|
pub fn audit_last(trail : AuditTrail) -> AuditEntry {
if trail.entries.length() == 0 {
{
sequence: -1,
operation: "none",
input_count: 0,
output_count: 0,
score: 0.0,
parameters: [],
}
} else {
trail.entries[trail.entries.length() - 1]
}
}
///|
pub fn audit_filter(
trail : AuditTrail,
minimum_score : Double,
) -> Array[AuditEntry] {
let result = []
for entry in trail.entries {
if entry.score >= minimum_score {
result.push(entry)
}
}
result
}
///|
pub fn audit_summary(trail : AuditTrail) -> Array[Double] {
[
audit_quality(trail),
trail.entries.length().to_double(),
trail.checksum,
audit_total_input(trail).to_double(),
audit_total_output(trail).to_double(),
]
}