feat(leads): 2-hour admin review window before leads appear in supplier feed

New visible_from column on lead_requests set to NOW + 2h on both the
direct insert (logged-in user) and the email verification update.

Supplier feed, notify_matching_suppliers, and send_weekly_lead_digest
all filter on visible_from <= datetime('now'), so no lead surfaces to
suppliers before the window expires.

Migration 0023 adds the column and backfills existing verified leads
with created_at so they remain immediately visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-25 09:53:19 +01:00
parent 607dc35a9d
commit 3c0f57c0fd
4 changed files with 15 additions and 5 deletions

View File

@@ -346,8 +346,8 @@ async def quote_request():
previous_supplier_contact, services_needed, additional_info, previous_supplier_contact, services_needed, additional_info,
contact_name, contact_email, contact_phone, contact_company, contact_name, contact_email, contact_phone, contact_company,
stakeholder_type, stakeholder_type,
heat_score, status, credit_cost, token, created_at) heat_score, status, credit_cost, token, created_at, visible_from)
VALUES (?, 'quote', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", VALUES (?, 'quote', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now', '+2 hours'))""",
( (
user_id, user_id,
form.get("court_count", 0), form.get("court_count", 0),
@@ -522,7 +522,7 @@ async def verify_quote():
credit_cost = compute_credit_cost(dict(lead)) credit_cost = compute_credit_cost(dict(lead))
now = utcnow_iso() now = utcnow_iso()
await execute( await execute(
"UPDATE lead_requests SET status = 'new', verified_at = ?, credit_cost = ? WHERE id = ?", "UPDATE lead_requests SET status = 'new', verified_at = ?, credit_cost = ?, visible_from = datetime('now', '+2 hours') WHERE id = ?",
(now, credit_cost, lead["id"]), (now, credit_cost, lead["id"]),
) )

View File

@@ -0,0 +1,9 @@
"""Migration 0023: Add visible_from to lead_requests for 2-hour admin review window."""
def up(conn) -> None:
conn.execute("ALTER TABLE lead_requests ADD COLUMN visible_from TEXT")
# Backfill: existing verified leads are already past review — make them visible immediately
conn.execute(
"UPDATE lead_requests SET visible_from = created_at WHERE status = 'new' AND verified_at IS NOT NULL"
)

View File

@@ -513,7 +513,7 @@ async def signup_success():
async def _get_lead_feed_data(supplier, country="", heat="", timeline="", q="", limit=50): async def _get_lead_feed_data(supplier, country="", heat="", timeline="", q="", limit=50):
"""Shared query for lead feed — used by standalone and dashboard.""" """Shared query for lead feed — used by standalone and dashboard."""
wheres = ["lr.lead_type = 'quote'", "lr.status = 'new'", "lr.verified_at IS NOT NULL"] wheres = ["lr.lead_type = 'quote'", "lr.status = 'new'", "lr.verified_at IS NOT NULL", "lr.visible_from <= datetime('now')"]
params: list = [] params: list = []
if country: if country:

View File

@@ -567,7 +567,7 @@ async def handle_notify_matching_suppliers(payload: dict) -> None:
lang = payload.get("lang", "en") lang = payload.get("lang", "en")
lead = await fetch_one( lead = await fetch_one(
"SELECT * FROM lead_requests WHERE id = ? AND status = 'new' AND verified_at IS NOT NULL", "SELECT * FROM lead_requests WHERE id = ? AND status = 'new' AND verified_at IS NOT NULL AND visible_from <= datetime('now')",
(lead_id,), (lead_id,),
) )
if not lead or not lead.get("country"): if not lead or not lead.get("country"):
@@ -652,6 +652,7 @@ async def handle_send_weekly_lead_digest(payload: dict) -> None:
f"""SELECT id, heat_score, country, court_count, facility_type, timeline, credit_cost, created_at f"""SELECT id, heat_score, country, court_count, facility_type, timeline, credit_cost, created_at
FROM lead_requests FROM lead_requests
WHERE lead_type = 'quote' AND status = 'new' AND verified_at IS NOT NULL WHERE lead_type = 'quote' AND status = 'new' AND verified_at IS NOT NULL
AND visible_from <= datetime('now')
AND country IN ({placeholders}) AND country IN ({placeholders})
AND created_at >= datetime('now', '-7 days') AND created_at >= datetime('now', '-7 days')
AND NOT EXISTS ( AND NOT EXISTS (