File size: 8,306 Bytes
c33f0cb
 
566bdbd
 
97376e1
c33f0cb
 
566bdbd
 
 
 
97376e1
c33f0cb
 
 
97376e1
 
566bdbd
c33f0cb
566bdbd
 
 
 
 
 
 
 
 
 
c33f0cb
566bdbd
c33f0cb
 
 
 
566bdbd
c33f0cb
 
566bdbd
97376e1
 
 
 
 
 
 
 
 
 
 
 
c33f0cb
 
 
 
 
 
 
 
 
566bdbd
c33f0cb
 
 
 
 
 
 
 
566bdbd
c33f0cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566bdbd
c33f0cb
 
 
 
 
 
 
566bdbd
c33f0cb
 
 
566bdbd
c33f0cb
 
 
 
 
 
 
 
 
566bdbd
c33f0cb
 
 
 
 
 
 
 
 
566bdbd
c33f0cb
 
 
 
 
 
566bdbd
c33f0cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import logging
from typing import Dict, Any, Optional, List, Callable
# Import lead_scoring_agent for lead scoring integration
from lead_scoring_agent import score_lead
from data_enrichment import DataEnrichmentManager

class OutreachAgent:
    """
    OutreachAgent handles lead intake, message tailoring, and multi-channel delivery.
    Integrates lead_scoring_agent to score leads at intake for prioritization and routing.
    """
    def __init__(self, username: str, channel_handlers: Optional[Dict[str, Callable]]=None):        self.logger = logging.getLogger("OutreachAgent")
        self.lead_assets = {}
        self.analytics = []
        self.channel_handlers = channel_handlers or {}
        self.username = username
        self.enrichment_manager = DataEnrichmentManager(username)
    
    def intake_lead(self, lead_profile: Dict[str, Any]) -> Dict[str, Any]:
        """
        Intake lead with scoring integration as first logic step.
        Calls score_lead(profile) to enrich profile with scoring metadata.
        """
        # FIRST STEP: Score the lead using lead_scoring_agent
        scored_profile = score_lead(lead_profile)
        self.logger.info(f"Lead scored: {scored_profile.get('name', 'Unknown')} - Score: {scored_profile.get('lead_score', 'N/A')}")
        
        # Enrich profile with additional data
        enriched_profile = self.validate_and_enrich_profile(scored_profile)
        self.logger.info(f"Intake for {enriched_profile.get('name', 'Unknown Lead')}")
        
        asset_id = enriched_profile.get('id')
        if asset_id:
            self.lead_assets[asset_id] = enriched_profile
        return enriched_profile
    
    def validate_and_enrich_profile(self, profile: Dict[str, Any]) -> Dict[str, Any]:
        # Enrich with historical, persona, engagements, etc.
        # Preserve scoring metadata from lead_scoring_agent
                
        # ENRICHMENT STEP: Use DataEnrichmentManager to enrich profile with 14 APIs
        try:
            enriched_data = self.enrichment_manager.enrich_contact_full(profile)
            if enriched_data:
                # Merge enriched data into profile (preserve existing fields)
                for key, value in enriched_data.items():
                    if key not in profile or not profile[key]:  # Only update if field is missing
                        profile[key] = value
                self.logger.info(f"Profile enriched with {len(enriched_data)} data sources")
        except Exception as e:
            self.logger.warning(f"Enrichment failed: {e}. Continuing with available data.")
        required_fields = ['persona', 'channel_fit']
        for field in required_fields:
            if field not in profile:
                profile[field] = 'unspecified'
        # Simulate enrichment logic
        profile['validation'] = 'success'
        if 'recent_posts' not in profile:
            profile['recent_posts'] = []
        return profile
    
    def select_template(self, profile: Dict[str, Any]) -> str:
        persona = profile.get('persona', 'Generic')
        pain = profile.get('pain_opportunity', 'N/A')
        buyer_signals = profile.get('buyer_signals', [])
        channel = profile.get('channel_fit', 'email')
        # Template selection logic (expandable)
        template = f"Hi {{name}}, as a {{persona}}, we noticed: {{pain}}. {{buyer_signals}}. Ready to discuss via {{channel}}?"
        return template
    
    def tailor_message(self, profile: Dict[str, Any], template: str) -> str:
        variables = {
            "name": profile.get("name", "there"),
            "persona": profile.get("persona", "Professional"),
            "pain": profile.get("pain_opportunity", "key considerations"),
            "buyer_signals": ', '.join(profile.get("buyer_signals", [])),
            "channel": profile.get("channel_fit", "email"),
            "context": profile.get("context_snippets", ""),
            "recent_posts": '\n'.join(profile.get("recent_posts", [])),
            "complaints": ', '.join(profile.get("customer_complaint_tags", [])),
            "hooks": ', '.join(profile.get("competitor_industry_hooks", [])),
            "urgency": profile.get("urgency_triggers", "")
        }
        message = template.format(**variables)
        # Attach recent posts, urgency, hooks etc with nuance
        if variables["context"]:
            message += f"\nContext: {variables['context']}"
        if variables["recent_posts"]:
            message += f"\nRecent posts: {variables['recent_posts']}"
        if variables["complaints"]:
            message += f"\nCustomer complaints: {variables['complaints']}"
        if variables["hooks"]:
            message += f"\nIndustry/competitor hooks: {variables['hooks']}"
        if variables["urgency"]:
            message += f"\nUrgency triggers: {variables['urgency']}"
        return message
    
    def deliver(self, profile: Dict[str, Any], message: str, channel: str) -> Dict[str, Any]:
        # Channel handler pattern for extensibility
        handler = self.channel_handlers.get(channel, self.default_handler)
        result = handler(profile, message)
        self.logger.info(f"Delivered to {profile.get('name')} via {channel}")
        self.update_analytics(profile, channel, result)
        return result
    
    def default_handler(self, profile: Dict[str, Any], message: str) -> Dict[str, Any]:
        # Simulate sending logic
        return {"status": "sent", "channel": profile.get('channel_fit', 'email'), "message": message}
    
    def browser_social_actions(self, profile: Dict[str, Any], action: str, extra: Optional[Dict[str, Any]]=None) -> Dict[str, Any]:
        # Simulate browser agent: connect, DM, comment, like, milestone outreach
        valid_actions = ["connect", "dm", "comment", "like", "milestone"]
        if action not in valid_actions:
            raise ValueError(f"Unsupported social action: {action}")
        self.logger.info(f"Performed {action} for {profile.get('name')}")
        result = {"action": action, "status": "success", "lead": profile.get('id')}
        self.update_analytics(profile, "social_action", result)
        return result
    
    def update_analytics(self, profile: Dict[str, Any], channel: str, result: Dict[str, Any]):
        entry = {
            "lead_id": profile.get("id"),
            "channel": channel,
            "result": result
        }
        self.analytics.append(entry)
        if profile.get("id") in self.lead_assets:
            self.lead_assets[profile.get("id")]["last_engagement"] = entry
    
    def feedback_loop(self, profile: Dict[str, Any], feedback: str):
        # Store feedback and auto-update asset
        pid = profile.get("id")
        if pid and pid in self.lead_assets:
            self.lead_assets[pid]["feedback"] = feedback
        self.logger.info(f"Feedback for {profile.get('name')}: {feedback}")
    
    def get_transcript(self, profile_id: str) -> Dict[str, Any]:
        asset = self.lead_assets.get(profile_id)
        return {
            "profile": asset,
            "last_message": asset.get("last_engagement", {}).get("result", {}).get("message", "") if asset else "",
            "engagement_stats": [a for a in self.analytics if a.get("lead_id") == profile_id]
        }

# Example usage handler for email
def email_handler(profile, message):
    # Integrate real email API here
    return {"status": "sent_via_email", "recipient": profile.get("email", "n/a"), "message": message}

# Example handlers for expansion for LinkedIn, SMS, browser agent, etc.
def linkedin_handler(profile, message):
    # Integrate real LinkedIn API calls here
    return {"status": "sent_via_linkedin", "recipient": profile.get("linkedin", "n/a"), "message": message}

def sms_handler(profile, message):
    # Integrate real SMS API calls here
    return {"status": "sent_via_sms", "recipient": profile.get("phone", "n/a"), "message": message}

# Usage:
# agent = OutreachAgent(channel_handlers={"email": email_handler, "linkedin": linkedin_handler, "sms": sms_handler})
# lead = agent.intake_lead({...})
# template = agent.select_template(lead)
# personalized_msg = agent.tailor_message(lead, template)
# agent.deliver(lead, personalized_msg, channel="email")
# agent.browser_social_actions(lead, "connect")