fix: smart routing in Browse — enrichment filters use /api/enriched, discovery uses /api/domains
Root cause: loadDomains() always hit /api/domains (DuckDB 72M rows) and filtered niche/site_type/prescreen_status client-side on a random page of 100 domains — virtually none had been classified, so Live+Beauty+Ecommerce always returned 0. - loadDomains() now routes to /api/enriched when any enrichment filter is active (prescreen_status, niche, site_type, country) — all filters are server-side SQLite - Falls back to /api/domains only when no enrichment filters are set (discovery mode) - alpha_only and no_sld supported in both modes: - DuckDB: existing regex support - SQLite: LIKE patterns (no hyphens/digits) + dot-count (no SLD) - Add alpha_only/no_sld params to /api/enriched endpoint and get_enriched() - Fix stale d.classified reference in prescreenOne toast Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -155,6 +155,8 @@ async def enriched(
|
|||||||
site_type: str = Query(None),
|
site_type: str = Query(None),
|
||||||
keyword: str = Query(None),
|
keyword: str = Query(None),
|
||||||
tld: str = Query(None),
|
tld: str = Query(None),
|
||||||
|
alpha_only: bool = Query(False),
|
||||||
|
no_sld: bool = Query(False),
|
||||||
page: int = Query(1, ge=1),
|
page: int = Query(1, ge=1),
|
||||||
limit: int = Query(100, ge=1, le=1000),
|
limit: int = Query(100, ge=1, le=1000),
|
||||||
):
|
):
|
||||||
@@ -162,6 +164,7 @@ async def enriched(
|
|||||||
min_score=min_score, country=country,
|
min_score=min_score, country=country,
|
||||||
prescreen_status=prescreen_status, niche=niche, site_type=site_type,
|
prescreen_status=prescreen_status, niche=niche, site_type=site_type,
|
||||||
keyword=keyword, tld=tld,
|
keyword=keyword, tld=tld,
|
||||||
|
alpha_only=alpha_only, no_sld=no_sld,
|
||||||
page=page, limit=limit,
|
page=page, limit=limit,
|
||||||
)
|
)
|
||||||
return {"page": page, "limit": limit, "total": total, "results": rows}
|
return {"page": page, "limit": limit, "total": total, "results": rows}
|
||||||
|
|||||||
12
app/db.py
12
app/db.py
@@ -336,6 +336,7 @@ async def get_enriched(min_score=0, cms=None, country=None, kit_digital=None,
|
|||||||
ai_only=False, lead_quality=None,
|
ai_only=False, lead_quality=None,
|
||||||
prescreen_status=None, niche=None, site_type=None,
|
prescreen_status=None, niche=None, site_type=None,
|
||||||
keyword=None, tld=None,
|
keyword=None, tld=None,
|
||||||
|
alpha_only=False, no_sld=False,
|
||||||
page=1, limit=100):
|
page=1, limit=100):
|
||||||
offset = (page - 1) * limit
|
offset = (page - 1) * limit
|
||||||
conditions = ["score >= ?"]
|
conditions = ["score >= ?"]
|
||||||
@@ -378,6 +379,17 @@ async def get_enriched(min_score=0, cms=None, country=None, kit_digital=None,
|
|||||||
tld_clean = tld.lower().lstrip(".")
|
tld_clean = tld.lower().lstrip(".")
|
||||||
conditions.append("LOWER(domain) LIKE ?")
|
conditions.append("LOWER(domain) LIKE ?")
|
||||||
params.append(f"%.{tld_clean}")
|
params.append(f"%.{tld_clean}")
|
||||||
|
if alpha_only:
|
||||||
|
# No hyphens, no digits anywhere in the domain name
|
||||||
|
conditions.append(
|
||||||
|
"domain NOT LIKE '%-%' AND domain NOT LIKE '%0%' AND domain NOT LIKE '%1%'"
|
||||||
|
" AND domain NOT LIKE '%2%' AND domain NOT LIKE '%3%' AND domain NOT LIKE '%4%'"
|
||||||
|
" AND domain NOT LIKE '%5%' AND domain NOT LIKE '%6%' AND domain NOT LIKE '%7%'"
|
||||||
|
" AND domain NOT LIKE '%8%' AND domain NOT LIKE '%9%'"
|
||||||
|
)
|
||||||
|
if no_sld:
|
||||||
|
# Exactly one dot → only name.tld, excludes shop.com.es style
|
||||||
|
conditions.append("(LENGTH(domain) - LENGTH(REPLACE(domain, '.', ''))) = 1")
|
||||||
where = "WHERE " + " AND ".join(conditions)
|
where = "WHERE " + " AND ".join(conditions)
|
||||||
async with aiosqlite.connect(SQLITE_PATH, timeout=30) as db:
|
async with aiosqlite.connect(SQLITE_PATH, timeout=30) as db:
|
||||||
db.row_factory = aiosqlite.Row
|
db.row_factory = aiosqlite.Row
|
||||||
|
|||||||
@@ -492,16 +492,24 @@ function app() {
|
|||||||
if (this.f.tld) p.set('tld', this.f.tld.trim());
|
if (this.f.tld) p.set('tld', this.f.tld.trim());
|
||||||
if (this.f.alpha_only) p.set('alpha_only', 'true');
|
if (this.f.alpha_only) p.set('alpha_only', 'true');
|
||||||
if (this.f.no_sld) p.set('no_sld', 'true');
|
if (this.f.no_sld) p.set('no_sld', 'true');
|
||||||
const d = await fetch('/api/domains?' + p).then(r=>r.json());
|
|
||||||
|
const hasEnrichFilter = this.f.prescreen_status || this.f.niche || this.f.site_type || this.f.country;
|
||||||
|
let endpoint;
|
||||||
|
if (hasEnrichFilter) {
|
||||||
|
// Enrichment filters require the SQLite table — add them server-side
|
||||||
|
if (this.f.prescreen_status) p.set('prescreen_status', this.f.prescreen_status);
|
||||||
|
if (this.f.niche) p.set('niche', this.f.niche);
|
||||||
|
if (this.f.site_type) p.set('site_type', this.f.site_type);
|
||||||
|
if (this.f.country) p.set('country', this.f.country.trim().toUpperCase());
|
||||||
|
endpoint = '/api/enriched';
|
||||||
|
} else {
|
||||||
|
// Discovery mode: search full 72M DuckDB index (e.g. "Not checked" keyword search)
|
||||||
|
endpoint = '/api/domains';
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = await fetch(endpoint + '?' + p).then(r=>r.json());
|
||||||
this.domainsTotal = d.total || 0;
|
this.domainsTotal = d.total || 0;
|
||||||
let rows = d.results || [];
|
this.domains = d.results || [];
|
||||||
// Client-side filters (same pattern as main DomGod)
|
|
||||||
if (this.f.prescreen_status === 'none') rows = rows.filter(r => !r.prescreen_status);
|
|
||||||
else if (this.f.prescreen_status) rows = rows.filter(r => r.prescreen_status === this.f.prescreen_status);
|
|
||||||
if (this.f.niche) rows = rows.filter(r => r.niche === this.f.niche);
|
|
||||||
if (this.f.site_type) rows = rows.filter(r => r.site_type === this.f.site_type);
|
|
||||||
if (this.f.country) rows = rows.filter(r => r.ip_country === this.f.country.trim().toUpperCase());
|
|
||||||
this.domains = rows;
|
|
||||||
} catch(e) { this.notify('Failed to load: '+e.message, 'error'); }
|
} catch(e) { this.notify('Failed to load: '+e.message, 'error'); }
|
||||||
finally { this.loading = false; }
|
finally { this.loading = false; }
|
||||||
},
|
},
|
||||||
@@ -596,7 +604,7 @@ function app() {
|
|||||||
method:'POST', headers:{'Content-Type':'application/json'},
|
method:'POST', headers:{'Content-Type':'application/json'},
|
||||||
body: JSON.stringify({domains:[domain]}),
|
body: JSON.stringify({domains:[domain]}),
|
||||||
}).then(r=>r.json());
|
}).then(r=>r.json());
|
||||||
this.notify(`${domain}: ${d.live?'live':'dead/parked'}, classified: ${d.classified}`, 'success');
|
this.notify(`${domain}: ${d.live?'✅ live':'☠ dead/parked'}${d.classifying?' · classifying in background':''}`, 'success');
|
||||||
await this.loadDomains();
|
await this.loadDomains();
|
||||||
} catch(e) { this.notify('Failed: '+e.message, 'error'); }
|
} catch(e) { this.notify('Failed: '+e.message, 'error'); }
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user