▲ Data Analytics layer

how-to-data-quality

Every tutorial runs checks. This one runs a gate: the raw load is genuinely BLOCKED on five defects, the ingestion fixes are applied with a quarantine ledger that reconciles every row, the same checks re-run to SHIP WITH HOLDS - and the fabrication suspect is routed to investigation, never "corrected". Real FAIL run, real PASS run, seed 42.

raw dumpDQ gatewarehousemartsscorecardagent (layer 4)
Case: Everrest · B2B2C retail platform seed = 42 pandera · pandas · deploy-html 6 dimensions · 10 checks every row reconciled
01
Step 1

Input - the raw dump, before anyone has cleaned anything

This gate sits at stage 0 of the lineage: the same four raw files the warehouse build consumes. Quality problems are ingestion problems - catch them where they enter, with a ledger, not downstream where they surface as a wrong board number.

raw_transactions.csv 107,841 rows
  • txn_id / order_id · line grain
  • order_ts_utc · claims UTC
  • customer_id + denormalized attrs
  • merchant_id · 'M0001' format
  • category_raw · free-text label
raw_payments_export.csv 46,007 rows
  • pmt_ref · one per payment
  • order_id · fk
  • amount_reported · merchant-reported
  • paid_at · processor timestamp
raw_products_export.csv 5,000 rows
  • product_id · pk
  • merchant_id · fk
  • catalog_price · current
raw_merchants_extract.json 400 rows
  • merchant_key · 'MER-0001' format
  • legacy_id · 'M0001' crosswalk
  • category / tier / onboarded
02
Step 2

Sample data - six planted defects, one per quality dimension

The raw generator plants six defects on purpose (seed 42, each documented in its docstring), and they map one-to-one onto the classic data-quality dimensions - so the gate is a complete tour of the discipline on data where the ground truth is known and recall can be verified.

Defect in the raw dumpDimensionGate check
Orphan customer refs (nulls after denormalization)completenesscustomer attributes present
14 category spellings for 8 real categoriesvalidityaccepted-values against the canon set
Orphan product FKs · MER-0001 vs M0001 key splitconsistencyreferential integrity + cross-system conformity
One region's payments logged in local time (UTC+8)consistency · event orderpaid_at not before order_ts
(none planted - measured anyway)timelinessevery feed fresh within a 24h SLA
~800 customers cloned under two idsuniquenessduplicate-identity scan
Merchant M0333: 100% round-hundred amountsaccuracyheaping / fabrication screen

Honesty note: the event-order defect was originally filed under "timeliness" - the expert panel corrected it to temporal consistency, and timeliness got a real freshness-SLA check instead. The page you are reading reflects the reviewed mapping.

03
Step 3

Objective - does Monday's load ship?

Not "run some checks". A gate answers a decision question with a verdict, and accounts for every row it touched on the way.

Does Monday's warehouse load ship or get blocked - and can we account for every row either way?
  • Which defects BLOCK the load, and which only warrant a warning?
  • What gets quarantined, what gets corrected, what gets merged - and what must only be HELD, never repaired?
  • Does rows_in = rows_loaded + rows_quarantined hold for every table?
  • What was the bad data about to cost, in dollars and metric points?
  • What does the board see - a score, or a verdict it can act on?
04
Step 4

Find-skills - the quality-gate toolbox

A gate needs declarative structural checks, custom forensic scans, a ledger discipline, and one self-contained artifact an executive can open anywhere.

OSS tool

pandera

Declarative structural schemas - types, ranges, nullability - validated lazily so every failure is collected, counted on row grain, and reported at once.

OSS tool

pandas

The four forensic scans no schema language expresses: cross-system key conformity, event-order latency, duplicate-identity groups, round-amount heaping.

Pattern

Quarantine ledger

Every row the gate touches gets a ledger entry with a reason: quarantined, corrected, merged or held. rows_in reconciles to rows_out, per table, by construction.

Skill

deploy-html

The DQ scorecard ships as one self-contained file - no CDN, no build - generated by the script from computed values, so not one figure is hand-typed.

Pattern

Route, never repair

Suspected fabrication is evidence, not a formatting defect: snapshot it immutably (sha256), hold it out of finance rollups, and hand it to an investigator.

OSS tool

Great Expectations

The production home for these checks once they stabilize - promote the gate's assertions to a versioned suite that runs on every load.

05
Step 5

Build - FAIL, fix, PASS, and account for every row

One script runs the gate on the raw dump (it genuinely blocks), applies the ingestion fixes with a ledger, re-runs the same check functions on what ships, and generates the scorecard from the results dict. Grab the real code below.

BLOCKED SHIP WITH HOLDS
As received: 5 blocking failures, DQ score 80.49. After the fixes: score 99.97 - not 100, because 83 payments from the fabrication suspect stay counted until the investigation closes. The verdict is not the score: a gate that averages its way past a blocker is decoration.

The gate at a glance - every check, before and after

Two-panel bar chart of ten checks: the FAIL run shows five blocking failures up to 100 percent error rate, the PASS run shows all zeros except a held 0.18 percent accuracy warning

Red = blocking failure, amber = warning, teal = pass. The PASS panel is genuinely re-executed on the fixed tables - only the fabrication hold survives, by design.

What tripped - the evidence, one chart per defect family

