data-quality-report — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited data-quality-report (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.
A governance-grade reporting skill. Produces auditable PDF deliverables aligned to DAMA-DMBOK2 standards, with traceable lineage and compliance checks.
Activate when the user has a dataset and needs a formal, archivable quality report — not just an exploratory analysis. Typical signals:
Pre-condition: the dataset should be loaded and reasonably structured. If raw quality is very low (>50% missing across many columns), call eda-explorer first to surface issues, then loop back to this skill once the user decides on a cleaning strategy.
Do NOT activate this skill for:
eda-explorerfeature-engineerFor every request, follow these 6 phases in order. Never skip a phase.
Run a complete profile of the dataset. Reuse the logic from eda-explorer if it produces results in the same conversation; otherwise, compute fresh:
Store all metrics as a Python dict — they will be reused in Phases 2-6.
Compute a score from 0 to 100 for each of the six dimensions:
| Dimension | Formula | Source |
|---|---|---|
| Completeness | 100 * (1 - mean_missing_pct / 100) | Phase 1 missing |
| Uniqueness | 100 * (1 - n_duplicate_rows / n_rows) | Phase 1 duplicates |
| Validity | 100 * (1 - mean_outlier_pct) | Phase 1 outliers (IQR) |
| Consistency | 100 * (1 - n_mixed_type_cols / n_object_cols) | Phase 1 type checks |
| Timeliness | See note below | Phase 1 datetime range |
| Accuracy | None — flag as "Manual assessment required" | N/A automatic |
Timeliness rule (heuristic):
None with note "No datetime column to assess"100806040Aggregate score: weighted average of the 5 computable dimensions:
Color code:
For each dimension scoring < 80, generate 1-3 specific, actionable recommendations referencing actual columns. Categorize as:
[CLEAN] — fix existing values[IMPUTE] — fill missing[DROP] — remove column or rows[INVESTIGATE] — manual review needed[GOVERN] — process / policy change neededRun three compliance modules:
#### 4a) RGPD — PII detection
Scan column names AND sample values (up to 50 rows) for these patterns:
| Category | Column name patterns | Value patterns |
|---|---|---|
| Direct identifiers | email, mail, phone, tel, name, nom, prenom, ssn, nir, dob, birth, address, adresse, cp, postal, iban, card | regex for emails, phone numbers, SSN/NIR formats |
| Indirect identifiers | user_id, customer_id, account_id, client_id, device_id, ip | UUIDs, IPs |
| Sensitive data (Art. 9 RGPD) | health, religion, politic, union, bio, genetic, gender, genre, ethnic, sexual | — |
Output: list of flagged columns with category and confidence (high/medium/low).
#### 4b) ISO 8000-110 — Data quality management checklist
Verify these 6 ISO 8000-110 requirements:
| Requirement | How we verify |
|---|---|
| Master data registry exists | Pass if user provides a registry; else flag |
| Data quality measured | Pass — done in Phase 2 |
| Quality criteria documented | Pass — done in this report |
| Quality issues tracked | Pass — done in Phase 3 |
| Provenance recorded | Pass — done in Phase 6 (audit log) |
| Standards conformance verified | Manual flag |
#### 4c) BCBS 239 — 14 principles spotlight (banking only)
Only run this section if the user explicitly mentions banking, finance, credit, or BCBS. Otherwise, skip with a note "Skipped — not a banking dataset".
When run, evaluate the 4 most measurable principles:
Build the PDF using ReportLab (required: pip install reportlab).
Format: A4, portrait, 1.5cm margins.
Color palette (CONEXT brand):
#0D0E52#00B5A3#F4F5FA#1A1A2E#6B6B85#C0392B#F2B705Typography:
Mandatory pages in this exact order:
Visual elements:
Save 3 files in outputs/quality/:
{
"skill_version": "0.1.0",
"generated_at": "2026-04-27T18:32:11Z",
"source": {
"filename": "sample_insurance.csv",
"shape": [2000, 10],
"size_bytes": 154280,
"content_hash_sha256": "ab12cd34...",
"loaded_at": "2026-04-27T18:32:08Z"
},
"dama_scores": {
"completeness": 96.4,
"uniqueness": 100.0,
"validity": 88.2,
"consistency": 90.0,
"timeliness": 80.0,
"accuracy": null,
"aggregate": 91.2,
"flag": "GREEN"
},
"compliance": {
"rgpd": {
"direct_identifiers_found": ["customer_id"],
"indirect_identifiers_found": [],
"sensitive_data_found": [],
"verdict": "ATTENTION — pseudonymization recommended"
},
"iso_8000_110": {
"passed": 4,
"total": 6,
"manual_required": ["master_data_registry", "standards_conformance"]
},
"bcbs_239": {
"applicable": false,
"reason": "Not a banking dataset"
}
},
"recommendations": [
{"category": "IMPUTE", "column": "satisfaction_score", "action": "..."},
{"category": "GOVERN", "column": "region", "action": "..."}
],
"audit": {
"skill_version": "0.1.0",
"audit_log_path": "outputs/quality/audit_log.txt",
"pdf_path": "outputs/quality/quality_report_2026-04-27_183211.pdf"
}
}After generation, respond with a concise summary:
# Data Quality Report — <filename>
**Score global : XX/100 [GREEN | YELLOW | RED]**
## Synthèse par dimension DAMA-DMBOK2
| Dimension | Score | Statut |
| Completeness | XX | 🟢/🟡/🔴 |
...
## Compliance
- RGPD : <verdict>
- ISO 8000-110 : X/6 vérifiés automatiquement
- BCBS 239 : <applicable ou skipped>
## Top 3 risques identifiés
1. ...
2. ...
3. ...
## Artefacts générés
- 📄 PDF : `outputs/quality/quality_report_<timestamp>.pdf` (X pages, X KB)
- 📋 JSON : `outputs/quality/quality_report_<timestamp>.json`
- 📜 Audit log mis à jour : `outputs/quality/audit_log.txt`
## Prochaines étapes recommandées
1. ...audit_log.txt (never overwrite — append-only is core to auditability)Use this as the structural backbone. Adapt to actual data, but keep the 6-phase structure intact.
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
import pandas as pd
import numpy as np
from reportlab.lib.pagesizes import A4
from reportlab.lib.colors import HexColor
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, Image,
)
# ─── Setup ──────────────────────────────────────────────
SKILL_VERSION = "0.1.0"
OUTPUT_DIR = Path("outputs/quality")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
NAVY = HexColor("#0D0E52")
TEAL = HexColor("#00B5A3")
LIGHT = HexColor("#F4F5FA")
INK = HexColor("#1A1A2E")
RED = HexColor("#C0392B")
YELLOW = HexColor("#F2B705")
GREEN = HexColor("#27AE60")
# ─── Phase 1: Profiling ─────────────────────────────────
def profile(df: pd.DataFrame) -> dict:
return {
"shape": df.shape,
"missing_pct": (df.isna().sum() / len(df) * 100).to_dict(),
"duplicates": int(df.duplicated().sum()),
"dtypes": {c: str(df[c].dtype) for c in df.columns},
# ... outliers, datetime range, etc.
}
# ─── Phase 2: DAMA dimensions ──────────────────────────
def compute_dama_scores(profile: dict, df: pd.DataFrame) -> dict:
completeness = 100 * (1 - np.mean(list(profile["missing_pct"].values())) / 100)
uniqueness = 100 * (1 - profile["duplicates"] / profile["shape"][0])
# ... validity, consistency, timeliness
aggregate = 0.25*completeness + 0.15*uniqueness + 0.20*validity + 0.15*consistency + 0.25*timeliness
flag = "GREEN" if aggregate >= 80 else ("YELLOW" if aggregate >= 60 else "RED")
return {"completeness": completeness, ..., "aggregate": aggregate, "flag": flag}
# ─── Phase 3: Recommendations ──────────────────────────
def build_recommendations(scores: dict, profile: dict) -> list:
recs = []
if scores["completeness"] < 80:
# find the top missing columns
top_missing = sorted(profile["missing_pct"].items(), key=lambda x: -x[1])[:3]
for col, pct in top_missing:
if pct > 30:
recs.append({"category": "DROP", "column": col, "action": f"...{pct:.1f}% missing — consider dropping"})
else:
recs.append({"category": "IMPUTE", "column": col, "action": f"Impute median or mode"})
# ... validity, consistency
return recs
# ─── Phase 4: Compliance ───────────────────────────────
PII_PATTERNS = {
"direct": ["email", "phone", "tel", "name", "nom", "ssn", "nir", "dob", "address"],
"indirect": ["user_id", "customer_id", "account_id", "ip"],
"sensitive": ["health", "religion", "politic", "ethnic", "gender"],
}
def check_rgpd(df: pd.DataFrame) -> dict:
flags = {"direct": [], "indirect": [], "sensitive": []}
for col in df.columns:
col_lower = col.lower()
for category, patterns in PII_PATTERNS.items():
if any(p in col_lower for p in patterns):
flags[category].append(col)
break
return flags
# ─── Phase 5: PDF generation ────────────────────────────
def hash_first_mb(filepath: Path) -> str:
with open(filepath, "rb") as f:
return hashlib.sha256(f.read(1024*1024)).hexdigest()
def generate_pdf(report_data: dict, output_path: Path):
doc = SimpleDocTemplate(str(output_path), pagesize=A4,
topMargin=1.5*cm, bottomMargin=1.5*cm,
leftMargin=1.8*cm, rightMargin=1.8*cm)
story = []
# Cover page, exec summary, dimensions, compliance, recommendations, audit
# Build using Paragraph, Spacer, Table, PageBreak
doc.build(story)
# ─── Phase 6: Export ───────────────────────────────────
def export_artifacts(report_data: dict, source: Path) -> dict:
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H%M%S")
pdf_path = OUTPUT_DIR / f"quality_report_{timestamp}.pdf"
json_path = OUTPUT_DIR / f"quality_report_{timestamp}.json"
log_path = OUTPUT_DIR / "audit_log.txt"
generate_pdf(report_data, pdf_path)
json_path.write_text(json.dumps(report_data, indent=2, default=str))
with open(log_path, "a") as f:
f.write(f"{timestamp} | v{SKILL_VERSION} | {source} | {report_data['source']['content_hash_sha256'][:12]} | score={report_data['dama_scores']['aggregate']:.1f}\n")
return {"pdf": pdf_path, "json": json_path, "log": log_path}~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.