data-auditor — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited data-auditor (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Analyze any uploaded CSV or Excel dataset and produce:
Both outputs are generated every time. The PDF is the primary deliverable for stakeholders; the Excel is the detailed backup for analysts.
Use these Python libraries (install if needed):
pandas — data loading and analysismatplotlib — chart generationseaborn — statistical visualizations and heatmapsnumpy — numerical computationsreportlab — PDF generation with embedded figuresopenpyxl — Excel workbook generationImport setup at the top of every script:
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg') # Non-interactive backend — REQUIRED
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.backends.backend_pdf import PdfPages
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib.colors import HexColor
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, PageBreak,
Table, TableStyle, Image as RLImage
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.chart import BarChart, PieChart, Reference
from datetime import datetime
import os
import tempfileUse this consistent palette across ALL charts and the PDF:
COLORS = {
'primary': '#1a56db',
'accent': '#10b981',
'warning': '#f59e0b',
'danger': '#ef4444',
'purple': '#8b5cf6',
'gray_dark': '#374151',
'gray_medium': '#6b7285',
'gray_light': '#f3f4f6',
'white': '#ffffff',
}
SEVERITY_COLORS = {
'High': '#ef4444',
'Medium': '#f59e0b',
'Low': '#10b981',
}
# For matplotlib charts
CHART_PALETTE = ['#1a56db', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16']Set matplotlib defaults:
plt.rcParams.update({
'figure.facecolor': 'white',
'axes.facecolor': 'white',
'axes.edgecolor': '#e5e7eb',
'axes.grid': True,
'grid.alpha': 0.3,
'grid.color': '#e5e7eb',
'font.family': 'sans-serif',
'font.size': 10,
'axes.titlesize': 13,
'axes.titleweight': 'bold',
'axes.labelsize': 10,
'figure.dpi': 150,
})Activate this skill when the user:
Perform ALL of the following checks on every applicable column:
#### Check 1: Missing Values
(missing_count / total_rows) * 100#### Check 2: Duplicate Rows
#### Check 3: Data Type Validation
#### Check 4: Outlier Detection (Numeric Columns Only)
#### Check 5: Date Format Consistency
#### Check 6: Text Casing Inconsistencies
#### Check 7: Whitespace Issues
#### Check 8: Column Name Quality
Compute an overall score out of 100:
| Category | Weight | Scoring |
|---|---|---|
| Completeness | 30% | 100 - (avg missing % across all columns) |
| Uniqueness | 15% | 100 - (duplicate row %) |
| Type Consistency | 20% | 100 - (avg type mismatch % across columns) |
| Outlier Reasonability | 15% | 100 - (avg outlier % across numeric cols) |
| Format Consistency | 10% | 100 if all dates consistent, else penalize |
| Text Quality | 10% | 100 - (avg whitespace/casing issue %) |
Interpretation:
Save all charts as temporary PNG files for embedding in the PDF. Use plt.savefig() with bbox_inches='tight' and dpi=150.
#### Figure 1: Health Score Gauge Create a donut/ring chart showing the overall health score:
fig, ax = plt.subplots(figsize=(4, 4))
score = health_score # 0-100
colors_gauge = [COLORS['primary'] if score >= 70 else COLORS['warning'] if score >= 50 else COLORS['danger'], '#e5e7eb']
ax.pie([score, 100 - score], colors=colors_gauge, startangle=90, counterclock=False,
wedgeprops={'width': 0.3, 'edgecolor': 'white', 'linewidth': 2})
ax.text(0, 0, f'{score}', fontsize=36, fontweight='bold', ha='center', va='center', color=COLORS['gray_dark'])
ax.text(0, -0.15, 'out of 100', fontsize=10, ha='center', va='center', color=COLORS['gray_medium'])
ax.set_title('Data Health Score', fontsize=14, fontweight='bold', pad=20)
plt.savefig(gauge_path, bbox_inches='tight', dpi=150)
plt.close()#### Figure 2: Missing Values Bar Chart Horizontal bar chart showing missing percentage per column:
fig, ax = plt.subplots(figsize=(8, max(4, len(columns_with_missing) * 0.4)))
# Sort by missing percentage descending
# Color bars by severity: red > 20%, orange 5-20%, green < 5%
# Add percentage labels at the end of each bar
# Add a vertical dashed line at 20% and 5% thresholds
ax.set_xlabel('Missing Values (%)')
ax.set_title('Missing Values by Column', fontsize=14, fontweight='bold')
plt.savefig(missing_path, bbox_inches='tight', dpi=150)
plt.close()Only include columns that have at least some missing values. If no columns have missing values, skip this chart and note "No missing values detected" in the report.
#### Figure 3: Health Score Breakdown Pie Chart Pie chart showing the weighted contribution of each quality category:
fig, ax = plt.subplots(figsize=(6, 6))
categories = ['Completeness', 'Uniqueness', 'Type Consistency', 'Outlier Reasonability', 'Format Consistency', 'Text Quality']
weights = [30, 15, 20, 15, 10, 10]
scores_per_category = [...] # Each category's score * weight / 100
colors_pie = CHART_PALETTE[:6]
explode = [0.03] * 6
wedges, texts, autotexts = ax.pie(scores_per_category, labels=categories, colors=colors_pie,
autopct='%1.0f%%', startangle=90, explode=explode,
textprops={'fontsize': 9})
ax.set_title('Health Score Breakdown by Category', fontsize=14, fontweight='bold')
plt.savefig(pie_path, bbox_inches='tight', dpi=150)
plt.close()#### Figure 4: Issue Severity Heatmap A grid heatmap with columns on x-axis, check types on y-axis, cells colored by severity:
fig, ax = plt.subplots(figsize=(max(8, len(columns) * 0.6), 5))
# Create a matrix: rows = check types, columns = dataset columns
# Values: 0 = no issue, 1 = Low, 2 = Medium, 3 = High
# Use seaborn heatmap with custom colormap
cmap = sns.color_palette([COLORS['white'], COLORS['accent'], COLORS['warning'], COLORS['danger']])
# OR use ListedColormap
from matplotlib.colors import ListedColormap
severity_cmap = ListedColormap(['#f3f4f6', '#d1fae5', '#fef3c7', '#fee2e2'])
sns.heatmap(severity_matrix, ax=ax, cmap=severity_cmap, vmin=0, vmax=3,
xticklabels=column_names, yticklabels=check_names,
linewidths=1, linecolor='white', cbar=False,
annot=severity_labels, fmt='s') # severity_labels = matrix of "H"/"M"/"L"/""
ax.set_title('Issue Severity Heatmap', fontsize=14, fontweight='bold')
plt.xticks(rotation=45, ha='right', fontsize=8)
plt.savefig(heatmap_path, bbox_inches='tight', dpi=150)
plt.close()If the dataset has more than 20 columns, only show the 20 columns with the most issues.
#### Figure 5: Distribution Histograms (Numeric Columns) For each numeric column (up to 6), create a subplot histogram:
numeric_cols = df.select_dtypes(include=[np.number]).columns[:6]
n_cols = len(numeric_cols)
if n_cols > 0:
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(12, 7))
axes = axes.flatten()
for i, col in enumerate(numeric_cols):
ax = axes[i]
data = df[col].dropna()
ax.hist(data, bins=30, color=CHART_PALETTE[i % len(CHART_PALETTE)],
edgecolor='white', alpha=0.85)
# Add vertical lines for Q1, median, Q3
q1, median, q3 = data.quantile([0.25, 0.5, 0.75])
ax.axvline(median, color=COLORS['danger'], linestyle='--', linewidth=1.5, label=f'Median: {median:.1f}')
# Mark IQR outlier boundaries
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
ax.axvline(lower, color=COLORS['warning'], linestyle=':', linewidth=1, alpha=0.7)
ax.axvline(upper, color=COLORS['warning'], linestyle=':', linewidth=1, alpha=0.7)
ax.set_title(col, fontsize=11, fontweight='bold')
ax.legend(fontsize=7)
# Hide unused subplots
for j in range(n_cols, len(axes)):
axes[j].set_visible(False)
fig.suptitle('Value Distributions (Numeric Columns)', fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig(hist_path, bbox_inches='tight', dpi=150)
plt.close()Skip this figure entirely if the dataset has no numeric columns.
#### Figure 6: Trend Lines (Only If Date Column Exists) If a date/datetime column is detected:
date_col = detected_date_column
numeric_cols_for_trend = df.select_dtypes(include=[np.number]).columns[:3]
if date_col and len(numeric_cols_for_trend) > 0:
fig, axes = plt.subplots(nrows=len(numeric_cols_for_trend), ncols=1,
figsize=(10, 3.5 * len(numeric_cols_for_trend)), sharex=True)
if len(numeric_cols_for_trend) == 1:
axes = [axes]
for i, col in enumerate(numeric_cols_for_trend):
ax = axes[i]
# Sort by date, resample if needed (daily/weekly/monthly based on date range)
sorted_df = df.sort_values(date_col)
ax.plot(sorted_df[date_col], sorted_df[col], color=CHART_PALETTE[i], alpha=0.4, linewidth=0.8)
# Add rolling average
window = max(7, len(sorted_df) // 20)
rolling = sorted_df[col].rolling(window=window, center=True).mean()
ax.plot(sorted_df[date_col], rolling, color=CHART_PALETTE[i], linewidth=2, label=f'{window}-period avg')
ax.set_ylabel(col, fontsize=10)
ax.legend(fontsize=8)
axes[-1].set_xlabel('Date')
fig.suptitle('Trends Over Time', fontsize=14, fontweight='bold', y=1.01)
plt.tight_layout()
plt.savefig(trend_path, bbox_inches='tight', dpi=150)
plt.close()Skip entirely if no date column is detected. Note "No time-series data detected" in the report.
Use reportlab to create a professional multi-page PDF. Structure:
Page 1: Cover Page
Page 2: Executive Summary
Page 3: Missing Values Analysis
Page 4: Issue Severity Overview
Page 5: Distribution Analysis
Page 6: Trend Analysis (only if date column exists)
Page 7: Detailed Findings Table
Page 8: Recommendations
PDF Formatting Rules:
In addition to the PDF, produce an Excel file with three sheets:
#### Sheet 1: "Summary" Rows: Dataset Name, Total Rows, Total Columns, Health Score, Health Rating, Critical Issues, Warnings, Info Items, Columns With Missing Data, Duplicate Rows, Date Generated.
#### Sheet 2: "Details" Columns: Column Name, Check Type, Severity, Finding, Affected Rows, Percentage, Example Values. Sort by Severity (High first), then Affected Rows (descending).
#### Sheet 3: "Recommendations" Columns: Priority, Issue, Recommendation, Affected Columns, Estimated Impact.
Excel Formatting:
audit_report_[dataset_name]_[YYYY-MM-DD].pdfaudit_data_[dataset_name]_[YYYY-MM-DD].xlsxplt.show() — always use plt.savefig() then plt.close()matplotlib.use('Agg') — will fail without display server~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.