diff --git a/salesflow-saas/backend/dealix_gtm_os/__init__.py b/salesflow-saas/backend/dealix_gtm_os/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/__init__.py b/salesflow-saas/backend/dealix_gtm_os/agents/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/base_agent.py b/salesflow-saas/backend/dealix_gtm_os/agents/base_agent.py new file mode 100644 index 00000000..03c36a7a --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/base_agent.py @@ -0,0 +1,9 @@ +from abc import ABC, abstractmethod + +class BaseAgent(ABC): + name: str = "base" + description: str = "" + + @abstractmethod + async def run(self, input_data: dict) -> dict: + pass diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/channel_strategy_agent.py b/salesflow-saas/backend/dealix_gtm_os/agents/channel_strategy_agent.py new file mode 100644 index 00000000..16ffb0ab --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/channel_strategy_agent.py @@ -0,0 +1,32 @@ +from dealix_gtm_os.agents.base_agent import BaseAgent +from dealix_gtm_os.models.message import ChannelType, AutomationLevel + +SECTOR_CHANNELS = { + "agency": (ChannelType.EMAIL, ChannelType.LINKEDIN_MANUAL), + "real_estate": (ChannelType.EMAIL, ChannelType.WHATSAPP_WARM), + "clinic": (ChannelType.WHATSAPP_WARM, ChannelType.EMAIL), + "saas": (ChannelType.EMAIL, ChannelType.LINKEDIN_MANUAL), + "ecommerce": (ChannelType.EMAIL, ChannelType.INSTAGRAM_INBOUND), + "construction": (ChannelType.EMAIL, ChannelType.PHONE), + "training": (ChannelType.EMAIL, ChannelType.WHATSAPP_WARM), + "consulting": (ChannelType.LINKEDIN_MANUAL, ChannelType.EMAIL), + "website_agency": (ChannelType.LINKEDIN_MANUAL, ChannelType.EMAIL), +} + +class ChannelStrategyAgent(BaseAgent): + name = "channel_strategy" + description = "Selects best outreach channel per target" + + async def run(self, input_data: dict) -> dict: + sector = input_data.get("sector", "").lower().replace(" ", "_") + channels = SECTOR_CHANNELS.get(sector, (ChannelType.EMAIL, ChannelType.LINKEDIN_MANUAL)) + primary, secondary = channels + manual_channels = {ChannelType.LINKEDIN_MANUAL, ChannelType.WHATSAPP_WARM, ChannelType.PHONE} + level = AutomationLevel.MANUAL_REQUIRED if primary in manual_channels else AutomationLevel.SEMI_AUTOMATED + return { + "primary_channel": primary.value, + "secondary_channel": secondary.value, + "automation_level": level.value, + "reason": f"Sector {sector} best reached via {primary.value}", + "risk_flags": ["manual_approval_required"] if level == AutomationLevel.MANUAL_REQUIRED else [], + } diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/company_research_agent.py b/salesflow-saas/backend/dealix_gtm_os/agents/company_research_agent.py new file mode 100644 index 00000000..d37dc904 --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/company_research_agent.py @@ -0,0 +1,25 @@ +import json +from dealix_gtm_os.agents.base_agent import BaseAgent +from dealix_gtm_os.agents.llm_client import call_llm +from dealix_gtm_os.models.company import CompanyInput, CompanyIntelligence + +class CompanyResearchAgent(BaseAgent): + name = "company_research" + description = "Understands a company from available data" + + async def run(self, input_data: dict) -> dict: + company = CompanyInput(**input_data) + result_json = await call_llm( + f"Analyze company: {company.name}, sector: {company.sector}, city: {company.city}", + context={"sector": company.sector or ""} + ) + data = json.loads(result_json) + intel = CompanyIntelligence( + name=company.name, + website=company.website, + sector=company.sector or data.get("sector", "unknown"), + city=company.city or "", + confidence=0.7, + **{k: v for k, v in data.items() if k in CompanyIntelligence.model_fields} + ) + return intel.model_dump() diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/compliance_agent.py b/salesflow-saas/backend/dealix_gtm_os/agents/compliance_agent.py new file mode 100644 index 00000000..e7c216d8 --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/compliance_agent.py @@ -0,0 +1,32 @@ +import yaml +from pathlib import Path +from dealix_gtm_os.agents.base_agent import BaseAgent +from dealix_gtm_os.models.message import AutomationLevel + +RULES_PATH = Path(__file__).parent.parent / "config" / "compliance_rules.yaml" + +class ComplianceAgent(BaseAgent): + name = "compliance" + description = "Enforces platform safety rules" + + def __init__(self): + if RULES_PATH.exists(): + with open(RULES_PATH) as f: + self.rules = yaml.safe_load(f) + else: + self.rules = {} + + async def run(self, input_data: dict) -> dict: + channel = input_data.get("channel", "email") + action = input_data.get("action", "send_message") + channel_key = channel.replace("_manual", "").replace("_warm", "").replace("_inbound", "").replace("_post", "").replace("_reply", "") + if channel_key == "linkedin": + channel_key = "linkedin" + elif channel_key in ("x", "twitter"): + channel_key = "x_twitter" + rules = self.rules.get(channel_key, {}) + if rules.get(action) == "prohibited" or rules.get("scraping") == "prohibited" and action == "scraping": + return {"allowed": False, "level": AutomationLevel.PROHIBITED.value, "reason": f"{action} on {channel} is prohibited by platform policy"} + if channel in ("linkedin_manual", "whatsapp_warm", "phone"): + return {"allowed": True, "level": AutomationLevel.MANUAL_REQUIRED.value, "reason": f"{channel} requires manual human approval"} + return {"allowed": True, "level": AutomationLevel.SEMI_AUTOMATED.value, "reason": f"{channel} is safe with opt-out"} diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/llm_client.py b/salesflow-saas/backend/dealix_gtm_os/agents/llm_client.py new file mode 100644 index 00000000..74d40b0d --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/llm_client.py @@ -0,0 +1,53 @@ +"""Mock LLM client — returns structured responses. Replace with real LLM later.""" +import json + +SECTOR_INTELLIGENCE = { + "agency": { + "business_summary": "وكالة تسويق تقدم خدمات إعلانية ورقمية للشركات", + "products_services": ["إعلانات رقمية", "إدارة سوشال ميديا", "تصميم", "محتوى"], + "target_customers": ["شركات صغيرة ومتوسطة", "عقارات", "عيادات", "متاجر"], + "revenue_model": "رسوم خدمات شهرية + نسبة من ميزانية الإعلان", + "lead_channels": ["موقع إلكتروني", "سوشال ميديا", "إحالات"], + "pain_points": ["عملاء يلومونهم على ضعف التحويل", "لا recurring revenue", "leads العميل تضيع بعد الإعلان"], + "partnership_potential": "عالي — يقدرون يبيعون Dealix كخدمة لعملائهم", + "opportunity_types": ["agency_partner", "co_selling_partner"], + }, + "real_estate": { + "business_summary": "شركة تسويق أو تطوير عقاري", + "products_services": ["بيع وتأجير عقارات", "تسويق مشاريع عقارية"], + "target_customers": ["مشترين أفراد", "مستثمرين", "مستأجرين"], + "revenue_model": "عمولات بيع/تأجير + رسوم تسويق", + "lead_channels": ["واتساب", "اتصالات", "نماذج موقع", "إعلانات"], + "pain_points": ["60% من الاستفسارات ما تُتابع", "المنافسة عالية", "فريق مبيعات صغير"], + "partnership_potential": "متوسط — عميل مباشر", + "opportunity_types": ["direct_customer"], + }, + "saas": { + "business_summary": "شركة تقنية تقدم حلول برمجية", + "products_services": ["برمجيات سحابية", "تطبيقات", "حلول تقنية"], + "target_customers": ["شركات", "مؤسسات"], + "revenue_model": "اشتراكات شهرية/سنوية", + "lead_channels": ["موقع إلكتروني", "إعلانات Google", "LinkedIn"], + "pain_points": ["leads من الموقع تبرد", "SDR مكلّف", "فريق صغير"], + "partnership_potential": "متوسط — عميل أو integration partner", + "opportunity_types": ["direct_customer", "integration_partner"], + }, +} + +DEFAULT_INTEL = { + "business_summary": "شركة تقدم خدمات في السوق السعودي", + "products_services": ["خدمات متنوعة"], + "target_customers": ["شركات ومؤسسات"], + "revenue_model": "رسوم خدمات", + "lead_channels": ["واتساب", "إيميل", "موقع"], + "pain_points": ["استفسارات ما تُتابع", "بطء الرد"], + "partnership_potential": "متوسط", + "opportunity_types": ["direct_customer"], +} + + +async def call_llm(prompt: str, context: dict | None = None) -> str: + """Mock LLM — returns sector-based intelligence. Replace with real API later.""" + sector = (context or {}).get("sector", "") + intel = SECTOR_INTELLIGENCE.get(sector, DEFAULT_INTEL) + return json.dumps(intel, ensure_ascii=False) diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/message_generation_agent.py b/salesflow-saas/backend/dealix_gtm_os/agents/message_generation_agent.py new file mode 100644 index 00000000..67000378 --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/message_generation_agent.py @@ -0,0 +1,61 @@ +from dealix_gtm_os.agents.base_agent import BaseAgent +from dealix_gtm_os.models.message import OutreachMessage, ChannelType, AutomationLevel + +SECTOR_MESSAGES = { + "agency": { + "first_line": "شفت أنكم تقدمون خدمات تسويق/دعاية لعملاء.", + "pain": "عملاؤكم يصرفون على إعلانات والـ leads تضيع بعد الكلك.", + "offer": "أضف خدمة متابعة leads لعملائك — 20% لك من كل عميل.", + }, + "real_estate": { + "first_line": "لاحظت أن نشاطكم في العقار يعتمد على الاستفسارات.", + "pain": "60% من استفسارات الأسعار والمواقع ما تُتابع خلال ساعة.", + "offer": "Dealix يرد خلال 45 ثانية ويحجز موعد معاينة.", + }, + "saas": { + "first_line": "شفت منتجكم — مشروع قوي.", + "pain": "الـ leads من الموقع تبرد خلال ساعة.", + "offer": "Dealix يرد فوراً ويؤهل ويحجز demo تلقائياً.", + }, +} + +class MessageGenerationAgent(BaseAgent): + name = "message_generation" + description = "Generates personalized Arabic outreach messages" + + async def run(self, input_data: dict) -> dict: + company = input_data.get("name", "الشركة") + sector = input_data.get("sector", "").lower().replace(" ", "_") + channel = input_data.get("channel", "email") + msgs = SECTOR_MESSAGES.get(sector, SECTOR_MESSAGES.get("saas")) + body = f"""السلام عليكم فريق {company}، + +أنا سامي من Dealix. + +{msgs['first_line']} + +المشكلة: {msgs['pain']} + +الحل: {msgs['offer']} + +نسوي pilot 7 أيام بـ 499 ريال مع ضمان استرداد كامل. +يناسبكم ديمو 10 دقائق؟ +📅 calendly.com/sami-assiri11/dealix-demo + +سامي العسيري | مؤسس Dealix | dealix.me + +إذا ما يناسبكم هالنوع من الرسائل، ردوا "إيقاف".""" + + msg = OutreachMessage( + target_company=company, + channel=ChannelType(channel) if channel in [c.value for c in ChannelType] else ChannelType.EMAIL, + automation_level=AutomationLevel.MANUAL_REQUIRED, + subject=f"فريق {company} — فكرة لتحسين متابعة العملاء", + first_line=msgs["first_line"], + body=body, + cta="يناسبكم ديمو 10 دقائق؟", + follow_up_24h=f"متابعة سريعة — أقدر أوريكم خلال 10 دقائق كيف Dealix يحول الاستفسارات لمتابعة وحجز.", + follow_up_72h=f"آخر متابعة مني. مهتم → رد 'مهتم'. إيقاف → رد 'إيقاف'.", + approval_required=True, + ) + return msg.model_dump() diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/scoring_agent.py b/salesflow-saas/backend/dealix_gtm_os/agents/scoring_agent.py new file mode 100644 index 00000000..8d352b5c --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/scoring_agent.py @@ -0,0 +1,31 @@ +from dealix_gtm_os.agents.base_agent import BaseAgent +from dealix_gtm_os.models.score import TargetScore + +SECTOR_SCORES = { + "agency": {"fit": 5, "urgency": 4, "partner": 5, "payment": 3, "case_study": 4}, + "real_estate": {"fit": 5, "urgency": 5, "partner": 2, "payment": 4, "case_study": 3}, + "saas": {"fit": 4, "urgency": 4, "partner": 3, "payment": 3, "case_study": 3}, + "clinic": {"fit": 4, "urgency": 4, "partner": 1, "payment": 4, "case_study": 3}, + "ecommerce": {"fit": 4, "urgency": 3, "partner": 2, "payment": 3, "case_study": 2}, + "construction": {"fit": 3, "urgency": 3, "partner": 1, "payment": 3, "case_study": 2}, +} + +class ScoringAgent(BaseAgent): + name = "scoring" + description = "Scores a target company" + + async def run(self, input_data: dict) -> dict: + sector = input_data.get("sector", "").lower().replace(" ", "_") + defaults = SECTOR_SCORES.get(sector, {"fit": 3, "urgency": 3, "partner": 2, "payment": 3, "case_study": 2}) + has_email = bool(input_data.get("email") or input_data.get("website")) + score = TargetScore( + company_name=input_data.get("name", "Unknown"), + fit=defaults["fit"], + urgency=defaults["urgency"], + access=4 if has_email else 2, + partner=defaults["partner"], + payment=defaults["payment"], + case_study=defaults["case_study"], + risk=2, + ) + return score.model_dump() diff --git a/salesflow-saas/backend/dealix_gtm_os/agents/supervisor_agent.py b/salesflow-saas/backend/dealix_gtm_os/agents/supervisor_agent.py new file mode 100644 index 00000000..aa327fb3 --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/agents/supervisor_agent.py @@ -0,0 +1,35 @@ +from dealix_gtm_os.agents.base_agent import BaseAgent +from dealix_gtm_os.agents.company_research_agent import CompanyResearchAgent +from dealix_gtm_os.agents.scoring_agent import ScoringAgent +from dealix_gtm_os.agents.channel_strategy_agent import ChannelStrategyAgent +from dealix_gtm_os.agents.compliance_agent import ComplianceAgent +from dealix_gtm_os.agents.message_generation_agent import MessageGenerationAgent + +class SupervisorAgent(BaseAgent): + name = "supervisor" + description = "Orchestrates all GTM agents into a complete pipeline" + + def __init__(self): + self.research = CompanyResearchAgent() + self.scoring = ScoringAgent() + self.channel = ChannelStrategyAgent() + self.compliance = ComplianceAgent() + self.message = MessageGenerationAgent() + + async def run(self, input_data: dict) -> dict: + intel = await self.research.run(input_data) + score = await self.scoring.run({**input_data, **intel}) + channel_plan = await self.channel.run(intel) + compliance = await self.compliance.run({"channel": channel_plan["primary_channel"], "action": "send_message"}) + msg_input = {**intel, "channel": channel_plan["primary_channel"]} + message = await self.message.run(msg_input) + return { + "company": input_data.get("name", "Unknown"), + "intelligence": intel, + "score": score, + "channel_plan": channel_plan, + "compliance": compliance, + "message": message, + "next_action": "send" if compliance["allowed"] else "manual_review", + "approval_required": message.get("approval_required", True), + } diff --git a/salesflow-saas/backend/dealix_gtm_os/config/channels.yaml b/salesflow-saas/backend/dealix_gtm_os/config/channels.yaml new file mode 100644 index 00000000..615c439c --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/config/channels.yaml @@ -0,0 +1,48 @@ +channels: + email: + automation_level: semi_automated + daily_limit: 10 + best_for: [b2b, agencies, companies_with_email] + requires: [identity, optout, relevant_reason] + + linkedin_manual: + automation_level: manual_required + daily_limit: 5 + best_for: [founders, agencies, b2b_decision_makers] + requires: [personalization, manual_send] + + whatsapp_warm: + automation_level: manual_required + daily_limit: 5 + best_for: [warm_network, optin_leads, demo_confirmations] + requires: [existing_relationship_or_optin, stop_condition] + + instagram_inbound: + automation_level: semi_automated + daily_limit: 3 + best_for: [local_brands, visual_businesses, inbound_engagement] + requires: [prior_engagement, official_api] + + x_post: + automation_level: fully_automated + daily_limit: 3 + best_for: [thought_leadership, founder_content, market_insights] + requires: [original_content] + + x_reply: + automation_level: manual_required + daily_limit: 5 + best_for: [engagement, conversations, founder_community] + requires: [value_add, no_spam] + + phone: + automation_level: manual_required + daily_limit: 3 + best_for: [warm_leads, post_demo, partner_conversations] + requires: [prior_context] + + partner_intro: + automation_level: manual_required + daily_limit: 2 + best_for: [agency_partners, referral_partners] + requires: [partner_relationship] diff --git a/salesflow-saas/backend/dealix_gtm_os/config/compliance_rules.yaml b/salesflow-saas/backend/dealix_gtm_os/config/compliance_rules.yaml new file mode 100644 index 00000000..52f3914f --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/config/compliance_rules.yaml @@ -0,0 +1,64 @@ +linkedin: + scraping: prohibited + auto_dm: prohibited + auto_connect: prohibited + auto_like: prohibited + auto_comment: prohibited + manual_dm: allowed + manual_dm_daily_limit: 5 + posts: allowed + comments: manual_only + research: allowed + +email: + cold_b2b: allowed_with_optout + max_sequence_length: 3 + identity_required: true + optout_required: true + daily_limit_initial: 10 + daily_limit_proven: 20 + misleading_subject: prohibited + +whatsapp: + cold_blast: prohibited + warm_optin: allowed + templates_with_approval: allowed + daily_limit_initial: 5 + daily_limit_proven: 10 + stop_words: + - "إيقاف" + - "stop" + - "لا" + - "لا شكراً" + - "ما يناسبني" + +instagram: + mass_dm: prohibited + inbound_reply: allowed + official_api: allowed + warm_dm_after_engagement: allowed + daily_dm_limit: 3 + +x_twitter: + auto_mentions: prohibited + auto_replies: prohibited + auto_dms: prohibited + posts: allowed + scheduled_posts: allowed + safe_manual_replies: allowed + daily_reply_limit: 5 + +tiktok: + dm_scraping: prohibited + mass_dm: prohibited + content: allowed + lead_form_ads: allowed_official + dm_ads: allowed_official + +general: + fake_accounts: prohibited + fake_engagement: prohibited + buying_lead_lists: prohibited + scraping_restricted_sites: prohibited + overclaiming_results: prohibited + guaranteed_revenue_claims: prohibited diff --git a/salesflow-saas/backend/dealix_gtm_os/config/offer_catalog.yaml b/salesflow-saas/backend/dealix_gtm_os/config/offer_catalog.yaml new file mode 100644 index 00000000..3a0b91da --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/config/offer_catalog.yaml @@ -0,0 +1,72 @@ +offers: + speed_to_lead_audit: + name_ar: "تحليل سرعة الرد" + price_sar: 0 + alternative_price: 99 + duration: "30 دقيقة" + purpose: "فتح الباب — ليس بيع" + deliverables: + - "تحليل أين تضيع الاستفسارات" + - "تقييم سرعة الرد الحالية" + - "توصية flow بسيط" + - "اقتراح pilot مخصص" + best_for: [real_estate, clinics, ecommerce] + cta: "أقدر أعمل لكم audit مجاني" + + pilot: + name_ar: "تجربة 7 أيام" + price_sar: 499 + duration: "7 أيام" + purpose: "أول دفع + أول proof" + deliverables: + - "قناة واحدة (واتساب/إيميل/نماذج)" + - "حد 20 lead" + - "رسائل متابعة مخصصة" + - "تصنيف ردود" + - "تقرير نهاية التجربة" + guarantee: "استرداد كامل" + best_for: [all_sectors] + cta: "نبدأ بعميل واحد / قناة واحدة / أسبوع واحد" + + starter: + name_ar: "المبتدئ" + price_sar: 990 + billing: monthly + purpose: "إيراد متكرر" + deliverables: + - "واتساب + إيميل" + - "follow-up sequences" + - "lead tracker" + - "booking CTA" + - "تقرير أسبوعي" + best_for: [proven_pilot_customers] + cta: "نكمّل بـ Starter" + + agency_addon: + name_ar: "باقة الوكالات" + price_sar_range: "1499-2999" + purpose: "الوكالة تبيع لعملائها" + deliverables: + - "إعداد لعميل واحد" + - "سكريبتات متابعة" + - "tracker + تقارير" + - "تدريب بسيط للوكالة" + - "قوالب بيع للعميل" + partner_share: "20% MRR + 50% setup fee" + best_for: [marketing_agencies, digital_agencies] + cta: "كن شريك وكالة" + + partner_ops: + name_ar: "شراكة مخصصة" + price_sar: custom + purpose: "شراكات استراتيجية" + models: [referral, co_selling, implementation, service_exchange] + best_for: [strategic_partners, large_agencies] + cta: "نصمم النموذج حسب حجمكم" + +payment: + bank: "مصرف الإنماء" + account_name: "سامي محمد زايد عسيري — ذكاء الاعمال" + account_number: "68207328877000" + iban: "SA3305000068207328877000" + swift: "INMASARIXXX" diff --git a/salesflow-saas/backend/dealix_gtm_os/config/scoring_weights.yaml b/salesflow-saas/backend/dealix_gtm_os/config/scoring_weights.yaml new file mode 100644 index 00000000..7df6cc38 --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/config/scoring_weights.yaml @@ -0,0 +1,48 @@ +weights: + fit: 1 + urgency: 1 + access: 1 + partner: 1 + payment: 1 + case_study: 1 + risk: -1 + +priority_thresholds: + send_today: 24 + send_this_week: 18 + send_this_month: 12 + backlog: 0 + +sector_defaults: + marketing_agency: + fit: 5 + partner: 5 + urgency: 4 + real_estate: + fit: 5 + urgency: 5 + partner: 2 + clinic: + fit: 4 + urgency: 4 + partner: 1 + ecommerce: + fit: 4 + urgency: 3 + partner: 2 + website_agency: + fit: 4 + partner: 4 + urgency: 3 + consulting: + fit: 3 + partner: 3 + urgency: 2 + construction: + fit: 3 + urgency: 3 + partner: 1 + training: + fit: 3 + urgency: 3 + partner: 1 diff --git a/salesflow-saas/backend/dealix_gtm_os/models/__init__.py b/salesflow-saas/backend/dealix_gtm_os/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/salesflow-saas/backend/dealix_gtm_os/models/company.py b/salesflow-saas/backend/dealix_gtm_os/models/company.py new file mode 100644 index 00000000..f24a117b --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/models/company.py @@ -0,0 +1,28 @@ +from pydantic import BaseModel, Field +from typing import Optional + +class CompanyInput(BaseModel): + name: str + website: Optional[str] = None + sector: Optional[str] = None + city: Optional[str] = None + description: Optional[str] = None + source: Optional[str] = None + email: Optional[str] = None + phone: Optional[str] = None + +class CompanyIntelligence(BaseModel): + name: str + website: Optional[str] = None + sector: str + city: str = "" + business_summary: str + products_services: list[str] = Field(default_factory=list) + target_customers: list[str] = Field(default_factory=list) + revenue_model: str = "" + lead_channels: list[str] = Field(default_factory=list) + pain_points: list[str] = Field(default_factory=list) + partnership_potential: str = "" + opportunity_types: list[str] = Field(default_factory=list) + sources: list[str] = Field(default_factory=list) + confidence: float = 0.5 diff --git a/salesflow-saas/backend/dealix_gtm_os/models/message.py b/salesflow-saas/backend/dealix_gtm_os/models/message.py new file mode 100644 index 00000000..dc78ffa8 --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/models/message.py @@ -0,0 +1,35 @@ +from enum import Enum +from pydantic import BaseModel, Field +from typing import Optional + +class ChannelType(str, Enum): + EMAIL = "email" + LINKEDIN_MANUAL = "linkedin_manual" + WHATSAPP_WARM = "whatsapp_warm" + INSTAGRAM_INBOUND = "instagram_inbound" + X_POST = "x_post" + X_REPLY = "x_reply" + TIKTOK_CONTENT = "tiktok_content" + PHONE = "phone" + PARTNER_INTRO = "partner_intro" + WEBSITE_FORM = "website_form" + +class AutomationLevel(str, Enum): + FULLY_AUTOMATED = "fully_automated" + SEMI_AUTOMATED = "semi_automated" + MANUAL_REQUIRED = "manual_required" + PROHIBITED = "prohibited" + +class OutreachMessage(BaseModel): + target_company: str + channel: ChannelType + automation_level: AutomationLevel + subject: Optional[str] = None + first_line: str + body: str + cta: str + follow_up_24h: str = "" + follow_up_72h: str = "" + stop_condition: str = "إذا ما يناسبكم، ردوا 'إيقاف'" + approval_required: bool = True + risk_flags: list[str] = Field(default_factory=list) diff --git a/salesflow-saas/backend/dealix_gtm_os/models/opportunity.py b/salesflow-saas/backend/dealix_gtm_os/models/opportunity.py new file mode 100644 index 00000000..bd2532bd --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/models/opportunity.py @@ -0,0 +1,32 @@ +from enum import Enum +from pydantic import BaseModel, Field + +class OpportunityType(str, Enum): + DIRECT_CUSTOMER = "direct_customer" + AGENCY_PARTNER = "agency_partner" + REFERRAL_PARTNER = "referral_partner" + CO_SELLING_PARTNER = "co_selling_partner" + IMPLEMENTATION_PARTNER = "implementation_partner" + SERVICE_EXCHANGE = "service_exchange" + INTEGRATION_PARTNER = "integration_partner" + CONTENT_PARTNER = "content_partner" + RESELLER_LATER = "reseller_later" + WHITELABEL_LATER = "whitelabel_later" + +class Opportunity(BaseModel): + company_name: str + opportunity_type: OpportunityType + fit_score: int = Field(ge=1, le=5) + urgency_score: int = Field(ge=1, le=5) + access_score: int = Field(ge=1, le=5) + partner_score: int = Field(ge=1, le=5) + payment_score: int = Field(ge=1, le=5) + risk_score: int = Field(ge=1, le=5) + total_score: int = 0 + reason: str = "" + recommended_offer: str = "" + recommended_channel: str = "" + + def model_post_init(self, __context): + self.total_score = (self.fit_score + self.urgency_score + self.access_score + + self.partner_score + self.payment_score - self.risk_score) diff --git a/salesflow-saas/backend/dealix_gtm_os/models/score.py b/salesflow-saas/backend/dealix_gtm_os/models/score.py new file mode 100644 index 00000000..0fbf6a0c --- /dev/null +++ b/salesflow-saas/backend/dealix_gtm_os/models/score.py @@ -0,0 +1,27 @@ +from pydantic import BaseModel, Field, computed_field + +class TargetScore(BaseModel): + company_name: str + fit: int = Field(ge=1, le=5) + urgency: int = Field(ge=1, le=5) + access: int = Field(ge=1, le=5) + partner: int = Field(ge=1, le=5) + payment: int = Field(ge=1, le=5) + case_study: int = Field(ge=1, le=5) + risk: int = Field(ge=1, le=5) + + @computed_field + @property + def total(self) -> int: + return self.fit + self.urgency + self.access + self.partner + self.payment + self.case_study - self.risk + + @computed_field + @property + def priority(self) -> str: + if self.total >= 24: + return "send_today" + elif self.total >= 18: + return "send_this_week" + elif self.total >= 12: + return "send_this_month" + return "backlog" diff --git a/salesflow-saas/backend/dealix_gtm_os/pipelines/__init__.py b/salesflow-saas/backend/dealix_gtm_os/pipelines/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/salesflow-saas/backend/dealix_gtm_os/tools/__init__.py b/salesflow-saas/backend/dealix_gtm_os/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/salesflow-saas/backend/scripts/gtm_os_dry_run.py b/salesflow-saas/backend/scripts/gtm_os_dry_run.py new file mode 100644 index 00000000..a76847a2 --- /dev/null +++ b/salesflow-saas/backend/scripts/gtm_os_dry_run.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +""" +Dealix GTM OS — Dry Run CLI +Analyzes a company and generates a complete GTM pack. +DRY-RUN ONLY — does NOT send any messages. +""" +import asyncio +import argparse +import json +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +from dealix_gtm_os.agents.supervisor_agent import SupervisorAgent + + +async def run(company_name: str, website: str, sector: str, city: str, email: str): + supervisor = SupervisorAgent() + result = await supervisor.run({ + "name": company_name, + "website": website, + "sector": sector, + "city": city, + "email": email, + }) + + print("=" * 60) + print(f" DEALIX GTM OS — DRY RUN") + print(f" Company: {company_name}") + print(f" ⚠️ DRY-RUN ONLY — لا يرسل رسائل") + print("=" * 60) + + intel = result["intelligence"] + print(f"\n{'━' * 40}") + print("1. COMPANY INTELLIGENCE") + print(f"{'━' * 40}") + print(f" Sector: {intel.get('sector', '?')}") + print(f" Summary: {intel.get('business_summary', '?')}") + print(f" Services: {', '.join(intel.get('products_services', []))}") + print(f" Customers: {', '.join(intel.get('target_customers', []))}") + print(f" Pain Points: {', '.join(intel.get('pain_points', []))}") + print(f" Partnership: {intel.get('partnership_potential', '?')}") + print(f" Opportunity: {', '.join(intel.get('opportunity_types', []))}") + print(f" Confidence: {intel.get('confidence', 0):.0%}") + + score = result["score"] + print(f"\n{'━' * 40}") + print("2. TARGET SCORE") + print(f"{'━' * 40}") + print(f" Fit: {score['fit']}/5 | Urgency: {score['urgency']}/5 | Access: {score['access']}/5") + print(f" Partner: {score['partner']}/5 | Payment: {score['payment']}/5 | Case Study: {score['case_study']}/5") + print(f" Risk: {score['risk']}/5") + print(f" TOTAL: {score['total']} → Priority: {score['priority']}") + + channel = result["channel_plan"] + print(f"\n{'━' * 40}") + print("3. CHANNEL STRATEGY") + print(f"{'━' * 40}") + print(f" Primary: {channel['primary_channel']}") + print(f" Secondary: {channel['secondary_channel']}") + print(f" Automation: {channel['automation_level']}") + print(f" Reason: {channel['reason']}") + if channel.get("risk_flags"): + print(f" Risk Flags: {', '.join(channel['risk_flags'])}") + + comp = result["compliance"] + print(f"\n{'━' * 40}") + print("4. COMPLIANCE") + print(f"{'━' * 40}") + print(f" Allowed: {'✅' if comp['allowed'] else '❌'}") + print(f" Level: {comp['level']}") + print(f" Reason: {comp['reason']}") + + msg = result["message"] + print(f"\n{'━' * 40}") + print("5. MESSAGE (DRAFT — NOT SENT)") + print(f"{'━' * 40}") + print(f" Channel: {msg['channel']}") + print(f" Subject: {msg.get('subject', 'N/A')}") + print(f" Approval Required: {'✅ YES' if msg['approval_required'] else 'No'}") + print(f"\n --- BODY ---") + for line in msg["body"].split("\n"): + print(f" {line}") + print(f" --- END ---") + print(f"\n Follow-up 24h: {msg['follow_up_24h'][:80]}...") + print(f" Follow-up 72h: {msg['follow_up_72h'][:80]}...") + print(f" Stop: {msg['stop_condition']}") + + print(f"\n{'━' * 40}") + print("6. NEXT ACTION") + print(f"{'━' * 40}") + print(f" Action: {result['next_action']}") + print(f" Approval Required: {'✅ YES — Sami must approve before sending' if result['approval_required'] else 'No'}") + + prohibited = [] + if "linkedin" in channel["primary_channel"]: + prohibited.append("LinkedIn scraping") + prohibited.append("LinkedIn auto-DM") + prohibited.extend(["WhatsApp cold blast", "Instagram mass DM", "Fake accounts"]) + print(f"\n{'━' * 40}") + print("7. PROHIBITED ACTIONS") + print(f"{'━' * 40}") + for p in prohibited: + print(f" ❌ {p}") + + print(f"\n{'=' * 60}") + print(" ⚠️ DRY-RUN COMPLETE — NO MESSAGES SENT") + print(f"{'=' * 60}") + + +def main(): + parser = argparse.ArgumentParser(description="Dealix GTM OS Dry Run") + parser.add_argument("--company-name", required=True) + parser.add_argument("--website", default="") + parser.add_argument("--sector", default="agency") + parser.add_argument("--city", default="الرياض") + parser.add_argument("--email", default="") + args = parser.parse_args() + asyncio.run(run(args.company_name, args.website, args.sector, args.city, args.email)) + + +if __name__ == "__main__": + main() diff --git a/salesflow-saas/docs/gtm_os/CHANNEL_AUTOMATION_POLICY.md b/salesflow-saas/docs/gtm_os/CHANNEL_AUTOMATION_POLICY.md new file mode 100644 index 00000000..dbf8f498 --- /dev/null +++ b/salesflow-saas/docs/gtm_os/CHANNEL_AUTOMATION_POLICY.md @@ -0,0 +1,73 @@ +# Dealix GTM OS — Channel Automation Policy + +## Per-Channel Rules + +### LinkedIn +| Action | Level | Rule | +|--------|-------|------| +| Profile viewing | Manual | Research only | +| Post publishing | Allowed | Via Sami's account | +| Commenting | Manual | Value-add only, no spam | +| Connection requests | Manual | Max 10/day with personal note | +| DMs | Manual | Max 5/day, personalized | +| Scraping | **PROHIBITED** | LinkedIn ToS violation | +| Auto-DM bots | **PROHIBITED** | Account ban risk | + +### Email +| Action | Level | Rule | +|--------|-------|------| +| Targeted B2B | Semi-auto | With opt-out, max 10/day initially | +| Follow-up sequence | Semi-auto | Max 3 per target | +| Mass cold | **PROHIBITED** | Until email infrastructure ready | +| Fake sender | **PROHIBITED** | Always use real identity | + +### WhatsApp +| Action | Level | Rule | +|--------|-------|------| +| Warm messages | Manual | People who know Sami | +| Opt-in follow-up | Semi-auto | Via Green API templates | +| Demo confirmation | Allowed | After booking | +| Cold blasting | **PROHIBITED** | Account ban + legal risk | +| Status updates | Allowed | Daily | + +### Instagram +| Action | Level | Rule | +|--------|-------|------| +| Posts/Stories/Reels | Allowed | Content + engagement | +| Inbound DM reply | Allowed | Within 24h window | +| Cold DM | **PROHIBITED** | Platform risk | +| Comment engagement | Manual | Value-add only | + +### X / Twitter +| Action | Level | Rule | +|--------|-------|------| +| Posts/Threads | Allowed | Scheduled or manual | +| Safe replies | Manual | Value-add, no spam | +| Auto mentions | **PROHIBITED** | X ToS violation | +| Auto DMs | **PROHIBITED** | X ToS violation | + +### TikTok +| Action | Level | Rule | +|--------|-------|------| +| Organic content | Allowed | Videos/posts | +| Lead form ads | Allowed | Official ad system | +| DM scraping | **PROHIBITED** | Platform risk | +| Mass DM | **PROHIBITED** | Platform risk | + +## Daily Limits (Initial) + +| Channel | Daily Limit | After Proof | +|---------|------------|-------------| +| Email | 10 | 20 | +| LinkedIn DM | 5 (manual) | 5 | +| WhatsApp warm | 5 | 10 | +| X replies | 5 | 10 | +| Instagram DM | 2 (inbound only) | 3 | + +## Approval Gates +- First message to any new person: **Sami approval** +- WhatsApp to non-warm: **Sami approval** +- Claims about results: **Sami approval** +- Payment link: **Sami approval** +- Partner terms: **Sami approval** +- Sending >10 messages/day: **Sami approval** diff --git a/salesflow-saas/docs/gtm_os/DATA_SOURCES_POLICY.md b/salesflow-saas/docs/gtm_os/DATA_SOURCES_POLICY.md new file mode 100644 index 00000000..bd961f1c --- /dev/null +++ b/salesflow-saas/docs/gtm_os/DATA_SOURCES_POLICY.md @@ -0,0 +1,33 @@ +# Dealix GTM OS — Data Sources Policy + +## Allowed Sources + +| Source | Type | Usage | Risk | +|--------|------|-------|------| +| Uploaded company files (Excel/CSV) | User data | Primary targeting | Low | +| Public company websites | Public | Research + enrichment | Low | +| Google Programmable Search | Official API | Web search | Low | +| Tavily | AI search API | Structured web results | Low | +| Official social media APIs | Official | Inbound + public data | Low | +| CRM data | Internal | Lead tracking | Low | +| Inbound messages | Opt-in | Customer conversations | Low | +| Manual imports by Sami | User action | Ad-hoc targeting | Low | +| Public business directories | Public | Company discovery | Low | + +## Prohibited Sources + +| Source | Reason | +|--------|--------| +| LinkedIn scraping/crawling | Platform ToS violation | +| Instagram profile scraping | Platform ToS violation | +| Purchased email/phone lists | Privacy + spam risk | +| Unauthorized data brokers | Legal risk | +| Scraping restricted websites | ToS violation | +| Personal data without consent | PDPL violation | + +## Rules +1. Every data point must have a traceable source +2. No invented/hallucinated company data +3. No personal data collection without legitimate basis +4. All enrichment from public or API-approved sources only +5. User can request deletion of their data diff --git a/salesflow-saas/docs/gtm_os/GTM_OS_ARCHITECTURE.md b/salesflow-saas/docs/gtm_os/GTM_OS_ARCHITECTURE.md new file mode 100644 index 00000000..3b2618e2 --- /dev/null +++ b/salesflow-saas/docs/gtm_os/GTM_OS_ARCHITECTURE.md @@ -0,0 +1,66 @@ +# Dealix GTM OS — Architecture + +## Overview +``` +Company Input → Research → Enrichment → ICP Detection → Opportunity Mapping +→ Channel Strategy → Message Generation → Compliance Check +→ Human Approval / Safe Automation → CRM Tracking → Learning Loop +``` + +## Layers + +### A. Data Layer +Collects from allowed sources only: +- Uploaded company files +- Public websites +- Google Programmable Search / Tavily +- Official APIs +- CRM data +- Inbound messages +- Manual imports + +### B. Intelligence Layer +13 specialized agents understand companies, markets, and opportunities. + +### C. Compliance Layer +Decides: allowed / manual_required / opt_in_required / prohibited + +### D. Execution Layer +Only safe actions: drafts, CRM tasks, scorecards, content packs, approved campaigns. + +### E. Learning Layer +Tracks replies, demos, conversions. Updates ICP, scoring, messages, channels weekly. + +## 13 Agents + +| Agent | Role | Input | Output | +|-------|------|-------|--------| +| Supervisor | Orchestrates all | CompanyInput | Full GTM Pack | +| Company Research | Understands company | Name/URL/sector | CompanyIntelligence | +| Web Search | Searches allowed sources | Query | SearchResults | +| Enrichment | Adds data | CompanyInput | EnrichedCompany | +| ICP Strategist | Determines ideal customers | CompanyIntelligence | ICPList | +| Partnership Strategist | Maps partnership types | CompanyIntelligence | PartnershipMap | +| Channel Strategy | Picks best channel | Company + Compliance | ChannelPlan | +| Message Generation | Writes Arabic messages | Company + Channel | OutreachMessage | +| Compliance | Enforces platform rules | Channel + Action | Decision | +| Campaign Orchestrator | Builds sequences | Company + Messages | CampaignSequence | +| Negotiation | Handles objections | Reply + Context | NegotiationResponse | +| CRM & Revenue | Tracks status | Events | StatusUpdate | +| Learning | Improves system | Results | UpdatedStrategy | + +## Automation Boundaries + +| Level | What | Examples | +|-------|------|---------| +| Fully Automated | Internal processing | Research, scoring, drafts, CRM tasks, reports | +| Semi-Automated | Approved channels | Email with opt-out, inbound chatbot, WhatsApp templates | +| Manual Required | Risky channels | LinkedIn DMs, Instagram DMs, phone calls | +| Prohibited | Policy violation | LinkedIn scraping, WhatsApp blast, fake accounts | + +## Safety Rules +- LinkedIn: NO scraping/bots/automated DMs +- X: NO unsolicited automated replies/mentions +- WhatsApp: opt-in only, stop on "إيقاف" +- Instagram: inbound/official API only +- TikTok: content + official ads/lead forms only