River
Y CombinatorBacked by Y Combinator
FREE TEMPLATE

Data Quality Monitoring Checklist Template

Four documents and five sheets that define every check against a named report consumer, so a failure arrives with an owner attached.

Free download  ·  No account needed

One Detection, Four Verdicts

14 August 2026. The null rate on shipment.delivered_at went from a 0.4% baseline to 3.1%, a factor of 7.8, after a carrier changed its webhook payload.

One condition. Four consumers read that field, and each had declared a different threshold on it.

ConsumerTheir toleranceVerdictSeverityAlerted
On-time delivery dashboard1.0% dailyBreached1 Wrong on screenT. Falconer
Carrier scorecard3.0% at month endBreached2 DegradedL. Beaumont
Monthly board KPI pack5.0% at month endWithin tolerancenonenobody
Customer SLA credit calculationzero in a credit windowBreached1 Wrong on screenD. Marchetti

Three alerts from one detection, at two severities, to three people, on three different clocks. The board pack was correctly told nothing: at a 5% month-end tolerance this excursion moves a company-wide on-time percentage by a tenth of a point. Billing tolerates zero, because a null there does not degrade a number, it issues a credit against a customer invoice. That month's credit window held 1,840 shipments, 57 of them null at 3.1%, which is $2,565 at $45 a shipment.

The standard way to build a check suite is to walk the schema. Enumerate the tables, put a null check on every nullable column, add a uniqueness check on every id, and route the lot into one channel. The size of the suite then gets set by the width of the warehouse, and a warehouse does not know which of its columns anybody reads. Pemberton Logistics, a freight brokerage, inherited thirty-four checks built exactly that way and four hundred and eleven detections in one quarter.

Replaying those detections against what six report owners actually said they needed, ninety-six were actionable to a named consumer. Seventy-seven percent mattered to nobody. The cross-tabulation is worse than the headline: sixty-eight of the one hundred and forty-three detections anybody triaged breached nobody's tolerance, and twenty-one that did breach a declared tolerance were never acknowledged. The team was not filtering the noise. They were sampling, and the sample had nothing to do with what mattered.

So in this pack a check belongs to a consumer rather than to a table, which makes severity a property of the (detection, consumer) pair. One field can carry four tolerances and that is correct, not untidy. Written for whoever gets asked why the dashboard was wrong on Tuesday. The definitions underneath belong in a metric register, the extract those checks run against in an export pipeline pack, and the reports worth watching at all get thinned first by a report inventory audit.

The rebuild, in three sheets

Forty-seven declared tolerances, the retain, delete and add verdict on all thirty-four inherited checks, and where each alert has to land.

Consumer Tolerances

Illustrative rows for a fictional freight brokerage, Pemberton Logistics. Nine of the forty-seven, showing the four consumers who read one timestamp.

ConsumerOwnerFieldDimensionToleranceSeverityCovered?
On-time delivery dashboardT. Falconershipment.delivered_atCompletenessNull rate at or below 1.0%1 Wrong on screenYes
Carrier scorecardL. Beaumontshipment.delivered_atCompletenessNull rate at or below 3.0%2 DegradedYes
Monthly board KPI packR. Nkemelushipment.delivered_atCompletenessNull rate at or below 5.0%1 Wrong on screenYes
Customer SLA credit calculationD. Marchettishipment.delivered_atCompletenessZero in a credit window1 Wrong on screenYes
On-time delivery dashboardT. Falconershipment.delivered_atTimelinessLoaded within 4 hours1 Wrong on screenYes
Monthly board KPI packR. Nkemeluinvoice.invoice_dateTimelinessClosed period loaded by the 3rd working day1 Wrong on screenNo, new check
Customer SLA credit calculationD. Marchettishipment.promised_atConsistencyNever later than delivered_at1 Wrong on screenNo, new check
Weekly pipeline reviewJ. Amankwahdeal.close_dateCompletenessNull rate at or below 2.0% on open deals2 DegradedYes
Marketing attribution modelS. Iyerdeal.close_dateCompletenessNull rate at or below 15.0%3 CosmeticYes

None of the four thresholds on that timestamp is the right one for the field. Each is the right one for a consumer. Collapse them to the tightest and the finance director gets paged about a condition that will never reach his pack; collapse them to the loosest and the credit run goes out wrong.

Quality Check Register

The inherited suite joined against the tolerance register in both directions. Ten of the forty-eight rows.

CheckFieldDimensionConsumersDetectionsActionableAlertsVerdict
DQ-001shipment.delivered_atCompleteness4411432Retained
DQ-002shipment.delivered_atTimeliness1631919Retained
DQ-005shipment.shipment_idUniqueness2448Retained
DQ-019deal.sourceCompleteness13433Retained
DQ-026shipment.trailer_typeValidity01200Deleted
DQ-028shipment.temp_setpointCompleteness0600Deleted
DQ-030carrier.faxValidity0100Deleted
DQ-032lane.zone_codeReferential integrity0900Deleted
DQ-105invoice.invoice_dateTimeliness2newnewnewAdded
DQ-111shipment.promised_atConsistency2newnewnewAdded

Nine checks mapped to no declared tolerance and produced fifty-two detections in the quarter between them: a reefer setpoint from a book of business the company sold, a fax column, a migration artefact, two superseded columns. Sixteen tolerances were covered by nothing at all, on fourteen distinct pairs, so fourteen checks closed them. Thirty-four checks became thirty-nine, and every tolerance had something behind it for the first time.

Owner Assignment

All six consumers. The route is set by when they read, and the severity 1 action is written before any alert fires.

