feat: assessed filter, 5000 per-page limit, auto-advance on empty Not-checked page
Assessed/Not assessed filter: - 'yes' → beauty_lead_quality IS NOT NULL (has been B2B assessed) - 'no' → beauty_lead_quality IS NULL (never assessed) - wired through /api/enriched → get_enriched(beauty_assessed=) Per-page limit: - options: 100 / 500 / 1000 / 2000 / 5000 - backend cap raised from le=1000 to le=5000 Auto-advance on empty Not-checked page: - after bulk validate/prescreen, loadDomains reloads the same DuckDB page - if every domain on that page is now processed (client-side filter → 0 rows) but the page still returned results, automatically increment page and retry - prevents "No domains found" after successfully processing a batch - capped at page 500 to avoid infinite loop Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -157,14 +157,16 @@ async def enriched(
|
|||||||
tld: str = Query(None),
|
tld: str = Query(None),
|
||||||
alpha_only: bool = Query(False),
|
alpha_only: bool = Query(False),
|
||||||
no_sld: bool = Query(False),
|
no_sld: bool = Query(False),
|
||||||
|
assessed: str = Query(None),
|
||||||
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=5000),
|
||||||
):
|
):
|
||||||
total, rows = await get_enriched(
|
total, rows = await get_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,
|
alpha_only=alpha_only, no_sld=no_sld,
|
||||||
|
beauty_assessed=assessed,
|
||||||
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}
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ async def get_enriched(min_score=0, cms=None, country=None, kit_digital=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,
|
alpha_only=False, no_sld=False,
|
||||||
|
beauty_assessed=None,
|
||||||
page=1, limit=100):
|
page=1, limit=100):
|
||||||
offset = (page - 1) * limit
|
offset = (page - 1) * limit
|
||||||
conditions = ["score >= ?"]
|
conditions = ["score >= ?"]
|
||||||
@@ -379,6 +380,10 @@ 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 beauty_assessed == "yes":
|
||||||
|
conditions.append("beauty_lead_quality IS NOT NULL")
|
||||||
|
elif beauty_assessed == "no":
|
||||||
|
conditions.append("beauty_lead_quality IS NULL")
|
||||||
if alpha_only:
|
if alpha_only:
|
||||||
# No hyphens, no digits anywhere in the domain name
|
# No hyphens, no digits anywhere in the domain name
|
||||||
conditions.append(
|
conditions.append(
|
||||||
|
|||||||
@@ -145,6 +145,11 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
|
|||||||
<option value="landing_page">Landing Page</option>
|
<option value="landing_page">Landing Page</option>
|
||||||
</select>
|
</select>
|
||||||
<input x-model="f.country" placeholder="Country (ES, FR…)" style="width:100px" @keyup.enter="goSearch()">
|
<input x-model="f.country" placeholder="Country (ES, FR…)" style="width:100px" @keyup.enter="goSearch()">
|
||||||
|
<select x-model="f.assessed" @change="goSearch()">
|
||||||
|
<option value="">Any B2B</option>
|
||||||
|
<option value="yes">Assessed</option>
|
||||||
|
<option value="no">Not assessed</option>
|
||||||
|
</select>
|
||||||
<label class="tog" style="font-size:12px;display:flex;align-items:center;gap:4px;cursor:pointer;white-space:nowrap">
|
<label class="tog" style="font-size:12px;display:flex;align-items:center;gap:4px;cursor:pointer;white-space:nowrap">
|
||||||
<input type="checkbox" x-model="f.alpha_only" @change="goSearch()"><span>Alpha only</span>
|
<input type="checkbox" x-model="f.alpha_only" @change="goSearch()"><span>Alpha only</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -152,10 +157,11 @@ textarea{width:100%;resize:vertical;font-family:monospace;font-size:12px}
|
|||||||
<input type="checkbox" x-model="f.no_sld" @change="goSearch()"><span>No SLD</span>
|
<input type="checkbox" x-model="f.no_sld" @change="goSearch()"><span>No SLD</span>
|
||||||
</label>
|
</label>
|
||||||
<select x-model="f.limit" @change="goSearch()">
|
<select x-model="f.limit" @change="goSearch()">
|
||||||
<option value="50">50</option>
|
|
||||||
<option value="100" selected>100</option>
|
<option value="100" selected>100</option>
|
||||||
<option value="200">200</option>
|
|
||||||
<option value="500">500</option>
|
<option value="500">500</option>
|
||||||
|
<option value="1000">1000</option>
|
||||||
|
<option value="2000">2000</option>
|
||||||
|
<option value="5000">5000</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="btn-primary" @click="goSearch()">Search</button>
|
<button class="btn-primary" @click="goSearch()">Search</button>
|
||||||
<button class="btn-secondary" @click="resetFilters()">Reset</button>
|
<button class="btn-secondary" @click="resetFilters()">Reset</button>
|
||||||
@@ -443,7 +449,7 @@ function app() {
|
|||||||
prescreening: false, validating: false,
|
prescreening: false, validating: false,
|
||||||
exportQuality: '', exportCountry: '',
|
exportQuality: '', exportCountry: '',
|
||||||
f: {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
f: {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
||||||
site_type:'ecommerce', country:'', alpha_only:false, no_sld:false, limit:'100', page:1},
|
site_type:'ecommerce', country:'', assessed:'', alpha_only:false, no_sld:false, limit:'100', page:1},
|
||||||
pf: {quality:'', country:'', limit:'100', page:1},
|
pf: {quality:'', country:'', limit:'100', page:1},
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@@ -493,17 +499,18 @@ function app() {
|
|||||||
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');
|
||||||
|
|
||||||
// 'none' (Not checked) = domains never in the pipeline → must search DuckDB.
|
// 'none' (Not checked) = domains never in the pipeline → DuckDB search.
|
||||||
// Any other enrichment filter (live/dead/parked, niche, site_type, country)
|
// Any real status (live/dead/…), niche, site_type, country, or assessed
|
||||||
// requires the SQLite enriched_domains table.
|
// requires the SQLite enriched_domains table (all server-side).
|
||||||
const hasEnrichFilter = (this.f.prescreen_status && this.f.prescreen_status !== 'none')
|
const hasEnrichFilter = (this.f.prescreen_status && this.f.prescreen_status !== 'none')
|
||||||
|| this.f.niche || this.f.site_type || this.f.country;
|
|| this.f.niche || this.f.site_type || this.f.country || this.f.assessed;
|
||||||
let endpoint;
|
let endpoint;
|
||||||
if (hasEnrichFilter) {
|
if (hasEnrichFilter) {
|
||||||
if (this.f.prescreen_status) p.set('prescreen_status', this.f.prescreen_status);
|
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.niche) p.set('niche', this.f.niche);
|
||||||
if (this.f.site_type) p.set('site_type', this.f.site_type);
|
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());
|
if (this.f.country) p.set('country', this.f.country.trim().toUpperCase());
|
||||||
|
if (this.f.assessed) p.set('assessed', this.f.assessed);
|
||||||
endpoint = '/api/enriched';
|
endpoint = '/api/enriched';
|
||||||
} else {
|
} else {
|
||||||
endpoint = '/api/domains';
|
endpoint = '/api/domains';
|
||||||
@@ -512,9 +519,21 @@ function app() {
|
|||||||
const d = await fetch(endpoint + '?' + p).then(r=>r.json());
|
const d = await fetch(endpoint + '?' + p).then(r=>r.json());
|
||||||
this.domainsTotal = d.total || 0;
|
this.domainsTotal = d.total || 0;
|
||||||
let rows = d.results || [];
|
let rows = d.results || [];
|
||||||
|
|
||||||
// 'Not checked': DuckDB returns all domains joined with enriched data;
|
// 'Not checked': DuckDB returns all domains joined with enriched data;
|
||||||
// filter client-side to keep only those with no prescreen_status yet.
|
// keep only those with no prescreen_status yet (truly unprocessed).
|
||||||
if (this.f.prescreen_status === 'none') rows = rows.filter(r => !r.prescreen_status);
|
if (this.f.prescreen_status === 'none') rows = rows.filter(r => !r.prescreen_status);
|
||||||
|
|
||||||
|
// Auto-advance: current DuckDB page was fully processed → try next page
|
||||||
|
// (prevents "0 results" after bulk-validating a page of Not checked domains)
|
||||||
|
if (rows.length === 0 && this.f.prescreen_status === 'none'
|
||||||
|
&& (d.results||[]).length > 0 && this.f.page < 500) {
|
||||||
|
this.f.page++;
|
||||||
|
this.loading = false;
|
||||||
|
await this.loadDomains();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.domains = rows;
|
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; }
|
||||||
@@ -535,7 +554,7 @@ function app() {
|
|||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.f = {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
this.f = {keyword:'', tld:'', prescreen_status:'live', niche:'beauty_cosmetics',
|
||||||
site_type:'ecommerce', country:'', alpha_only:false, no_sld:false, limit:'100', page:1};
|
site_type:'ecommerce', country:'', assessed:'', alpha_only:false, no_sld:false, limit:'100', page:1};
|
||||||
this.selected = [];
|
this.selected = [];
|
||||||
this.loadDomains();
|
this.loadDomains();
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user