A brief for Nathan · What's built, what's tested, what's next
CRM Cleanup Factory takes a messy CRM export and walks an operator through five review gates until the file is clean enough to reload. A real person sits at every gate. Underneath, a shared enrichment cache (the Brain) drives the cost of every next job down. The origin was one client's disaster CSV. What follows is a snapshot of where the project is right now.
Done & liveTested end-to-endIn progressPlanned
The 5 gates
01Intake & column mapping
Tested
Ingests CSV, XLSX, TSV. Handles UTF-8 BOM and Latin-1 files (patched after the first client's Excel export broke intake). The operator confirms which uploaded columns map to canonical fields before anything runs downstream.
02Enrichment review
Working, hardening
LLM fills gaps (title, company, LinkedIn, domain). Operator sees each enriched cell before it's committed. Model config is locked to Qwen2.5-72B-Instruct on DeepInfra after the first real run caught a silent substitution to a reasoning model that was hallucinating output.
03Dedup key selection
Done
Operator picks the clustering keys (default: company_domain + parent_company) and the tiebreak priority (default: email > person_linkedin > phone > name). Drag-and-drop reorder, no config file needed.
04Dedup pair review
Tested
Every suspected duplicate pair is shown side by side. Operator approves the merge, rejects it, or keeps both. Nothing collapses without a human click.
05Delivery
Coverage gates in review
Exports a clean CSV (UTF-8 BOM, CRLF, RFC 4180) plus a scorecard, an audit log, and a SHA-256 manifest. Coverage thresholds (email %, phone %) currently warn on delivery. Hard-blocking below a floor is still under review.
The Brain
The Brain is a shared Supabase cache of enriched contacts. The first time anyone cleans a record it goes in. The next time the same contact runs through, the answer is basically free. Every job ends with a "saved X credits from the Brain" line at the bottom. The more jobs run, the cheaper the next job gets.
Storage
Supabase (Postgres). One shared pool across clients.
Match key
Email (exact) primary. Cascading fallback on name + company_domain + company_name + title.
Match confidence
Strict. Fuzzy matches surface to the operator, never auto-return.
Freshness
180 days. After that, next hit re-enriches. Old rows stay as history.
Operator UX
Per-row status badge: "From Brain (45 days fresh)", "Needs confirm", "Expired — re-enriching".
Current status
Schema, client, smoke tests all green on Supabase (2026-06-26). Runner wiring in progress — Brain modules expect dict records, current runner uses class objects.
First real run
Job job_f1e2c3dbe41b — real client CRM, June 2026.
99
Rows in
96
Rows out
2
Dupes collapsed
1
Junk row killed
The export format held up cleanly: schema conforms, manifest and SHA-256 present, encoding right. The two duplicate pairs collapsed correctly. More importantly, the run caught a silent model-substitution bug that was hallucinating enrichment data before I ever showed the output to the client. A hand review would have missed it.
Findings that shaped what came next: default model has since been pinned to a non-reasoning variant, the scorecard's enrichment_calls counter was found to be wrong, and audit-log record_id now threads through the enrichment path. All logged, some already patched.
What's next
Finish Brain runner wiring
Brain modules and the existing runner disagree on record shape. Once bridged, the Brain lookup goes live between mapping and enrichment.
Harden Gate 5 delivery gates
Decide whether low email/phone coverage should hard-block delivery or stay advisory with a required override.
Community release
Package it so a Clay Bootcamp operator could run their own cleanup end-to-end without me on a call. This is the version I most want to get to.
Private-mode toggle for the Brain
Per-client silo for regulated industries. Deliberately deferred until there's a client who needs it.
Read-only Brain API
Standalone lookup product. Long-tail idea, not scoped yet.