feat(prospecting): add complete prospecting module for lead discovery and scoring
Some checks failed
Some checks failed
Migrates scanning pipeline from marketing-.lu-domains app into Orion module. Supports digital (domain scan) and offline (manual capture) lead channels with enrichment, scoring, campaign management, and interaction tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
99
app/modules/prospecting/routes/api/admin_leads.py
Normal file
99
app/modules/prospecting/routes/api/admin_leads.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# app/modules/prospecting/routes/api/admin_leads.py
|
||||
"""
|
||||
Admin API routes for lead filtering and export.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from math import ceil
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.modules.prospecting.services.lead_service import lead_service
|
||||
from app.modules.tenancy.schemas.auth import UserContext
|
||||
|
||||
router = APIRouter(prefix="/leads")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_leads(
|
||||
page: int = Query(1, ge=1),
|
||||
per_page: int = Query(20, ge=1, le=100),
|
||||
min_score: int = Query(0, ge=0, le=100),
|
||||
max_score: int = Query(100, ge=0, le=100),
|
||||
lead_tier: str | None = Query(None),
|
||||
channel: str | None = Query(None),
|
||||
has_email: bool | None = Query(None),
|
||||
has_phone: bool | None = Query(None),
|
||||
reason_flag: str | None = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: UserContext = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get filtered leads with scores."""
|
||||
leads, total = lead_service.get_leads(
|
||||
db,
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
min_score=min_score,
|
||||
max_score=max_score,
|
||||
lead_tier=lead_tier,
|
||||
channel=channel,
|
||||
has_email=has_email,
|
||||
has_phone=has_phone,
|
||||
reason_flag=reason_flag,
|
||||
)
|
||||
return {
|
||||
"items": leads,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"pages": ceil(total / per_page) if per_page else 0,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/top-priority")
|
||||
def top_priority_leads(
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: UserContext = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get top priority leads (score >= 70)."""
|
||||
return lead_service.get_top_priority(db, limit=limit)
|
||||
|
||||
|
||||
@router.get("/quick-wins")
|
||||
def quick_win_leads(
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: UserContext = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get quick win leads (score 50-69)."""
|
||||
return lead_service.get_quick_wins(db, limit=limit)
|
||||
|
||||
|
||||
@router.get("/export/csv")
|
||||
def export_leads_csv(
|
||||
min_score: int = Query(0, ge=0, le=100),
|
||||
lead_tier: str | None = Query(None),
|
||||
channel: str | None = Query(None),
|
||||
limit: int = Query(1000, ge=1, le=10000),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: UserContext = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Export filtered leads as CSV download."""
|
||||
csv_content = lead_service.export_csv(
|
||||
db,
|
||||
min_score=min_score,
|
||||
lead_tier=lead_tier,
|
||||
channel=channel,
|
||||
limit=limit,
|
||||
)
|
||||
return StreamingResponse(
|
||||
iter([csv_content]),
|
||||
media_type="text/csv",
|
||||
headers={"Content-Disposition": "attachment; filename=leads_export.csv"},
|
||||
)
|
||||
Reference in New Issue
Block a user