feat: add Leads tab and Hide Assessed filter in Browse
- db.py: get_enriched() accepts ai_only + lead_quality params - main.py: /api/enriched exposes ai_only + lead_quality query params; new /api/export/leads endpoint produces CSV with contacts + pitch - index.html: - New "Leads 🤖" tab shows all AI-assessed domains with contacts (quality/country/limit filters, per-row 📋 copy email, 🔍 modal, CSV export, pagination, auto-refreshes every 3s) - Browse: "Hide assessed" checkbox filters out already-processed domains so you can focus on fresh targets - Poll cycle refreshes Leads tab when active Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
51
app/main.py
51
app/main.py
@@ -156,12 +156,15 @@ async def enriched(
|
||||
cms: str = Query(None),
|
||||
country: str = Query(None),
|
||||
kit_digital: Optional[bool] = Query(None),
|
||||
ai_only: bool = Query(False),
|
||||
lead_quality: str = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
):
|
||||
total, rows = await get_enriched(
|
||||
min_score=min_score, cms=cms, country=country,
|
||||
kit_digital=kit_digital, page=page, limit=limit,
|
||||
kit_digital=kit_digital, ai_only=ai_only, lead_quality=lead_quality,
|
||||
page=page, limit=limit,
|
||||
)
|
||||
return {"page": page, "limit": limit, "total": total, "results": rows}
|
||||
|
||||
@@ -286,6 +289,52 @@ async def export_csv(
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/export/leads")
|
||||
async def export_leads_csv(lead_quality: str = Query(None), country: str = Query(None)):
|
||||
import json as _json
|
||||
|
||||
async def generate():
|
||||
yield "domain,lead_quality,score,best_contact_channel,best_contact_value,emails,phones,whatsapp,social,cms,ip_country,page_title,ai_pitch\n"
|
||||
p = 1
|
||||
while True:
|
||||
_, rows = await get_enriched(ai_only=True, lead_quality=lead_quality,
|
||||
country=country, page=p, limit=500)
|
||||
if not rows:
|
||||
break
|
||||
for r in rows:
|
||||
contacts = {}
|
||||
try:
|
||||
contacts = _json.loads(r.get("contact_info") or "{}")
|
||||
except Exception:
|
||||
pass
|
||||
def esc(v):
|
||||
return f'"{str(v or "").replace(chr(34), chr(39))}"'
|
||||
emails = esc(";".join(contacts.get("emails", [])[:3]))
|
||||
phones = esc(";".join(contacts.get("phones", [])[:3]))
|
||||
whatsapp = esc(";".join(contacts.get("whatsapp",[])[:2]))
|
||||
social = esc(";".join(contacts.get("social", [])[:4]))
|
||||
line = ",".join([
|
||||
esc(r.get("domain")),
|
||||
esc(r.get("ai_lead_quality")),
|
||||
esc(r.get("score")),
|
||||
esc(r.get("ai_contact_channel")),
|
||||
esc(r.get("ai_contact_value")),
|
||||
emails, phones, whatsapp, social,
|
||||
esc(r.get("cms")),
|
||||
esc(r.get("ip_country")),
|
||||
esc(r.get("page_title")),
|
||||
esc(r.get("ai_pitch")),
|
||||
])
|
||||
yield line + "\n"
|
||||
p += 1
|
||||
|
||||
qual = f"_{lead_quality.lower()}" if lead_quality else ""
|
||||
return StreamingResponse(
|
||||
generate(), media_type="text/csv",
|
||||
headers={"Content-Disposition": f'attachment; filename="domgod_leads{qual}.csv"'},
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/score/run")
|
||||
async def score_run():
|
||||
return await run_scoring()
|
||||
|
||||
Reference in New Issue
Block a user