Data Quality
Beacon monitors the health of your tables through data contracts — scheduled, rule-based checks that produce a weighted quality score, track trends over time, and alert your team when a table drops below its agreed threshold.
Table of contents
Overview
A data contract is an agreement about the expected state of a single table: how fresh its data should be, how many rows it should contain, which columns must be unique or non-null, and so on. Each contract:
- Targets one table (schema + table) on one database data source
- Contains one or more rules (checks), each with its own severity and weight
- Runs on a cron schedule and/or on demand
- Produces an overall score (0–100%) per evaluation, computed as a severity- and weight-adjusted average of rule scores
- Optionally alerts recipients when the score falls below a failure threshold
Every evaluation is stored, so you get a full history per contract plus a per-table latest score with a trend direction (improving / stable / degrading).
Data contracts apply to database data sources (PostgreSQL, SQL Server, MySQL). REST API data sources cannot be targeted by a contract.
Rule Types
Each rule has a type, an optional column, and a configuration (JSON). The table below lists all supported types with example configurations.
| Rule Type | What it checks | Example configuration |
|---|---|---|
| Freshness | The newest value in a timestamp column is no older than a maximum age | {"column": "updated_at", "maxAgeMinutes": 60} |
| Volume | Row count is within an expected range (minRows, maxRows, or both) |
{"minRows": 100} |
| Null rate | Percentage of NULLs in a column stays at or below a threshold | {"column": "email", "maxNullPercent": 5} |
| Uniqueness | A column contains no duplicate values | {"column": "id"} |
| Referential | Every non-null value in a column exists in a reference table (no orphans) | {"column": "user_id", "referenceTable": "users", "referenceColumn": "id"} |
| Range | Numeric values stay between min and max (either bound optional) |
{"column": "age", "min": "0", "max": "150"} |
| Pattern | All values match a pattern | {"column": "email", "pattern": "^[^@]+@[^@]+$"} |
| Custom SQL | Any check you can express as SQL | {"sql": "SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS passed FROM ..."} |
You do not need to include schema or table in the configuration — Beacon injects them automatically from the contract’s target table.
Referential rules default the reference table to the contract’s schema; add "referenceSchema": "..." to point at a different schema.
Pattern matching is engine-dependent. On PostgreSQL the pattern is a regular expression (
!~), on MySQL it usesNOT REGEXP, but on SQL Server it is evaluated withNOT LIKE— useLIKEwildcard syntax (%,_) for SQL Server sources, not regex.
Custom SQL rules
A Custom SQL rule runs your query verbatim and reads the first row of the result. Return these columns:
| Column | Required | Meaning |
|---|---|---|
passed |
Yes | 1 = pass, 0 = fail |
score |
No | Score 0–100; defaults to 100 on pass, 0 on fail |
actual_value |
No | Shown as the observed value in results |
message |
No | Shown as the result message |
If you already maintain reusable checks as saved queries, a Custom SQL rule is the natural way to fold the same logic into a quality score.
Severity and weight
Each rule carries a severity and a weight — both feed the scoring math (see below).
| Setting | Values | Effect |
|---|---|---|
| Severity | Low, Medium, High, Critical | Multiplies the rule’s influence: Low ×1, Medium ×2, High ×3, Critical ×4 |
| Weight | 0.1 – 10 (default 1.0) | Fine-grained influence within the same severity |
| Enabled | on/off per rule | Disabled rules are skipped entirely and don’t affect the score |
How Evaluations Run
Evaluations run in two ways:
- Scheduled — every enabled contract gets a recurring job (via the host’s scheduler) driven by its cron expression. The job is created when you save an enabled contract, updated when you change the schedule, and removed when you disable or delete the contract.
- On demand — click Evaluate now on the contract detail page (or
POST /beacon/api/data-quality/contracts/{id}/evaluate).
During an evaluation, Beacon:
- Loads the contract and its enabled rules
- Generates engine-specific SQL for each rule and runs it directly against the data source (60-second timeout per rule)
- Interprets each result into a pass/fail, a rule score, an actual vs. expected value, and a message
- Computes the overall score, stores the evaluation with all rule results, and updates the table’s latest score and trend
- Sends alert notifications if the score is below the failure threshold (scheduled runs only — see Alerting)
If a single rule errors out (bad configuration, unreachable column, timeout), that rule is recorded as failed with score 0 and the remaining rules still run — one broken rule never aborts the evaluation.
Scoring Methodology
Per-rule score
Most rule types are binary — 100 if the check passes, 0 if it fails. Two types score proportionally:
| Rule Type | Score on failure |
|---|---|
| Null rate | max(0, 100 − (actualNullPercent − maxNullPercent)) — small overruns cost little, large overruns cost a lot |
| Range | (1 − outOfRangeRows / totalRows) × 100 — proportional to how many rows violate the range |
| Custom SQL | Whatever your query returns in score (defaults to 100/0) |
Overall score
The overall score is a weighted average of rule scores, where each rule’s effective weight is:
effectiveWeight = ruleWeight × severityMultiplier
with severity multipliers Critical = 4, High = 3, Medium = 2, Low = 1. Then:
overallScore = Σ(ruleScore × effectiveWeight) / Σ(effectiveWeight)
rounded to two decimals. A contract with no enabled rules scores 100.
Example: a contract with a passing Critical freshness rule (weight 1, score 100) and a failing Low volume rule (weight 1, score 0) scores (100×4 + 0×1) / (4+1) = 80% — the critical rule dominates.
Table scores and trends
After every evaluation, the target table’s latest score is updated (one score per data source + schema + table). Beacon keeps the previous score and derives a trend direction:
| Trend | Condition |
|---|---|
| Improving | New score more than 1 point above the previous |
| Stable | Within ±1 point |
| Degrading | New score more than 1 point below the previous |
Alerting
Each contract has three alert settings:
| Setting | Default | Description |
|---|---|---|
| Alert on failure | Enabled | Master switch for notifications on this contract |
| Failure threshold (%) | 80 | Notifications fire when the overall score drops below this value |
| Recipients | — | Who gets notified (same recipient list used by subscriptions) |
When a scheduled evaluation scores below the threshold, Beacon sends a notification titled [Data Quality] {contract name} to every configured recipient. The payload includes the overall score, the threshold, the target table, pass/fail counts, and a detailed breakdown of every failed rule (rule name, score, expected vs. actual value, message).
📚 Learn more about delivery channels →
Manual Evaluate now runs record the evaluation and update the score but do not send alert notifications — only scheduled runs alert.
Using the UI
Data Quality page (/data-quality)
Click Data Quality in the left navigation. The page shows:
- Per-data-source overview cards — average score, table count, healthy tables (score ≥ 80), degrading tables, and active contract count, plus per-table scores
- Contracts list — each contract with its target table, latest score, enabled/disabled state, and View / Edit / Delete actions
Creating a contract
- Click New contract
- Fill in the details:
| Field | Description | Example |
|---|---|---|
| Contract name | Descriptive name | Orders freshness & volume |
| Data source | Database source to check | Select from dropdown |
| Schema / Table name | The target table | public / orders |
| Cron expression | Evaluation schedule | 0 */6 * * * (every 6 hours) |
| Failure threshold (%) | Alert cutoff | 80 |
| Enabled | Whether the schedule is active | ✓ |
| Alert on failure | Send notifications below threshold | ✓ |
| Notification recipients | Who gets alerted | Select recipients |
- Add one or more rules — for each: name, type, optional column, severity, weight, and the configuration JSON (the dialog shows a type-specific example as a hint)
- Click Save — if the contract is enabled, the recurring evaluation job is scheduled immediately
Contract detail page (/data-quality/{id})
The detail page shows four KPIs — latest score, rule count, schedule, and failure threshold — plus three tabs:
- Rules — the configured rule set
- Evaluations — evaluation history, newest first (each entry shows the overall score, pass/fail counts, and execution time)
- Latest results — per-rule results from the most recent evaluation: pass/fail pill, rule score, expected vs. actual value, and message
Use Evaluate now to trigger an immediate evaluation, or Edit / Delete to manage the contract. Deleting a contract archives it and removes its scheduled job.
Editing a contract replaces its entire rule set. Stored per-rule results for the old rules are purged as part of the update (evaluation summaries and their overall scores remain). If you need to preserve detailed rule-level history, export it before editing.
API Endpoints
All endpoints live under /beacon/api/data-quality and require authentication.
| Method | Endpoint | Description |
|---|---|---|
GET |
/data-quality/overview |
Per-data-source quality overview (optional ?dataSourceId=) |
GET |
/data-quality/contracts |
List contracts (optional ?dataSourceId=) |
GET |
/data-quality/contracts/{id} |
Contract detail with rules and latest score |
POST |
/data-quality/contracts |
Create a contract |
PUT |
/data-quality/contracts/{id} |
Update a contract (replaces rules and recipients) |
DELETE |
/data-quality/contracts/{id} |
Archive a contract and unschedule it |
GET |
/data-quality/contracts/{id}/evaluations |
Evaluation history (optional ?take=, default 20) |
POST |
/data-quality/contracts/{id}/evaluate |
Run an evaluation immediately |
Quality scores also surface elsewhere in Beacon: the MCP server exposes a per-project quality report resource, and table-level scores appear in search results and documentation.
Troubleshooting
- A rule always fails with “Execution failed: …“
- The generated SQL could not run against the source. Check that the column named in the configuration exists, the configuration JSON has the required properties for the rule type, and the data source connection is healthy.
- “Data contract’s data source must be a database type” error
- The contract targets a REST API data source. Contracts can only be evaluated against database sources.
- Score is 100% but I expected checks to run
- A contract with no enabled rules scores 100 by definition. Verify that at least one rule has its Enabled toggle on.
- No notifications despite a failing score
- Notifications require all three: Alert on failure enabled, at least one recipient configured, and the overall score strictly below the failure threshold. Also note that manual Evaluate now runs never send notifications — only scheduled runs do.
- Pattern rule behaves differently across engines
- PostgreSQL and MySQL treat the pattern as a regular expression; SQL Server uses
LIKEsyntax. Adjust the pattern to the target engine. - Evaluation history shows fewer entries than expected
- The history endpoint returns the 20 most recent evaluations by default; pass a larger
?take=value to fetch more. Rule-level details for old rules are also removed when a contract is edited.