data-quality — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited data-quality (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.
Pipelines succeed (exit 0) even when they load garbage. A missing WHERE clause, a silent NULL coercion, an upstream schema change — none of these throw exceptions. The result is a dashboard that looks fine until someone cross-references it with reality.
Systematic data quality checks turn silent failures into loud ones. The goal is to catch issues at the pipeline boundary — not after a stakeholder finds them.
Every check falls into one of these categories:
| Dimension | What it catches | Examples |
|---|---|---|
| Completeness | Missing or null values | user_id IS NULL, row count = 0 |
| Validity | Values outside expected domain | Negative revenue, future birthdates, unknown status codes |
| Consistency | Internal contradictions | end_date < start_date, child with no parent |
| Freshness | Data is stale or arrived late | Last updated_at is 3 days ago when it should be hourly |
dbt's built-in tests cover the most common checks with zero code:
# models/staging/schema.yml
models:
- name: stg_orders
columns:
- name: order_id
tests:
- not_null
- unique
- name: status
tests:
- accepted_values:
values: ['pending', 'processing', 'shipped', 'delivered', 'cancelled']
- name: customer_id
tests:
- not_null
- relationships:
to: ref('dim_customers')
field: customer_id
- name: revenue_usd
tests:
- not_null
- dbt_utils.expression_is_true:
expression: ">= 0"Run on every CI push: dbt test --select stg_orders.
Install dbt-utils and dbt-expectations for extended checks:
- name: order_date
tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: "'2020-01-01'"
max_value: "current_date"
- name: email
tests:
- dbt_expectations.expect_column_values_to_match_regex:
regex: "^[^@]+@[^@]+\\.[^@]+$"Use Great Expectations (GE) when you need validation outside dbt, or when you want to checkpoint data mid-pipeline before loading.
import great_expectations as gx
context = gx.get_context()
ds = context.sources.add_pandas("my_source")
da = ds.add_dataframe_asset("orders_asset")
batch = da.get_batch_request()
validator = context.get_validator(batch_request=batch, expectation_suite_name="orders_suite")
# Completeness
validator.expect_column_values_to_not_be_null("order_id")
validator.expect_column_values_to_not_be_null("customer_id")
# Validity
validator.expect_column_values_to_be_in_set("status", ["pending", "shipped", "delivered"])
validator.expect_column_values_to_be_between("revenue_usd", min_value=0)
# Freshness
validator.expect_column_max_to_be_between(
"created_at",
min_value=str(datetime.utcnow() - timedelta(hours=25)),
max_value=str(datetime.utcnow())
)
results = validator.validate()
if not results.success:
raise ValueError(f"Data quality check failed: {results}")Place a GE checkpoint immediately after loading raw data and before any transformation. If validation fails, halt the pipeline and alert — don't let bad raw data propagate downstream.
A pipeline that loads zero rows is almost always a bug, not a business reality.
def assert_row_count(df, min_rows=1, max_deviation_pct=50, baseline=None):
count = len(df)
assert count >= min_rows, f"Too few rows: {count}"
if baseline:
deviation = abs(count - baseline) / baseline * 100
assert deviation <= max_deviation_pct, \
f"Row count {count} deviates {deviation:.1f}% from baseline {baseline}"Catch when a key metric shifts unexpectedly between runs:
-- Store yesterday's total
SELECT SUM(revenue_usd) AS total FROM fct_orders WHERE order_date = CURRENT_DATE - 1;
-- Alert if today's total deviates > 30%
WITH yesterday AS (SELECT 1000000 AS total), -- parameterize this
today AS (SELECT SUM(revenue_usd) AS total FROM fct_orders WHERE order_date = CURRENT_DATE)
SELECT
today.total,
yesterday.total,
ABS(today.total - yesterday.total) / yesterday.total AS deviation
FROM today, yesterday
WHERE ABS(today.total - yesterday.total) / yesterday.total > 0.30;Upstream sources change their schemas silently. Check that expected columns exist before loading:
EXPECTED_COLUMNS = {"order_id", "customer_id", "status", "revenue_usd", "created_at"}
def assert_schema(df):
missing = EXPECTED_COLUMNS - set(df.columns)
extra = set(df.columns) - EXPECTED_COLUMNS
assert not missing, f"Missing expected columns: {missing}"
# Extra columns are usually OK — just log them
if extra:
logging.warning(f"Unexpected new columns: {extra}")# Fail on any null in required columns
for col in ["order_id", "customer_id", "created_at"]:
null_count = df[col].isna().sum()
assert null_count == 0, f"Column {col} has {null_count} nulls"import pandas as pd
assert pd.api.types.is_datetime64_any_dtype(df["created_at"]), "created_at must be datetime"
assert pd.api.types.is_numeric_dtype(df["revenue_usd"]), "revenue_usd must be numeric"assert df["revenue_usd"].ge(0).all(), "revenue_usd must be non-negative"
assert df["quantity"].between(1, 10000).all(), "quantity out of expected range"
assert df["order_date"].max() <= pd.Timestamp.today(), "order_date in the future"valid_customer_ids = set(customers_df["customer_id"])
orphaned = df[~df["customer_id"].isin(valid_customer_ids)]
assert len(orphaned) == 0, f"{len(orphaned)} orders reference unknown customer_ids"Source → [EXTRACT checks] → Raw load → [FRESHNESS + SCHEMA checks] → Transform → [BUSINESS RULE checks] → Mart load → [METRIC DRIFT checks]~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.