ConsumerOwnerReads itTolerancesAlerts in the quarterHow an alert has to reach themWhat they do on a severity 1
On-time delivery dashboardT. FalconerDaily 07:00949Push by 06:30, ahead of the readPublish with a banner naming the affected lanes, or hold
Weekly pipeline reviewJ. AmankwahMonday 09:00822Slack to the sales channel by 07:00 MondayMark the affected deals in the review, or postpone the slice
Carrier scorecardL. BeaumontMonthly, 4th working day818Email three working days before it runsRerun the scorecard, or ship it with the carrier excluded
Customer SLA credit calculationD. MarchettiMonthly, 2nd working day718Page immediately, the run is irreversible once postedHold the credit run until the field is backfilled
Monthly board KPI packR. NkemeluMonthly, 6th working day87Email three working days before the pack is cutRestate the prior month, or footnote the affected KPI
Marketing attribution modelS. IyerWeekly Wednesday77Email by Tuesday, ahead of the reallocationFreeze the channel split at last week's numbers

One hundred and twenty-one alerts across the quarter, and the distribution is meant to be lopsided: the daily consumer with the tightest tolerances takes forty-nine while the board pack takes seven. What to look for is a consumer over about one alert per read, which will start being ignored, and a consumer on zero for a whole quarter, who either set the thresholds far too loose or is not reading what they said they read.

What you get

01

Consumer Tolerances

One row per declared tolerance: the consumer, one named person, the field, the dimension, a number with a period attached, and the severity if it breaches. Forty-seven rows in the worked example, and nothing else in the pack can be filled in before this sheet exists.

02

Quality Check Register

Every inherited check joined against the tolerance register in both directions, carrying a retain, delete or add verdict with the detections it produced. Deleted rows stay on the sheet with their reason, which is what stops somebody rebuilding them in eighteen months.

03

Failure Log

One row per detection and consumer pair rather than one per detection, so four consumers disagreeing about one incident is visible side by side. The observed magnitude against its baseline is on every row, because a detection logged as pass or fail cannot be replayed when a tolerance moves.

04

Owner Assignment

The alert route worked backwards from each consumer's read time, with the severity 1 and severity 3 actions written down before anything fires. A push at 06:30 for a 07:00 dashboard, three working days for a monthly pack, and an immediate page for a run that posts irreversibly.

05

Time to Fix

What each owner actually did, with acknowledgement and resolution hours and the loss avoided where there was one. Severity 1 acknowledgement is tracked on its own, because cosmetic alerts are batched deliberately and a mean across all three levels measures the batching policy.

06

How a Tolerance Is Declared

The six mandatory columns, the three-level severity ladder stated as consequences rather than feelings, and the four-thresholds-on-one-timestamp case worked through with what collapsing them costs in each direction.

07

Replaying an Inherited Suite

The method for scoring a suite you did not build, using detections that already happened. The actionable rate, the coverage gap in both directions, and the cross-tabulation against what was acknowledged, which is where the finding actually lives.

08

What This Does Not Catch

Five limits with numbers rather than caveats. An undeclared field is unwatched by construction, correct data in a wrong timezone passes every mechanical check, and a tolerance is somebody's guess until it has survived a quarter of acknowledgement history.

How it works

  1. 1

    List the consumers, not the tables

    Every artifact that reads data and produces a decision gets a row, with one named owner and the hour or working day they read it. River asks where numbers go rather than where data comes from.

  2. 2

    Get a number out of each owner

    For each field and dimension, the level of wrongness at which they would have published something they would want back. If they cannot name one, the field probably is not read and it comes off their list.

  3. 3

    Replay whatever is already running

    A quarter of existing detections scored against tolerances that did not exist when the checks were written. Out comes the actionable rate, the alert volume, and the checks no report reads.

  4. 4

    Route each severity to a decision

    Alert deadlines worked backwards from each consumer's clock, and the default action on a severity 1 written as a sentence in advance, so the decision takes a minute instead of a meeting.

Frequently asked questions

Is this free, and what format are the downloaded files?

Free, and no account is needed for the download. Documents arrive as .docx and sheets as .csv, so they open in Word, Pages, Google Docs, Excel, Numbers or Sheets without a conversion step. Edit with AI is the other button: it installs the same pack as a private space and fills it in from your own reports and alert history.

Can I not just set a threshold on the check itself?

You can, and well. dbt's test configuration takes conditional expressions on the failure count, so that one duplicate record can warn where ten error. What it takes is one threshold per test. Four consumers reading one timestamp at four thresholds is four register rows resolved when the alert routes, not one config value.

Is it not simpler to give a field one strict threshold?

Simpler, and it fails in a specific way. Take the tightest of Pemberton's four and the finance director gets paged about a condition that never reaches his pack, which costs the credibility the mechanism needs. Take the loosest and the credit run posts $2,565 of credits for deliveries that arrived on time.

Does declaring tolerances per consumer mean the suite gets bigger?

It got smaller in the worked example. Thirty-four inherited checks became thirty-nine, but nine watching fields no report read were deleted and fourteen were added against tolerances nothing had been covering. One check per field and dimension pair, with the per-consumer thresholds sitting on top of it rather than multiplying the checks.

Is quality not an objective property of the data?

Not usefully. The W3C's Data Quality Vocabulary is explicit that it does not define quality formally, and instead supplies a way to describe data so a potential user can judge its fitness for purpose. Here the consumer is that user and the tolerance is the judgment, written before the incident rather than argued about during it.

What if two reports disagree and neither has failed a check?

Then this is the wrong space, because nothing here compares two sources. Every dimension in the pack is mechanical: nulls, duplicates, allowed values, load times, one field against another. Two numbers that disagree while both pass belong in a conflicting report reconciliation, and what gets built at all in a dashboard spec.

Find out how much of last quarter anybody could act on

Send the reports people actually read and whatever alert history you have. River gets a number out of each owner, replays your existing detections against it, and reports the actionable rate.

Edit with AI