---
id: "7.6"
title: "Data quality"
dept: data-and-analytics
status: draft
owner: Param
branded: false
last_verified: 2026-10-26
sources:
  - "brew/fact-gate.js:1-6, 65-88"
  - "brew/facts-desk.js:1-5, 34-46"
  - "brew/pack-numbers.js:1-4"
  - "brew/health.js:5-7"
  - "brew/connectors/index.js:37, 40, 51-61"
  - "brew/SCOPE.md:50"
  - "brew/DUMMY.md:27-30"
  - "memory brew-connected-facts, brew-stories-and-cards, brew-proof-plain-make, brew-move-freshness, brew-qa-audit-2026-10-20, brew-audit-2026-10-15, feed-for-all-brands, fix-as-product"
---

# Data quality

## Answer

Every number a brand sees must trace to a fact, an account or a sum, stay fresh, and never pass a dummy or stale value as true; this page sets those gates and how we check them, for every brand at once.

## Today

### The gates

| Gate | What it stops | Source |
|---|---|---|
| Fact gate | A money amount or percentage on a card that the fact sheet does not back; cards written from Plays also gate multipliers and counts of a thousand or more | [src: brew/fact-gate.js:1-6] |
| Plan numbers | A number in a sentence that says target, estimate, assumption or to confirm never backs a card | [src: brew/fact-gate.js:65-77] |
| Retired numbers | A number that stopped being true, a product gone or a price changed, is cut from Plays before they back a card | [src: brew/fact-gate.js:79-88] |
| One number, one source | The headline numbers are worked out once, the same way for every brand | [src: brew/pack-numbers.js:1-4] |
| The fact sheet | The team corrects it with choices or a pasted link, never a typed number; a free daily check keeps prices and stock in line; each edit is recorded and flags the Plays that quote the old value | [src: brew/facts-desk.js:1-5] |
| Connected truth | For members, a connected number beats the site's; a stale or simulated one never counts | [src: brew/facts-desk.js:34-46] [src: memory brew-connected-facts] |
| Not read is not zero | A health part or ring that could not be read is left out, never scored as zero | [src: brew/health.js:5-7] |
| Stories | Quote only numbers in their data; a failed source says so on its ring | [src: memory brew-stories-and-cards] |
| Proof sheet | Every card's numbers matched to a fact line, account, rival ad, quote or sum, with link and date; unmatched numbers are named; a Play is never proof | [src: memory brew-proof-plain-make] |
| Whole Plays | A Play missing any of its six sections is not saved | [src: memory brew-qa-audit-2026-10-20] |
| Weekly Plays | Marked verified only when every number is backed | [src: brew/SCOPE.md:50] |
| Dummies | Simulated rows keep a demo flag and stay out of Plays, the fact sheet, briefs and stale flags | [src: brew/DUMMY.md:27-30] |

### Freshness

| Connector | Read every | Read again when a member opens, if older than | Source |
|---|---|---|---|
| Shopify | 3 h | 1 h | [src: brew/connectors/index.js:37, 40] |
| Meta | 3 h | 1 h | [src: brew/connectors/index.js:37, 40] |
| Google Ads, GA4 | 6 h | 2 h | [src: brew/connectors/index.js:37, 40] |
| Judge.me | 6 h | 2 h | [src: brew/connectors/index.js:37, 40] |
| Klaviyo | 12 h | 6 h | [src: brew/connectors/index.js:37, 40] |

- Stale after 3 failed reads in a row, or no good read for four cadences and at least 24 hours; stale numbers stop being evidence. [src: brew/connectors/index.js:51-61]
- A connected number that changes by 10% from the value a Play was written with flags that Play; flagged Plays refresh within a weekly budget. [src: memory brew-move-freshness]
- A product out of stock or gone flags every Play naming it at once, with no AI. [src: memory brew-move-freshness]

### Past failures and the lesson each left

| Date | What went wrong | Lesson | Source |
|---|---|---|---|
| 16 Oct | One 15ml variant out of stock flagged 54 of Arata's Plays: a short name matched the 200ml | Check a matcher against real brand data, variants included | [src: memory brew-move-freshness] |
| 16 Oct | Arata's connected ledger held simulated Shopify data with no flag | Simulated rows always carry the demo flag | [src: memory brew-audit-2026-10-15] |
| 16 Oct | Cozy was set to India though it sells in the US, so its Plays were written for India | Country comes from the store, never a default | [src: memory brew-audit-2026-10-15] |

## Tomorrow

| Item | Version | Tag |
|---|---|---|
| Every gate above, for every brand | v1 | Stays |
| A nightly data check per brand: unbacked numbers, stale connections, Plays flagged too long | v1 | New, proposed |
| A post never goes out with an unbacked number | v2 | New |
| Results read only from fresh, non simulated account rows | v3 | New |
| Ad rules refuse to act on stale numbers | v4 | New |

## How we do it

1. Every fix works for every brand, every card and every view, and heals saved data on read. [src: memory feed-for-all-brands] [src: memory fix-as-product]
2. Audit what the product shows against the platform, read only:

```bash
cd /Users/param/Documents/ChatGPT/brew
node tools/audit-cards.mjs --bucket
FEED_VAULT_KEY=$(gcloud secrets versions access latest --secret=FEED_VAULT_KEY) node tools/meta-audit.mjs <slug>
FEED_VAULT_KEY=$(gcloud secrets versions access latest --secret=FEED_VAULT_KEY) node tools/shopify-audit.mjs <slug>
node tools/audit-brand-book.mjs <dir with a copy of the brand files>
```

The two account audits read with staging's vault key; a brand at home on production needs `FEED_VAULT_KEY_PROD`. All four write nothing. [src: brew/tools/meta-audit.mjs:1-3] [src: brew/tools/shopify-audit.mjs:1-3] [src: brew/tools/audit-cards.mjs:1-6]

3. Run the honesty and number tests before every deploy:

```bash
AI_MOCK=1 node --test test/honesty.test.js test/claims.test.js
```

4. A new connector needs entries in `EVERY_H`, `OPEN_H`, `NAMES` and `PRECEDENCE`. [src: memory brew-connected-facts]

## Numbers

| Metric | Definition | Where measured | Status |
|---|---|---|---|
| Unbacked numbers | Numbers on shown cards with no proof match, per brand | Proof sheet counts | Proposed |
| Stale connections | Connections marked stale | `connections.json`; `db/status.mjs` | Live data, not reported |
| Facts to confirm | Facts marked to confirm and not yet corrected | The fact sheet | Proposed |
| Days flagged | Days a Play stays flagged before its refresh | Updates per brand | Proposed |

## Open

1. **Run a nightly data check?** Proposal: yes, one report to hello@ listing each partner's unbacked numbers and stale connections.
2. **How long may a Play stay flagged?** Proposal: 7 days, then it leaves the feed until it is refreshed.
