Data quality
Agree on what a good table looks like, then score it.
A data contract states the expected state of one table: how fresh, how large, which columns must be unique, present or valid. Beacon checks it on a schedule, turns the rule results into one weighted score, keeps the history and the trend, and alerts when the score drops below the agreed threshold.
- 8rule types
- 0–100%weighted score
- 3database engines
The tick on each ring marks the 80% failure threshold.
A table, a set of weighted rules, one score.
A contract targets one table on one database data source and runs on a cron schedule. Each rule has a type, an optional column, a JSON configuration, a severity and a weight. Together they produce one score per evaluation.
- Data source
- Shop DB (PostgreSQL)
- Table
- public.customers
- Schedule
- 0 */6 * * *
- Failure threshold
- 80%
- Alert on failure
- on · #data-team
- Owner
- Ana Horvat
Every evaluation is stored. The table keeps a latest score and a trend.
Eight checks, from freshness to your own SQL.
Rules stay on the contract's data source: Beacon takes the schema and table from the contract, checks every table and column a rule names against the data source's stored metadata, and quotes them as identifiers, so a rule cannot reach another schema, database or catalog.
Freshness
The newest value in a timestamp column is no older than a maximum age.
{"column": "updated_at", "maxAgeMinutes": 60}Volume
The row count is within an expected range: minRows, maxRows, or both.
{"minRows": 100}Null rate
The percentage of NULLs in a column stays at or below a threshold.
{"column": "email", "maxNullPercent": 5}Small overruns cost little.Uniqueness
A column contains no duplicate values.
{"column": "id"}Referential
Every non-null value exists in a reference table: no orphans.
{"column": "user_id", "referenceTable": "users", "referenceColumn": "id"}Range
Numeric values stay between min and max; either bound is optional.
{"column": "age", "min": "0", "max": "150"}Scores by the share of rows in range.Pattern
All values match a pattern: a regular expression on PostgreSQL and MySQL, LIKE on SQL Server.
{"column": "email", "pattern": "^[^@]+@[^@]+$"}Custom SQL
Any check you can express as one read-only SELECT. Created only by an Admin.
{"sql": "SELECT … AS passed FROM …"}Returns its own score.Most rules score 100 on pass and 0 on fail. Null rate scores max(0, 100 − (actual − max)), Range scores the share of rows within range, and Custom SQL returns its own score.
Scoring the customers table.
Eight rules on public.customers, each with a severity and a weight. Toggle rules off to see how the weighted average moves: a disabled rule is skipped and does not affect the score.
| On | Rule | Result | Score | Severity | Weight | Effective | Score × effective |
|---|---|---|---|---|---|---|---|
| Fresh updatesFreshness | Passnewest row 12 minutes old | 100 | Critical ×4 | 1 | 4 | 400 | |
| Expected sizeVolume | Pass48,000 rows | 100 | Medium ×2 | 1 | 2 | 200 | |
| Unique idsUniqueness | Passno duplicates | 100 | Critical ×4 | 1 | 4 | 400 | |
| Email presentNull rate | Fail5.5% null: 100 − (5.5 − 2) | 96.5 | High ×3 | 1.5 | 4.5 | 434.25 | |
| Plausible ageRange | Fail480 of 48,000 out of range: (1 − 0.01) × 100 | 99 | Low ×1 | 1 | 1 | 99 | |
| Email formatPattern | Fail214 values do not match | 0 | Medium ×2 | 1 | 2 | 0 | |
| Known countryReferential | Passno orphans | 100 | High ×3 | 1 | 3 | 300 | |
| Active have signup dateCustom SQL | Fail30 active customers without one | 70 | Medium ×2 | 1 | 2 | 140 |
At or above the 80% threshold: no alert.
Effective weight is the rule's weight times its severity multiplier. The overall score is rounded to two decimals. Alerts are sent only by scheduled evaluations, never by Evaluate now.
What happens on every run.
Each enabled contract is a recurring job on the host's scheduler, and Evaluate now runs one on demand. A scheduled evaluation runs as the contract's owner.
- Schedule firesThe cron expression, or Evaluate now on the contract page.
- Check the ownerThe owner must still run saved SQL in a project that links the data source.
- Run each ruleEngine-specific SQL per enabled rule, read-only, 60 seconds each.
- ScorePass or fail, a rule score, expected and actual values, a message.
- StoreThe evaluation with every rule result; the table score and trend.
- AlertBelow the threshold, a scheduled run notifies the recipients.
One failing rule stays contained
A rule with a bad configuration, a missing column or a timeout is recorded as failed with score 0, and the other rules still run.
Skipped when the owner lost access
No rule runs and no score, history or alert is written. The contract shows Owner not authorized, and an Admin or UserAdmin can assign a new owner with Change owner.
PostgreSQL, SQL Server and MySQL
Contracts evaluate on these three database engines. Pattern rules follow each engine's own matching: regular expressions, or LIKE on SQL Server.
Any check you can write as one SELECT.
A Custom SQL rule runs your query and reads the first row of the result. Only an Admin creates, changes or deletes a contract that contains one, and when the approval workflow is enabled, a second person approves its SQL before it is evaluated.
SELECT CASE WHEN COUNT(*) = 0 THEN 1 ELSE 0 END AS passed, GREATEST(0, 100 - COUNT(*)) AS score, COUNT(*) AS actual_value, 'Active customers without a signup date' AS messageFROM public.customersWHERE status = 'active' AND signed_up_at IS NULL;| Column | Required | Meaning |
|---|---|---|
| passed | Yes | 1 pass, 0 fail |
| score | No | 0 to 100; defaults to 100 on pass, 0 on fail |
| actual_value | No | Shown as the observed value |
| message | No | Shown as the result message |
Read-only and bounded
One read-only SELECT in the data source's dialect, checked when the contract is saved and on every evaluation. Beacon caps the result at one row itself, so the query sets no outer LIMIT, TOP or FETCH, and it names only the data source's own database.
Masked observations
actual_value and message reach the history and alerts. A PII column read into them is masked first, under the strictest PII setting of the projects that link the data source.
Scores where people and assistants look.
- Overall score
- 73.50% (threshold 80%)
- Table
- public.invoice_lines
- Rules
- 5 passed, 2 failed
| Failed rule | Score | Expected | Actual |
|---|---|---|---|
| Line totals in range | 88 | 0 to 100,000 | 12% out of range |
| Fresh lines | 0 | ≤ 60 min | 7 h old |
Delivered to the contract's recipients on any channel: email, Teams, Slack, Jira or a webhook.
Data Quality page
Overview cards per data source: average score, tables, Healthy tables (80 or more), Degrading tables and contracts, then the contracts list with each latest score.
Contract page
Rules, Evaluations (history with scores, pass and fail counts) and Latest results per rule, with Evaluate now in the header.
AI assistants over MCP
Table scores appear in get_documentation, so an assistant sees a table's quality next to its schema, and get_context shows each data source's overall score.
Control Tower
When contracts are not evaluated because their owner may no longer run them, the Control Tower shows how many and links to Data Quality.