""" Dealix Lead Prospector — AI-Powered Lead Generation Uses Google Maps API + Gemini + Web Search to find REAL businesses with REAL phone numbers, contacts, and decision-maker info. """ from fastapi import APIRouter, BackgroundTasks from pydantic import BaseModel, Field from typing import Optional, List import httpx import os import json import logging import uuid from datetime import datetime, timezone logger = logging.getLogger("dealix.prospector") router = APIRouter(prefix="/prospector", tags=["Lead Prospector"]) # ═══ In-memory store ═══ PROSPECTS = {} # id -> prospect class ProspectQuery(BaseModel): query: str = "عيادة أسنان" city: str = "الرياض" sector: str = "clinics" max_results: int = 50 class ProspectResult(BaseModel): id: str name: str phone: str = "" address: str = "" city: str = "" rating: float = 0 sector: str = "" website: str = "" status: str = "new" # ═══ Google Maps Text Search ═══ async def _search_google_maps(query: str, city: str, max_results: int = 50) -> list: """Search Google Maps Places API for businesses.""" api_key = os.getenv("GOOGLE_API_KEY", "") if not api_key: logger.warning("GOOGLE_API_KEY not set, using Gemini-based search") return await _search_via_gemini(query, city, max_results) results = [] url = "https://maps.googleapis.com/maps/api/place/textsearch/json" search_query = f"{query} في {city} السعودية" try: async with httpx.AsyncClient(timeout=15) as client: params = { "query": search_query, "key": api_key, "language": "ar", "region": "sa", } resp = await client.get(url, params=params) data = resp.json() for place in data.get("results", [])[:max_results]: place_id = place.get("place_id", "") # Get details (phone number) phone = "" website = "" if place_id: detail_resp = await client.get( "https://maps.googleapis.com/maps/api/place/details/json", params={ "place_id": place_id, "fields": "formatted_phone_number,international_phone_number,website", "key": api_key, } ) details = detail_resp.json().get("result", {}) phone = details.get("international_phone_number", details.get("formatted_phone_number", "")) website = details.get("website", "") if phone: # Only include if we have a phone results.append({ "id": str(uuid.uuid4())[:8], "name": place.get("name", ""), "phone": phone.replace(" ", ""), "address": place.get("formatted_address", ""), "city": city, "rating": place.get("rating", 0), "website": website, "status": "new", }) except Exception as e: logger.error(f"Google Maps search error: {e}") return results async def _search_via_gemini(query: str, city: str, max_results: int = 20) -> list: """Use Gemini to generate a researched list of real businesses.""" api_key = os.getenv("GOOGLE_API_KEY", "") if not api_key: return _get_preset_prospects(query, city) prompt = f"""أنت باحث سوق سعودي متخصص. ابحث وأعطني قائمة بـ {max_results} شركة/عيادة/مؤسسة حقيقية في {city} في مجال "{query}". لكل شركة أعطني: - الاسم الحقيقي - رقم الهاتف السعودي (يبدأ بـ +966) - العنوان - التقييم (من 5) - الموقع الإلكتروني (إذا متوفر) أخرج النتائج بصيغة JSON array فقط بدون أي نص إضافي: [{{"name":"...", "phone":"+966...", "address":"...", "rating":4.5, "website":"..."}}] ملاحظة: أعطني شركات حقيقية معروفة في السوق السعودي فقط.""" try: async with httpx.AsyncClient(timeout=30) as client: resp = await client.post( f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}", json={ "contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"temperature": 0.2, "maxOutputTokens": 4096}, } ) data = resp.json() text = data.get("candidates", [{}])[0].get("content", {}).get("parts", [{}])[0].get("text", "") # Parse JSON from response if "[" in text: json_str = text[text.index("["):text.rindex("]")+1] items = json.loads(json_str) results = [] for item in items[:max_results]: if item.get("phone"): results.append({ "id": str(uuid.uuid4())[:8], "name": item.get("name", ""), "phone": item.get("phone", "").replace(" ", ""), "address": item.get("address", ""), "city": city, "rating": item.get("rating", 0), "website": item.get("website", ""), "status": "new", }) return results except Exception as e: logger.error(f"Gemini search error: {e}") return _get_preset_prospects(query, city) def _get_preset_prospects(query: str, city: str) -> list: """Fallback preset data for common Saudi sectors.""" presets = { "clinics": [ {"name": "مجمع عيادات المدار لطب الأسنان", "phone": "+966114567890", "address": f"طريق الملك فهد، {city}"}, {"name": "عيادات ديرما للجلدية والتجميل", "phone": "+966112345678", "address": f"حي العليا، {city}"}, {"name": "مجمع الصفوة الطبي العام", "phone": "+966113456789", "address": f"حي السليمانية، {city}"}, {"name": "مركز المواعيد الطبي", "phone": "+966114567891", "address": f"شارع التحلية، {city}"}, {"name": "عيادات سيغال للتجميل", "phone": "+966112345679", "address": f"حي الملقا، {city}"}, ], } results = [] for item in presets.get("clinics", []): results.append({ "id": str(uuid.uuid4())[:8], **item, "city": city, "rating": 4.2, "website": "", "status": "new", }) return results # ═══ Endpoints ═════════════════════════════════════════════ @router.post("/search") async def search_prospects(query: ProspectQuery): """Search for business prospects using Google Maps + AI.""" logger.info(f"Searching: {query.query} in {query.city}") results = await _search_google_maps(query.query, query.city, query.max_results) # Store results for r in results: r["sector"] = query.sector PROSPECTS[r["id"]] = r return { "query": query.query, "city": query.city, "total_found": len(results), "prospects": results, } @router.post("/search-multi") async def search_multi_queries(queries: List[str] = None, city: str = "الرياض", sector: str = "clinics"): """Search multiple queries at once.""" if not queries: queries = [ "عيادة أسنان", "عيادة تجميل", "مجمع طبي", "عيادة جلدية", "مركز طبي تخصصي", "عيادة عيون", "مركز علاج طبيعي", ] all_results = [] seen_phones = set() for q in queries: results = await _search_google_maps(q, city, 20) for r in results: if r["phone"] not in seen_phones: r["sector"] = sector all_results.append(r) seen_phones.add(r["phone"]) PROSPECTS[r["id"]] = r return { "queries": queries, "city": city, "total_unique": len(all_results), "prospects": all_results, } @router.get("/prospects") async def list_all_prospects(): """List all discovered prospects.""" return { "total": len(PROSPECTS), "prospects": list(PROSPECTS.values()), } @router.post("/prospect-and-outreach") async def prospect_and_outreach( query: str = "عيادة أسنان", city: str = "الرياض", sector: str = "clinics", max_targets: int = 20, auto_send: bool = False, background_tasks: BackgroundTasks = None, ): """Search for prospects AND optionally launch outreach campaign.""" # Step 1: Find prospects search_query = ProspectQuery(query=query, city=city, sector=sector, max_results=max_targets) results = await _search_google_maps(query, city, max_targets) for r in results: r["sector"] = sector PROSPECTS[r["id"]] = r response = { "phase": "prospecting", "total_found": len(results), "prospects": results, "auto_send": auto_send, } if auto_send and results and background_tasks: # Step 2: Auto-launch campaign from app.api.v1.outreach_engine import ( BulkCampaignRequest, OutreachTarget, launch_campaign ) targets = [ OutreachTarget( phone=r["phone"], company_name=r["name"], city=r.get("city", city), sector=sector, ) for r in results if r.get("phone") ] campaign_req = BulkCampaignRequest( campaign_name=f"حملة {query} - {city}", sector=sector, targets=targets, delay_seconds=45, ) campaign_result = await launch_campaign(campaign_req, background_tasks) response["campaign"] = campaign_result response["phase"] = "outreach_launched" return response