Bar chart of 14 raw category labels, eight canon in teal and six dirty variants in amber including Grocary and Beauty with a trailing space
Histogram of order-to-payment hours showing 3,798 impossible negative-latency payments in amber, corrected to positive after the UTC shift in teal
Bar chart sizing the two orphan populations: 2,696 orphan product references and 1,074 orphan customer references, all quarantined
Scatter of round-amount share per merchant: every peer sits near zero percent while M0333 sits at 100 percent of 85 payments, 9,184 times the peer rate

Top-left: 14 labels arrive for 8 categories - the fix is an explicit reviewed mapping that raises on anything unmapped, never fuzzy matching. Top-right: 3,798 payments precede their own orders, every one in a single region: a timezone diagnosis, so the whole region cohort is shifted to UTC and any residual negative latency is quarantined instead of blanket-"corrected". Bottom-left: two orphan populations, quarantined with reasons. Bottom-right: one merchant's amounts land on round hundreds 9,184x the peer rate (suspect excluded from its own baseline) - held, not edited.

Every row accounted for

Linear bar chart: 107,841 transaction lines in, 3,742 quarantined with reasons, 104,099 loaded

Transactions: 107,841 in = 104,099 loaded + 3,742 quarantined. Payments: 46,007 in = 45,081 loaded + 926 quarantined - those 926 followed their quarantined orders out, because a fix that strands orphan payments is itself a defect (a reviewer caught v1 doing exactly that). Corrected (3,754), merged (662) and held (83) rows live in the ledger.

The scorecard - live below, every number computed

Generated by the script from the results dict - zero hand-typed figures. Badge language: PASS = was never dirty, FIXED = repaired at ingestion, HOLD = routed to investigation, still open.

Everrest DQ scorecard · raw dump → warehouse ingestion · seed 42Open full scorecard ↗
Planted defectCaught byFix appliedRecall
Orphan customer refs (1,074 lines)completeness checkquarantined with reasons✓ exact
14 dirty category labels (8.88%)validity checkreviewed mapping, raises on unmapped✓ exact
Orphan product FKs (2,696 · 2.50%)referential checkquarantined; payments cascade too✓ exact
MER-/M- key split (100% mismatch)conformity checkconformed via the master's own crosswalk✓ exact
UTC+8 export (3,798 payments)event-order checkwhole region cohort shifted; residuals quarantined✓ exact
800 cloned customersuniqueness scan662 merged - all that exist in the shipped facts (800 x 0.91² = 662; only pairs where both ids transact can double-count)✓ all observable
M0333 round-amount fabricationheaping screenHELD + sha256 evidence snapshot - never edited✓ routed
06
Step 6

Expert review - the panel that found the gate's own defects

Four senior reviewer agents - data-quality governance, analytics engineering, payments fraud, and board-facing insights, each 10+ years - reviewed the real code and outputs. They found defects in the gate itself. Every fix below is in the shipped code, and the numbers on this page are from the re-run.

DQ / Governance Lead
14 yrs · ingestion gates

"Your fix quarantines 3,742 transaction lines - and silently ships 926 payments whose orders just left. They appear in no ledger. Your memo's 'every row is accounted for' is untrue for the payments table."

Fix applied: a payments-to-orders referential check + cascade quarantine - payments follow their orders out, and the payments reconciliation (46,007 = 45,081 + 926) is now printed, not asserted.
Analytics Engineer
12 yrs · pipeline test suites

"The timezone fix shifts whatever shows negative latency. That is symptom-conditioned: a genuinely broken timestamp in another region would get +8h and a ledger entry claiming a local-time export - a false audit trail."

Fix applied: the shift now applies to the diagnosed region cohort (>=90% impossible latencies), and residual negatives outside a cohort are quarantined, never blanket-corrected.
Payments Fraud Lead
11 yrs · marketplace risk

"Your 500x anomaly is diluted - the suspect supplies 92% of its own base rate. Peers-only, it is 9,184x. And the file is named benford_*.png for a test you explicitly do not run. Preserve evidence an investigator can anchor on."

Fix applied: peer base rate excludes flagged merchants; the chart is renamed heaping_m0333.png; held payments are snapshotted as received with the raw file's sha256 in the ledger.
Head of Insights
14 yrs · executive committees

"The log-scale funnel reads as 'we lost a third of the data'. Four tiles wear PASS while describing live defects in present tense. And there is no money anywhere - SHIP WITH HOLDS is a decision with no stake attached."

Fix applied: linear transactions-only funnel; three-badge language (PASS / FIXED / HOLD) with past-tense notes; a computed "what the bad data was about to cost" table on the memo and the scorecard.
before → after · the cascade the panel forced
# v1 (before): quarantine transaction lines... and strand their payments
tx = tx[~orphan_cust]
tx = tx[~orphan_prod]        # 926 payments now reference orders that no longer ship -
                             # they load anyway, in no ledger. "Every row accounted for" = false.

# v2 (after): payments follow their orders out, with reasons
orphan_pay = ~pay.order_id.isin(tx.order_id)
for r in pay[orphan_pay].itertuples():
    ledger_rows.append({"table": "payments", "row_id": r.pmt_ref,
                        "action": "quarantined",
                        "reason": f"order {r.order_id} was quarantined upstream"})
pay = pay[~orphan_pay]       # 46,007 = 45,081 loaded + 926 quarantined - printed every run

Run this gate on your own loads

Install once, point it at your raw files, rewrite the check set to your schema - keep the three rules: a gate not a report, every row accounted for, route fabrication instead of repairing it.

terminal
/plugin marketplace add phoebefu6/phoebe-data-skills
/plugin install how-to-data-quality@phoebe-data-skills