use std::io::Write;
use anyhow::Result;
use dataprof::{ColumnStats, PatternCategory, Profiler};
const MESSY_ORDERS: &str = "\
order_id,customer_email,amount_eur,discount_code,shipped_at
1001,ada@example.com,49.90,,2026-01-04
1002,grace@example.com,120.00,,2026-01-04
1003,,15.50,SPRING,2026-01-05
1004,alan@example.com,-30.00,,2026-01-05
1005,,89.99,,2026-01-06
1006,edsger@example.com,not_available,SPRING,2026-01-06
1006,edsger@example.com,64.00,,2026-01-07
1008,,22.10,,2026-01-07
1009,barbara@example.com,310.00,SUMMER,2026-01-08
1010,,45.00,,2026-01-08
";
fn main() -> Result<()> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("orders.csv");
write!(std::fs::File::create(&path)?, "{MESSY_ORDERS}")?;
let report = Profiler::new()
.positive_columns(vec!["amount_eur".to_string()])
.identifier_columns(vec!["order_id".to_string()])
.temporal_columns(vec!["shipped_at".to_string()])
.analyze_file(&path)?;
println!(
"orders.csv: {} rows x {} columns\n",
report.execution.rows_processed,
report.column_profiles.len()
);
println!(
"{:<16} {:<10} {:>7} {:>9}",
"column", "type", "nulls", "distinct"
);
println!("{}", "-".repeat(46));
for col in &report.column_profiles {
let null_pct = 100.0 * col.null_count as f64 / col.total_count.max(1) as f64;
let distinct = col
.unique_count
.map(|n| n.to_string())
.unwrap_or_else(|| "-".to_string());
println!(
"{:<16} {:<10} {:>6.0}% {:>9}",
col.name,
format!("{:?}", col.data_type).to_lowercase(),
null_pct,
distinct
);
}
println!("\nwhat to worry about");
println!("{}", "-".repeat(46));
for col in &report.column_profiles {
let null_pct = 100.0 * col.null_count as f64 / col.total_count.max(1) as f64;
if null_pct >= 20.0 {
println!(
" {}: {:.0}% missing -- is it optional, or is the export broken?",
col.name, null_pct
);
}
if let (Some(distinct), true) = (col.unique_count, col.name == "order_id") {
let present = col.total_count - col.null_count;
if distinct < present {
println!(
" {}: {} rows but only {} distinct -- duplicate key",
col.name, present, distinct
);
}
}
if let Some(patterns) = &col.patterns {
for p in patterns {
if matches!(
p.category,
PatternCategory::Contact | PatternCategory::Financial
) {
println!(
" {}: looks like {} ({:.0}% of rows) -- treat as sensitive",
col.name, p.name, p.match_percentage
);
}
}
}
if let ColumnStats::Numeric(stats) = &col.stats
&& stats.min < 0.0
&& col.name == "amount_eur"
{
println!(
" {}: minimum is {:.2}, but amounts should never be negative",
col.name, stats.min
);
}
}
if let Some(quality) = &report.quality {
println!("\noverall quality: {:.1}/100", quality.score());
if let Some(accuracy) = &quality.metrics.accuracy
&& accuracy.negative_values_in_positive > 0
{
println!(
" {} negative value(s) in a positive-only column",
accuracy.negative_values_in_positive
);
}
if let Some(uniqueness) = &quality.metrics.uniqueness
&& uniqueness.duplicate_rows > 0
{
println!(" {} fully duplicate row(s)", uniqueness.duplicate_rows);
}
if let Some(consistency) = &quality.metrics.consistency
&& consistency.data_type_consistency < 100.0
{
println!(
" type consistency is {:.1}% -- some cells do not match their column's type",
consistency.data_type_consistency
);
}
}
Ok(())
}