report-generator — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited report-generator (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.
Transform a dataset into two professional deliverables:
Both outputs are generated every time.
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg') # REQUIRED — no display server
import matplotlib.pyplot as plt
import seaborn as sns
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, HRFlowable
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment
from datetime import datetime
import os
import tempfileSame palette as the Data Auditor for visual consistency:
COLORS = {
'primary': '#1a56db',
'primary_light': '#e8eefb',
'accent': '#10b981',
'accent_light': '#d1fae5',
'warning': '#f59e0b',
'danger': '#ef4444',
'purple': '#8b5cf6',
'gray_dark': '#374151',
'gray_medium': '#6b7285',
'gray_light': '#f3f4f6',
'white': '#ffffff',
}
CHART_PALETTE = ['#1a56db', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16']
plt.rcParams.update({
'figure.facecolor': 'white',
'axes.facecolor': 'white',
'axes.edgecolor': '#e5e7eb',
'axes.grid': True,
'grid.alpha': 0.3,
'font.family': 'sans-serif',
'font.size': 10,
'figure.dpi': 150,
})Activate this skill when the user:
df = pd.read_csv(filepath) # or pd.read_excel()pd.to_datetime on string columns)(non_null_values / total_values) * 100Save every chart as a temporary PNG. Always use plt.savefig(path, bbox_inches='tight', dpi=150) then plt.close().
#### Figure 1: Dataset Overview Bar Chart A horizontal bar showing key metrics:
fig, ax = plt.subplots(figsize=(8, 3))
metrics = ['Total Records', 'Total Fields', 'Numeric Fields', 'Categorical Fields', 'Completeness %']
values = [total_rows, total_cols, n_numeric, n_categorical, completeness_pct]
colors = [COLORS['primary'], COLORS['primary'], COLORS['accent'], COLORS['purple'], COLORS['warning']]
bars = ax.barh(metrics, values, color=colors, edgecolor='white', height=0.6)
# Add value labels on bars
for bar, val in zip(bars, values):
ax.text(bar.get_width() + max(values)*0.02, bar.get_y() + bar.get_height()/2,
f'{val:,.0f}' if val > 1 else f'{val:.1f}%', va='center', fontsize=10, fontweight='bold')
ax.set_title('Dataset Overview', fontsize=14, fontweight='bold')
ax.set_xlim(0, max(values) * 1.2)
plt.savefig(overview_path, bbox_inches='tight', dpi=150)
plt.close()#### Figure 2: Data Composition Pie Chart Pie chart showing the proportion of numeric vs categorical vs datetime columns:
fig, ax = plt.subplots(figsize=(5, 5))
type_counts = [n_numeric, n_categorical, n_datetime, n_other]
type_labels = ['Numeric', 'Categorical', 'Datetime', 'Other']
# Filter out zero-count types
non_zero = [(l, c) for l, c in zip(type_labels, type_counts) if c > 0]
labels, counts = zip(*non_zero)
colors_pie = [COLORS['primary'], COLORS['accent'], COLORS['warning'], COLORS['purple']][:len(labels)]
wedges, texts, autotexts = ax.pie(counts, labels=labels, colors=colors_pie,
autopct='%1.0f%%', startangle=90, textprops={'fontsize': 10},
wedgeprops={'edgecolor': 'white', 'linewidth': 2})
ax.set_title('Column Type Distribution', fontsize=14, fontweight='bold')
plt.savefig(composition_path, bbox_inches='tight', dpi=150)
plt.close()#### Figure 3: Distribution Histograms (Numeric Columns) For up to 6 numeric columns, create a grid of histograms:
numeric_cols = df.select_dtypes(include=[np.number]).columns
# Exclude likely ID columns (all unique values)
numeric_cols = [c for c in numeric_cols if df[c].nunique() < len(df) * 0.9]
plot_cols = numeric_cols[:6]
if len(plot_cols) > 0:
n_rows = (len(plot_cols) + 2) // 3
fig, axes = plt.subplots(nrows=n_rows, ncols=3, figsize=(12, 3.5 * n_rows))
axes = np.array(axes).flatten()
for i, col in enumerate(plot_cols):
ax = axes[i]
data = df[col].dropna()
ax.hist(data, bins=min(30, max(10, len(data)//50)),
color=CHART_PALETTE[i % len(CHART_PALETTE)], edgecolor='white', alpha=0.85)
# Add median line
median_val = data.median()
ax.axvline(median_val, color=COLORS['danger'], linestyle='--', linewidth=1.5)
ax.set_title(f'{col}', fontsize=11, fontweight='bold')
# Add stats annotation
skew = data.skew()
skew_label = 'Left-skewed' if skew < -0.5 else 'Right-skewed' if skew > 0.5 else 'Symmetric'
ax.text(0.97, 0.95, f'Median: {median_val:,.1f}\n{skew_label}',
transform=ax.transAxes, ha='right', va='top', fontsize=7,
bbox=dict(boxstyle='round,pad=0.3', facecolor='white', edgecolor='#e5e7eb', alpha=0.9))
for j in range(len(plot_cols), len(axes)):
axes[j].set_visible(False)
fig.suptitle('Value Distributions', fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig(hist_path, bbox_inches='tight', dpi=150)
plt.close()#### Figure 4: Top Categories Bar Charts (Categorical Columns) For up to 4 categorical columns, show the top 8 most frequent values:
cat_cols = df.select_dtypes(include=['object', 'category']).columns
# Exclude likely ID or high-cardinality columns
cat_cols = [c for c in cat_cols if 2 < df[c].nunique() < 50][:4]
if len(cat_cols) > 0:
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 8))
axes = axes.flatten()
for i, col in enumerate(cat_cols):
ax = axes[i]
top_values = df[col].value_counts().head(8)
bars = ax.barh(top_values.index.astype(str), top_values.values,
color=CHART_PALETTE[i % len(CHART_PALETTE)], edgecolor='white')
ax.set_title(f'{col} — Top Values', fontsize=11, fontweight='bold')
ax.invert_yaxis()
# Add count labels
for bar, val in zip(bars, top_values.values):
ax.text(bar.get_width() + max(top_values.values)*0.02,
bar.get_y() + bar.get_height()/2, f'{val:,}', va='center', fontsize=8)
for j in range(len(cat_cols), len(axes)):
axes[j].set_visible(False)
fig.suptitle('Top Categories', fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.savefig(categories_path, bbox_inches='tight', dpi=150)
plt.close()#### Figure 5: Correlation Heatmap (If 3+ Numeric Columns)
numeric_for_corr = df[numeric_analysis_cols].dropna()
if len(numeric_analysis_cols) >= 3:
corr_matrix = numeric_for_corr.corr()
fig, ax = plt.subplots(figsize=(max(6, len(numeric_analysis_cols)*0.8),
max(5, len(numeric_analysis_cols)*0.7)))
mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
sns.heatmap(corr_matrix, mask=mask, annot=True, fmt='.2f', cmap='RdBu_r',
center=0, vmin=-1, vmax=1, ax=ax, linewidths=0.5,
cbar_kws={'shrink': 0.8, 'label': 'Correlation'},
annot_kws={'fontsize': 8})
ax.set_title('Correlation Matrix', fontsize=14, fontweight='bold')
plt.xticks(rotation=45, ha='right', fontsize=9)
plt.yticks(fontsize=9)
plt.savefig(corr_path, bbox_inches='tight', dpi=150)
plt.close()#### Figure 6: Trend Lines (Only If Date Column Exists)
if date_col is not None:
trend_cols = numeric_analysis_cols[:3]
if len(trend_cols) > 0:
fig, axes = plt.subplots(nrows=len(trend_cols), ncols=1,
figsize=(10, 3.5 * len(trend_cols)), sharex=True)
if len(trend_cols) == 1:
axes = [axes]
sorted_df = df.sort_values(date_col)
for i, col in enumerate(trend_cols):
ax = axes[i]
ax.plot(sorted_df[date_col], sorted_df[col],
color=CHART_PALETTE[i], alpha=0.3, linewidth=0.8)
# 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.5, label=f'{window}-period moving avg')
ax.set_ylabel(col, fontsize=10)
ax.legend(fontsize=8, loc='upper left')
# Shade min/max range
ax.fill_between(sorted_df[date_col], sorted_df[col].min(), sorted_df[col],
alpha=0.05, color=CHART_PALETTE[i])
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()Structure the PDF as follows:
Page 1: Cover Page
Page 2: Executive Summary & Composition
Page 3: Distribution Analysis
Page 4: Categorical Analysis
Page 5: Correlation Analysis (only if 3+ numeric columns)
Page 6: Trend Analysis (only if date column exists)
Page 7: Detailed Column Profiles
Page 8: Notable Findings & Methodology
PDF Formatting Rules:
Create a companion Excel file with four sheets:
#### Sheet 1: "Overview" Key metrics in a clean two-column layout: Dataset Name, Total Rows, Total Columns, Completeness Rate, Numeric Columns, Categorical Columns, Date Range (if applicable), Generated On.
#### Sheet 2: "Column Profiles" One row per column with: Column Name, Data Type, Non-Null Count, Null Count, Null %, Unique Values, Top Value, Top Value %, Mean (numeric), Median (numeric), Std Dev (numeric), Min (numeric), Max (numeric).
#### Sheet 3: "Correlations" The full correlation matrix for numeric columns. Apply conditional formatting: strong positive (> 0.7) in green, strong negative (< -0.7) in red.
#### Sheet 4: "Top Categories" For each categorical column: Column Name, Value, Count, Percentage. Include top 10 values per column.
Excel formatting: Same as Data Auditor (bold headers, alternating rows, auto-width, filters enabled).
report_[dataset_name]_[YYYY-MM-DD].pdfreport_data_[dataset_name]_[YYYY-MM-DD].xlsxplt.show() — always plt.savefig() then plt.close()matplotlib.use('Agg') at the top~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.