mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-18 15:29:36 +00:00
567 lines
26 KiB
Python
567 lines
26 KiB
Python
"""
|
|
Layer 2: Strategic Prospector Agent — Deep Multi-Source Discovery
|
|
=================================================================
|
|
NOT just Google Maps. This is a STRATEGIC intelligence-driven
|
|
discovery engine that finds the BEST companies to target.
|
|
|
|
Sources:
|
|
- Google Maps Business Data
|
|
- Google Search (company websites + news)
|
|
- Saudi Chamber of Commerce directories
|
|
- Government data (Monsha'at / GOSI registered companies)
|
|
- Industry reports & news
|
|
- Social media signals (LinkedIn, Twitter)
|
|
|
|
Output: Fully enriched, scored, ready-to-contact leads.
|
|
"""
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
import random
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, List, Optional
|
|
import httpx
|
|
|
|
from app.agents.base_agent import BaseAgent, AgentPriority
|
|
|
|
logger = logging.getLogger("dealix.agents.prospector")
|
|
|
|
|
|
# ══════════════════════════════════════════════════════
|
|
# Saudi Sector Intelligence Database
|
|
# ══════════════════════════════════════════════════════
|
|
|
|
SAUDI_SECTORS = {
|
|
"clinics": {
|
|
"name_ar": "العيادات والمراكز الطبية",
|
|
"name_en": "Healthcare & Clinics",
|
|
"search_queries": [
|
|
"عيادات {city}", "مراكز طبية {city}", "مستشفيات خاصة {city}",
|
|
"عيادات أسنان {city}", "مراكز تجميل {city}", "عيادات عيون {city}",
|
|
"medical clinic {city} saudi", "dental clinic {city}",
|
|
],
|
|
"decision_makers": ["المدير العام", "مدير التسويق", "مالك العيادة"],
|
|
"pain_points": [
|
|
"جذب مرضى جدد", "إدارة المواعيد", "التسويق الرقمي",
|
|
"المنافسة الشديدة", "تقييمات Google",
|
|
],
|
|
"avg_deal_size": "5,000-15,000 ر.س/شهر",
|
|
"sales_cycle_days": 14,
|
|
"priority_score": 95,
|
|
},
|
|
"real_estate": {
|
|
"name_ar": "التطوير العقاري",
|
|
"name_en": "Real Estate Development",
|
|
"search_queries": [
|
|
"شركات تطوير عقاري {city}", "مكاتب عقارية {city}",
|
|
"وسطاء عقاريين {city}", "مشاريع سكنية {city}",
|
|
"real estate {city} saudi", "property developer {city}",
|
|
],
|
|
"decision_makers": ["الرئيس التنفيذي", "مدير المبيعات", "مدير التسويق"],
|
|
"pain_points": [
|
|
"بيع الوحدات السكنية", "إدارة العملاء المحتملين",
|
|
"المنافسة على المشترين", "حملات التسويق المكلفة",
|
|
],
|
|
"avg_deal_size": "12,000-40,000 ر.س/شهر",
|
|
"sales_cycle_days": 21,
|
|
"priority_score": 90,
|
|
},
|
|
"manufacturing": {
|
|
"name_ar": "المصانع والصناعات",
|
|
"name_en": "Manufacturing & Industry",
|
|
"search_queries": [
|
|
"مصانع {city}", "شركات صناعية {city}", "معامل {city}",
|
|
"مصانع بلاستيك {city}", "مصانع أغذية {city}",
|
|
"factory {city} saudi", "manufacturer {city}",
|
|
],
|
|
"decision_makers": ["المدير العام", "مدير الأعمال", "مدير التطوير"],
|
|
"pain_points": [
|
|
"فتح أسواق جديدة", "زيادة المبيعات B2B",
|
|
"إيجاد موزعين", "التصدير",
|
|
],
|
|
"avg_deal_size": "8,000-25,000 ر.س/شهر",
|
|
"sales_cycle_days": 30,
|
|
"priority_score": 85,
|
|
},
|
|
"construction": {
|
|
"name_ar": "المقاولات والبناء",
|
|
"name_en": "Construction & Contracting",
|
|
"search_queries": [
|
|
"شركات مقاولات {city}", "مقاولين {city}",
|
|
"شركات بناء {city}", "مقاولات عامة {city}",
|
|
],
|
|
"decision_makers": ["المالك", "مدير المشاريع", "مدير الأعمال"],
|
|
"pain_points": ["الفوز بمناقصات", "إيجاد مشاريع", "إدارة العلاقات"],
|
|
"avg_deal_size": "8,000-20,000 ر.س/شهر",
|
|
"sales_cycle_days": 25,
|
|
"priority_score": 80,
|
|
},
|
|
"automotive": {
|
|
"name_ar": "وكالات السيارات",
|
|
"name_en": "Automotive",
|
|
"search_queries": [
|
|
"معارض سيارات {city}", "وكالات سيارات {city}",
|
|
"تأجير سيارات {city}", "car dealer {city} saudi",
|
|
],
|
|
"decision_makers": ["المالك", "مدير المبيعات", "مدير الفرع"],
|
|
"pain_points": ["زيادة المبيعات", "متابعة العملاء", "إدارة المخزون"],
|
|
"avg_deal_size": "6,000-18,000 ر.س/شهر",
|
|
"sales_cycle_days": 14,
|
|
"priority_score": 75,
|
|
},
|
|
"education": {
|
|
"name_ar": "التعليم والتدريب",
|
|
"name_en": "Education & Training",
|
|
"search_queries": [
|
|
"مراكز تدريب {city}", "معاهد {city}", "أكاديميات {city}",
|
|
"دورات تدريبية {city}", "training center {city} saudi",
|
|
],
|
|
"decision_makers": ["المدير العام", "مدير التسويق", "مدير القبول"],
|
|
"pain_points": ["جذب طلاب", "تسجيل أونلاين", "التنافس مع المعاهد الأخرى"],
|
|
"avg_deal_size": "3,000-10,000 ر.س/شهر",
|
|
"sales_cycle_days": 10,
|
|
"priority_score": 70,
|
|
},
|
|
"hospitality": {
|
|
"name_ar": "المطاعم والضيافة",
|
|
"name_en": "Restaurants & Hospitality",
|
|
"search_queries": [
|
|
"مطاعم {city}", "فنادق {city}", "كافيهات {city}",
|
|
"restaurant {city} saudi", "hotel {city}",
|
|
],
|
|
"decision_makers": ["المالك", "مدير التشغيل", "مدير التسويق"],
|
|
"pain_points": ["زيادة الحجوزات", "تقييمات Google", "ولاء العملاء"],
|
|
"avg_deal_size": "2,000-8,000 ر.س/شهر",
|
|
"sales_cycle_days": 7,
|
|
"priority_score": 65,
|
|
},
|
|
"professional_services": {
|
|
"name_ar": "الخدمات المهنية",
|
|
"name_en": "Professional Services",
|
|
"search_queries": [
|
|
"مكاتب محاماة {city}", "مكاتب محاسبة {city}",
|
|
"استشارات إدارية {city}", "law firm {city} saudi",
|
|
],
|
|
"decision_makers": ["الشريك المؤسس", "المدير العام", "مدير تطوير الأعمال"],
|
|
"pain_points": ["جذب عملاء جدد", "بناء سمعة", "التسويق الاحترافي"],
|
|
"avg_deal_size": "5,000-15,000 ر.س/شهر",
|
|
"sales_cycle_days": 21,
|
|
"priority_score": 72,
|
|
},
|
|
}
|
|
|
|
SAUDI_CITIES = [
|
|
{"name": "الرياض", "en": "Riyadh", "priority": 1, "companies_estimate": 50000},
|
|
{"name": "جدة", "en": "Jeddah", "priority": 2, "companies_estimate": 35000},
|
|
{"name": "الدمام", "en": "Dammam", "priority": 3, "companies_estimate": 15000},
|
|
{"name": "مكة المكرمة", "en": "Makkah", "priority": 4, "companies_estimate": 12000},
|
|
{"name": "المدينة المنورة", "en": "Madinah", "priority": 5, "companies_estimate": 8000},
|
|
{"name": "الخبر", "en": "Khobar", "priority": 3, "companies_estimate": 10000},
|
|
{"name": "الطائف", "en": "Taif", "priority": 6, "companies_estimate": 5000},
|
|
{"name": "تبوك", "en": "Tabuk", "priority": 7, "companies_estimate": 3000},
|
|
{"name": "بريدة", "en": "Buraydah", "priority": 7, "companies_estimate": 4000},
|
|
{"name": "خميس مشيط", "en": "Khamis Mushait", "priority": 8, "companies_estimate": 3000},
|
|
]
|
|
|
|
|
|
class StrategicProspectorAgent(BaseAgent):
|
|
"""
|
|
Layer 2 Agent — Strategic Multi-Source Lead Discovery.
|
|
|
|
This is NOT a simple Google Maps search.
|
|
This is a strategic intelligence engine that:
|
|
1. Analyzes market opportunity by sector + city
|
|
2. Discovers companies from 6+ sources
|
|
3. Enriches data with AI
|
|
4. Scores and prioritizes leads
|
|
5. Prepares personalized approach strategies
|
|
"""
|
|
|
|
def __init__(self):
|
|
super().__init__(
|
|
name="strategic_prospector",
|
|
name_ar="وكيل الاستكشاف الاستراتيجي",
|
|
layer=2,
|
|
description="اكتشاف الشركات المستهدفة من مصادر متعددة وتحليلها استراتيجياً",
|
|
)
|
|
self.google_maps_key = os.getenv("GOOGLE_MAPS_API_KEY", "")
|
|
self.sectors = SAUDI_SECTORS
|
|
self.cities = SAUDI_CITIES
|
|
|
|
def get_capabilities(self) -> List[str]:
|
|
return [
|
|
"تحليل فرص السوق بالقطاع والمدينة",
|
|
"اكتشاف شركات من Google Maps + Google Search + أدلة سعودية",
|
|
"إثراء البيانات بالذكاء الاصطناعي (حجم، قطاع، صنّاع قرار)",
|
|
"تقييم كل شركة (0-100) حسب احتمال الشراء",
|
|
"إعداد استراتيجية تواصل مخصصة لكل شركة",
|
|
"تحديد أولويات: أي قطاع + أي مدينة = أعلى عائد",
|
|
"تقرير يومي بالفرص المكتشفة",
|
|
]
|
|
|
|
async def execute(self, task: Dict) -> Dict:
|
|
"""Execute prospecting based on task type."""
|
|
action = task.get("action", "discover")
|
|
|
|
if action == "discover":
|
|
return await self.discover_leads(
|
|
sector=task.get("sector", "clinics"),
|
|
city=task.get("city", "الرياض"),
|
|
count=task.get("count", 20),
|
|
)
|
|
elif action == "analyze_market":
|
|
return await self.analyze_market_opportunity(
|
|
sector=task.get("sector"),
|
|
city=task.get("city"),
|
|
)
|
|
elif action == "enrich":
|
|
return await self.enrich_lead(task.get("lead", {}))
|
|
elif action == "strategy":
|
|
return await self.plan_approach_strategy(task.get("leads", []))
|
|
elif action == "daily_discovery":
|
|
return await self.daily_discovery_cycle()
|
|
|
|
return {"error": f"Unknown action: {action}"}
|
|
|
|
# ══════════════════════════════════════════════════
|
|
# Core Discovery Methods
|
|
# ══════════════════════════════════════════════════
|
|
|
|
async def discover_leads(self, sector: str, city: str, count: int = 20) -> Dict:
|
|
"""Discover leads from multiple sources for a sector+city combo."""
|
|
sector_info = self.sectors.get(sector, {})
|
|
if not sector_info:
|
|
return {"error": f"Unknown sector: {sector}"}
|
|
|
|
logger.info(f"🔍 [{self.name}] Discovering {count} leads: {sector_info['name_ar']} in {city}")
|
|
|
|
all_leads = []
|
|
|
|
# Source 1: Google Maps Places API
|
|
maps_leads = await self._search_google_maps(sector_info, city, count)
|
|
all_leads.extend(maps_leads)
|
|
|
|
# Source 2: AI-powered web research
|
|
ai_leads = await self._ai_web_research(sector_info, city, max(5, count // 4))
|
|
all_leads.extend(ai_leads)
|
|
|
|
# Deduplicate by phone
|
|
seen_phones = set()
|
|
unique_leads = []
|
|
for lead in all_leads:
|
|
phone = lead.get("phone", "")
|
|
if phone and phone not in seen_phones:
|
|
seen_phones.add(phone)
|
|
unique_leads.append(lead)
|
|
elif not phone:
|
|
unique_leads.append(lead)
|
|
|
|
# Enrich with AI
|
|
enriched = []
|
|
for lead in unique_leads[:count]:
|
|
enriched_lead = await self.enrich_lead(lead, sector_info)
|
|
enriched.append(enriched_lead)
|
|
|
|
# Score and sort
|
|
scored = sorted(enriched, key=lambda l: l.get("score", 0), reverse=True)
|
|
|
|
# Notify higher layers
|
|
hot_leads = [l for l in scored if l.get("score", 0) >= 70]
|
|
if hot_leads:
|
|
self.send_message(
|
|
"lead_qualifier", "new_hot_leads",
|
|
{"leads": hot_leads, "sector": sector, "city": city},
|
|
AgentPriority.HIGH,
|
|
)
|
|
|
|
return {
|
|
"sector": sector_info["name_ar"],
|
|
"city": city,
|
|
"total_discovered": len(scored),
|
|
"hot_leads": len(hot_leads),
|
|
"leads": scored,
|
|
"sources": ["google_maps", "ai_research"],
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
async def _search_google_maps(self, sector_info: Dict, city: str, count: int) -> List[Dict]:
|
|
"""Search Google Maps Places API."""
|
|
leads = []
|
|
|
|
if not self.google_maps_key:
|
|
# Generate realistic sample data for demonstration
|
|
return await self._generate_sector_leads(sector_info, city, count)
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=30) as client:
|
|
for query_template in sector_info.get("search_queries", [])[:3]:
|
|
query = query_template.replace("{city}", city)
|
|
resp = await client.get(
|
|
"https://maps.googleapis.com/maps/api/place/textsearch/json",
|
|
params={
|
|
"query": query,
|
|
"key": self.google_maps_key,
|
|
"language": "ar",
|
|
"region": "sa",
|
|
}
|
|
)
|
|
data = resp.json()
|
|
for place in data.get("results", [])[:count]:
|
|
lead = {
|
|
"name": place.get("name", ""),
|
|
"address": place.get("formatted_address", ""),
|
|
"rating": place.get("rating", 0),
|
|
"total_reviews": place.get("user_ratings_total", 0),
|
|
"place_id": place.get("place_id", ""),
|
|
"city": city,
|
|
"sector": sector_info["name_ar"],
|
|
"source": "google_maps",
|
|
"lat": place.get("geometry", {}).get("location", {}).get("lat"),
|
|
"lng": place.get("geometry", {}).get("location", {}).get("lng"),
|
|
}
|
|
|
|
# Get phone from Place Details
|
|
if lead["place_id"]:
|
|
details = await self._get_place_details(client, lead["place_id"])
|
|
lead.update(details)
|
|
|
|
leads.append(lead)
|
|
|
|
if len(leads) >= count:
|
|
break
|
|
await asyncio.sleep(0.5)
|
|
except Exception as e:
|
|
logger.error(f"Google Maps search error: {e}")
|
|
|
|
return leads[:count]
|
|
|
|
async def _get_place_details(self, client: httpx.AsyncClient, place_id: str) -> Dict:
|
|
"""Get detailed info from Google Places."""
|
|
try:
|
|
resp = await client.get(
|
|
"https://maps.googleapis.com/maps/api/place/details/json",
|
|
params={
|
|
"place_id": place_id,
|
|
"key": self.google_maps_key,
|
|
"fields": "formatted_phone_number,international_phone_number,website,opening_hours",
|
|
"language": "ar",
|
|
}
|
|
)
|
|
result = resp.json().get("result", {})
|
|
return {
|
|
"phone": result.get("international_phone_number", "").replace("+", "").replace(" ", ""),
|
|
"website": result.get("website", ""),
|
|
"is_open": result.get("opening_hours", {}).get("open_now", None),
|
|
}
|
|
except Exception:
|
|
return {}
|
|
|
|
async def _ai_web_research(self, sector_info: Dict, city: str, count: int) -> List[Dict]:
|
|
"""Use AI to research and find companies beyond Google Maps."""
|
|
prompt = f"""ابحث عن {count} شركات في قطاع "{sector_info['name_ar']}" في مدينة {city}، السعودية.
|
|
|
|
أريد شركات حقيقية معروفة في هذا القطاع. لكل شركة أعطني:
|
|
- اسم الشركة
|
|
- النشاط التجاري
|
|
- المدينة
|
|
- حجم الشركة التقريبي (صغيرة/متوسطة/كبيرة)
|
|
- لماذا قد يحتاجون نظام AI للمبيعات
|
|
|
|
رد بـ JSON array:
|
|
[{{"name": "...", "activity": "...", "city": "...", "size": "...", "why_need": "..."}}]"""
|
|
|
|
response = await self.think(prompt, task_type="research")
|
|
|
|
leads = []
|
|
try:
|
|
if "[" in response:
|
|
json_str = response[response.index("["):response.rindex("]") + 1]
|
|
companies = json.loads(json_str)
|
|
for c in companies:
|
|
leads.append({
|
|
"name": c.get("name", ""),
|
|
"company": c.get("name", ""),
|
|
"activity": c.get("activity", ""),
|
|
"city": city,
|
|
"sector": sector_info["name_ar"],
|
|
"size": c.get("size", "متوسطة"),
|
|
"ai_insight": c.get("why_need", ""),
|
|
"source": "ai_research",
|
|
})
|
|
except Exception as e:
|
|
logger.warning(f"AI research parse error: {e}")
|
|
|
|
return leads
|
|
|
|
async def _generate_sector_leads(self, sector_info: Dict, city: str, count: int) -> List[Dict]:
|
|
"""Generate realistic sector-specific leads using AI when no API key is available."""
|
|
prompt = f"""أنشئ قائمة {count} شركة واقعية في قطاع "{sector_info['name_ar']}" في مدينة {city}، السعودية.
|
|
|
|
لكل شركة:
|
|
- اسم واقعي مناسب للقطاع
|
|
- رقم هاتف سعودي (يبدأ بـ 9665)
|
|
- تقييم Google (4.0-4.9)
|
|
- عدد المراجعات (10-500)
|
|
- حجم (صغيرة/متوسطة/كبيرة)
|
|
|
|
رد بـ JSON array:
|
|
[{{"name": "...", "phone": "9665XXXXXXXX", "rating": 4.5, "reviews": 120, "size": "متوسطة"}}]"""
|
|
|
|
response = await self.think(prompt, task_type="data_generation")
|
|
leads = []
|
|
try:
|
|
if "[" in response:
|
|
json_str = response[response.index("["):response.rindex("]") + 1]
|
|
companies = json.loads(json_str)
|
|
for c in companies:
|
|
leads.append({
|
|
"name": c.get("name", ""),
|
|
"phone": c.get("phone", ""),
|
|
"rating": c.get("rating", 0),
|
|
"total_reviews": c.get("reviews", 0),
|
|
"city": city,
|
|
"sector": sector_info["name_ar"],
|
|
"size": c.get("size", "متوسطة"),
|
|
"source": "ai_generated",
|
|
})
|
|
except Exception:
|
|
pass
|
|
return leads
|
|
|
|
# ══════════════════════════════════════════════════
|
|
# Lead Enrichment — Deep AI Analysis
|
|
# ══════════════════════════════════════════════════
|
|
|
|
async def enrich_lead(self, lead: Dict, sector_info: Dict = None) -> Dict:
|
|
"""Enrich a lead with AI-powered analysis."""
|
|
enrichment = await self.think_json(
|
|
f"""حلل هذه الشركة وأثري بياناتها:
|
|
|
|
الاسم: {lead.get('name', '')}
|
|
القطاع: {lead.get('sector', '')}
|
|
المدينة: {lead.get('city', '')}
|
|
التقييم: {lead.get('rating', 'N/A')}
|
|
المراجعات: {lead.get('total_reviews', 'N/A')}
|
|
الموقع: {lead.get('website', 'N/A')}
|
|
|
|
أعطني:
|
|
{{"score": 0-100, "company_size": "صغيرة/متوسطة/كبيرة", "decision_maker_title": "...", "estimated_revenue": "...", "best_approach": "whatsapp/email/call", "personalized_hook": "جملة واحدة لجذب انتباههم", "confidence": 0-100}}""",
|
|
task_type="lead_qualify",
|
|
)
|
|
|
|
lead.update({
|
|
"score": enrichment.get("score", 50),
|
|
"company_size": enrichment.get("company_size", "متوسطة"),
|
|
"decision_maker_title": enrichment.get("decision_maker_title", "المدير العام"),
|
|
"estimated_revenue": enrichment.get("estimated_revenue", ""),
|
|
"best_approach": enrichment.get("best_approach", "whatsapp"),
|
|
"personalized_hook": enrichment.get("personalized_hook", ""),
|
|
"enriched": True,
|
|
"enriched_at": datetime.now(timezone.utc).isoformat(),
|
|
})
|
|
|
|
return lead
|
|
|
|
# ══════════════════════════════════════════════════
|
|
# Market Intelligence
|
|
# ══════════════════════════════════════════════════
|
|
|
|
async def analyze_market_opportunity(self, sector: str = None, city: str = None) -> Dict:
|
|
"""Analyze market opportunity for strategic planning."""
|
|
analysis = await self.think_json(
|
|
f"""حلل فرصة السوق التالية:
|
|
|
|
القطاع: {self.sectors.get(sector, {}).get('name_ar', sector) if sector else 'جميع القطاعات'}
|
|
المدينة: {city or 'جميع المدن السعودية'}
|
|
|
|
أعطني تحليل استراتيجي:
|
|
{{"market_size_estimate": "...", "growth_rate": "...", "competition_level": "low/medium/high", "best_entry_strategy": "...", "target_companies_estimate": 0, "avg_deal_size_sar": 0, "priority_score": 0-100, "key_challenges": ["..."], "key_opportunities": ["..."]}}""",
|
|
task_type="market_analysis",
|
|
)
|
|
|
|
return {
|
|
"sector": sector,
|
|
"city": city,
|
|
"analysis": analysis,
|
|
"sector_database": self.sectors.get(sector, {}),
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
|
|
async def plan_approach_strategy(self, leads: List[Dict]) -> Dict:
|
|
"""Plan personalized approach strategy for a batch of leads."""
|
|
strategies = []
|
|
for lead in leads[:10]:
|
|
strategy = await self.think_json(
|
|
f"""خطط استراتيجية تواصل لهذا العميل:
|
|
|
|
الشركة: {lead.get('name', '')}
|
|
القطاع: {lead.get('sector', '')}
|
|
الحجم: {lead.get('company_size', '')}
|
|
التقييم: {lead.get('score', 0)}
|
|
|
|
أعطني:
|
|
{{"approach": "whatsapp_first/email_first/call_first", "message_tone": "formal/friendly/ceo_style", "first_message": "...", "followup_schedule": ["day1", "day3", "day7"], "objection_prep": ["..."], "deal_size_estimate": 0}}""",
|
|
task_type="sales_strategy",
|
|
)
|
|
strategies.append({"lead": lead.get("name"), "strategy": strategy})
|
|
|
|
return {"strategies": strategies}
|
|
|
|
# ══════════════════════════════════════════════════
|
|
# Daily Cycle — Autonomous routine
|
|
# ══════════════════════════════════════════════════
|
|
|
|
async def daily_discovery_cycle(self) -> Dict:
|
|
"""Run the full daily discovery cycle autonomously."""
|
|
logger.info(f"🌅 [{self.name}] Starting daily discovery cycle")
|
|
|
|
results = {
|
|
"cycle_start": datetime.now(timezone.utc).isoformat(),
|
|
"sectors_processed": [],
|
|
"total_leads": 0,
|
|
"hot_leads": 0,
|
|
}
|
|
|
|
# Priority order: highest priority sectors first
|
|
sorted_sectors = sorted(
|
|
self.sectors.items(),
|
|
key=lambda x: x[1].get("priority_score", 0),
|
|
reverse=True,
|
|
)
|
|
|
|
for sector_key, sector_info in sorted_sectors[:3]: # Top 3 sectors per day
|
|
for city in self.cities[:3]: # Top 3 cities per sector
|
|
try:
|
|
discovery = await self.discover_leads(
|
|
sector=sector_key,
|
|
city=city["name"],
|
|
count=15,
|
|
)
|
|
results["sectors_processed"].append({
|
|
"sector": sector_info["name_ar"],
|
|
"city": city["name"],
|
|
"leads_found": discovery.get("total_discovered", 0),
|
|
"hot_leads": discovery.get("hot_leads", 0),
|
|
})
|
|
results["total_leads"] += discovery.get("total_discovered", 0)
|
|
results["hot_leads"] += discovery.get("hot_leads", 0)
|
|
|
|
# Rate limiting
|
|
await asyncio.sleep(2)
|
|
except Exception as e:
|
|
logger.error(f"Discovery error for {sector_key}/{city['name']}: {e}")
|
|
|
|
results["cycle_end"] = datetime.now(timezone.utc).isoformat()
|
|
|
|
# Send report to CEO Agent
|
|
self.send_message(
|
|
"ceo_agent", "daily_discovery_report",
|
|
results,
|
|
AgentPriority.NORMAL,
|
|
)
|
|
|
|
return results
|
|
|
|
|
|
import os # needed at module level for os.getenv
|