feat: bulk validator tab + status/niche/type browse filters

- New app/validator.py: background HTTP checker for entire dataset
  - 50 concurrent checks, skips already-validated domains
  - Extracts prescreen_status, server, IP, load_time_ms
  - start/stop/status API at /api/validator/start|stop|status

- New dedicated "Validator 🔬" tab with stats grid, TLD filter,
  Start/Stop controls, live progress indicator

- Browse tab: "Live" column replaced with "Status" dot (color-coded
  ● from prescreen_status, falls back to is_live)
- Browse tab: new Status / Niche / Type filter dropdowns

- db.py: added ip TEXT + load_time_ms INTEGER columns + migrations;
  get_enriched() supports prescreen_status/niche/site_type filters

- main.py: /api/enriched extended with prescreen_status/niche/site_type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 08:27:24 +02:00
parent 468d76387d
commit 8f387cada2
4 changed files with 450 additions and 14 deletions

View File

@@ -40,7 +40,9 @@ CREATE TABLE IF NOT EXISTS enriched_domains (
prescreen_status TEXT,
niche TEXT,
site_type TEXT,
prescreen_at TEXT
prescreen_at TEXT,
ip TEXT,
load_time_ms INTEGER
);
CREATE TABLE IF NOT EXISTS job_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -84,6 +86,8 @@ _MIGRATIONS = [
"ALTER TABLE enriched_domains ADD COLUMN niche TEXT",
"ALTER TABLE enriched_domains ADD COLUMN site_type TEXT",
"ALTER TABLE enriched_domains ADD COLUMN prescreen_at TEXT",
"ALTER TABLE enriched_domains ADD COLUMN ip TEXT",
"ALTER TABLE enriched_domains ADD COLUMN load_time_ms INTEGER",
]
# Index build state
@@ -315,7 +319,9 @@ async def get_stats():
# ── Enrichment helpers ───────────────────────────────────────────────────────
async def get_enriched(min_score=0, cms=None, country=None, kit_digital=None,
ai_only=False, lead_quality=None, page=1, limit=100):
ai_only=False, lead_quality=None,
prescreen_status=None, niche=None, site_type=None,
page=1, limit=100):
offset = (page - 1) * limit
conditions = ["score >= ?"]
params: list = [min_score]
@@ -333,6 +339,17 @@ async def get_enriched(min_score=0, cms=None, country=None, kit_digital=None,
if lead_quality:
conditions.append("ai_lead_quality = ?")
params.append(lead_quality.upper())
if prescreen_status == "none":
conditions.append("prescreen_status IS NULL")
elif prescreen_status:
conditions.append("prescreen_status = ?")
params.append(prescreen_status)
if niche:
conditions.append("niche = ?")
params.append(niche)
if site_type:
conditions.append("site_type = ?")
params.append(site_type)
where = "WHERE " + " AND ".join(conditions)
async with aiosqlite.connect(SQLITE_PATH) as db:
db.row_factory = aiosqlite.Row