Audit mode
Compares a user's existing business list against fresh discovery results, surfacing gaps in both directions. Read this when a reference list is in play.
When audit mode triggers
| Signal | Example |
|---|---|
| Spreadsheet link in the request | a shared Google Sheet or similar |
| CSV file path | my-practices.csv, a downloads-folder file |
| Inline list (3+ businesses) | pasted names/addresses, one per line |
| Explicit audit language | "audit my list", "compare against", "what am I missing" |
A list without stated intent gets one question: "Want me to audit it against fresh discovery, or just use it as a starting point?" Audit language without a list gets a request for the list — never proceed on an imagined reference.
Reference list parsing
Detect the format, extract the rows, then normalize every record to a uniform shape.
Spreadsheet: extract the sheet as a table. Map columns by header name, case-insensitive:
| Target field | Accepted column headers |
|---|---|
name |
name, business name, practice name, company, organization |
domain |
domain, website, url, web, site |
city |
city, location, metro, area |
state |
state, st, region |
phone |
phone, telephone, tel, phone number |
address |
address, street, full address |
CSV: read the file, same column mapping.
Inline list: parse each line — business name is the first segment before any delimiter; pull city/state, domain, or phone if present. If the format is unclear, ask once: "What columns does your list have? (e.g. name, city, phone)"
Normalized record:
{
"name": "Acme Dental",
"domain": "acmedental.com",
"city": "Miami",
"state": "FL",
"phone": "3051234567",
"raw": "original row as-is, for traceability"
}
- Domain: strip protocol,
www., trailing/, and path segments - Phone: strip all non-digits; keep the last 10 digits (US) or the full international number
- Name: trim whitespace; preserve original casing for display
- Missing fields: set to
null— matching layers skip null fields
Failure handling: if extraction returns garbage or the CSV is malformed, ask the user to paste the data inline. If the parsed list has zero valid records, warn and offer to switch to plain discovery.
Matching algorithm
Three layers, in order. Once an entity matches at any layer, stop — don't re-match at lower layers. Track which layer produced each match; it's reported per row.
Layer 1 — domain match (primary)
Compare normalized root domains.
reference: acmedental.com ↔ discovered: acmedental.com → MATCH
reference: acmedental.com ↔ discovered: acme-dental.com → NO MATCH
Root domain only (not subdomains). Exact match required — no fuzzy domain matching, ever: near-miss domains are usually different businesses. Skip pairs where either side has domain: null.
Layer 2 — name + city fuzzy match (secondary)
For entities unmatched after Layer 1, compare names within the same city.
Normalization: lowercase both names; remove punctuation (.,'-&!()[]); remove common suffixes (inc, llc, ltd, corp, co, pllc, pa, pc, dds, md, dmd, do, group, associates, and associates); split into word tokens.
Matching: both entities must share the same city (case-insensitive, trimmed). Word overlap = shared_words / max(words_a, words_b). Threshold: 80% overlap = match.
"Acme Dental Associates" in Miami ↔ "Acme Dental" in Miami
→ tokens [acme, dental] vs [acme, dental] → 100% → MATCH
"Acme Dental" in Miami ↔ "Acme Health" in Miami
→ tokens [acme, dental] vs [acme, health] → 50% → NO MATCH
Skip pairs where either side has city: null.
Layer 3 — phone match (tertiary)
For still-unmatched entities, compare normalized phones (non-digits stripped, last 10 digits).
reference: (305) 123-4567 → 3051234567
discovered: 305-123-4567 → 3051234567 → MATCH
Skip pairs where either side has phone: null.
Categorization
| Category | Definition | Business meaning |
|---|---|---|
matched |
In both reference list AND discovery | Validated — the list is accurate here |
discovered_only |
Found by discovery, NOT in reference list | Expansion candidates — gaps in the user's list |
reference_only |
In reference list, NOT found by discovery | Coverage gaps — may have closed, moved, rebranded, or be missed by sources |
Coverage score: matched / reference_count × 100. Zero matches across all three layers is a valid result — report the 0% honestly; it usually means the list and the discovery scope describe different markets, which is itself the finding.
Output template
# Market Audit: [Business Type] in [Geography]
*Audited [R] reference entries against [D] discovered businesses | [Date]*
## TL;DR
[M/R × 100]% coverage — [M] of [R] reference entries verified, [DO] new
businesses discovered, [RO] in your list but not found by discovery.
## Summary
- **Reference list:** [R] entries
- **Discovered:** [D] businesses
- **Matched:** [M] ([M/R]% of reference list verified)
- **Discovered only:** [DO] expansion candidates
- **Reference only:** [RO] coverage gaps
## Matched ([M])
| # | Name | Location | Match Layer | Discovery Strength | Sources |
|---|------|----------|-------------|--------------------|---------|
## Discovered Only ([DO]) — Expansion Candidates
| # | Name | Location | Domain | Rating | Strength | Sources |
|---|------|----------|--------|--------|----------|---------|
## Reference Only ([RO]) — Coverage Gaps
| # | Name | Location | Domain | Phone | Possible Reason |
|---|------|----------|--------|-------|-----------------|
## What This Means
[1–3 sentences of interpretation — the coverage percentage in plain terms,
geographic clusters in discovered_only, notable reference_only entries.]
Save the report plus a structured data file (all three categories, with match layer per entity) under the market slug in your workspace — the structured file is what downstream exports and follow-up investigations read.
Follow-ups to offer
- Export discovered-only for outreach — a CSV of expansion candidates. Confirm before generating anything intended for bulk outreach.
- Investigate reference-only gaps — targeted searches per entity to determine closed vs. moved vs. rebranded vs. simply unlisted.
- Re-run against a different geography — same list, broader or narrower area.