Skip to content

Model & govern / Data quality

Data quality ​

Keel suggests, runs and scores data-quality rules on the tables behind your model, opens incidents when critical checks fail, and turns the results into each metric's trust score.

Rules ​

TypeChecksTypical suggestion
NotNull, UniqueKeys and grain columnsPrimary keys, grains
ReferentialIntegrityOrphan rows on a joinAccepted joins (0% tolerance for declared foreign keys, 1% for inferred)
AcceptedValuesA known value setLow-cardinality, non-sensitive columns (≤ 20 values)
Range, NonNegativeNumeric boundsObserved min..max ± 10%; amounts and quantities
RegexA patternEmails, ISO codes (SQL Server uses a LIKE fallback)
FreshnessLag of the newest timestampFact timestamps (limit = 2 × the observed cadence)
RowCountDelta, VolumeAnomalyVolume changesEvery table (50% delta); same-weekday baseline, robust z-score > 3.5
DistributionDriftPSI against a baselineMeasures (baseline from the first run)
CustomSqlYour query returning the failing rows—

Rules belong to one of seven dimensions: Completeness, Uniqueness, Validity, Integrity, Timeliness, Consistency, Reasonableness. Severity is Critical, Warning or Info; only Critical failures open incidents and cap trust.

Suggested rules ​

After every successful scan Keel suggests rules from profiles and classifications — as proposals. Review them in Data quality › Review suggested rules (confidence, evidence, bulk accept ≥ 0.85); rejected suggestions never come back. Accepted rules run on their schedule: freshness hourly, everything else daily at 02:00 UTC (five-field UTC cron).

Custom SQL ​

A custom rule is one read-only SELECT or WITH query that returns the failing rows. A tokenizer rejects DDL/DML, INTO, SET, FOR UPDATE/SHARE and multiple statements (POST …/quality/custom-sql:validate checks it without saving). Keel runs it as SELECT COUNT(*) FROM (…) q with bound parameters on the source.

bash
curl -X POST "$KEEL/api/v1/workspaces/$WS/quality/rules" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
        "dataSourceId": "'"$SOURCE"'", "schema": "public", "table": "invoice", "column": null,
        "name": "invoice_total_positive", "type": "CustomSql", "severity": "Critical", "dimension": "Validity",
        "description": "Invoices must have a positive total.",
        "parameters": { "sql": "SELECT invoice_id FROM public.invoice WHERE total <= 0" },
        "schedule": "0 6 * * *"
      }'

Runs and privacy ​

Rules run pushed down on the source through its dialect (all eight connector kinds). A run stores the measured value and at most 50 failing rows' key columns — never values of Confidential, Restricted or PII columns; with no safe key, counts only. Re-run check (:run) runs one rule now; assets:run runs every rule of a table.

Scorecards ​

Scores 0–100 per dimension and overall, with a daily trend, for any asset: workspace, source, table, column, entity, metric or domain (GET …/quality/scorecard?asset=). Entity, metric and domain scores roll up through lineage.

Incidents ​

A Critical failure opens an incident (one per rule) and notifies Owners, Admins and Stewards (in-app, email, and any subscribed Slack/Teams/webhook channel). Acknowledge assigns it to you; it resolves itself after 3 consecutive passing runs, or by hand. Open incidents appear on Home and as a dot in the navigation.

In change requests ​

The change-request data quality check reads stored results for the tables behind changed metrics. It warns; it never blocks the merge.

Known gaps

  • Importing dbt tests and Great Expectations suites as rules is part of M9 (Imports).
  • Incident summaries don't carry the threshold and unit; the dashboard shows the latest 50 results without paging; acknowledgers and assignees show as ids.

Anchor Keel is a working product name: one platform, a shared core and licensed modules (Keel Semantic, Keel Migrate). Documentation built from the repository — facts are derived from code, ADRs and the decisions log.